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

如何让Yup Schema读取React应用中其他位置的状态?

问题描述

背景

我正在学习React+TypeScript,做一个基础的待办事项列表应用。Yup校验规则写在单独的文件里,App.tsx里存着整个应用的状态,用createContext/useContext做属性透传,Formik+Yup的表单校验目前运行正常。

需求

想给Yup加个自定义校验:检查用户输入的项目名称是否已经在App.tsx的allProjects状态里存在,防止创建同名项目报错,但不知道怎么实现,也不确定有没有更简便的方案。

已尝试的代码

我试着通过Context获取数据,用Yup的test方法检查项目名是否在allProjects的键集合中,但代码无法正常运行:

import * as yup from "yup";
import parse from "date-fns/parse";
import React, {useContext} from "react";
import { AllContext } from "../App";


// 尝试读取App.tsx中声明的state
const {allProjects, selectedProject, currentTask} = React.useContext(AllContext);
const [allProjectsCopy, setAllProjects] = allProjects;
let keys = Object.keys(allProjectsCopy);


export const projectNameSchema = yup.object().shape({
    projectName: yup
    .string()
    .min(2)
    .test('project-exists', 'Project already exists', value => !(keys.includes(value)))
    .required("Required"),
});
解决方案

你的代码为啥跑不起来?

  • Yup的Schema是静态创建的,在组件渲染前就会执行,而useContext只能在React组件或自定义Hook内部调用,直接在Schema文件中使用useContext违反了React的Hook规则,根本无法正确获取allProjects状态。
  • 就算侥幸拿到数据,keys也是Schema创建时的旧数据快照,不会随着allProjects的状态更新同步,校验逻辑永远用的是过时数据。

正确实现方式

方法1:将Schema改为工厂函数,在组件内动态生成

把Schema定义成接收allProjects参数的函数,在组件中获取Context数据后再生成Schema,这样每次状态更新都会生成最新的校验规则:

// schema.ts
import * as yup from "yup";

// 定义工厂函数,接收allProjects作为参数
export const createProjectNameSchema = (allProjects: Record<string, any>) => {
  const keys = Object.keys(allProjects);
  return yup.object().shape({
    projectName: yup
      .string()
      .min(2)
      .test('project-exists', 'Project already exists', value => {
        if (!value) return true; // 空值交给required校验处理
        return !keys.includes(value);
      })
      .required("Required"),
  });
};

在使用Formik的组件中调用:

import { useContext } from "react";
import { Formik, Form, Field } from "formik";
import { AllContext } from "../App";
import { createProjectNameSchema } from "./schema";

const ProjectForm = () => {
  const { allProjects } = useContext(AllContext);
  const [allProjectsCopy] = allProjects;
  // 动态生成最新的Schema
  const projectNameSchema = createProjectNameSchema(allProjectsCopy);

  return (
    <Formik
      initialValues={{ projectName: "" }}
      validationSchema={projectNameSchema}
      onSubmit={(values) => {
        // 处理提交逻辑
      }}
    >
      {({ errors, touched }) => (
        <Form>
          <Field name="projectName" placeholder="输入项目名" />
          {errors.projectName && touched.projectName && <div className="error">{errors.projectName}</div>}
          <button type="submit">创建项目</button>
        </Form>
      )}
    </Formik>
  );
};

export default ProjectForm;

方法2:直接使用Formik的validate自定义校验函数

如果不想修改Yup Schema,也可以直接在Formik中使用validate属性,直接访问组件内的allProjects状态:

const ProjectForm = () => {
  const { allProjects } = useContext(AllContext);
  const [allProjectsCopy] = allProjects;
  const keys = Object.keys(allProjectsCopy);

  const validate = (values: { projectName: string }) => {
    const errors: { projectName?: string } = {};
    if (!values.projectName) {
      errors.projectName = "必填项";
    } else if (values.projectName.length < 2) {
      errors.projectName = "至少输入2个字符";
    } else if (keys.includes(values.projectName)) {
      errors.projectName = "该项目名已存在";
    }
    return errors;
  };

  return (
    <Formik
      initialValues={{ projectName: "" }}
      validate={validate}
      onSubmit={(values) => {
        // 处理提交逻辑
      }}
    >
      {/* 表单内容和之前一致 */}
    </Formik>
  );
};

方案对比

  • 方法1保留了Yup的链式校验风格,适合复杂表单场景,校验规则可复用。
  • 方法2更简洁直接,适合简单校验场景,无需额外维护Schema函数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 20:30:55