React Antd4 Form 实现与思考

使用范式及设计思路

代码地址:https://github.com/kizuner-bonely/oh-my-mini-react

首先我们需要一个表单,它的使用范式如下所示:

  1. import { createRef, Component, useEffect } from react
  2. import Form, { Field } from @form
  3. import { Input } from @form/components
  4. const nameRules = { required: true, message: 请输入姓名! }
  5. const passwordRules = { required: true, message: 请输入密码! }
  6. export default function MyRCFieldForm() {
  7. const [form] = Form.useForm()
  8. const onFinish = (val: any) => {
  9. console.log(onFinish, val)
  10. }
  11. // 表单校验失败执行
  12. const onFinishFailed = (val: any) => {
  13. console.log(onFinishFailed, val)
  14. }
  15. useEffect(() => {
  16. console.log(form, form)
  17. form.setFieldValue({ username: default })
  18. }, [])
  19. return (
  20. <div>
  21. {/* 函数组件 */}
  22. <h3>MyRCFieldForm</h3>
  23. <Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed}>
  24. <Field name="username" rules={[nameRules]}>
  25. <Input placeholder="input UR Username" />
  26. </Field>
  27. <Field name="password" rules={[passwordRules]}>
  28. <Input placeholder="input UR Password" />
  29. </Field>
  30. <button>Submit</button>
  31. </Form>
  32. {/* 类组件 */}
  33. <MyClassFieldForm />
  34. </div>
  35. )
  36. }
  37. class MyClassFieldForm extends Component {
  38. formRef = createRef()
  39. onFinish = (val: any) => {
  40. console.log(onFinish, val)
  41. }
  42. // 表单校验失败执行
  43. onFinishFailed = (val: any) => {
  44. console.log(onFinishFailed, val)
  45. }
  46. render() {
  47. return (
  48. <div>
  49. <h3>MyClassFieldForm</h3>
  50. <Form
  51. ref={this.formRef}
  52. onFinish={this.onFinish}
  53. onFinishFailed={this.onFinishFailed}
  54. >
  55. <Field name="username" rules={[nameRules]}>
  56. <Input placeholder="input UR Username" />
  57. </Field>
  58. <Field name="password" rules={[passwordRules]}>
  59. <Input placeholder="input UR Password" />
  60. </Field>
  61. <button>Submit</button>
  62. </Form>
  63. </div>
  64. )
  65. }
  66. }

通过使用范式我们可以得到以下结论:

  1. 在组件层面,我们需要 <Form><Field>,它们最终渲染的是 <Field> 包裹的表单元素
  2. 对于函数组件,我们需要使用 useForm() 来生成 Form 实例,并将其通过参数传给 <Form>
  3. 对于类组件,我们需要创建一个 ref 来获取 Form 实例
  4. 示例中的 button 没有绑定事件也能正常提交

根据以上结论,我们可以推导出以下设计思路:

  • 根据结论1,无论是 <Form> 还是 <Field>,它们都会装饰并返回 children

  • 根据结论2、3:

    • 类组件和函数组件对于 Form 的控制方式不同:

      • 函数组件先通过 useForm() 生成 Form 实例,并通过参数传给 <Form>,这样函数组件就能通过该 Form 实例对 Form 进行控制。
      • 类组件是先生成一个 ref,通过参数传给 <Form>,这样类组件就能通过 ref 对 Form 进行控制。
    • 对于 <Form>,无论使用它的是类组件还是函数组件都需要有一个 Form 实例,对于函数组件来说 Form 实例是函数组件自己生成并当作参数传给 <Form>。 而对于类组件来说并没有自己生成 Form 实例,因此在 <Form> 内部要对这两种情况作兼容。 兼容方式很明显,如果使用者有通过参数传 Form 实例就用外界传来的,如果没有则自己生成并将该 Form 实例返回给外界传入的 ref。

    • 整个 Form 是通过 Form 实例进行控制,比如说规则校验、提交等等。

  • 根据结论4,<Form> 的子组件肯定包括 <form>

