从版本16.8.0开始,React向我们介绍了一种无需编写类即可使用状态和其他React功能的方法-React Hooks 。
这是对经典Class范例的惊人改进,它使我们能够在组件之间重用有状态逻辑。 毫不奇怪的是,它带有一个学习曲线,可能会导致性能下降。
让我们深入了解最流行的应用程序,并设法弄清楚如何避免它们。
好了,我们确定使用Hooks时可能会遇到一些性能问题,但是它们来自何处?
本质上,Hooks的大多数问题都来自不必要的组件渲染。
看下面的例子:
const Incrementor = () => {
const [, setA] = useState( 0 );
const [, setB] = useState( 0 );
const incrementA = () => setA( a => a + 1 );
const incrementB = () => setB( a => a + 1 );
const incrementAB = () => {
incrementA();
incrementB();
};
const incrementABLater = () => {
setTimeout( () => {
incrementA();
incrementB();
}, 1000 );
};
console .log( "Re-rendered" );
return (
< div >
div >
);
};
这是一个具有两个状态A和B以及对其执行四个增量操作的组件。 我添加了console.log
方法,以查看每个渲染器上的消息。 前两个动作是基本增量,仅将A或B值增加一。
让我们单击a ++,b ++按钮并查看控制台:每次单击时,应该只有一个渲染。 这真的很好,因为这就是我们想要的。
现在按1s后的a ++,b ++按钮:每次单击,您将看到两个渲染。 如果您想知道底层发生了什么,答案很简单。
React将同步状态更新批处理成一个。
另一方面,对于异步函数,每个setState
函数都会触发一个render方法。
但是,如果您想要保持一致的行为怎么办? 这是胡克斯的第一条规则。
假设您有两个独立的状态。 然后,需求发生了变化,因此一种状态的更新导致另一种状态的更新。
在这种情况下,您必须将它们连接到一个对象中: const { A, B } = useState({ A: 0, B: 0})
。 或者,利用useReducer
函数。
此规则的另一个很好的例子是数据加载。 通常,您需要三个变量来处理它: isLoading
, data
和error
。 不要试图将它们分开,而最好使用useReducer
。
它使您可以将状态逻辑与组件分开,并帮助您避免错误。 具有一个具有这三个属性的对象也将是一个解决方案,但不会那么明显且容易出错。
相信我,我已经看到很多人忘记设置isLoading: false
时设置isLoading: false
。
现在,我们已经找到了如何在单个组件中管理useState
,让我们将增量功能移到外部以在不同的地方使用。
const useIncrement = ( defaultValue = 0 ) => {
const [value, setValue] = useState(defaultValue)
const increment = () => setValue( value => value + 1 )
return [value, increment]
}
const ExampleWithCustomHook = () => {
const [a, incrementA] = useIncrement()
useEffect( () => {
incrementA()
}, [incrementA])
console .log( 'Re-rendered' )
return < h1 > {a} h1 >
}
我们将增量逻辑重构为其自己的Hook,然后使用useEffect
函数运行一次。
请注意,我们必须提供的incrementA
,因为我们正在使用它里面的依赖性阵列setter和它是由执行胡克ESLint规则 。 (如果您之前没有这样做,请启用它们!) 。
如果尝试渲染此组件,则由于无限重新渲染,页面将被冻结。 要解决此问题,我们需要定义挂钩的第二条规则。
上面的组件始终是重新渲染的,因为increment
Hook每次都会返回一个新函数。 为避免每次都创建新函数,请将其包装在useCallback
函数中。
const useIncrement = ( defaultValue = 0 ) => {
const [value, setValue] = useState(defaultValue);
const increment = useCallback( () => setValue( value => value + 1 ), []);
return [value, increment];
};
const ExampleWithCustomHook = () => {
const [a, incrementA] = useIncrement();
useEffect( () => {
incrementA();
}, [incrementA]);
console .log( "Re-rendered" );
return < h1 > {a} h1 > ;
};
现在可以安全使用此挂钩了。
有时,您需要从自定义Hooks返回一个普通对象,请确保仅在使用useMemo
更改其内容时才对其进行useMemo
。
通常,在导致性能问题之前先找到这些问题很麻烦,因此您必须使用特定的工具预先检测它们。
其中之一是“ why-did-you-render
库,该库告诉您可避免的重新渲染。
将您的组件标记为MyComponent.whyDidYouRender = true
,开始与它进行交互,然后在控制台中查找消息。
我保证您会在接下来的五分钟内发现新的事物。
另一个选择是使用React Dev Tools扩展中的Profiler选项卡。 尽管您必须考虑希望从组件中重新渲染多少个,但是此选项卡仅显示重新渲染的数量。
让我知道您在使用Hooks时遇到了哪些其他挑战,让我们一起解决它们。
From: https://hackernoon.com/adventuring-into-react-hooks-performance-practices-rly36xq