You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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>
  );
}

方案说明

  1. 职责分离:表单的业务逻辑(Realm操作、表单验证)依然保留在 AddRecipeForm 组件内,符合组件设计原则。
  2. 上下文兼容:模态页面和表单组件都处于 RealmProvider 内部,不会出现Realm上下文错误。
  3. 动态头部配置:通过 useLayoutEffect 在模态页面渲染时设置头部按钮,确保能正确访问表单组件的提交方法。

内容的提问来源于stack exchange,提问作者Jand

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 09:50:06