React Hooks 和组件生命周期等价物

Posted

技术标签:

【中文标题】React Hooks 和组件生命周期等价物【英文标题】:React Hooks and Component Lifecycle Equivalent 【发布时间】:2019-04-14 16:38:15 【问题描述】:

componentDidMountcomponentDidUpdatecomponentWillUnmount 生命周期钩子使用 React 钩子(如 useEffect)的等价物是什么?

【问题讨论】:

React 文档跳过元素reactjs.org/docs/…。涉及下面给出的关于带有 [] 的 componentDidMount 的解决方案 【参考方案1】:

componentDidMount

将一个空数组作为第二个参数传递给useEffect(),以仅在挂载时运行回调。

function ComponentDidMount() 
  const [count, setCount] = React.useState(0);
  React.useEffect(() => 
    console.log('componentDidMount');
  , []);

  return (
    <div>
      <p>componentDidMount: count times</p>
      <button
        onClick=() => 
          setCount(count + 1);
        
      >
        Click Me
      </button>
    </div>
  );


ReactDOM.render(
  <div>
    <ComponentDidMount />
  </div>,
  document.querySelector("#app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

componentDidUpdate

componentDidUpdate() 在更新发生后立即被调用。初始渲染不调用此方法。 useEffect 在包括第一个渲染在内的每个渲染上运行。所以如果你想有一个严格等价的componentDidUpdate,你必须使用useRef来确定组件是否已经挂载过一次。如果您想更严格,请使用useLayoutEffect(),但它会同步触发。大多数情况下,useEffect() 就足够了。

这个answer is inspired by Tholle,所有功劳归他所有。

function ComponentDidUpdate() 
  const [count, setCount] = React.useState(0);

  const isFirstUpdate = React.useRef(true);
  React.useEffect(() => 
    if (isFirstUpdate.current) 
      isFirstUpdate.current = false;
      return;
    

    console.log('componentDidUpdate');
  );

  return (
    <div>
      <p>componentDidUpdate: count times</p>
      <button
        onClick=() => 
          setCount(count + 1);
        
      >
        Click Me
      </button>
    </div>
  );


ReactDOM.render(
  <ComponentDidUpdate />,
  document.getElementById("app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

componentWillUnmount

在useEffect的回调参数中返回一个回调,它会在卸载之前被调用。

function ComponentWillUnmount() 
  function ComponentWillUnmountInner(props) 
    React.useEffect(() => 
      return () => 
        console.log('componentWillUnmount');
      ;
    , []);

    return (
      <div>
        <p>componentWillUnmount</p>
      </div>
    );
  
  
  const [count, setCount] = React.useState(0);

  return (
    <div>
      count % 2 === 0 ? (
        <ComponentWillUnmountInner count=count />
      ) : (
        <p>No component</p>
      )
      <button
        onClick=() => 
          setCount(count + 1);
        
      >
        Click Me
      </button>
    </div>
  );


ReactDOM.render(
  <div>
    <ComponentWillUnmount />
  </div>,
  document.querySelector("#app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

【讨论】:

很好的答案,对于那些可能不明白为什么 [] 对 componentDidMount 有效的人来说,这告诉 React 你的效果不依赖于任何来自 props 或 state 的值,所以它永远不需要重新跑步。 ***.com/a/53253411/611628【参考方案2】:

来自React docs:

如果你熟悉 React 类生命周期方法,你可以想 useEffect Hook 为 componentDidMount、componentDidUpdate 和 componentWillUnmount 组合。

他们的意思是:

componentDidMount 有点像useEffect(callback, [])

componentDidUpdate 有点像useEffect(callback, [dep1, dep2, ...]) - deps 数组告诉 React:“如果其中一个 deps 发生变化,则在渲染后运行回调”。 p>

componentDidMount + componentDidUpdate 有点像useEffect(callback)

componentWillUnmount 是回调中返回的函数:

useEffect(() =>  
    /* some code */
    return () =>  
      /* some code to run when rerender or unmount */
    
)

借助Dan Abramov 他的blog 的措辞以及我自己的一些补充:

虽然您可以使用这些钩子,但它并不是完全等价的。与componentDidMountcomponentDidUpdate 不同,它将捕获 道具和状态。因此,即使在回调内部,您也会看到特定渲染的道具和状态(这意味着在componentDidMount 中是初始道具和状态)。如果您想查看“最新”的内容,可以将其写入 ref。但是通常有一种更简单的方法来构建代码,这样您就不必这样做了。 返回的函数应该是componentWillUnmount 的替代函数,也不是完全等价的,因为该函数将在每次组件重新渲染和组件卸载时运行。 请记住,效果的心理模型与组件的生命周期不同,试图找到它们的确切等价物可能会让您感到困惑,而不是帮助。为了提高工作效率,您需要“思考效果”,他们的心智模型更接近于实现同步,而不是响应生命周期事件。

来自 Dan 博客的示例:

function Counter() 
  const [count, setCount] = useState(0);

  useEffect(() => 
    setTimeout(() => 
      console.log(`You clicked $count times`);
    , 3000);
  );

  return (
    <div>
      <p>You clicked count times</p>
      <button onClick=() => setCount(count + 1)>
        Click me
      </button>
    </div>
  );

如果我们使用类实现:

componentDidUpdate() 
  setTimeout(() => 
    console.log(`You clicked $this.state.count times`);
  , 3000);

this.state.count 始终指向最新计数,而不是属于特定渲染的计数。

【讨论】:

感谢分享有关它捕获该渲染的状态/道具的信息。这太有趣了。到目前为止,我只认为这是一件好事,并且很难想到 useEffect 处理这种情况的方式有什么问题。 +1 用于显示捕获的状态/道具的示例。当我遇到需要异步实现数据之间最终一致性的情况时,我一定会记录下来【参考方案3】:

为了简单说明,我想展示一个视觉参考

正如我们在上图中简单看到的那样 -

componentDidMount :

useEffect(() =>         
   console.log('componentWillMount');
, []);

componentDidUpdate :

useEffect(() =>         
   console.log('componentWillUpdate- runs on every update');
);

useEffect(() =>         
   console.log('componentWillUpdate - runs if dependency value changes ');
,[Dependencies]);

组件将卸载:

  useEffect(() => 
    return () => 
        console.log('componentWillUnmount');
    ;
   , []);

【讨论】:

【参考方案4】:

这是来自React Hooks FAQ 列出类生命周期方法的 Hooks 等效项的一个很好的总结:

constructor: 函数组件不需要构造函数。您可以在useState 调用中初始化状态。如果计算初始状态的开销很大,您可以将函数传递给useState

getDerivedStateFromProps:改为安排更新while rendering。

shouldComponentUpdate:见 React.memo below。

render:这是函数组件体本身。

componentDidMountcomponentDidUpdatecomponentWillUnmountuseEffect Hook 可以表达这些的所有组合(包括lesscommon 的情况)。

componentDidCatchgetDerivedStateFromError:目前没有这些方法的 Hook 等效项,但很快就会添加。


componentDidMount

useEffect(() =>  /*effect code*/ , []);

[] 将使效果仅在挂载时运行一次。通常你最好specify your dependencies。要获得与componentDidMount 相同的布局时序,请查看useLayoutEffect(大多数情况下不需要)。

componentWillUnmount

useEffect(() =>  /*effect code*/ ; return ()=>  /*cleanup code*/  , [deps]); 

componentWillUnmount对应cleanup的效果。

componentDidUpdate

const mounted = useRef();
useEffect(() => 
  if (!mounted.current) mounted.current = true;
  else 
    // ... on componentDidUpdate 
  
);

要拥有与componentDidUpdate 相同的布局时序,请查看useLayoutEffect(大多数情况下不需要)。另请参阅 this post 以更详细地了解 componentDidUpdate 钩子等效项。

【讨论】:

以上是关于React Hooks 和组件生命周期等价物的主要内容,如果未能解决你的问题,请参考以下文章

React Hooks 介绍及与传统 class 组件的生命周期函数对比

react hooks的缺点(针对状态不同步和没有生命周期)

react hooks的缺点(针对状态不同步和没有生命周期)

react生命周期

极品-React生命周期详情,hooks

极品-React生命周期详情,hooks