react 小记

3 阅读8分钟

没什么好看的,请路过。

React 与 Ant Design 开发关键点

1. 定义变量

  • React 中变量可以通过 useState 定义。
  • useState 定义的变量会触发页面更新。

示例:

tsx
复制代码
import React, { useState } from 'react';

const MyComponent: React.FC = () => {
  const [count, setCount] = useState(0); // 定义 count 变量,初始值为 0

  const increment = () => setCount(count + 1); // 修改变量

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={increment}>增加计数</button>
    </div>
  );
};

export default MyComponent;

2. 使用 Ref

  • Ref 用于直接操作 DOM 或保存一些不会触发重新渲染的变量。
  • 在 React 中使用 useRef

示例:操作输入框:

tsx
复制代码
import React, { useRef } from 'react';

const MyInput: React.FC = () => {
  const inputRef = useRef<HTMLInputElement>(null); // 创建 ref

  const focusInput = () => {
    inputRef.current?.focus(); // 直接聚焦输入框
  };

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="输入内容" />
      <button onClick={focusInput}>聚焦输入框</button>
    </div>
  );
};

export default MyInput;

3. 父子组件传参

父组件可以通过 props 向子组件传递数据和回调函数。

父组件传递数据:

tsx
复制代码
import React from 'react';
import Child from './Child';

const Parent: React.FC = () => {
  const handleSubmit = (data: string) => {
    console.log('子组件传来的数据:', data);
  };

  return <Child name="React 新人" onSubmit={handleSubmit} />;
};

export default Parent;

子组件接收数据:

tsx
复制代码
import React from 'react';

interface ChildProps {
  name: string;
  onSubmit: (data: string) => void;
}

const Child: React.FC<ChildProps> = ({ name, onSubmit }) => {
  return (
    <div>
      <p>你好,{name}!</p>
      <button onClick={() => onSubmit('子组件提交的数据')}>提交数据</button>
    </div>
  );
};

export default Child;

4. 表单与提交数据

Ant Design 提供了强大的表单组件,可以方便地获取用户输入。

使用 Antd 表单组件:

tsx
复制代码
import React from 'react';
import { Form, Input, Button } from 'antd';

const MyForm: React.FC = () => {
  const [form] = Form.useForm(); // 创建表单实例

  const handleFinish = (values: any) => {
    console.log('表单提交数据:', values);
  };

  return (
    <Form form={form} onFinish={handleFinish}>
      <Form.Item
        label="用户名"
        name="username"
        rules={[{ required: true, message: '请输入用户名!' }]}
      >
        <Input />
      </Form.Item>

      <Form.Item
        label="密码"
        name="password"
        rules={[{ required: true, message: '请输入密码!' }]}
      >
        <Input.Password />
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit">
          提交
        </Button>
      </Form.Item>
    </Form>
  );
};

export default MyForm;

表单验证:

  • 使用 rules 定义表单字段的验证规则。
  • onFinish 方法会在验证通过后提交表单数据。

5. 数据提交

数据提交通常通过封装好的请求库(如 axios)向后端发送请求。

封装请求库:

ts
复制代码
// src/utils/request.ts
import axios from 'axios';

const request = axios.create({
  baseURL: 'https://api.example.com', // 后端 API 地址
  timeout: 5000, // 超时时间
});

// 添加请求拦截器
request.interceptors.request.use(
  (config) => {
    // 在请求头中添加 token
    config.headers['Authorization'] = 'Bearer your-token';
    return config;
  },
  (error) => Promise.reject(error)
);

export default request;

表单提交后发送请求:

tsx
复制代码
import React from 'react';
import { Form, Input, Button, message } from 'antd';
import request from '../utils/request';

const MyForm: React.FC = () => {
  const [form] = Form.useForm();

  const handleFinish = async (values: any) => {
    try {
      const response = await request.post('/submit', values); // 提交数据到后端
      message.success('提交成功!');
      console.log('服务器响应:', response.data);
    } catch (error) {
      message.error('提交失败,请重试!');
      console.error('提交失败:', error);
    }
  };

  return (
    <Form form={form} onFinish={handleFinish}>
      <Form.Item
        label="用户名"
        name="username"
        rules={[{ required: true, message: '请输入用户名!' }]}
      >
        <Input />
      </Form.Item>

      <Form.Item
        label="密码"
        name="password"
        rules={[{ required: true, message: '请输入密码!' }]}
      >
        <Input.Password />
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit">
          提交
        </Button>
      </Form.Item>
    </Form>
  );
};

export default MyForm;

在 React 中,父组件调用子组件的表单校验是否完整,可以通过 refuseImperativeHandle 暴露子组件的表单校验方法给父组件。以下是一个完整的实现示例。


实现:父组件调用子组件表单校验

6. 子组件定义表单及校验方法

在子组件中:

  1. 使用 Ant Design 的 Form 组件管理表单。
  2. 使用 forwardRefuseImperativeHandle 将表单校验逻辑暴露给父组件。
