共计 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-…