# 最基本的理解
受控组件跟非受控组件的区别可以粗糙的理解为:组件受不受外部环境控制,如果受影响,则是受控组件;否则如果状态都封闭在组件内部,则是非受控组件。
最大区别:受不受外部控制
举个例子:
// 非受控组件,内部有自己的状态,不受外部控制
const Input = () => {
const [value, onChange] = useState('')
return <input type="text" value={value} onChange={(e) => onChange(e.target.value)} />
}
// 受控组件,组件的状态来自外部的props
const ControlledInput = (props) => {
const { value, onChange } = props
return <input type="text" value={value} onChange={onChange} />
}
# 可以"全都要吗?"
那有没有可能一个组件既可以是受控也可以是非受控的,答案是有的;这么想:如果外部有传值进来,我们就接收处理,此时为受控组件;否则内部自己维护状态,此时为非受控组件;
const InputPlus = (props) => {
// 如果没有传,则是undefeind
const isControlled = props.value !== undefined
const [value, setValue] = useState('')
useEffect(() => {
if (isControlled) {
setValue(props.value)
}
})
return <input
value={value}
onChange={(e) => {
setValue(e.target.value)
if (isControlled) {
props.onChange(e.target.value)
}
}}
/>
}
此时我们在组件内部增加了一段逻辑,当如果是isControlled
,则每次onChange
到Parent
,再触发Child
的组件更新,走进useEffect的SetValue
如果不是受控组件,则正常的setValue,内部自行更新状态;
完美?no,上面暴露了一些问题
child状态同步慢一个周期
我们input的value拿的是useState返回值,需要在下个effect后执行setvalue才能更新到最新的props value值
setState(setValue)多了一个render周期
useEffect是在dom挂载后执行的effect函数,这时候在里面再进行一次setState会重新render
# 处理
const InputPlus = (props) => {
// 如果没有传,则是undefeind
const isControlled = props.value !== undefined
const valueRef = useRef(props.value)
if (isControlled) {
valueRef.current = prosp.value
}
const [, forceUpdate] = useState({})
return <input
value={valueRef.current}
onChange={(e) => {
valueRef.current = e.target.value
forceUpdate({})
if (isControlled) {
props.onChange(e.target.value)
}
}}
/>
}
首先我们引用了一个Ref类型数据,在value上绑定了valueRef.current,在onChange的时候及时修改current,不需要等到Parent传props.value在更新value了,这就解决了渲染慢一个周期的问题
第二个问题:我们自定义了一个forceUpdate函数,在onChange的时候触发render,这样就不用等到useEffect再更新input上的value了