React挂钩:性能陷阱以及如何轻松避免它们

从版本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 >
  );
};

这是一个具有两个状态AB以及对其执行四个增量操作的组件。 我添加了console.log方法,以查看每个渲染器上的消息。 前两个动作是基本增量,仅将AB值增加一。

让我们单击a ++,b ++按钮并查看控制台:每次单击时,应该只有一个渲染。 这真的很好,因为这就是我们想要的。

现在按1s后a ++,b ++按钮:每次单击,您将看到两个渲染。 如果您想知道底层发生了什么,答案很简单。

React将同步状态更新批处理成一个。

另一方面,对于异步函数,每个setState函数都会触发一个render方法。

但是,如果您想要保持一致的行为怎么办? 这是胡克斯的第一条规则。

规则1:不要在连接的数据模型的多个useState方法之间拆分状态

假设您有两个独立的状态。 然后,需求发生了变化,因此一种状态的更新导致另一种状态的更新。

在这种情况下,您必须将它们连接到一个对象中: const { A, B } = useState({ A: 0, B: 0}) 或者,利用useReducer函数。

此规则的另一个很好的例子是数据加载。 通常,您需要三个变量来处理它: isLoadingdataerror 不要试图将它们分开,而最好使用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规则 。 (如果您之前没有这样做,请启用它们!)

如果尝试渲染此组件,则由于无限重新渲染,页面将被冻结。 要解决此问题,我们需要定义挂钩的第二条规则。

规则2.确保仅在自定义挂钩更改后才从它们中返回新对象

上面的组件始终是重新渲染的,因为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时遇到了哪些其他挑战,让我们一起解决它们。

参考文献

  • React Hooks API
  • 如何使用Dev Tools Profiler分析React应用

From: https://hackernoon.com/adventuring-into-react-hooks-performance-practices-rly36xq

你可能感兴趣的:(React挂钩:性能陷阱以及如何轻松避免它们)