1-整体架构.png

图1:Form 整体架构

1. 最简实现

首先把整体架构搭好,后面再补充细节,为了实现最简版本,一开始只考虑调用组件为函数组件。

从图一可以看出,Form Instance 需要和 <Form> 交互,也要和 <Field> 交互,需要注意的是 <Form><Field> 不一定是父子组件,中间可能隔着 <div> 这样的组件。因此通过参数传递有点不合适,使用 context 能保证它们都能拿到同一 Form Instance

  • Form.tsx
  • Field.tsx
  • useForm.ts
  • FieldContext.ts
  • form.d.ts

form.d.ts

  1. import { FormStore } from ./useForm
  2. export type FormProps = {
  3. form?: ReturnType<FormStore[getForm]>
  4. children: JSX.Element | JSX.Element[]
  5. } & FormCallbacks
  6. export type FieldProps = {
  7. name: string
  8. children: ReactElement<any, string | JSXElementConstructor<any>>
  9. rules?: Array<{ required: boolean, message: string }>
  10. }
  11. export type FieldContextType = ReturnType<FormStore[getForm]>

FieldContext.ts

  1. import type { FieldContextType } from ./form.d
  2. import { createContext } from react
  3. import { FormStore } from ./useForm
  4. // 这里的参数只是为了不让 ts 报错,真正的 Form 实例还是要在 <Form> 中获得
  5. export const FieldContext = createContext<FieldContextType>(new FormStore().getForm())

useForm.ts

  1. import { useRef } from react
  2. export default function useForm() {
  3. const store = useRef(new FormStore()).current
  4. return [store]
  5. }
  6. // Form 实例仓库,要实现两个功能点
  7. // 1.存储状态
  8. // 2.更新状态 -> 更新仓库值 + 通知组件更新
  9. export class FormStore {
  10. // store 存的是一个个键值对,键名是 <Field> 的 name,键值是 <Field> 包裹表单的值
  11. private store: Record<string, any> = {}
  12. // 取
  13. getFieldsValue = () => {
  14. return { ...this.store }
  15. }
  16. getFieldValue = (name: string) => {
  17. return this.store[name]
  18. }
  19. // 存
  20. setFieldValue = (val: Record<string, any>) => {
  21. // 1.更新仓库值
  22. this.store = { ...this.store, ...val }
  23. // todo 2.通知组件更新
  24. }
  25. // 统一接口
  26. getForm = () => {
  27. return {
  28. getFieldsValue: this.getFieldsValue,
  29. getFieldValue: this.getFieldValue,
  30. setFieldValue: this.setFieldValue,
  31. }
  32. }
  33. }

Form.tsx

  1. import Field from ./Field
  2. import useForm from ./useForm
  3. import { FieldContext } from ./FieldContext
  4. export default function Form(props: FormProps) {
  5. const { children, form } = props
  6. return (
  7. <form>
  8. <FieldContext.Provider value={form}>
  9. {children}
  10. </FieldContext.Provider>
  11. </form>
  12. )
  13. }
  14. Form.Field = Field
  15. Form.useForm = useForm

Field.tsx

  1. import type { FieldProps } from ./form.d
  2. import { cloneElement, useCallback, useContext } from react
  3. import { FieldContext } from ./FieldContext
  4. export default function Field(props: FieldProps) {
  5. const { children, name } = props
  6. const { getFieldValue, setFieldValue } = useContext()
  7. const controller = useCallback(() => {
  8. return {
  9. value: getFieldValue(name),
  10. onChange(e: Event) {
  11. const target = e.target ass HTMLInputElement | HTMLButtonElement
  12. const newValue = target.value
  13. setFieldValue({ [name]: newValue })
  14. },
  15. }
  16. }, [])
  17. return cloneElement(children, controller())
  18. }

至此,Demo 应该能把页面渲染出来了,但是输入时表单还没能变化。

