跳到主要内容
版本:7.x

useFocusEffect

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

为了使这更容易,该库导出了一个 useFocusEffect Hook

import { useFocusEffect } from '@react-navigation/native';

function ProfileScreen() {
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, [])
);

return <View />;
}
Snack 上尝试
警告

为了避免过于频繁地运行 effect,重要的是在将回调传递给 useFocusEffect 之前,将其包裹在 useCallback 中,如示例所示。

useFocusEffect 类似于 React 的 useEffect Hook。唯一的区别是它只在屏幕当前获得焦点时运行。

每当传递给 React.useCallback 的依赖项发生更改时,effect 都会运行,即,如果屏幕获得焦点,它将在初始渲染时运行,以及在依赖项已更改的后续渲染时运行。 如果你不将 effect 包裹在 React.useCallback 中,如果屏幕获得焦点,effect 将在每次渲染时运行。

当需要清理先前的 effect 时,清理函数会运行,即,当依赖项更改并且计划新的 effect 时,以及当屏幕卸载或失焦时。

运行异步 effect

当运行异步 effect(例如从服务器获取数据)时,重要的是要确保在清理函数中取消请求(类似于 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 调用中的竞争条件,你可能会最终得到不一致的数据。

延迟 effect 直到过渡完成

useFocusEffect Hook 在屏幕获得焦点后立即运行 effect。 这通常意味着,如果屏幕更改存在动画,则动画可能尚未完成。

React Navigation 在原生线程中运行其动画,因此在许多情况下这不是问题。 但是,如果 effect 更新 UI 或渲染某些昂贵的东西,则可能会影响动画性能。 在这种情况下,我们可以使用 InteractionManager 来延迟我们的工作,直到动画或手势完成

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

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

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

当屏幕获得焦点时,会触发 focus 事件。 由于它是一个事件,如果屏幕在你订阅该事件时已获得焦点,则不会调用你的监听器。 这也没有提供一种在屏幕失去焦点时执行清理函数的方法。 你可以订阅 blur 事件并手动处理它,但这可能会变得混乱。 除了这些事件之外,你通常还需要处理 componentDidMountcomponentWillUnmount,这会使情况更加复杂。

useFocusEffect 允许你在获得焦点时运行 effect,并在屏幕失去焦点时清理它。 它还在卸载时处理清理。 当依赖项更改时,它会重新运行 effect,因此你无需担心监听器中的陈旧值。

何时改用 focusblur 事件

useEffect 类似,可以从 useFocusEffect 中的 effect 返回清理函数。 清理函数旨在清理 effect - 例如,中止异步任务、取消订阅事件监听器等。 它不旨在用于在 blur 上执行某些操作。

例如,不要这样做

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

每当 effect 需要清理时,清理函数都会运行,即在 blur、卸载、依赖项更改等时。 它不是更新状态或执行应在 blur 上发生的操作的好地方。 你应该监听 blur 事件。

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

return unsubscribe;
}, [navigation]);

同样,如果你想在屏幕接收焦点时执行某些操作(例如,跟踪屏幕焦点),并且它不需要清理或不需要在依赖项更改时重新运行,那么你应该改用 focus 事件

与类组件一起使用

你可以为你的 effect 创建一个组件,并在你的类组件中使用它

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