在带有 hooks 的 React 中,生命周期不存在

很久很久以前,我们使用过带有类的 React,还记得吗?

那时,我们有生命周期方法的概念,即类中的方法接受在特定时刻执行的回调。主要有三种:挂载时、更新时和卸载时。

古老但黄金级别的课程

这很重要,在类组件上,返回的 JSX 是在 render 方法上生成的,状态附加到组件的 this 上,应用程序开发人员需要一种方法来知道在特定时刻执行的操作。我们对组件的生命周期有这样的想法:

  • componentDidMount 是组件首次渲染并将元素添加到 DOM 的时刻,也是开始连接和 API 请求等副作用的时刻。
  • shouldComponentUpdate 允许您手动设置逻辑来比较下一个 props 和 state 并返回一个布尔值来定义是否可以跳过重新渲染。
  • componentDidUpdate 是状态或属性发生变化的时刻,再次调用 render 方法并执行对身份差异的协调更改并应用于 DOM,以便将状态与新属性同步并执行逻辑操作。
  • componentWillUnmount 是 React 从 DOM 中删除元素的时候,是清理内容和避免内存泄漏的好地方。
  • 当然,您有一个重要的 API,例如“forceUpdate”,如果您使用与 React 状态更新无关的外部数据,它允许您手动触发**重新渲染**。

    从概念层面上讲,我们有一种更直接的方式来引导应用程序的流程。生命周期方法遵循与 DOM 元素类似的生命周期,您可以自己执行“memo”和“forceUpdates”,同步状态是执行逻辑的默认方式。

    这种直接性被认为是简单的,与反应式模型相比,学习这些概念更容易。但是后来,钩子出现了,改变了一切。

    未命名的反应性

    这种转变令人困惑。首先,为了简化并保持开发人员对 React 模型的概念愿景,许多交流都试图展示钩子模型的相似之处。为了拥有 3 个主要生命周期方法,他们展示了使用“useEffect”的解决方法。

    // componentDidMount
     useEffect(() => {
        // code...
    
        // componentWillUnmount:
        return function cleanup() {
          // code...
        };
      }, []);
    
    // componentDidUpdate
     useEffect(() => {
        // code...
    
      }, [dependencyState, dependencyProp]);

    因此,大多数使用 hooks 编写的新 React 代码都遵循了这种思路,开始同步状态是一个自然的过程。为了保持生命周期方法的相同思路,它是调用 setState 并触发重新渲染过程的地方。

    同步状态成为一个问题,错误使用“useEffect”成为一个问题,双重重新渲染成为一个问题,过多的重新渲染成为一个问题,性能成为一个问题。

    至少对我来说,React 的这一步有点令人困惑。因为转向 hooks 就是转向响应式模型,即使它是一个粗粒度的模型。但沟通表明并没有发生什么重大变化。没有关于响应式概念和理论的内容,即使我已经使用 React 多年,我也只是在阅读 Ryan Carniato 关于响应式和 Solidity 的博客文章后才开始真正理解响应式。

    即使知道 `useEffect` 存在误用,我也确实不明白为什么,而且缺乏关于反应性的概念理论使得使用 hooks 时犯错误变得如此容易。`useEffect` 成为了最令人讨厌的 hook,有些人称之为 **“useFootgun”**。关键是,React 中存在概念上的混淆,表现为我们今天看到的 `useEffect` 的所有问题。

    **useEffect 问题不是问题的原因,而是结果。**

    那么带钩子的生命周期呢

    事情是这样的,在反应性的概念中,没有生命周期。

    你有一个变化,你对它做出反应,产生副作用。效果是结果,而不是原因。没有状态同步,也没有挂载和卸载的概念。

    在卸载之前,它是第一次、第十次还是最后一次渲染都无关紧要,顺便说一下,钩子不会关心它,即使是“useEffect”也是如此。

    尝试一下:

    function EffectExample() {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        console.log('effect', count);
    
        return () => {
          console.log('clean up', count);
        }
      }, [count]);
    
      return (
        
      )
    }

    您将在“控制台”上看到每次状态更新时都会执行这两个函数。首先是清理函数,然后是效果回调。如果您使用“useEffect”和某些状态或属性进行订阅,则每次依赖项发生变化时,都会调用清理函数,然后调用新的回调,再次执行订阅,但使用新值。

    你应该将你的应用程序代码视为简化的 React 模型:

    UI = fn(state)

    如果您有如下组件:

    function Example() {
      const [count, setCount] = useState(0);
    
      return (
        
      )
    }

    当您单击按钮并将计数增加 +1 时,**从概念上**,您真正拥有的是这样的:

    // first render
     = fn({ count: 0 });
    // button click - re-render
     = fn({ count: 1 });
    // button click - re-render
     = fn({ count: 2 });
    // button click - re-render
     = fn({ count: 3 });

    每次点击都会再次调用 fn,并采用新状态,从而生成新版本的 UI。状态应根据用户的操作或应使用异步派生生成的异步值而改变。

    这样你就保持了干净的想法:

  • 状态转换进行新的 fn 调用
  • 有了新的状态,你就可以得到 UI 描述
  • 如果不同,则更新屏幕。
  • 一个干净且一致的模型。

    渲染器需要关注在屏幕上添加、更新和删除元素。在组件级别,重要的是:

  • 如果状态发生变化
  • 应用程序是否可以处理用户操作
  • JSX 中返回的结构。
  • Hooks 及其响应式模型使 React 与浏览器解耦,让应用代码不再关心您处于屏幕渲染过程的哪个时刻。您不再需要强制更新,甚至不再需要按照自己的规则处理备忘录,这对应用开发人员来说不那么直接,但在模型方面却更直接。

    **每次重新渲染都会生成一个结构,React 会处理剩下的部分。**