这时在 FormStore 的 setFieldValue 中打印 this.store 可以发现在仓库中状态值确实已经改变了,但是对于 React 组件来说感知不到,因此要在 <Field> 中创建某种更新手段,让 FormStore 在 setFieldValue() 之后调用。

思考题1:在 Field.tsx 中,controller 能否用 useMemo() 代替实现,如下面代码所示

  1. const controller = useMemo(() => {
  2. return {
  3. value: getFieldValue(name),
  4. onChange(e: Event) {
  5. const target = e.target ass HTMLInputElement | HTMLButtonElement
  6. const newValue = target.value
  7. setFieldValue({ [name]: newValue })
  8. },
  9. }
  10. }, [name, getFieldValue, setFieldValue])

2. 实现组件更新

首先想明白组件更新的时机,观察上面架构,我们需要在 FormStore 进行仓库值更新之后通知组件更新,为了达到该目的我们需要在 FormStore 内部拿到 <Field> 实例,或者 <Field> 给 FormStore 所需的东西。

对于类组件实现的 <Field> 来说,把组件实例 this 直接给 FormStore,FormStore 就能调用上面的更新组件方法,比如说 this.forceUpdate

对于函数组件实现的 <Field> 来说,可以把一个对象给 FormStore,这个对象包含更新 <Field> 方法。

这样在更新仓库值之后,就可以调用 <Field> 传来的更新函数对 <Field> 进行更新了。

  • useForm.ts
  • Field.tsx
  • form.d.ts

useForm.ts

  1. // ++++++++++++++++++++++++++++++++
  2. import type { Entity } from ./form.d
  3. // ++++++++++++++++++++++++++++++++
  4. import { useRef } from react
  5. export default function useForm() {
  6. const store = useRef(new FormStore()).current
  7. return [store]
  8. }
  9. // Form 实例仓库,要实现两个功能点
  10. // 1.存储状态
  11. // 2.更新状态 -> 更新仓库值 + 通知组件更新
  12. export class FormStore {
  13. // store 存的是一个个键值对,键名是 <Field> 的 name,键值是 <Field> 包裹表单的值
  14. private store: Record<string, any> = {}
  15. // ++++++++++++++++++++++++++++++++
  16. private fieldEntities: Set<Entity> = new Set()
  17. // 注册 <Field> 组件实例
  18. registerEntities = (entity: Entity) => {
  19. this.fieldEntities.add(entity)
  20. // 组件卸载时注销相关实例
  21. return () => {
  22. this.fieldEntities.delete(entity)
  23. delete this.store[entity.props.name]
  24. }
  25. }
  26. // ++++++++++++++++++++++++++++++++
  27. // 取
  28. getFieldsValue = () => {
  29. return { ...this.store }
  30. }
  31. getFieldValue = (name: string) => {
  32. return this.store[name]
  33. }
  34. // 存
  35. setFieldValue = (val: Record<string, any>) => {
  36. // 1.更新仓库值
  37. this.store = { ...this.store, ...val }
  38. // ++++++++++++++++++++++++++++++++
  39. // 2.通知组件更新
  40. const updateKeys = Object.keys(val)
  41. this.fieldEntities.forEach(entity => {
  42. if (updateKeys.includes(entity.props.name)) {
  43. // 规定 <Field> 更新自己的方法名就叫 onStoreChanged
  44. entity.onStoreChanged()
  45. }
  46. })
  47. // ++++++++++++++++++++++++++++++++
  48. }
  49. // 统一接口
  50. getForm = () => {
  51. return {
  52. // ++++++++++++++++++++++++++++++++
  53. registerEntities: this.registerEntities,
  54. // ++++++++++++++++++++++++++++++++
  55. getFieldsValue: this.getFieldsValue,
  56. getFieldValue: this.getFieldValue,
  57. setFieldValue: this.setFieldValue,
  58. }
  59. }
  60. }

