共计 15493 个字符,预计需要花费 39 分钟才能阅读完成。
八、React 生命周期
1. 初始化阶段
2. 运行中阶段
3. 销毁阶段
老生命周期的问题
componentWillMount
,在 ssr 中这个方法将会被多次调用,所以会重复触发多遍,同时在这里如果绑定事件,将无法解绑,导致内存泄漏,变得不够安全高效逐步废弃componentWillReceiveProps
外部组件多次频繁更新传入多次不同的 props,会导致不必要的异步请求componetWillupdate
,更新前记录 DOM 状态, 可能会做一些处理,与componentDidUpdate
相隔时间如果过长,会导致状态不太信
新生命周期的提替代
getDerivedStateFromProps
第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子),返回一个对象作为新的 state,返回 null 则说明不需要在这里更新 state
// 老的生命周期的写法
componentDidMount() {
if(this.props.value !== undefined){
this.setState({
current:this.props.value
})
}
}
componentWillReceiveProps(nextProps){
if(nextProps.value !==undefined){
this.setState({
current: nextProps.value
})
}
}
// 新的生命周期写法
static getDerivedStateFromProps(nextProps) {
if(nextProps.value !== undefined){
return {
current: nextProps.value
}
}
return null
}
getSnapshotBeforeUpdate
取代了componetWillUpdate
,触发时间为 update 发生的时候,在 render 之后 dom 渲染之前返回一个值,作为componentDidUpdate
的第三个参数
// 新的数据不断插入数据前面, 导致我正在看的数据向下走,如何保持可视区依旧是我之前看的数据呢?
getSnapshotBeforeUpdate(){
return this.refs.wrapper.scrollHeight
}
componentDidUpdate(prevProps, prevState,preHeight) {
// if(preHeight===200) return;
this.refs.wrapper.scrollTop += this.refs.wrapper.scrollHeight-preHeight
}
<div style={{height:"200px",overflow:"auto"}}} ref="wrapper">
<ul>
.........
</ul>
</div>
react 中性能优化的方案
1. shouldComponentUpdate
控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下, 需要进行优化。
2. PureComponent
import React, { PureComponent } from 'react'
PureComponent
会帮你比较新 props 跟旧的 props,新的 state 和老的 state(值相等,或者对象含有相同的属性、且属性值相等),决定 shouldcomponentUpdate
返回 true 或者 false,从而决定要不要呼叫 render function
注意:如果你的 state 或 props 『永远都会变』,那
PureComponent
并不会比较快,因为shallowEqual
也需要花时间。
九、React Hooks
使用 hooks 理由
- 高阶组件为了复用,导致代码层级复杂
- 生命周期的复杂
- 写成 functional 组件,无状态组件,因为需要状态,又改成了 class,成本高
useState
(保存组件状态)
const [state, setstate] = useState(initialState)
useEffect
(处理副作用)和 useLayoutEffect
(同步执行副作用)
Function Component 不存在生命周期,所以不要把 Class Component 的生命周期概念搬过来试图对号入座。
useEffect(() => {
// effect
return () => {
// cleanup
};
}, [依赖的状态;空数组,表示不依赖])
不要对
Dependencies
撒谎,如果你明明使用了某个变量,却没有申明在依赖中,你等于向 React 撒了谎,后果就是,当依赖的变量改变时,useEffect
也不会再次执行, eslint 会报警告。
Preview
页面改造成函数式组件,在路径上从 id=1 切换到 id=2 也会自动重新加载,比 class 组件方便
let id = props.match.params.myid
useEffect(()=>{
axios.get(`/articles/${id}`).then(res => {
settitle(res.data.title)
setcontent(res.data.content)
setcategory(res.data.category)
})
},[id])
useEffect
和 useLayoutEffect
有什么区别?
简单来说就是调用时机不同,useLayoutEffect
和原来 componentDidMount
& componentDidUpdate
一致,在 react 完成 DOM 更新后马上同步调用的代码,会阻塞页面渲染。而 useEffect
是会在整个页面渲染完才会调用的代码。
官方建议优先使用
useEffect
However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.
在实际使用时如果想避免页面抖动(在 useEffect
里修改 DOM 很有可能出现)的话,可以把需要操作 DOM 的代码放在 useLayoutEffect
里。在这里做点 dom 操作,这些 dom 修改会和 react 做出的更改一起被一次性渲染到屏幕上,只有一次回流、重绘的代价。
useCallback
(记忆函数)
防止因为组件重新渲染,导致方法被重新创建 ,起到缓存作用; 只有第二个参数 变化了,才重新声明一次
var handleClick = useCallback(()=>{
console.log(name)
},[name])
<button onClick={()=>handleClick()}>hello</button>
// 只有 name 改变后, 这个函数才会重新声明一次,
// 如果传入空数组, 那么就是第一次创建后就被缓存, 如果 name 后期改变了,拿到的还是老的 name。
// 如果不传第二个参数,每次都会重新声明一次,拿到的就是最新的 name。
useMemo
(记忆组件)
useCallback
的功能完全可以由 useMemo
所取代,如果你想通过使用 useMemo
返回一个记忆函数也是完全可以的。
useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).
唯一的区别是:useCallback
不会执行第一个参数函数,而是将它返回给你,而 useMemo
会执行第一个函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。
所以 useCallback
常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo
更适合经过函数计算得到一个确定的值,比如记忆组件。
useRef
(保存引用值)
const myswiper = useRef(null)
<Swiper ref={myswiper}/>
useReducer
和 useContext
(减少组件层级)
import React from 'react'
var GlobalContext= React.createContext()
// 注意此时的 reduecer 返回值是一个对象 {isShow:false,list:[]}
function App(props){
let [state,dispatch] = useReducer(reducer,{isShow:true,list:[]})
return (
<GlobalContext.Provider value={{dispatch}}>
<div>
{state.isShow ? <div>我是选项卡</div> : null}
{props.children}
</div>
</GlobalContext.Provider>
)
}
function Detail () {
var { dispatch } = useContext(GlobalContext)
useEffect(() => {
//隐藏
dispatch({
type: "Hide",
payload: false
})
return () => {
//显示
dispatch({
type: "Show",
payload: true
})
}
}, [])
return (
<div>
detail
</div>
)
}
自定义 hooks
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。
必须以 “use” 开头吗?必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。
十、React 路由 v5
1. 什么是路由?
路由是根据不同的 url 地址展示不同的内容或页面。
一个针对 React 而设计的路由解决方案、可以友好的帮你解决 React components 到 URl 之间的同步映射关系。
2. 路由安装
https://reactrouter.com/en/main
npm install react-router-dom@5
3. 路由使用
(1) 路由方法导入
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
(2) 定义路由以及重定向
<HashRouter>
<Switch>
<Route path="/films" component={Films}/>
<Route path="/cinemas" component={Cinemas}/>
<Route path="/center" component={Center}/>
<Redirect from="/" to="/films" />
{/*
<Redirect from="/" to="/films" exact/>
<Route path="*" component={NotFound}/>
*/}
</Switch>
</HashRouter>
注意:
a.<Redirect from="/" to="/home" />
b.
exact
精确匹配(Redirect
即使使用了exact
,外面还要嵌套Switch
来用)c. Warning: Hash history cannot PUSH the Same path; a new entry will not be added to the history stack. 这个警告只有在 hash 模式会出现
(3) 嵌套路由
<Switch>
<Route path="/films/nowplaying" component={Nowplaying}/>
<Route path="/films/comingsoon" component={Comingsoon}/>
<Redirect from="/films" to="/films/nowplaying"/>
</Switch>
(4) 路由跳转方式
- 声明式导航
<NavLink to="/films" activeClassName="active">films</NavLink>
<NavLink to="/cinemas" activeClassName="active">cinemas</NavLink>
<NavLink to="/center" activeClassName="active">center</NavLink>
- 编程式导航
this.props.history.push(`/center`)
(5) 路由传参
// 推荐
<Route path="/detail/:id" component={Detail} />
this.props.history.push('/detail/' + id)
this.props.match.params.id
// (1)
this.props.history.push({ pathname : '/user' ,query : { day: 'Friday'} })
this.props.location.query.day
// (2)
this.props.history.push({ pathname:'/user',state:{day : 'Friday' } })
this.props.location.state.day
(6) 路由拦截
<Route path="/center" render={()=>isAuth()?<Center/>:<Login/>}/>
(7) withRouter的应用与原理
You can get access to the
history
object’s properties and the closest<Route>
‘smatch
via thewi thRouter
higher-order component.withRouter
will pass updatedmatch
,location
, andhistory
props to the wrapped component whenever it renders.
import { withRouter } from "react-router-dom";
withRouter(MyComponent);
withRouter(connect(...)(MyComponent))
const WithFilmItem = withRouter(FilmItem)
4.项目注意
(1) 反向代理
https://create-react-app.dev/docs/proxying-api-requests-in-development
npm install http-proxy-middleware --save
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
);
};
setupProxy.js 文件更新完要重启服务器
注意:无需在任何地方导入此文件。启动开发服务器时会自动注册它。
(2) css module
https://create-react-app.dev/docs/adding-a-css-modules-stylesheet
CSS 文件名改成 以 .module.css
结尾 filename.module.css
// 导入
import style from 'filename.module.css'
// 使用
<NavLink to="main" activeClassName={style.active}>Main</NavLink>
全局
:global(.active){
}
注意:对标签选择器无效
十一、Flux 与 Redux
Flux 是一种架构思想,专门解决软件的结构问题。它跟 MVC 架构是同一类东西,但是更加简单和清晰。Flux存在多种实现(至少15种)
https://github.com/voronianski/flux-comparison
Facebook Flux 是用来构建客户端 Web 应用的应用架构。它利用单向数据流的方式来组合 React 中的视图组件。它更像一个模式而不是一个正式的框架,开发者不需要太多的新代码就可以快速的上手 Flux。
Redux 最主要是用作应用状态的管理。简言之,Redux 用一个单独的常量状态树(state 对象)保存这一整个应用的状态,这个对象不能直接被改变。当一些数据变化了,一个新的对象就会被创建(使用 actions 和 reducers),这样就可以进行数据追踪,实现时光旅行。
1. redux 使用的三大原则
- state 以单一对象存储在 store 对象中
- state 只读(每次都返回有个新的对象)
- 使用纯函数 reducer 执行 state 更新
2. redux 工作流
同步
异步
3. 与 react 绑定后使用 redux 实现案例
// store.js
import { createStore } from 'redux'
const reducer = (state = 'defaultState', data = {}) => {
let { type, payload } = data
switch(type) {
case 'changetitle':
return payload
default:
return state
}
}
const store = createStore(reducer)
export default store
import store from 'store'
store.dispath({
type: 'changetitle',
payload: res.data.film.name
})
componentDidMount() {
store.subscribe(()=>{
this.setStaet({
title: store.getState()
})
})
}
import { SHOW_TABBAR, HIDE_TABBAR } '../store/type'
4. redux 原理解析
store 是通过 createStore
创建出来的,所以它的结构:
export const createStore = function(reducer, initialState) {
...
return {
// dispath 用于 action 的分发,改变 store 里面的 state(currentState = reducer(currentState, action))
// 并在内部遍历 subcribe 注册的监听器 subcribe,注册 listener,store 里面 state 发生变化后
// 执行该 listener getState,取 store 里面的 state
...
}
}
function createStore(reducer) {
var list = []
var state = reducer()
function subscribe(callback) {
list.push(callback)
}
function dispatch(data) {
state = reducer(state, data)
for(var i in list) {
list[i]()
}
}
function getState() {
return state
}
return {
subscribe,
dispatch,
getState
}
}
5. reducer 扩展
如果如果不同的 action 所处理的属性之间没有联系,我们可以把 Reducer 函数拆分。不同的函数负责处理不同属性,最终把它们合并成一个大的 Reducer 即可。
import {combineReducers} from "redux";
const reducer = combineReducers({
a: functionA,
b: functionB,
c: functionC
})
// 访问:
(state)=>{
return {
kerwinstate:state.a (不同的命名空间)
}
}
6. redux 中间件
在 redux 里,action 仅仅是携带了数据的普通 js 对象。action creator 返回的值是这个 action 类型的对象。然后通过 store.dispatch()
进行分发。同步的情况下一切都很完美,但是 reducer 无法处理异步的情况。
那么我们就需要在 action 和 reducer 中间架起一座桥梁来处理异步。这就是 middleware。
(1). 中间件的由来与原理、机制
export default function thunkMiddleware({ dispatch, getState }) {
return next => action =>
typeof action === 'function' ? action(dispatch, getState) : next(action);
}
这段代码的意思是,中间件这个桥梁接受到的参数 action,如果不是 function 则和过去一样直接执行 next 方法(下一步处理),相当于中间件没有做任何事。如果 action 是 function,则先执行 action,action 的处理结束之后,再在 action 的内部调用 dispatch。
(2). 常用异步中间件
redux-thunk (store.dispatch 参数可以是一个 function)
// store.js
import thunk from 'redux-thunk';
import {applyMiddleware} from "redux";
const store = createStore(fetchReducer, applyMiddleware(thunk));
const getComingSoon = ()=>{
//进行异步请求
return (dispatch,store)=>{
}
}
redux-promise (store.dispatch 参数可以是一个 promise 对象)
import promiseMiddleware from 'redux-promise';
const store = createStore(fetchReducer, applyMiddleware(thunk,promiseMiddleware));
const getComingSoon = ()=>{
//进行异步请求
return axios.get(`****`).then(res=>{
return {
type:"cominglist",
info:res.data.data
}
})
}
7. Redux DevTools Extension
https://github.com/zalmoxisus/redux-devtools-extension
// redux/store.js
import { createStore, compose} from 'redux'
import reducer from './reducer'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, /* preloadedState, */ composeEnhancers())
export default store
十二、react-redux
1. 介绍
https://github.com/reactjs/react-redux
2. 容器组件与 UI 组件
(1) UI组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用 this.state 这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
(2) 容器组件
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
3. Provider 与 connect
(1)React-Redux 提供 Provider 组件,可以让容器组件拿到 state。
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
const rootElement = document.getElementById('root')
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
(2)React-Redux 提供 connect 方法,用于从 UI 组件生成容器组件。connect 的意思,就是将这两种组件连起来。
import { connect } from 'react-redux'
import { increment, decrement, reset } from './actionCreators'
// const Counter = ...
const mapStateToProps = (state /*, ownProps*/) => {
return {
counter: state.counter
}
}
const mapDispatchToProps = { increment, decrement, reset }
// connect(将来给孩子传的属性, 将来给孩子传的回调函数)
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
4. HOC 与 context 通信在 react-redux 底层中的应用
- connect 是 HOC,高阶组件
- Provider 组件,可以让容器组件拿到 state ,使用了 context
5. 高阶组件构建与应用
HOC 不仅仅是一个方法,确切说应该是一个组件工厂,获取低阶组件,生成高阶组件。
- 代码复用,代码模块化
- 增删改 props
- 渲染劫持
// Child.js
// 高阶函数
function Control(wrappedComponent) {
return class MyControl extends React.Component {
render(){
if(!this.props.data) {
return <div>loading...</div>
}
return <wrappedComponent {...props} />
}
}
}
class MyComponent extends React.Component {
render(){
return <div>{this.props.data}</div>
}
}
export default Control(MyComponent); //高阶组件
// Parent.js
import MyControlComponent from "./Child"
<MyControlComponent data={this.state.value}/>
// 在父级传入 data 是 null 的时候,这一块儿就只会显示 loading...
// 不会显示组件的具体内容,如果 data 不为 null,就显示真实组件信息
import React, { useEffect } from 'react'
function NotFound (props) {
useEffect(() => {
console.log(props)
return () => {
}
}, [props])
return (
<div>404 NotFound</div>
)
}
function joeConnect (cb, obj) {
var value = cb()
return (MyComponent) => {
return (props) => {
return (
<div style={{ color: 'red' }}>
<MyComponent {...value} {...props} {...obj}></MyComponent>
</div>
)
}
}
}
export default joeConnect(() => {
return {
a: 1,
b: 1
}
}, {
aa () { },
bb () { }
}
)(NotFound)
6. Redux 持久化
import {persistStore, persistReducer} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
const persistConfig = {
key: 'kerwin',
storage: storage,
// localStorage: import storage from 'redux-persist/lib/storage'
// sessionStorage: import storageSession from 'redux-persist/lib/storage/session'
stateReconciler: autoMergeLevel2
// 控制在本地存储中,新老状态怎么合并,覆盖?或者合并?
};
// 改造 reducer
const myPersistReducer = persistReducer(persistConfig, reducer)
// 改造 store
export const persistor = persistStore(store)
// 改造根组件
import {persistor} from './Store'
import {PersistGate} from 'redux-persist/lib/integration/react';
<PersistGate loading={null} persistor={persistor}>
...
</PersistGate>
十三、UI 组件库
Ant Design 是一个致力于提升『用户』和『设计者』使用体验的设计语言 ;旨在统一中台项目的前端 UI 设计,屏蔽不必要的设计差异和实现成本,解放设计和前端的研发资源; 包含很多设计原则和配套的组件库。
1. ant-design (PC端)
镜像库,快 https://ant-design.gitee.io/index-cn
2. antd-mobile (移动端)
十四、Immutable
var obj = { /* 一个复杂结构的对象 */ };
doSomething(obj);
// 上面的函数之行完后,此时的 obj 还是最初的那个 obj 吗?
// deepCopy?
1. Immutable.js 介绍
https://github.com/immutable-js/immutable-js
每次修改一个 Immutable 对象时都会创建一个新的不可变的对象,在新对象上操作并不会影响到原对象的数据。
这个库的实现是深拷贝还是浅拷贝?
2. 深拷贝与浅拷贝的关系
var arr = { } ; arr2 = arr
Object.assign()
只是一级属性复制,比浅拷贝多拷贝了一层而已const obj1 = JSON.parse(JSON.stringify(obj))
数组,对象都好用的方法(缺点: 不能有undefined
)
3. Immutable 优化性能的方式
Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
4. Immutable 中常用类型(Map,List)
1. Map
const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50
import { Map } from 'immutable'
let a = Map({
select: 'users',
filter: Map({ name: 'Cam' })
})
let b = a.set('select', 'people')
a === b // false
a.get('filter') === b.get('filter') // true
挺深:如果上述 select 属性给一个组件用,因为此值改变了,
shouldComponentUpdate
应该返回treu
,而 filter 属性给另一个组件用,通过判断并无变化,shouldComponentUpdate
应该返回false
,此组件就避免了重复进行 diff 对比
2. List
const { List } = require('immutable');
const list1 = List([ 1, 2 ]);
const list2 = list1.push(3, 4, 5);
const list3 = list2.unshift(0);
const list4 = list1.concat(list2, list3);
assert.equal(list1.size, 2);
assert.equal(list2.size, 5);
assert.equal(list3.size, 6);
assert.equal(list4.size, 13);
assert.equal(list4.get(0), 1);
// push, set, unshift or splice 都可以直接用,返回一个新的 immutable 对象
3. merge, concat
const { Map, List } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3, d: 4 });
const map2 = Map({ c: 10, a: 20, t: 30 });
const obj = { d: 100, o: 200, g: 300 };
const map3 = map1.merge(map2, obj);
// Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }
const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);
const array = [ 7, 8, 9 ];
const list3 = list1.concat(list2, array);
// List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
4. toJS
const { Map, List } = require('immutable');
const deep = Map({ a: 1, b: 2, c: List([ 3, 4, 5 ]) });
console.log(deep.toObject()); // { a: 1, b: 2, c: List [ 3, 4, 5 ] }
console.log(deep.toArray()); // [ 1, 2, List [ 3, 4, 5 ] ]
console.log(deep.toJS()); // { a: 1, b: 2, c: [ 3, 4, 5 ] }
JSON.stringify(deep); // '{"a":1,"b":2,"c":[3,4,5]}'
5. fromJS
const { fromJS } = require('immutable');
const nested = fromJS({ a: { b: { c: [ 3, 4, 5 ] } } });
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } }
const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } });
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
console.log(nested2.getIn([ 'a', 'b', 'd' ])); // 6
// 如果取一级属性 直接通过get方法,如果取多级属性 getIn(["a","b","c"]])
// setIn 设置新的值
const nested3 = nested2.setIn([ 'a', 'b', 'd' ], "kerwin");
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: "kerwin" } } }
// updateIn 回调函数更新
const nested3 = nested2.updateIn([ 'a', 'b', 'd' ], value => value + 1);
console.log(nested3);
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }
const nested4 = nested3.updateIn([ 'a', 'b', 'c' ], list => list.push(6));
// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
5. Immutable + Redux 的开发方式
// reducer.js
const initialState = fromJS({
category:"",
material:""
})
const reducer = (prevstate = initialState,action={})=>{
let {type,payload} = action
switch(type){
case GET_HOME:
var newstate =prevstate.set("category",fromJS(payload.category))
var newstate2 =newstate.set("material",fromJS(payload.material))
return newstate2;
default:
return prevstate
}
}
// home.js
const mapStateToProps = (state)=>{
return {
category:state.homeReducer.getIn(["category"]) || Map({}),
material:state.homeReducer.getIn(["material"]) || Map({})
}
}
this.props.category.get("相关属性")
this.props.category.toJS() // 或者转成普通对象
6. 缺点
容易跟原生混淆
文档与调试不方便