useFocusEffect
有时我们希望在屏幕获得焦点时运行副作用。副作用可能涉及添加事件监听器、获取数据、更新文档标题等。虽然可以使用 focus
和 blur
事件来实现这一点,但它不是很符合人体工程学。
为了使这更容易,该库导出了一个 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 />;
}
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 />;
}
为了避免过于频繁地运行 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
事件并手动处理它,但这可能会变得混乱。 除了这些事件之外,你通常还需要处理 componentDidMount
和 componentWillUnmount
,这会使情况更加复杂。
useFocusEffect
允许你在获得焦点时运行 effect,并在屏幕失去焦点时清理它。 它还在卸载时处理清理。 当依赖项更改时,它会重新运行 effect,因此你无需担心监听器中的陈旧值。
何时改用 focus
和 blur
事件
与 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 */}
</>
);
}
}