如何在expo-router模态框头部添加带保存功能的HeaderButton
解决方案:将保存按钮移至 Expo Router 模态框头部并保留组件内逻辑
你的核心需求是不把业务逻辑移到路由布局文件,同时将保存按钮放到模态框的头部,以下是可行的实现方案,核心思路是通过组件引用(Ref)让模态页面调用表单组件的提交方法,同时保持逻辑封装在表单组件内:
步骤1:改造表单组件,暴露提交方法
修改 AddRecipeForm.tsx,使用 forwardRef 和 useImperativeHandle 将表单的提交方法暴露给父组件,同时移除组件内的保存按钮:
import React, { useCallback, useImperativeHandle, forwardRef } from 'react'; import { useRealm } from '@realm/react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { StyleSheet, View } from 'react-native'; import { router } from 'expo-router'; import { TextField } from '@components/common/fields/TextField'; import { Recipe } from '@models/Recipe'; import colors from '@styles/colors'; interface AddRecipeFormProps { userId?: string; } interface FormData { userId?: string; description: string; name: string; } // 定义组件Ref的类型,明确暴露的方法 interface AddRecipeFormHandle { submit: () => void; } export const AddRecipeForm = forwardRef<AddRecipeFormHandle, AddRecipeFormProps>(({ userId, }, ref) => { const { control, handleSubmit } = useForm<FormData>(); const realm = useRealm(); const handleAddRecipe = useCallback( (name: string, description: string): void => { if (!description && !name) return; realm.write(() => { realm.create(Recipe, { name, description, userId: userId ?? 'SYNC_DISABLED', }); }); }, [realm, userId], ); const onSubmit: SubmitHandler<FormData> = (data) => { handleAddRecipe(data.name, data.description); router.back(); // 提交成功后关闭模态框 }; // 向父组件暴露submit方法 useImperativeHandle(ref, () => ({ submit: handleSubmit(onSubmit), })); return ( <View style={styles.container}> <Controller control={control} render={({ field: { onChange, value } }) => ( <TextField value={value} onChangeText={onChange} placeholder="name" /> )} name="name" /> <Controller control={control} render={({ field: { onChange, value } }) => ( <TextField value={value} onChangeText={onChange} placeholder="description" /> )} name="description" /> </View> ); }); AddRecipeForm.displayName = 'AddRecipeForm'; const styles = StyleSheet.create({ container: { padding: 16, backgroundColor: colors.white, flex: 1, }, });
步骤2:在模态页面动态设置头部按钮
修改 modal.tsx,通过 useRef 获取表单组件的实例,再用 useLayoutEffect 动态设置模态框的头部右侧按钮:
import React, { useRef, useLayoutEffect } from 'react'; import { StatusBar } from 'expo-status-bar'; import { View } from 'react-native'; import { useNavigation } from 'expo-router'; import { AddRecipeForm } from '@screens/recipe/AddRecipeForm'; import { HeaderButton } from '@components/common/fields/HeaderButton'; // 复用表单组件的Ref类型 interface AddRecipeFormHandle { submit: () => void; } export default function Modal() { const formRef = useRef<AddRecipeFormHandle>(null); const navigation = useNavigation(); useLayoutEffect(() => { // 动态设置头部右侧的保存按钮 navigation.setOptions({ headerRight: () => ( <HeaderButton displayText="Save" handleClick={() => formRef.current?.submit()} /> ), }); }, [navigation]); return ( <View style={{ flex: 1 }}> <StatusBar style="light" /> <AddRecipeForm ref={formRef} /> </View> ); }
步骤3:调整路由布局文件
修改 _layout.tsx,移除模态框页面中注释的 headerRight 配置,保留原有的头部标题设置即可:
import React from 'react'; import { RealmProvider } from '@realm/react'; import { router, Stack } from 'expo-router'; import { HeaderButton } from '@components/common/fields/HeaderButton'; import { schemas } from '@models/index'; export default function Layout() { return ( <RealmProvider schema={schemas} deleteRealmIfMigrationNeeded={true}> <Stack> <Stack.Screen name="index" options={{ headerShown: false, }} /> <Stack.Screen name="modal" options={{ presentation: 'modal', headerTitle: () => ( <HeaderButton displayText="<" handleClick={() => router.back()} /> ), // 移除原有的headerRight注释代码,由modal页面自行处理 }} /> </Stack> </RealmProvider> ); }
方案说明
- 职责分离:表单的业务逻辑(Realm操作、表单验证)依然保留在
AddRecipeForm组件内,符合组件设计原则。 - 上下文兼容:模态页面和表单组件都处于
RealmProvider内部,不会出现Realm上下文错误。 - 动态头部配置:通过
useLayoutEffect在模态页面渲染时设置头部按钮,确保能正确访问表单组件的提交方法。
内容的提问来源于stack exchange,提问作者Jand
相关产品推荐
相关产品推荐

