React Hooks让咱们的工做方方面面都变得更好。可是但有时候一些性能问题也是很棘手的。咱们能够运用Hooks编写高性能应用,可是前提是你对于下面的这些问题有清醒的认知。react
Memoization:在计算机科学中,记忆化(英语:memoization而非memorization)是一种提升程序运行速度的优化技术。经过储存大计算量函数的返回值,当这个结果再次被须要时将其从缓存提取,而不用再次计算来节省计算时间。 记忆化是一种典型的时间存储平衡方案。 --维基百科typescript
React在大多数使用场景中已经足够高性能。若是你的应用足够快而且没有任何渲染问题,那么就不必往下看了。不要尝试去解决假想的渲染问题,因此在提升性能以前,确认下你是否是熟悉React Profiler
。数组
若是你已经明确知道加载慢的问题在哪,Memoization是最好的尝试方法。缓存
React.memo
是提升性能工具,同时也是一个HOC(高阶组件)。它和React.PureComponent
很类似,只不过它应用于函数组件而不是class组件。若是函数组件根据给定的相同的props渲染相同的结果,React会记住这些,跳过渲染组件,而且复用最后一次渲染结果。函数
默认状况下它会对复杂的props对象进行浅比较。若是你想要控制比较过程,也能够提供一个自定义的比较函数做为钩子函数的第二个参数。工具
咱们来举一个不使用Memoization的例子,而后看为何这样会形成问题。性能
function List({ items }) {
log('renderList');
return items.map((item, key) => (
<div key={key}>item: {item.text}</div>
));
}export default function App() {
log('renderApp'); function List({ items }) {
log('renderList');
return items.map((item, key) => <div key={key}>item: {item.text}</div>);
}
export default function App() {
log('renderApp');
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10));
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>inc</button>
<List items={items} />
</div>
);
}
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10)); return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
inc
</button>
<List items={items} />
</div>
);
}
复制代码
每次点击inc
,renderApp
和renderList
都会打印,甚至List
中没有任何改变。若是这棵数据树足够大,那么它会很容易引起性能问题。咱们须要减小渲染次数。flex
const List = React.memo(({ items }) => {
log('renderList');
return items.map((item, key) => <div key={key}>item: {item.text}</div>);
});
export default function App() {
log('renderApp');
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10));
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>inc</button>
<List items={items} />
</div>
);
}
复制代码
在这个例子中Memoization作了不少工做,而且减小了不少渲染次数。在加载过程当中renderApp
和renderList
都打印了,可是点击inc
的时候只有renderApp
打印了。优化
咱们来作一个小修改,在全部List
条目中添加上inc
按钮。记住,向组件中传入回调函数来记忆化组件会形成微妙的bug。spa
function App() {
log('renderApp');
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10));
return (
<div>
<div style={{ display: 'flex' }}>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>inc</button>
</div>
<List items={items} inc={() => setCount(count + 1)} />
</div>
);
}
复制代码
在这个例子中,咱们的Memoization失败了。由于咱们使用了内联匿名函数,每次渲染的时候都会生成新的渲染结果,这样React.mome
就失效了。因此在咱们记住这个组件以前,咱们要想办法记住这个函数。
十分幸运的是,React有两个内建钩子能够提供上面的功能:useMemo
和useCallback
。useMemo
对于复杂计算颇有用,useCallback
对于传递回调函数给须要提升性能的子组件十分有用。
function App() {
log('renderApp');
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10));
const inc = useCallback(() => setCount(count + 1));
return (
<div>
<div style={{ display: 'flex' }}>
<h1>{count}</h1>
<button onClick={inc}>inc</button>
</div>
<List items={items} inc={inc} />
</div>
);
}
复制代码
在这个例子中,咱们的Memoization又失败了。renderList
在每次inc
被点击的时候都会调用。useCallback
的默认行为是函数实例每次挂载的时候都计算新值。由于内联匿名函数每次渲染的时候都会建立新实例,默认设置下useCallback
无效。
const inc = useCallback(() => setCount(count + 1), [count]);
useCallback
接收的第二个参数是一个输入变量数组,当且仅当这些输入变量变化的时候,useCallback
会返回新值。在这个例子中,当count
变化的时候useCallback
会返回新值。每次count
在渲染的时候改变,useCallback
都会跟着改变。这样也没有实现很好的记忆功能。
const inc = useCallback(() => setCount(count + 1), []);
useCallback
也能够接收一个空数组做为第二个参数,这样内联匿名函数就只会调用一次。此次代码实现了记忆功能,点击按钮会打印renderApp
,主要按钮的功能也正常,可是内部inc
按钮中止正常工做了。
计数器从0增长到1后就中止工做了。匿名函数被建立一次,调用了屡次。由于匿名函数被建立的时候count是0,他的表现就像下面这样: const inc = useCallback(() => setCount(1), []);
这个问题的根本缘由在于,咱们试图同时读写state
。幸运的是,react
提供了解决上面问题的方法:
const inc = useCallback(() => setCount(c => c + 1), []);
useState
返回的setters能够将函数做为参数,这个函数中能够读取上一state
的值。在这个例子中,Memoization正确工做,没有bug。
const [count, dispatch] = useReducer(c => c + 1, 0);
useReducer memorization
和useState
中的例子同样正确运行了。由于dispatch
要保证渲染过程当中参考对象一致,就不须要useCallback
了,dispatch
使得代码更少的犯Memoization相关错误。
useReducer
更适合管理复杂对象或者下一state
依赖上一state
的状况。使用useReducer
的常见模式是和useContext
一块儿使用,避免在大型组件树中显示传递回调。 我比较推荐用useState
来管理组件内部数据,useReducer
适合管理须要在父子组件中传递的特殊双向数据。
总之,
React.memo
和useReducer
是最好的朋友;React.memo
和useState
是时而产生冲突的兄弟,会形成一些问题;谨慎使用useCallback
。
生活的一部分是工做,工做的一部分是解决问题取悦生活,因此好好生活,好好工做,好好热爱(●ˇ∀ˇ●)