React Hook介绍(二):Effect Hook

一、了解Effect Hook之前,先补一下React组件的生命周期

React组件的生命周期分为三个阶段:

  1. 组件初始化 Mounting(也可以mount这个单词翻译为组件安装)

  2. 组件运行时 Updating

  3. 组件卸载时 Unmounting

React Hook介绍(二):Effect Hook

Mounting — 组件初始化

  • constructor():组件被创建时,首先被调用的方法,通常用来初始化组件state以及绑定事件处理方法,该方法中不能调用setState(),该构造方法接受一个props参数,必须在方法中首先调用super(props)才能保证props被传入组件中。

  • componentWillMount():组件渲染之前调用,只会被调用一次。在这个方法里setState不生效,不会触发re-render,而是会进行state合并。实际开发中很少用这个方法,因为都可以放到constructor()中来处理,而且新版react已经弃用并认为该方法是legacy(老式的)不安全的。

  • render():这是定义组件时唯一必要的方法,负责渲染真正的DOM。它是个纯函数,不能用来执行任何有副作用的操作,所以不能在render中执行setState,这会改变组件的状态。

  • componentDidMount():组件渲染后调用,只会被调用一次,这个时候组件已经被挂载,组件已经生成对应的DOM结构,需要操作DOM可以在这个时期。同时也是从远端取数据,发送ajax请求,设置监听,定时任务的好地方。这里可以setState。

Updating — 组件运行时

  • componentWillReceiveProps(nextProps):只会在外部props变化时才会调用,state变化不会调用。这里可以setState(),但是不会触发re-render,而是会进行state合并。方法的参数nextProps是父组件传递给当前组件的新的props,但是父组件的render方法并不能保证传给子组件的props发生变化,也就是说nextProps可能和当前props的值相等,所以需要比较props是否变化再setState,否则可能会发生多次调用。

  • shouldComponentUpdate(nextProps,nextState):该方法决定组件是否继续执行更新过程,返回布尔值。一但返回false,后续方法都不再执行,组件但更新过程终止。一般通过比较nextProps,nextState和当前props,state决定方法的返回值。该方法可以用来减少组件不必要的渲染,从而优化组件的性能。不能调用setState,否则会引起循环调用问题,render永远无法调用,组件也无法正常渲染。在使用forceUpdate时不被调用。

  • componentWillUpdate(nextProps,nextState):该方法在组件render之前调用,可以做为组件更新前执行某些工作的地方,很少用到且新版react已经弃用。不能调用setState,否则会引起循环调用问题,render永远无法调用,组件也无法正常渲染。这个方法可以被componentDidUpdate()替代。在使用forceUpdate的时候可以调用。

  • componentDidUpdate(prevProps,prevState):组件更新后被调用,可以做为操作更新后DOM的地方,可以调用setState。

Unmounting — 组件卸载时

 

componentWillUnmount():该方法在组件卸载之前调用,可以在这里执行一些清理工作,如清楚组件中的定时器,取消某些网络请求,清除componentDidUpdate手动创建的DOM元素等,以避免引起内存泄露。

 

二、Effect Hook (effect翻译是影响的意思,这边改叫:副作用的特殊函数)

它可以在函数组件中执行副作用的操作,这个副作用(即Effect Hook)相当于React组件生命周期componentDidMountcomponentDidUpdate 和 componentWillUnmount 这三个函数的组合.

哪些算副作用:数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。

例 子:

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate: 
  useEffect(() => {    // Update the document title using the browser API    
    document.title = `You clicked ${count} times`; 
 });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

 

三、React组件有两种副作用:无需要清除的effct和需要清除的effct 其实effect Hook 使用同一个 API 来满足这两种情况。

(一)无需要清除的effct(即有一些副作用不需要清除)

       我们经常需要在React更新Dom后运行一些额外的代码(例如发送网络请求,和动变更Dom,记录日志等),即我们内心希望组件每次渲染后均执行一段代码,但React没有这样的方法,只提供了

组件加载和更新的两个方法来满足要求,即:componentDidMount和componentDidUpdate,所以经常写代码需要在这两个方法均要有才能满足需求。

      useEffect的工作原理:

    通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。在这个 effect 中,我们设置了 document 的 title 属性,不过我们也可以执行数据获取或调用其他命令式的 API。

     useEffect 会在每次渲染后都执行: 默认情况下,它在第一次渲染之后每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

     经验丰富的 JavaScript 开发人员可能会注意到,传递给 useEffect 的函数在每次渲染中都会有所不同,这是刻意为之的。事实上这正是我们可以在 effect 中获取最新的 count 的值,而不用担心其过期的原因。每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect “属于”一次特定的渲染。     

(二)需要清除的effct(即有一些副作用需要清除)

原来React一般在componentDidMount 和 componentWillUnmount 之间相互对应来完成副作用的清除动作,现在改用effect hook,例如:

 

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => { 
   function handleStatusChange(status) { 
     setIsOnline(status.isOnline);   
   }   
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    
    // Specify how to clean up after this effect:  
    return function cleanup() {      
       ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); 
   }; 
 });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

   effect只要定义一个返回函数,它就可以实现副作用的清除,如下面:

  return function cleanup() {      
       ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); 
   }; 

为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

注意:并不是必须为 effect 中返回的函数命名。这里我们将其命名为 cleanup 是为了表明此函数的目的,但其实也可以返回一个箭头函数或者给起一个别的名字,例如:

useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

其他的 effect 可能不必清除,所以不需要返回。

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
四、多个Effect Hook的定义,实现关注点的分离,即把不相关的逻辑分离到不同的effect中。 五、Effect Hook的特点:每一次组件渲染均会执行( 如何跳过effect进行性能优化)

     这样可以避免原来React组件老忘了处理componentDidUpdate而产生的bug。但每次渲染均要执行,也会影响性能,如何做呢:

     这是很常见的需求,所以它被内置到了 useEffect 的 Hook API 中。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

 

对于有清除操作的 effect 同样适用:

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

 

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

 

除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得额外操作很方便。

六、如何让effect有条件的执行,即默认effect是在每轮渲染结束后延迟执行,但我们可以让它只在某些值改变时才执行

  这就需要给effect这个函数传递第二个参数(值数组),第一个参数是函数,例如:

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

 

 

 

 

 

 

 

 

上一篇:React Hook介绍(一):React State HooK


下一篇:[TP] 浅谈 ThinkPHP 的 Hook 行为事件及监听执行