Field.tsx

  1. import type { FieldProps } from ./form.d
  2. // ++++++++++++++++++++++++++++++++
  3. import { cloneElement, useCallback, useContext, useEffect, useReducer } from react
  4. // ++++++++++++++++++++++++++++++++
  5. import { FieldContext } from ./FieldContext
  6. export default function Field(props: FieldProps) {
  7. const { children, name } = props
  8. // ++++++++++++++++++++++++++++++++
  9. const { getFieldValue, setFieldValue, registerEntities } = useContext()
  10. const [, forceUpdate] = useReducer(x => x + 1, 0)
  11. // 组件挂载时注册组件实例,卸载时注销
  12. useEffect(() => {
  13. const unregister = registerEntities({ props, onStoreChanged: forceUpdate })
  14. return () => {
  15. unregister()
  16. }
  17. }, [props])
  18. // ++++++++++++++++++++++++++++++++
  19. const controller = useCallback(() => {
  20. return {
  21. value: getFieldValue(name),
  22. onChange(e: Event) {
  23. const target = e.target ass HTMLInputElement | HTMLButtonElement
  24. const newValue = target.value
  25. setFieldValue({ [name]: newValue })
  26. },
  27. }
  28. }, [])
  29. return cloneElement(children, controller())
  30. }

form.d.ts

  1. import type { ReactElement } from react
  2. import { FormStore } from ./useForm
  3. export type FormProps = {
  4. form?: ReturnType<FormStore[getForm]>
  5. children: JSX.Element | JSX.Element[]
  6. } & FormCallbacks
  7. export type FieldProps = {
  8. name: string
  9. children: ReactElement<any, string | JSXElementConstructor<any>>
  10. rules?: Array<{ required: boolean, message: string }>
  11. }
  12. export type FieldContextType = ReturnType<FormStore[getForm]>
  13. // ++++++++++++++++++++++++++++++++
  14. export type Entity = ReactElement & { onStoreChanged(): void }
  15. // ++++++++++++++++++++++++++++++++

至此,Demo 中的输入框输入内容时就能看到新输入的内容了。

注意在函数组件实现主动更新使用了一个比较巧妙的方法:

  1. const [, forceUpdate] = useReducer(x => x + 1, 0)

回顾一下 React 组件更新条件:

  • 主动更新

    • 类组件

      • this.setState()
      • this.forceUpdate()
    • 函数组件

      • const [val, setVal] = useState(0)
      • const [val, setVal] = useReducer(reducer, initialVal)
  • 被动更新

    • props 发生更新
    • 接收的 context 发生更新

useReduceruseState 都是给 Fiber 挂上对应的状态,因此调用更新函数就可以主动更新自己的状态,由于我们并不关心这个值具体是什么,因此只要取更新函数即可。

3. 实现提交

回顾一下 Demo,我们是在 <Form> 组件中传入的 onFinishonFinishFailedfield context<form> 都是在 <Form> 当中,很明显提交方法要在 <Form> 实现。

当然在提交前我们要实现校验,这样我们才能知道是调用 onFinish 还是 onFinishFailed

  • Form.tsx
  • useForm.ts
  • form.d.ts

Form.tsx

  1. import Field from ./Field
  2. import useForm from ./useForm
  3. import { FieldContext } from ./FieldContext
  4. // ++++++++++++++++++++++++++++++++
  5. import { useCallback, useEffect } from react
  6. // ++++++++++++++++++++++++++++++++
  7. export default function Form(props: FormProps) {
  8. // ++++++++++++++++++++++++++++++++
  9. const { children, form, onFinish, onFinishFailed } = props
  10. // 组件挂载时注册 onFinish 和 onFinishFailed
  11. useEffect(() => {
  12. form.setCallbacks({ onFinish, onFinishFailed })
  13. }, [form, onFinish, onFinishFailed])
  14. const handleSubmit = useCallback((e: FormEvent<HTMLFormElement>) => {
  15. e.preventDefault()
  16. form.submit()
  17. }, [form])
  18. // ++++++++++++++++++++++++++++++++
  19. return (
  20. <form onSubmit={handleSubmit}>
  21. <FieldContext.Provider value={form}>
  22. {children}
  23. </FieldContext.Provider>
  24. </form>
  25. )
  26. }
  27. Form.Field = Field
  28. Form.useForm = useForm

