useMemo
useMemo的功能是记忆某个结果,只有当依赖项发生改变时才会更新输出结果
组件初始化:
- 执行计算函数,获取计算结果
- 缓存计算结果和依赖项
- 计算返回结果
组件更新:
- 判断依赖项和缓存的依赖项是否相同,相同则直接返回已缓存的计算结果
- 不同则执行计算函数,获取新的计算结果,缓存新计算结果和新的依赖项并返回计算结果
注意:
- React会使用Object.is()对新旧依赖数组中的每一个元素进行强比较
- 利用数组保存计算结果和依赖项,解决多次调用useMemo共享memorizedState的问题
实现:
-
let memorizedState=[];//利用数组解决多次调用useMemo共享memorizedState的问题 let index=0; function useMemo(fn,deps){ if(!memorizedState[index]){ //组件初始化:缓存计算结果和依赖项,返回计算结果 memorizedState[index++]=[fn(),deps]; return fn(); }else{ //组件更新 //判断依赖项是否改变 const [lastValue,lastDeps]=memorizedState[index]; const isSame=deps.every((item,idx)=>Object.is(item,lastDeps[idx])); if(isSame){ //依赖项未改变,返回缓存的结果 index++; return lastValue; }else{ //依赖项改变,重新计算结果,缓存结果和依赖项,返回值 memorizedState[index++]=[fn(),deps]; return fn(); } } }
useCallback
useCallback返回一个回调函数,该回调函数只会在依赖项改变时才会更新,回调函数更新后引用不相等,当将回调函数通过props传递给子组件时,会触发子组件重新渲染
实现原理和useMemo类似,不同的是useMemo缓存值,useCallback缓存函数
let memorizedState=[];//利用数组解决多次调用useCallback共享memorizedState的问题
let index=0;
function useCallback(fn,deps){
if(!memorizedState[index]){
//组件初始化:缓存函数和依赖项,返回函数
memorizedState[index++]=[fn,deps];
return fn;
}else{
//组件更新
//判断依赖项是否改变 - Object.is()强比较
const [lastFn,lastDeps]=memorizedState[index];
const isSame=deps.every((item,idx)=>Object.is(item,lastDeps[idx]));
if(isSame){
//依赖项未改变,返回缓存的函数
index++;
return lastFn;
}else{
//依赖项改变,缓存函数和依赖项,返回函数
memorizedState[index++]=[fn,deps];
return fn;
}
}
}
useState
useState的功能是设置一个状态的初始值,并返回当前状态和设置状态的函数
实现思路:
- 若初始状态为函数,则将函数执行结果作为当前状态,否则设置当前状态为初始状态
- 生成设置状态函数
- 若参数是函数,把prevState传入函数,将函数执行结果作为新的状态
- 缓存最新状态
- 触发视图更新
render()
- 返回当前状态和设置状态函数
注意:
- 利用数组保存状态处理多次调用useState的情况
- 利用闭包维护每个状态对应的设置状态函数
实现:
-
let memorizedState=[]; let index=0; function useState(initialState){ if(!memorizedState[index]){ //若initialState是函数,则将函数执行结果作为当前状态 memorizedState[index]=typeof initialState==='function'?initialState():initialState; } let curidx=index;//利用闭包维护每个状态对应的设置状态函数 function setState(newState){ if(typeof newState==='function'){ //传入prevState newState=newState(memorizedState[curidx]); } //更新状态 memorizedState[curidx]=newState; //触发视图更新 render(); } return [memorizedState[index++],setState]; }
useReducer
useReducer和redux中的reducer很像,useState内部就是靠useReducer来实现的
useReducer初始化有两种形式:
- 简单初始化:
[state, dispatch] = useReducer(reducer, initialState)
,state初始值为initialState
- 惰性初始化:
[state, dispatch] = useReducer(reducer, initialArg, init)
,state初始值为init(initialArg)
实现思路:
- state初始化
- 生成dispatch
- 调用
reducer(state,action)
,将返回值作为更新后的状态 - 触发视图更新
render()
- 注意参数action不能是函数
- 调用
- 返回当前状态和设置状态函数
实现:
-
let memorizedState=[]; let index=0; function useReducer(reducer,initialState,init){ if(!memorizedState[index]){ //当传入了第三个参数init时,调用init(initialState),将返回结果作为状态初始值 //否则初始值为initialState memorizedState[index]=init?init(initialState):initialState; } let curidx=index; function dispatch(action){ if(typeof action==='function'){ throw new Error(`${action.name} isn't allowed to be a function`); } //更新状态 memorizedState[curidx]=reducer(memorizedState[curidx],action); //更新视图 render(); } return [memorizedState[index++],dispatch]; }
useRef
useRef返回一个可变的ref对象,其current属性被初始化为传入的参数,这个ref对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref对象都是同一个
注意:使用React.createRef时,每次重新渲染组件都会重新创建ref
let lastRef;
function useRef(value) {
lastRef = lastRef || { current: value };
return lastRef;
}
const container = useRef(null)
return <div ref={container}></div>
React 都会将 container.current
设置为对应的 DOM 节点
useEffect
useEffect给函数组件增加了操作副作用的能力,它跟类式组件中的componentDidMount、componentDidUpdate和componentWillUnMount具有相同的用途,只不过被合并成了一个API
不同的是,使用useEffect调度的effect不会阻塞浏览器更新视图,应用响应更快
useEffect的第二个参数:
- 不传,组件每次render之后useEffect都会调用
- 传入一个空数组,useEffect值会调用一次
- 传入一个非空数组,依赖项发生变化时,触发useEffect执行
实现:
-
let memorizedState=[]; let index=0; function useEffect(fn,deps){ if(!memorizedState[index]){ //组件初始化,缓存依赖,执行effect函数 memorizedState[index++]=deps; fn(); }else{ //组件更新,判断依赖项是否改变 const lastDeps=memorizedState[index]; const isSame=deps.every((item,index)=>Object.is(item,lastDeps[index])); if(isSame){ //依赖项未改变,不做任何操作 index++; }else{ //依赖项发生改变,缓存依赖,执行effect函数 memorizedState[index++]=deps; fn(); } } }
Object.is()
Object在严格相等的基础上修复了一些特殊情况下的失误(-0和+0,NaN和NaN)
Object.is=function(a,b){
if(a===b){
//运行到1/a===1/b时,a和b均为0,计算结果为+Infinity或-Infinity
return a!==0||b!==0||1/a===1/b;
}else{
//NaN===NaN为false,会进入else
//当a!==a为true时,a一定为NaN,b同理,当a、b同时为NaN时返回true
return a!==a&&b!==b;
}
}
console.log(Object.is(+0,-0));//false
console.log(Object.is(NaN,NaN));//true