一個訂閱好友在線的組件
我們在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 中的任何值,所以它永遠都不需要重複執行。這並不屬於特殊情況 —— 它依然遵循依賴數組的工作方式。