tsx
复制代码
// 子组件:ApplicantForm.tsx
import React, { forwardRef, useImperativeHandle } from 'react';
import { Form, Input, Button } from 'antd';

export interface ApplicantFormRef {
  validateForm: () => Promise<any>;
}

const ApplicantForm = forwardRef<ApplicantFormRef>((_, ref) => {
  const [form] = Form.useForm();

  // 暴露给父组件的方法
  const validateForm = async () => {
    try {
      const values = await form.validateFields(); // 表单校验
      console.log('表单数据:', values);
      return values; // 返回表单数据
    } catch (error) {
      console.error('校验失败:', error);
      throw error; // 抛出错误,供父组件捕获
    }
  };

  // 使用 useImperativeHandle 暴露方法
  useImperativeHandle(ref, () => ({
    validateForm,
  }));

  return (
    <Form form={form} layout="vertical">
      <Form.Item
        label="用户名"
        name="username"
        rules={[{ required: true, message: '请输入用户名!' }]}
      >
        <Input />
      </Form.Item>
      <Form.Item
        label="邮箱"
        name="email"
        rules={[{ required: true, type: 'email', message: '请输入有效的邮箱地址!' }]}
      >
        <Input />
      </Form.Item>
    </Form>
  );
});

export default ApplicantForm;

2. 父组件调用子组件校验方法

在父组件中:

  1. 使用 useRef 引用子组件。
  2. 在需要时调用子组件暴露的 validateForm 方法。
tsx
复制代码
// 父组件:ParentComponent.tsx
import React, { useRef } from 'react';
import { Button, message } from 'antd';
import ApplicantForm, { ApplicantFormRef } from './ApplicantForm';

const ParentComponent: React.FC = () => {
  const formRef = useRef<ApplicantFormRef>(null); // 定义 ref 类型

  // 点击保存按钮时调用子组件校验方法
  const handleSave = async () => {
    if (formRef.current) {
      try {
        const formData = await formRef.current.validateForm(); // 调用子组件校验
        console.log('提交的数据:', formData);
        message.success('表单校验通过,已提交!');
      } catch (error) {
        message.error('表单校验未通过,请检查输入!');
      }
    }
  };

  return (
    <div>
      <h1>父组件</h1>
      <ApplicantForm ref={formRef} />
      <div style={{ marginTop: 20 }}>
        <Button type="primary" onClick={handleSave}>
          保存
        </Button>
      </div>
    </div>
  );
};

export default ParentComponent;

运行逻辑解析

子组件:ApplicantForm

  1. 定义表单:使用 Form.useForm() 管理表单实例。
  2. 校验方法:通过 form.validateFields() 实现校验逻辑。
  3. 暴露方法:用 useImperativeHandlevalidateForm 方法暴露给父组件。

父组件:ParentComponent

  1. 使用 useRef 获取子组件的引用。
  2. 在按钮点击事件中,调用子组件的 validateForm 方法,捕获返回值或错误信息。

关键点说明

  1. 校验方法:

    • form.validateFields():会校验表单字段,若通过则返回数据,若失败则抛出错误。
  2. 暴露方法:

    • 使用 forwardRefuseImperativeHandle,使父组件可以调用子组件的逻辑。
  3. 错误处理:

    • 在父组件中通过 try-catch 捕获子组件抛出的校验错误,提示用户。

效果展示

  1. 初始页面显示子组件表单。

  2. 点击保存按钮,调用子组件的校验逻辑:

    • 如果校验通过,打印数据到控制台,提示“表单校验通过,已提交!”。
    • 如果校验失败,提示“表单校验未通过,请检查输入!”。

总结

  • 子组件负责表单校验逻辑,父组件通过 ref 调用子组件的校验方法。
  • useImperativeHandle 是连接父子组件交互的桥梁。
  • 使用 Ant Design 的 Form 提供的校验机制,可以轻松实现字段验证和数据获取。

这种模式适合处理复杂的表单嵌套逻辑,提高代码可读性和复用性。

如果父组件引用了两个子组件,并需要分别校验两个子组件的表单是否完整,可以通过以下方式实现:

  1. 为每个子组件分别设置 ref
  2. 在每个子组件中定义独立的表单校验方法,通过 forwardRefuseImperativeHandle 暴露给父组件。
  3. 父组件统一调用两个子组件的校验方法,并根据返回值或抛出的错误判断表单是否验证通过。

完整实现示例

子组件定义表单校验方法

每个子组件(如 ChildFormAChildFormB)独立管理自己的表单,并暴露校验方法。


子组件 1:ChildFormA

tsx
复制代码
import React, { forwardRef, useImperativeHandle } from 'react';
import { Form, Input } from 'antd';

export interface ChildFormRef {
  validateForm: () => Promise<any>;
}

