ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

React Hook介绍(二):Effect Hook

2021-06-02 14:04:54  阅读:211  来源: 互联网

标签:调用 Effect effect React Hook props 组件 useEffect


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

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

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

  2. 组件运行时 Updating

  3. 组件卸载时 Unmounting

https://img1.sycdn.imooc.com/5d2d98b30001509e07230556.jpg

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],
);

 

 

 

 

 

 

 

 

标签:调用,Effect,effect,React,Hook,props,组件,useEffect
来源: https://blog.51cto.com/u_10976476/2845741

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有