useForm.ts

  1. // ++++++++++++++++++++++++++++++++
  2. import type { Entity, FormCallbacks } from ./form.d
  3. // ++++++++++++++++++++++++++++++++
  4. import { useRef } from react
  5. export default function useForm() {
  6. const store = useRef(new FormStore()).current
  7. return [store]
  8. }
  9. // Form 实例仓库,要实现两个功能点
  10. // 1.存储状态
  11. // 2.更新状态 -> 更新仓库值 + 通知组件更新
  12. export class FormStore {
  13. // store 存的是一个个键值对,键名是 <Field> 的 name,键值是 <Field> 包裹表单的值
  14. private store: Record<string, any> = {}
  15. private fieldEntities: Set<Entity> = new Set()
  16. // ++++++++++++++++++++++++++++++++
  17. private callbacks: FormCallbacks = {} as any
  18. private valiedate = () => {
  19. const err: Array<{ [K in string]: { message: string } }> = []
  20. this.fieldEntities.forEach(entity => {
  21. const { rules, name } = entity.props
  22. const rule = rules[0]
  23. if (rule?.isRequired && isFalsy(this.getFieldValue(name))) {
  24. err.push({ [name]: rule.message })
  25. }
  26. })
  27. return err
  28. }
  29. submit = () => {
  30. const { onFinish, onFinishFailed } = callbacks
  31. const err = this.validate()
  32. return err.length ? onFinishFailed(err, this.getFieldsValue()) : onFinish(this.getFieldsValue())
  33. }
  34. setCallbacks = (callbacks: Partial<FormCallbacks>) => {
  35. this.callbacks = { ...this.callbacks, ...callbacks }
  36. }
  37. // ++++++++++++++++++++++++++++++++
  38. // 注册 <Field> 组件实例
  39. registerEntities = (entity: Entity) => {
  40. this.fieldEntities.add(entity)
  41. // 组件卸载时注销相关实例
  42. return () => {
  43. this.fieldEntities.delete(entity)
  44. delete this.store[entity.props.name]
  45. }
  46. }
  47. // 取
  48. getFieldsValue = () => {
  49. return { ...this.store }
  50. }
  51. getFieldValue = (name: string) => {
  52. return this.store[name]
  53. }
  54. // 存
  55. setFieldValue = (val: Record<string, any>) => {
  56. // 1.更新仓库值
  57. this.store = { ...this.store, ...val }
  58. // 2.通知组件更新
  59. const updateKeys = Object.keys(val)
  60. this.fieldEntities.forEach(entity => {
  61. if (updateKeys.includes(entity.props.name)) {
  62. // 规定 <Field> 更新自己的方法名就叫 onStoreChanged
  63. entity.onStoreChanged()
  64. }
  65. })
  66. }
  67. // 统一接口
  68. getForm = () => {
  69. return {
  70. // ++++++++++++++++++++++++++++++++
  71. submit: this.submit,
  72. setCallbacks: this.setCallbacks,
  73. // ++++++++++++++++++++++++++++++++
  74. registerEntities: this.registerEntities,
  75. getFieldsValue: this.getFieldsValue,
  76. getFieldValue: this.getFieldValue,
  77. setFieldValue: this.setFieldValue,
  78. }
  79. }
  80. }
  81. // ++++++++++++++++++++++++++++++++
  82. function isFalsy(val: unknown) {
  83. return val === undefined || val === null || val === || val === false
  84. }
  85. // ++++++++++++++++++++++++++++++++

