Antd v4 表单控件数据绑定问题解析

2,392 阅读2分钟

<FormItem/> 中 不需要显式给 <Input>传递onChange 进行数据绑定的原因 ?

antd的<Form/>组件,自带数据域管理,所以不需要在<Input/> 去显式的添加onChange方法,去管理数据的绑定。

<Form.Item label="Username name="username" >
//并没有显示的传递 onchange方法。但组件内可以获取到 props.onChange 去传递修改的值
    <Input />  
</Form.Item>

原因是: FormItem 是通过使用 React.cloneElement的方法 clone 了一个新的 Element,去替换老的 Element, 在这个新的Element 的 props上去添加了传递过来的onChange事件。源码链接

  //给childProps 挂载对于 control 里的 props      
  triggers.forEach(eventName => {
    childProps[eventName] = (...args: any[]) => {
      mergedControl[eventName]?.(...args);
      children.props[eventName]?.(...args);
    };
  });

  childNode = (
    <MemoInput
      value={mergedControl[props.valuePropName || 'value']}
      update={updateRef.current}
    >
      //调用 cloneElement 创建新的 挂载上 onChange 的 element去代替原来写的。
      {cloneElement(children, childProps)}
    </MemoInput>
  }

//替换Element的源码
export function replaceElement(
  element: React.ReactNode,
  replacement: React.ReactNode,
  props: RenderProps,
): React.ReactNode {
  if (!isValidElement(element)) return replacement;

  return React.cloneElement(
    element,
    typeof props === 'function' ? props(element.props || {}) : props,
  );
}

export function cloneElement(element: React.ReactNode, props?: RenderProps): React.ReactElement {
  return replaceElement(element, element, props) as React.ReactElement;
}

为什么在新的Element添加 onChange去替换, 而不是在原Element的props上挂载onChange方法

例如

 const onChange = () => {};
 const element = React.createElement('div', { style: {width: '20px'} }, '*')
 element.props.onChange = onChange;

尝试运行上面的方法会报 object is not extensible 的错误。

image.png

具体原因是 ReactElement 在描述一个JSX 时候 调用了 Object.freeze(element.props) 使props 冻结,不可扩展。

ReactElement.js 源码

const ReactElement = function (type, key, ref, self, source, owner, props) {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type,
    key,
    ref,
    props,
    _owner: owner,
  };

  if (Object.freeze) {
    // 冻结 不可扩展
      Object.freeze(element.props);
      Object.freeze(element);
  }

  return element;
};

在自定义表单控件的时候,需要使用上FormItem的数据管理能力 , children 需注意必须是单独的,而不是一个数组

像下面的情况如果 FormItem 内是一个数组的话 自定义的组件,是拿不到 传递过来的 onChange方法,必须要单独使用。

<Form.Item name="name" >
    <CustomInput/> // 自定义组件
    <div>新建</div>
</Form.Item>

//单独使用

 <Form.Item name="name" >
    <CustomInput/> // 自定义组件 
</Form.Item>

原因是: 源码中如果判断children 是一个数组的话,会直接赋值,而不会重新Clone 替换。

 if (Array.isArray(children) && hasName) {
      childNode = children;
 }

抖音电商火热内推:

我们是字节跳动 - 抖音电商业务研发团队,主要负责字节系平台(抖音、火山、头条、西瓜等)电商核心级商业产品的研发工作,你的每一行代码,都在影响亿级消费者。

我们在北京、上海、杭州、武汉都有 office 。

招聘 前端工程师,客户端工程师,后端工程师,大量HC。

内推链接:job.toutiao.com/s/eh6VNHL

邮箱:chenlei.leo@bytedance.com