const ChildFormA = forwardRef<ChildFormRef>((_, ref) => {
  const [form] = Form.useForm();

  // 暴露校验方法
  const validateForm = async () => {
    try {
      const values = await form.validateFields(); // 校验表单
      console.log('子组件A表单数据:', values);
      return values;
    } catch (error) {
      console.error('子组件A校验失败:', error);
      throw error;
    }
  };

  useImperativeHandle(ref, () => ({
    validateForm,
  }));

  return (
    <Form form={form} layout="vertical">
      <Form.Item
        label="用户名A"
        name="usernameA"
        rules={[{ required: true, message: '请输入用户名A!' }]}
      >
        <Input />
      </Form.Item>
    </Form>
  );
});

export default ChildFormA;

子组件 2:ChildFormB

tsx
复制代码
import React, { forwardRef, useImperativeHandle } from 'react';
import { Form, Input } from 'antd';

export interface ChildFormRef {
  validateForm: () => Promise<any>;
}

const ChildFormB = forwardRef<ChildFormRef>((_, ref) => {
  const [form] = Form.useForm();

  // 暴露校验方法
  const validateForm = async () => {
    try {
      const values = await form.validateFields(); // 校验表单
      console.log('子组件B表单数据:', values);
      return values;
    } catch (error) {
      console.error('子组件B校验失败:', error);
      throw error;
    }
  };

  useImperativeHandle(ref, () => ({
    validateForm,
  }));

  return (
    <Form form={form} layout="vertical">
      <Form.Item
        label="用户名B"
        name="usernameB"
        rules={[{ required: true, message: '请输入用户名B!' }]}
      >
        <Input />
      </Form.Item>
    </Form>
  );
});

export default ChildFormB;

7. 父组件统一调用子组件的校验方法

父组件通过 useRef 分别获取两个子组件的引用,并在提交时逐一调用其 validateForm 方法。

tsx
复制代码
import React, { useRef } from 'react';
import { Button, message } from 'antd';
import ChildFormA, { ChildFormRef } from './ChildFormA';
import ChildFormB from './ChildFormB';

const ParentComponent: React.FC = () => {
  const formARef = useRef<ChildFormRef>(null); // 子组件A的ref
  const formBRef = useRef<ChildFormRef>(null); // 子组件B的ref

  const handleSave = async () => {
    try {
      // 分别校验两个子组件的表单
      const formAValues = await formARef.current?.validateForm();
      const formBValues = await formBRef.current?.validateForm();

      console.log('子组件A数据:', formAValues);
      console.log('子组件B数据:', formBValues);

      message.success('所有表单校验通过!');
    } catch (error) {
      message.error('请检查表单是否填写完整!');
    }
  };

  return (
    <div>
      <h1>父组件</h1>
      <ChildFormA ref={formARef} />
      <ChildFormB ref={formBRef} />
      <div style={{ marginTop: 20 }}>
        <Button type="primary" onClick={handleSave}>
          提交
        </Button>
      </div>
    </div>
  );
};

export default ParentComponent;

代码解析

  1. 子组件暴露校验方法

    • 使用 forwardRefuseImperativeHandle,将 validateForm 方法暴露给父组件。
    • validateForm 方法使用 form.validateFields() 校验表单,并返回表单数据。
  2. 父组件管理多个子组件的引用

    • 父组件通过 useRef 创建多个引用,用于分别引用子组件。
    • 调用子组件的 validateForm 方法时,注意添加空值检查。
  3. 统一校验逻辑

    • handleSave 方法中,依次调用两个子组件的校验方法。
    • 如果任意一个子组件校验失败,会抛出异常并提示用户。

运行效果

  1. 父组件加载时会展示两个子组件的表单。

  2. 点击提交按钮时:

    • 调用 ChildFormAChildFormB 的校验方法。
    • 如果校验通过,控制台输出两个表单的数据,并提示“所有表单校验通过!”。
    • 如果任意一个表单校验失败,提示“请检查表单是否填写完整!”。

优化建议

  1. 并行校验

    • 使用 Promise.all 并行调用两个表单的校验方法,加快校验速度。
    tsx
    复制代码
    const handleSave = async () => {
      try {
        const [formAValues, formBValues] = await Promise.all([
          formARef.current?.validateForm(),
          formBRef.current?.validateForm(),
        ]);
    
        console.log('子组件A数据:', formAValues);
        console.log('子组件B数据:', formBValues);
    
        message.success('所有表单校验通过!');
      } catch (error) {
        message.error('请检查表单是否填写完整!');
      }
    };
    
  2. 代码复用

    • 如果多个子组件的表单结构和逻辑相似,可以提取公共逻辑到自定义 Hook 中,避免重复代码。

总结

  • 核心方法

    • forwardRef:用于让子组件接受父组件的 ref
    • useImperativeHandle:用于在子组件中定义暴露给父组件的方法。
    • useRef:用于在父组件中管理子组件的引用。
  • 校验逻辑

    • 每个子组件独立校验,父组件统一调用。
    • 使用 form.validateFields() 进行表单验证。
  • 扩展

    • 通过 Promise.all 并行处理多个表单校验,提高性能。