form.d.ts

  1. import type { ReactElement } from react
  2. import { FormStore } from ./useForm
  3. export type FormProps = {
  4. form?: ReturnType<FormStore[getForm]>
  5. children: JSX.Element | JSX.Element[]
  6. } & FormCallbacks
  7. export type FieldProps = {
  8. name: string
  9. children: ReactElement<any, string | JSXElementConstructor<any>>
  10. rules?: Array<{ required: boolean, message: string }>
  11. }
  12. export type FieldContextType = ReturnType<FormStore[getForm]>
  13. export type Entity = ReactElement & { onStoreChanged(): void }
  14. // ++++++++++++++++++++++++++++++++
  15. export type FormCallbacks = {
  16. onFinish(val: Record<string, any>): void
  17. onFinishFailed(err: any[], val: Record<string, any>): void
  18. }
  19. // ++++++++++++++++++++++++++++++++

至此,Demo 在点击提交按钮时,如果两个输入框都有内容,则会触发 onFinish();只要有一个输入框无输入,点击提交后就会触发 onFinishFailed()

4. 兼容类组件

在上面实现中,调用 <Form> 的必须是函数组件,因为必须通过 useForm() 获取 Form 实例并传参,而钩子函数只能在函数组件中使用,因此目前类组件还不能使用 <Form>

为了解决这个问题,我们必须要把应用到整个表单的 Form 的设置放在 <Form> 中,如果参数有传入 Form 实例则用该示例,否则就自己创建一个并将这个 Form 实例返回出去。

  • Form.tsx
  • useForm.ts

Form.tsx

  1. import Field from ./Field
  2. import useForm from ./useForm
  3. import { FieldContext } from ./FieldContext
  4. // ++++++++++++++++++++++++++++++++
  5. import { forwardRef, useCallback, useEffect, useImperativeHandle } from react
  6. type FormType = typeof Form & { Field: typeof Field; useForm: typeof useForm }
  7. // ++++++++++++++++++++++++++++++++
  8. function Form(props: FormProps, ref: ForwardedRef<any>) {
  9. const { children, form, onFinish, onFinishFailed } = props
  10. // ++++++++++++++++++++++++++++++++
  11. const FormInstance = useForm(form)
  12. // 类组件传入 ref,<Form ref={formRef}>xxx</Form>
  13. // 调用这个钩子可以将 formRef.current 设置成 FormInstance
  14. // 这样类组件就能通过传入 ref 来控制 Form 实例
  15. useImperativeHandle(ref, () => FormInstance)
  16. // ++++++++++++++++++++++++++++++++
  17. useEffect(() => {
  18. FormInstance.setCallbacks({ onFinish, onFinishFailed })
  19. }, [FormInstance, onFinish, onFinishFailed])
  20. const handleSubmit = useCallback((e: FormEvent<HTMLFormElement>) => {
  21. e.preventDefault()
  22. FormInstance.submit()
  23. }, [FormInstance])
  24. return (
  25. <form onSubmit={handleSubmit}>
  26. <FieldContext.Provider value={FormInstance}>
  27. {children}
  28. </FieldContext.Provider>
  29. </form>
  30. )
  31. }
  32. export const _Form = forwardRef(Form) as any as FormType
  33. _Form.Field = Field
  34. _Form.useForm = useForm
  35. export default _Form

useForm.ts

  1. // ++++++++++++++++++++++++++++++++
  2. import type { Entity, FormCallbacks, FormProps } from ./form.d
  3. // ++++++++++++++++++++++++++++++++
  4. import { useRef } from react
  5. // ++++++++++++++++++++++++++++++++
  6. // 有传入 form 代表调用 <Form> 的是函数组件,否则就是类组件
  7. export default function useForm(form?: FormProps[form]) {
  8. const store = useRef(form ? form : new FormStore()).current
  9. return [store]
  10. }
  11. // ++++++++++++++++++++++++++++++++
  12. export class FormStore {...}
  13. function isFalsy(val: unknown) {
  14. return val === undefined || val === null || val === || val === false
  15. }

至此,一个简易 Form 表单的封装就已经完成了。

