React Native传递useState setter函数遇序列化警告的解决方法
问题场景
你在React Native里想通过navigation.navigate把多个useState的setter函数传给Profile页面,当前代码如下:
const [firstName, setfirstName] = useState(''); const [dob, setDOB] = useState(''); const [medicalConditions, setMedicalConditions] = useState(''); const [allergies, setAllergies] = useState(''); const [bloodtype, setBloodtype] = useState(''); const [height, setHeight] = useState(''); const [weight, setWeight] = useState(''); return ( <KeyboardAvoidingView style={styles.container} behavior="padding"> <View style={styles.buttonContainer}> <TouchableOpacity onPress={() => navigation.navigate('Profile', { setMedicalConditions: (a) => setMedicalConditions(a), setAllergies: (b) => setAllergies(b), setBloodtype: (c) => setBloodtype(c), setHeight: (d) => setHeight(d), setWeight: (e) => setWeight(e) })} style={[styles.button, styles.buttonOutline]}> <Text style={styles.buttonText}>Edit</Text> </TouchableOpacity> </View> </KeyboardAvoidingView> )
代码能正常运行,但会收到以下警告:
Non-serializable values were found in the navigation state. Check:
Profile > params.setMedicalConditions (Function)
This can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead.
你试过用DeviceEventEmitter但该API已废弃,需要其他解决办法。
解决办法
方法1:用React Context共享状态和setter
把需要跨页面共享的状态和setter放到Context里,这样Profile页面可以直接从Context获取setter,不用通过导航参数传递,从根源解决序列化问题。
- 创建
ProfileContext.js:
import { createContext, useContext, useState } from 'react'; const ProfileContext = createContext(); export function ProfileProvider({ children }) { const [firstName, setFirstName] = useState(''); const [dob, setDOB] = useState(''); const [medicalConditions, setMedicalConditions] = useState(''); const [allergies, setAllergies] = useState(''); const [bloodtype, setBloodtype] = useState(''); const [height, setHeight] = useState(''); const [weight, setWeight] = useState(''); return ( <ProfileContext.Provider value={{ firstName, setFirstName, dob, setDOB, medicalConditions, setMedicalConditions, allergies, setAllergies, bloodtype, setBloodtype, height, setHeight, weight, setWeight }}> {children} </ProfileContext.Provider> ); } export function useProfile() { return useContext(ProfileContext); }
- 在根组件(比如
App.js)包裹ProfileProvider:
import { ProfileProvider } from './ProfileContext'; export default function App() { return ( <ProfileProvider> {/* 你的导航组件和其他页面 */} </ProfileProvider> ); }
- 原页面不用传参数,直接跳转:
<TouchableOpacity onPress={() => navigation.navigate('Profile')} style={[styles.button, styles.buttonOutline]}> <Text style={styles.buttonText}>Edit</Text> </TouchableOpacity>
- Profile页面直接用
useProfile获取setter:
import { useProfile } from './ProfileContext'; function Profile() { const { setMedicalConditions, setAllergies, setBloodtype, setHeight, setWeight } = useProfile(); // 直接调用这些setter即可更新状态 return ( {/* 你的编辑界面 */} ); }
方法2:编辑完成后返回数据给父页面
不用传递setter,而是把当前的状态数据传给Profile页面,编辑完成后把新数据带回来,父页面再更新状态。
- 父页面跳转时传当前数据:
<TouchableOpacity onPress={() => navigation.navigate('Profile', { currentProfile: { medicalConditions, allergies, bloodtype, height, weight } })} style={[styles.button, styles.buttonOutline]}> <Text style={styles.buttonText}>Edit</Text> </TouchableOpacity>
- Profile页面接收数据,编辑完成后返回:
import { useRoute, useNavigation } from '@react-navigation/native'; function Profile() { const route = useRoute(); const navigation = useNavigation(); const [medicalConditions, setMedicalConditions] = useState(route.params.currentProfile.medicalConditions); // 其他状态同理初始化 const handleSave = () => { // 把编辑后的数据返回给父页面 navigation.navigate('原来的页面名称', { updatedProfile: { medicalConditions, allergies, bloodtype, height, weight } }); }; return ( {/* 编辑界面,最后加个保存按钮调用handleSave */} ); }
- 父页面监听返回的更新数据:
用useFocusEffect监听页面重新聚焦时的参数变化:
import { useFocusEffect } from '@react-navigation/native'; import { useCallback } from 'react'; // 父页面里 useFocusEffect( useCallback(() => { const updatedProfile = route.params?.updatedProfile; if (updatedProfile) { setMedicalConditions(updatedProfile.medicalConditions); setAllergies(updatedProfile.allergies); setBloodtype(updatedProfile.bloodtype); setHeight(updatedProfile.height); setWeight(updatedProfile.weight); // 清空参数,避免重复触发 navigation.setParams({ updatedProfile: undefined }); } }, [route.params, navigation]) );
方法3:用轻量状态管理库(比如Zustand)
如果你的状态需要在更多组件里共享,用Zustand会比Context更简洁。
- 安装Zustand:
npm install zustand # 或者 yarn add zustand
- 创建store:
import { create } from 'zustand'; export const useProfileStore = create((set) => ({ firstName: '', dob: '', medicalConditions: '', allergies: '', bloodtype: '', height: '', weight: '', setMedicalConditions: (value) => set({ medicalConditions: value }), setAllergies: (value) => set({ allergies: value }), setBloodtype: (value) => set({ bloodtype: value }), setHeight: (value) => set({ height: value }), setWeight: (value) => set({ weight: value }), }));
- 父页面直接用store的状态:
import { useProfileStore } from './profileStore'; function ParentPage() { const { medicalConditions, allergies } = useProfileStore(); return ( <KeyboardAvoidingView style={styles.container} behavior="padding"> <View style={styles.buttonContainer}> <TouchableOpacity onPress={() => navigation.navigate('Profile')} style={[styles.button, styles.buttonOutline]}> <Text style={styles.buttonText}>Edit</Text> </TouchableOpacity> </View> </KeyboardAvoidingView> ); }
- Profile页面直接调用store的setter:
import { useProfileStore } from './profileStore'; function Profile() { const { setMedicalConditions, setAllergies } = useProfileStore(); // 直接调用setter更新状态 return ( {/* 编辑界面 */} ); }
临时方案:屏蔽警告(不推荐)
如果只是想暂时去掉警告,不解决根本问题,可以在导航配置里自定义序列化器,但这样会破坏状态持久化的功能,不建议长期使用:
import { NavigationContainer } from '@react-navigation/native'; export default function App() { return ( <NavigationContainer serializer={{ serialize: (state) => JSON.stringify(state), deserialize: (str) => JSON.parse(str), warnNonSerializable: false, }} > {/* 你的导航结构 */} </NavigationContainer> ); }
内容的提问来源于stack exchange,提问作者kirasam72

