class 组件中性能优化能够经过 shouldComponentUpdate
实现或者继承自 PureComponent
,固然后者也是经过 shouldComponentUpdate
去作的,内部对 state
和 props
进行了 shallowEqual。数组
对于函数组件来讲并无这个生命周期能够调用,所以想实现性能优化只能经过 React.memo(<Component />)
来作,这种作法和继承 PureComponent
的原理一致。性能优化
另外若是你的函数组件须要拿到它的 ref,可使用如下工具函数:微信
function memoForwardRef<N, P>(comp: RefForwardingComponent<N, P>) {
return memo(forwardRef<N, P>(comp));
}
复制代码
可是并非以上作法之后性能就万事大吉了,你还得保证传递的 props
以及内部的状态的引用不发生预期以外的变化。闭包
对于函数组件来讲,变量的引用是须要重点关注的问题,不管是函数亦或者对象。函数
const Child = React.memo(({ columns }) => {
return <Table columns={columns} />
})
const Parent = () => {
const data = [];
return <Child columns={data} />
}
复制代码
对于以上组件来讲,每次 Parent
渲染的时候虽然 columns
内容没有变,可是 columns
的引用已经变了。当 props
传递给 Child
的时候,即便使用了 React.memo
可是性能优化也失效了。工具
对于这种状况,能够经过 useMemo
将引用存储起来,依赖不变引用也就不变。性能
const data = useMemo(() => [], [])
复制代码
useMemo
的场景可能是用于值的计算。好比密集型计算场景下你确定不但愿组件从新渲染的时候,依赖项没有变动缺重复执行计算函数获得相同的值。优化
对于函数来讲,若是你想保存它的引用的话可使用 useCallback
来作。ui
function Counter() {
const [count, setCount] = useState(0)
// 这样写函数,每次从新渲染都会再次建立一个新的函数
const onIncrement = () => {
setCount(count => count + 1)
}
const onIncrement = useCallback(() => {
setCount(count => count + 1)
}, [])
return (
<div> <button onClick={onIncrement}>INCREMENT</button> <p>{count}</p> </div>
)
}
复制代码
对于以上代码来讲,组件每次渲染的时候使用了 useCallback
包裹的 onIncrement
函数引用不会改变,这也就意味着不须要频繁建立及销毁函数了。spa
可是在 useCallback
存在依赖的状况下函数引用并不必定按照你的想法正常保持不变,好比以下案例:
function Counter() {
const [count, setCount] = useState(0)
const onIncrement = useCallback(() => {
setCount(count => count + 1)
}, [])
const onLog = useCallback(() => {
console.log(count)
}, [count])
return (
<div> <button onClick={onIncrement}>INCREMENT</button> <button onClick={onLog}>Log</button> <p>{count}</p> </div>
)
}
复制代码
当 count
每次改变形成组件从新渲染的时候,onLog
函数都会从新建立一次。两种常规方法能够保持在这种状况下函数引用不被改变。
useEventCallback
useReducer
function useEventCallback(fn, dependencies) {
const ref = useRef(() => {
throw new Error('Cannot call an event handler while rendering.');
});
useEffect(() => {
ref.current = fn;
}, [fn, ...dependencies]);
return useCallback(() => {
const fn = ref.current;
return fn();
}, [ref]);
}
复制代码
useEventCallback
使用了 ref
不变的特性,保证回调函数的引用永远不变。另外在 Hooks 中,dispatch
也是不变的,因此把依赖 ref
改为 dispatch
,而后在回调中调用 dispatch
就是另外一种作法了。
凡事都有两面性,在引入以上这些性能优化的时候你已经下降了本来的性能,毕竟它们都是有使用代价的,咱们能够来阅读下 useCallback
及 useMemo
的核心源码:
function updateCallback(callback, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
function updateMemo(nextCreate, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
复制代码
上述源码实现思路大体是从 fiber 中取出 memoizedState
,而后对比先后 Deps,对比的实现也采用了 shallowEqual,最后若是有变化的话就重置 memoizedState
。
能够看出来,本文中讲到的性能优化方案基本都是采用了 shallowEqual 来对比先后差别,因此不必为了性能优化而优化。
Hooks 的坑 99% 都是闭包引发的,咱们经过一个例子来了解下什么状况下会由于闭包致使问题。
function App() {
const [state, setState] = React.useState(0)
// 连点三次你以为答案会是什么?
const handleClick = () => {
setState(state + 1)
setTimeout(() => {
console.log(state)
}, 2000)
}
return (
<>
<div>{state}</div>
<button onClick={handleClick} />
</>
)
}
复制代码
上述代码触发三次 handleClick
后你以为答案会是什么?可能答案与你所想的不大同样,结果是:
0 1 2
由于每次 render 都有一份新的状态,所以上述代码中的 setTimeout
使用产生了一个闭包,捕获了每次 render 后的 state
,也就致使了输出了 0、一、2。
若是你但愿输出的内容是最新的 state
的话,能够经过 useRef
来保存 state
。前文讲过 ref
在组件中只存在一份,不管什么时候使用它的引用都不会产生变化,所以能够来解决闭包引起的问题。
function App() {
const [state, setState] = React.useState(0)
// 用 ref 存一下
const currentState = React.useRef(state)
// 每次渲染后更新下值
useEffect(() => {
currentState.current = state
})
const handleClick = () => {
setState(state + 1)
// 这样定时器里经过 ref 拿到最新值
setTimeout(() => {
console.log(currentState.current)
}, 2000)
}
return (
<>
<div>{state}</div>
<button onClick={handleClick} />
</>
)
}
复制代码
其实闭包引起的问题多半是保存了 old 的值,只要想办法拿到最新的值其实基本上就解决问题了。
若是你以为我有遗漏什么或者写的不对的,欢迎指出。
我很想听听你的想法,谢谢阅读。
微信扫码关注公众号,订阅更多精彩内容 | 加笔者微信群聊技术 |
---|---|
![]() |
![]() |