值得注意的是,使用 forwardRef 要和 useImperativeHandle 搭配使用,如果不是很熟悉该 API ,可以先到官方文档了解一下。

思考题2:在 Form.tsx 中,能否用下面代码代替最后的导出

  1. function Form(props) {
  2. xxx
  3. }
  4. Form.Field = Field
  5. Form.useForm = useForm
  6. export const _Form = forwardRef(Form) as any as FormType
  7. export default _Form

总结与反思

其实这个 Form 在类型设置方面并不完美,很多地方使用了 any,像要照顾类组件的地方如果不用 any 会非常麻烦,像:

  1. export type FunctionFieldProps = {
  2. children: ReactElement<any, string | JSXElementConstructor<any>>
  3. name: string
  4. rules?: RuleType[]
  5. }
  6. export const _Form = forwardRef(Form) as any as FormType

如果只要考虑函数组件,一切都会简单得多,毕竟函数组件本质就是函数。这也是提倡使用函数组件的一个点吧,无论是做业务还是轮子的封装。

当然另一方面还要考虑到为了成为技术大神,掌握 TS 是必须的,在写这个 Form 之前我也学过很多类型体操的套路,刷了一百多道类型体操习题,但是从实践看来要是做封装轮子的工作还差得很远,后面还需要进一步掌握 TS 源码才行。与君共勉~

思考题参考答案

思考题1:在 Field.tsx 中,controller 能否用 useMemo() 代替实现

  1. 不能。仔细观察 controller 里面的内容,在 FormStore 里面发生状态变更后,controller 引用的外界变量 (name, getFieldValue, setFieldValue) 并没有发生变化。
  2. 因此使用 useMemo() 并不会使 controller 里面的 value 发生变化,从而导致页面上看到的 value 永远没变。
  3. const controller = useCallback(() => {
  4. return {
  5. value: getFieldValue(name),
  6. onChange(e: Event) {
  7. const target = e.target ass HTMLInputElement | HTMLButtonElement
  8. const newValue = target.value
  9. setFieldValue({ [name]: newValue })
  10. },
  11. }
  12. }, [])
  13. 因此我们要将其做成一个函数,在每次 render 的时候都能拿到最新值:
  14. return cloneElement(children, controller())

思考题2:在 Form.tsx 中,能否用下面代码代替最后的导出

  1. 不能。forwardRef() 会对传入组件进行一层包装并返回包装过的组件。
  2. 因此,就像是给 <Form> 套了一层 <div>,在外界访问的是包装过的组件而不是被包装的组件。因此给被包装的组件挂载方法,在外界是访问不到的。
  3. 这里重点是对 forwardRef() 的把握。

文章标签:

原文连接:https://juejin.cn/post/7116146679472095239

相关推荐

Webpack学习系列 - Webpack5 怎么集成Babel ?

我在淘宝做弹窗,2022 年初的回顾与展望

看完这篇,你也可以搞定有趣的动态曲线绘制

低代码平台的属性面板该如何设计?

34个图片压缩工具集合,包含在线压缩和CLI工具

冴羽答读者问:过程比结果重要吗?如果是,怎么理解?如果不是,又怎么解?

中杯超大杯中间的新选择——vue2.7+vite+ts实践

LiveData源码分析

亚马逊Prime:流媒大战杀手锏

Vue详解知识概括

基于 Docker 来部署 Vue 或 React 前端项目及 Node 后端服务

完美解决自定义字体垂直方向上下偏移的问题

使用vuepress从零开始搭建我的技术文档|已部署到线上

【Vue.js 3.0源码】AST 转换之节点内部转换

小程序+电商,该如何寻找营销增长点

前端如何开始深度学习,那不妨试试JAX

你可能不知道的 前端浏览器(window) 对本地文件操作(File System Access API)

爱奇艺向抖音开启授权,打开内容价值的新大门

使用ComposeDesktop开发一款桌面端多功能APK工具

一个简洁、强大、可扩展的前端项目架构是什么样的?