React Navigation:如何检测新屏幕已完全完成聚焦?
我太懂这个坑了!你提到的navigation.isFocused()和withNavigationFocus的isFocused属性,确实会在导航刚启动、转场动画还在运行的时候就返回true,这对相机、地图这类渲染开销极大的组件来说简直是灾难——提前加载不仅浪费资源,还可能导致转场卡顿甚至组件崩溃。
下面给你两个精准适配需求的解决方案,分别对应函数组件和类组件:
方案1:函数组件用useFocusEffect钩子
React Navigation专门提供了useFocusEffect,它会在屏幕完全完成转场、真正处于聚焦状态时执行回调,还自带清理机制,完美适配你的场景:
import { useFocusEffect } from '@react-navigation/native'; import React, { useState, useCallback } from 'react'; import { View, StyleSheet } from 'react-native'; import { Camera } from 'expo-camera'; export default function CameraScreen() { const [hasCameraPermission, setHasCameraPermission] = useState(null); const [cameraActive, setCameraActive] = useState(false); // 转场结束后激活相机,失去焦点时关闭 useFocusEffect( useCallback(() => { setCameraActive(true); // 清理函数:屏幕失去焦点时触发 return () => setCameraActive(false); }, []) ); // 相机权限申请逻辑 React.useEffect(() => { (async () => { const { status } = await Camera.requestCameraPermissionsAsync(); setHasCameraPermission(status === 'granted'); })(); }, []); return ( <View style={styles.container}> {/* 只有当屏幕真正聚焦且权限通过时,才渲染相机 */} {cameraActive && hasCameraPermission && ( <Camera style={styles.camera} type={Camera.Constants.Type.back} /> )} </View> ); } const styles = StyleSheet.create({ container: { flex: 1 }, camera: { flex: 1 }, });
这个钩子的核心优势是:只会在屏幕完全进入、转场动画结束后执行激活逻辑,返回的清理函数会在屏幕被推开(比如push新页面)或者返回上一页时自动触发,刚好用来销毁重组件,避免内存泄漏。
方案2:类组件用导航事件监听
如果你的项目还在使用类组件,可以通过监听focus和blur事件来实现同样的效果:
import React, { Component } from 'react'; import { View, StyleSheet } from 'react-native'; import { Camera } from 'expo-camera'; class MapScreen extends Component { state = { mapActive: false, hasCameraPermission: null, }; componentDidMount() { // 监听屏幕聚焦事件(转场结束后触发) this.focusListener = this.props.navigation.addListener('focus', () => { this.setState({ mapActive: true }); }); // 监听屏幕失焦事件,用来清理组件 this.blurListener = this.props.navigation.addListener('blur', () => { this.setState({ mapActive: false }); }); // 权限申请逻辑 (async () => { const { status } = await Camera.requestCameraPermissionsAsync(); this.setState({ hasCameraPermission: status === 'granted' }); })(); } componentWillUnmount() { // 移除监听,避免内存泄漏 this.focusListener(); this.blurListener(); } render() { const { mapActive, hasCameraPermission } = this.state; return ( <View style={styles.container}> {mapActive && hasCameraPermission && ( <Camera style={styles.camera} type={Camera.Constants.Type.back} /> )} </View> ); } } const styles = StyleSheet.create({ container: { flex: 1 }, camera: { flex: 1 }, });
为什么之前的方法不好用?
navigation.isFocused()和withNavigationFocus的isFocused属性,判断的是组件是否已经挂载——当导航开始转场时,目标组件就已经开始挂载了,这时候isFocused会直接返回true,但转场动画还在进行,屏幕并没有真正处于“可用”状态。而上面的两种方案,都是监听屏幕真正完成聚焦的事件,完全避开了这个问题。
额外小提示
如果是自定义转场动画,或者需要更精确的时机,可以结合React Navigation的transitionEnd事件,但绝大多数场景下,focus事件和useFocusEffect已经足够满足需求啦。
内容的提问来源于stack exchange,提问作者blaz

