结合React的Effect-Hook分析组件副作用的清除

42次阅读

共计 3264 个字符,预计需要花费 9 分钟才能阅读完成。

一个订阅好友在线的组件

我们在 DidMount 的时候通过 ID 订阅了好友的在线状态
并且为了防止内存泄漏,我们需要在 WillUnmount 清除订阅

但是当组件已经显示在屏幕上时,friend prop 发生变化时会发生什么?我们的组件将继续展示原来的好友状态。这是一个 bug。而且我们还会因为取消订阅时使用错误的好友 ID 导致内存泄露或崩溃的问题。

class FriendStatus extends React.Component {constructor(props) {super(props);
    this.state = {isOnline: null};
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({isOnline: status.isOnline});
  }

  render() {if (this.state.isOnline === null) {return 'Loading...';}
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

优化订阅好友在线的组件

为了解决 props 更新导致的 BUG 我们需要在 DidUpdate 中进行修改订阅


  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
    );
  }

引入 Hooks 代码会变得十分简单

Effect Hook

useEffect() 可以让你在函数组件中执行副作用操作
默认情况下,它在第一次渲染之后和每次更新之后都会执行。

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

这是 React 官网最基础的 Hooks 应用

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>
  );
}

需要清除的 effect

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

React 何时清除 effect?React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。

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';

现在假设 props.friend.id 在更新 100->200->300
我们并不需要特定的代码来处理更新逻辑,因为 useEffect 默认就会处理。它会在调用一个新的 effect 之前对前一个 effect 进行清理。这样基于不会产生之前因为 Props 改变而产生的 BUG

// Mount with {friend: { id: 100} } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 运行第一个 effect

// Update with {friend: { id: 200} } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 运行下一个 effect

// Update with {friend: { id: 300} } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 运行下一个 effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一个 effect

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

参考文献

  • https://react-1251415695.cos-…

正文完
 0