跳至主要内容
版本: 6.x

useFocusEffect

有时我们希望在屏幕获得焦点时运行副作用。副作用可能包括添加事件监听器、获取数据、更新文档标题等。虽然可以使用 focusblur 事件来实现这一点,但它并不十分符合人体工程学。

为了简化操作,库导出一个 useFocusEffect hook

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 Navigation 在原生线程中运行其动画,因此在许多情况下这不是问题。但是,如果效果更新 UI 或渲染一些昂贵的操作,那么它可能会影响动画性能。在这种情况下,我们可以使用 InteractionManager 将我们的工作推迟到动画或手势完成为止。

useFocusEffect(
React.useCallback(() => {
const task = InteractionManager.runAfterInteractions(() => {
// Expensive task
});

return () => task.cancel();
}, [])
);

useFocusEffect 与添加 focus 事件监听器有何不同

当屏幕进入焦点时,focus 事件会触发。由于它是一个事件,如果屏幕在您订阅事件时已经处于焦点状态,则您的监听器不会被调用。这也不提供一种在屏幕失去焦点时执行清理函数的方法。您可以订阅 blur 事件并手动处理它,但这可能会变得很乱。您通常还需要处理 componentDidMountcomponentWillUnmount 以及这些事件,这会使它更加复杂。

useFocusEffect 允许您在获得焦点时运行一个效果,并在屏幕失去焦点时清理它。它还在卸载时处理清理。当依赖项发生变化时,它会重新运行效果,因此您无需担心监听器中的陈旧值。

何时使用 focusblur 事件代替

useEffect 一样,可以在 useFocusEffect 中的效果中返回一个清理函数。清理函数旨在清理效果 - 例如,中止异步任务、取消订阅事件监听器等。它不打算用于在 blur 上执行操作。

例如,**不要执行以下操作**

useFocusEffect(
React.useCallback(() => {
return () => {
// Do something that should run on blur
};
}, [])
);

清理函数在效果需要清理时运行,即在 blur、卸载、依赖项更改等情况下。它不是更新状态或执行应该在 blur 上发生的操作的好地方。您应该改为使用监听 blur 事件

React.useEffect(() => {
const unsubscribe = navigation.addListener('blur', () => {
// Do something when the screen blurs
});

return unsubscribe;
}, [navigation]);

类似地,如果您想在屏幕获得焦点时执行某些操作(例如,跟踪屏幕焦点),并且它不需要清理或需要在依赖项更改时重新运行,那么您应该使用 focus 事件代替

与类组件一起使用

您可以为您的效果创建一个组件,并在您的类组件中使用它

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