useFocusEffect
有時候,我們會希望在畫面獲得焦點時執行動作。這個動作可能包含新增事件聆聽器、擷取資料、更新文件標題等。雖然可以使用 focus
和 blur
事件就能達成,但這樣做不太符合人體工學。
為了讓這件事變簡單,此函式庫匯出了 useFocusEffect
掛勾
import { useFocusEffect } from '@react-navigation/native';
function Profile({ userId }) {
const [user, setUser] = React.useState(null);
useFocusEffect(
React.useCallback(() => {
const unsubscribe = API.subscribe(userId, (user) => setUser(user));
return () => unsubscribe();
}, [userId])
);
return <ProfileContent user={user} />;
}
為避免動作執行太過頻繁,很重要的一點要在將回呼函式傳遞給 useFocusEffect
之前先將其包在 useCallback
裡,如同範例所示。
useFocusEffect
類似於 React 的 useEffect
掛勾。唯一不同的是只有畫面目前獲得焦點時才會執行。
當傳遞至 React.useCallback
的相依項變更時,這個動作便會執行,亦即會在初始呈現 (如果畫面獲得焦點) 時執行,以及後續呈現時 (如果相依項有變更)。如果您未將動作包在 React.useCallback
裡,則畫面獲得焦點時就會執行這個動作。
清理函式在需要清理上一個效果時執行,例如當相依項變更且安排新效果時,以及當畫面卸載或模糊時。
執行非同步效果
當執行非同步效果(例如從伺服器擷取資料)時,務必確認你在清理函式中取消要求(類似於 React.useEffect
)。如果你使用沒有提供取消機制的 API,請務必略過狀態更新
useFocusEffect(
React.useCallback(() => {
let isActive = true;
const fetchUser = async () => {
try {
const user = await API.fetch({ userId });
if (isActive) {
setUser(user);
}
} catch (e) {
// Handle error
}
};
fetchUser();
return () => {
isActive = false;
};
}, [userId])
);
如果你沒有略過結果,你可能會因為 API 呼叫中的競爭狀態而得到不一致的資料。
推遲效果直到轉場結束
useFocusEffect
掛勾會在畫面切換為焦點後立即執行效果。這通常意謂著如果畫面變更中有動畫,它可能尚未結束。
React 導覽在原生執行緒中執行其動畫,因此在許多案例中這不是問題。但如果效果更新 UI 或呈現某個昂貴的東西,那麼它會影響動畫效能。在這種情況下,我們可以使用 InteractionManager
來延後我們的作業,直到動畫或手勢結束
useFocusEffect(
React.useCallback(() => {
const task = InteractionManager.runAfterInteractions(() => {
// Expensive task
});
return () => task.cancel();
}, [])
);
useFocusEffect
與新增 focus
事件的監聽方式有何不同
當畫面切換為焦點時,focus
事件會觸發。因為它是事件,如果你在訂閱事件時畫面已經具有焦點,你的監聽不會被呼叫。這也無法提供一種在畫面失去焦點時執行清理函式的方法。你可以訂閱 blur
事件並手動處理,但可能會很混亂。通常你也需要處理 componentDidMount
和 componentWillUnmount
,此外還要處理這些事件,這讓它更複雜。
useFocusEffect
讓你能夠在焦點時執行一個效果,然後在畫面失去焦點時清理它。它也會在卸載時處理清理。它會在相依項變更時重新執行效果,因此你不用擔心監聽器中的值過時。
在什麼時候改用 focus
和 blur
事件
像 useEffect
,一個清理函式可以從 useFocusEffect
中的效果傳回。清理函式的目的是清理效果 - 例如中止非同步工作、取消訂閱事件監聽等等。它不是用來在 blur
中做某件事的。
例如,請勿執行以下操作
useFocusEffect(
React.useCallback(() => {
return () => {
// Do something that should run on blur
};
}, [])
);
清除功能會在效果需要清除時執行,例如在「模糊」、卸載或變更依賴項時執行。這並不是更新狀態或執行應在「模糊」時發生的動作的好地方。您應該改為傾聽「模糊」事件
React.useEffect(() => {
const unsubscribe = navigation.addListener('blur', () => {
// Do something when the screen blurs
});
return unsubscribe;
}, [navigation]);
此外,如果您想要在畫面獲得焦點時執行動作(例如追蹤畫面焦點),而不需要清除或不需要在依賴項變更時重新執行,則應改為使用「焦點」事件
與類別元件一起使用
您可以為您的效果製作一個元件,並在您的類別元件中使用它
function FetchUserData({ userId, onUpdate }) {
useFocusEffect(
React.useCallback(() => {
const unsubscribe = API.subscribe(userId, onUpdate);
return () => unsubscribe();
}, [userId, onUpdate])
);
return null;
}
// ...
class Profile extends React.Component {
_handleUpdate = (user) => {
// Do something with user object
};
render() {
return (
<>
<FetchUserData
userId={this.props.userId}
onUpdate={this._handleUpdate}
/>
{/* rest of your code */}
</>
);
}
}