antd对于form中输入控件的抽象十分简单,只要能接收value和onChange属性的组件都可以成为Form.Item的子组件,为Form对应的字段提供值。对于输入控件的抽象我认为这已经达到了极致,事件(onChange)产生值(value)。事件向上,值向下,完全符合React哲学。
如何实现一个标准的输入组件?
对于输入组件首先需要具备接收value和onChange属性的能力,这两个属性被提供的情况下这个组件被称为受控组件,它受到父组件控制,状态来自父组件。当父组件没有提供value的情况下该组件是非受控组件,有自己的状态。
是否受控主要取决于value属性是否提供,因为value决定了组件当前的状态,当前状态被父组件控制,所以称之为受控组件。
输入组件的状态是会改变的,最常见的方式就是来自用户行为,例如在input中输入内容,该组件的状态(value)就会改变。
- 因为组件可以作为非受控组件,所以组件自身有innerValue和setInnerValue用于展示和响应用户输入。
- 如果父组件没有提供value
- 组件使用自身的setInnerValue响应用户输入,改变自身状态innerValue
- 如果父组件提供onChange,在setInnerValue同时触发onChange事件通知父组件
- 如果父组件提供了value,组件失去了决定自身状态的能力(自身不会直接响应用户输入)
- 如果父组件提供onChange函数,则使用该函数响应用户输入
- 组件自身的setInnerValue不会响应用户输入(setInnerValue的作用是修改innerValue,因为有父组件的value,innerValue会响应value的变化,所以setInnerValue不应该直接修改innerValue导致其和value不一致)
function Input (props) {
const [innerValue, setInnerValue] = useState(props.value);
useEffect(() => {
if ('value' in props) setInnerValue(props.value)
}, [props.value])
function handleChange (ev) {
if (!('value' in props)) {
setInnerValue(ev.target.value)
}
props.onChange?.(ev.target.value)
}
return <input value={innerValue} onChange={handleChange} />
}
跨组件响应
<Form>
<Form.Item name="name">
<div>
<Input />
</div>
</Form.Item>
</Form>
如果按照上面的理论,因为中间隔了一层,按说Form是收不到Input的响应的.
但是事实是可以收到响应,因为它本质上是通过事件完成的响应。而不只是属性onChange和value。
Form.Item会为childNode添加onChange事件监听,当input内容发生改变的时候会触发onChange事件,即可通过这个事件拿到对应值。
antd的策略是拿到onchange的元素的value属性返回,如果没有那么就直接事件将入参返回。
Form.Item的onChange事件和value无法起作用
上面说了onChange会被Form.Item写给子组件,所以子组件只能有一个,这也是这个方案的限制,如果有两个,到底哪个元素会触发onchange?这个也不好说。所以子组件只能有一个。
又因为onChange事件不能在自定义组件的节点上冒泡,所以导致下例失效,必须要组件Abc手动执行onChange才可以帮助Form.Item拿到最新的值。
function Abc() {
return <input />
}
<Form>
<Form.Item name="name">
<Abc />
</Form.Item>
</Form>
Form.Item的value无法向下传递
<Form>
<Form.Item name="name">
<div>
<input />
</div>
</Form.Item>
</Form>
Form.Item的子组件是真实的dom情况下input的onChange事件可以被添加到div上的onChange监听,也就是input的值可以更新到Form中,但是这种形式的结构会导value的传递失效,无法响应通过Form.setValues设置的值。也就是value的修改无法回显给input元素。这种情况下虽然Form中的值和input中的看起来一样,但是input的中的值其实并不受控。
这种结构主要来源于antd对于react事件系统的理解。这很精髓。