我认识的useEffet

引入:什么是Hook

Hook 是什么? Hook 是一个特殊的函数,它可以让你**“钩入” React 的特性**。它们名字通常都以 use 开始。

例如,useEffect 是允许你在 React 函数组件中添加 "effect" 的 Hook。

基本使用

useEffect接受两个参数,第一个参数为"effect"函数,第二个参数为依赖项数组,是可选参数。

"effect"函数:在"effect"中,进行一些副作用的操作。如果副作用的操作需要清除,"effect"可以返回一个函数。每次要执行更新的"effect"之前,都会先执行这个函数,进行副作用的清除工作。

依赖项数组:如果依赖数组中的项在两次重渲染之间没有发生变化,你可以通知React跳过对"effect"的调用。

  • 依赖项数组为空数组,那么"effect"只会执行一次。因为你的"effect" 不依赖于 propsstate中的任何值,所以它永远都不需要重复执行。
  • 如果依赖项数组没有填写,那么"effect"每次渲染都执行

应用&场景(如何使用useEffect替代生命周期函数)

在探讨useEffect的应用之前,我们可以先看一下下面这张react的生命周期图。

众所周知,render 函数是不应该有任何副作用的。因为在这里执行操作太早了,我们基本上都希望在render后才执行我们的操作。

由图可知, componentDidMountcomponentDidUpdate 都是在render后执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGoZkO8j-1592908559641)(images/我理解的useEffect.png)]

useEffect会在每次render后执行。

因此在此处,我们可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdate 这两个函数的组合,它的作用是在函数中执行副作用。让我们看看以下三个场景。

场景一:页面初始化

在第一次渲染之后发起一个ajax请求获取数据。

  componentDidMount() {
      //发起请求
  }
	
	||
    
  useEffect(()=>{
    //发起请求
  },[])
场景二:每次渲染后,更新document.title
 function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
     document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

  componentDidMount() {
        document.title = `You clicked ${this.state.count} times`;
    }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

    ||

  useEffect(() => {
      document.title = `You clicked ${count} times`;
 	});

场景三:每次渲染后,只根据某个变量的值来进行更新
  componentDidUpdate(prevProps, prevState) {
    if (prevState.count !== this.state.count) {
      document.title = `You clicked ${this.state.count} times`;
    }
  }

  ||

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

以上三种场景都是不需要清除的副作用。

而副作用有两种

  • 不需要清除的副作用。例如数据获取,设置订阅以及手动更改 React 组件中的DOM
  • 需要清除的副作用。例如订阅外部数据源

对于需要清除的副作用来说,useEffect相当于 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

场景四:订阅外部数据源。

如果**friend prop**发生变化,我们必须取消之前的订阅,再订阅新的数据源。

 //以下的场景为:组件第一次渲染后订阅数据源,friend prop发生变化时取消订阅。
 	componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate(prevProps) {
    // 取消订阅之前的 friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // 订阅新的 friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
	
		||
      
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  }); 
场景五: 每次渲染后,更新document.title,同时更新订阅
 componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
    // 取消订阅之前的 friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // 订阅新的 friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
	||
	useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

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

相比生命周期函数的优势

  1. 多个useEffect实现关注点分离:如场景五,useEffect相当于和componentDidMountcomponentDidUpdatecomponentWillUnmount这三个函数的组合 ,它既可以把分散在多个生命周期的相关代码聚合在一个useEffect中,也可以把聚合在一个生命中期的无关代码分散在多个useEffect当中。

    并且代码中的useEffect会按书写顺序由上而下的执行。每个"effect"返回的清除函数在下次的"effect"执行之前,也会由上到下的执行。

    场景:屏幕中有一个按钮,count的初始值为0,点击按钮之后,count的数目会加一。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNehlZ6a-1592908559645)(images/image-20200622134005225.png)]

 function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log('先执行的逻辑1',count);
    return ()=>{
      console.log('逻辑1的副作用要先被清除了');
    }
  },[count]);
  useEffect(() => {
    console.log('后执行的逻辑2',count);
    return ()=>{
      console.log('逻辑2的副作用也要被清除了');
    }
  },[count]);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

下图为点击了一次按钮后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ANhR257b-1592908559646)(images/image-20200621163809279.png)]

  1. componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 "effect" 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,"effect" 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

踩的一些坑&注意事项⚠️

  1. 初始值就算是一个变量,也只能初始化一次
function App() {
  const [num,setNum] = useState(0);
  const [count, setCount] = useState(num);
  useEffect(() => {
    console.log(num)
    console.log(count);
  });
  return (
    <div>
      <button onClick={() => setNum(num + 1)}>
        Click me
      </button>
    </div>
  );
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aa79SwzW-1592908559648)(images/image-20200622153536732.png)]

  1. 出现死循环的问题

场景:你在useEffect中发起了一个请求,并且没有设置依赖项。并根据请求的结果改变了组件的状态。组件因为状态的改变重新渲染。由于你没有设置依赖项,所以在重新渲染后useEffect再次执行,形成了死循环。

  1. 不要欺骗依赖项

场景:设置一个定时器,并且在定时器不需要使用的时候进行清除。

一开始我们没有填写依赖项。企图在设置了定时器之后清除,但是由于没有依赖项,"effect"不会更新,

"effect"中的state和props是固化的,所以只是重复的进行setCount(0+1)的动作。又因为,清除的动作是在"effect"更新之前执行的,因此清除动作也不会执行。

useEffect(() => {
    const id = setInterval(() => {
      console.log('设置')
      setCount(count+1);
    }, 1000);
    return () => {
      console.log('清除')
      return clearInterval(id);
    }
  }, []);

如果我们填写了依赖项呢?

useEffect(() => {
    const id = setInterval(() => {
      console.log('设置')
      setCount(count+1);
    }, 1000);
    return () => {
      console.log('清除')
      return clearInterval(id);
    }
  }, [count]);

此时在count更新的过程中,会不断生成新的"effect",因此会不断的设置和清除定时器,会造成一定的时差以及性能损耗。

那此时我们要怎么办呢 => 抽除依赖项count

此时的useEffect不再依赖count变量,它可以成功的设置定时器,并且进行count变量的更新。

useEffect(() => {
    const id = setInterval(() => {
      console.log('设置')
      setCount(c=>c+1);
    }, 1000);
    return () => {
      console.log('清除')
      return clearInterval(id);
    }
}, []);
  1. 不能用判断包裹useEffect

  1. 在effect里拿到的是旧的state或prop

Effect拿到的总是定义它的那次渲染中的props和state。

深入useEffect的原理

本文参考

react官方文档(使用 Effect Hook)

精读《useEffect 完全指南》

终于搞懂 React Hooks了!!!!!

React Hooks 详解 【近 1W 字】+ 项目实战

useEffect完整指南

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值