useFocusEffect
有时我们希望在屏幕获得焦点时运行副作用。副作用可能包括添加事件监听器、获取数据、更新文档标题等。虽然可以使用 focus
和 blur
事件来实现这一点,但它并不十分符合人体工程学。
为了简化操作,库导出一个 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
事件并手动处理它,但这可能会变得很乱。您通常还需要处理 componentDidMount
和 componentWillUnmount
以及这些事件,这会使它更加复杂。
useFocusEffect
允许您在获得焦点时运行一个效果,并在屏幕失去焦点时清理它。它还在卸载时处理清理。当依赖项发生变化时,它会重新运行效果,因此您无需担心监听器中的陈旧值。
何时使用 focus
和 blur
事件代替
与 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 */}
</>
);
}
}