# 最基本的理解

受控组件跟非受控组件的区别可以粗糙的理解为:组件受不受外部环境控制,如果受影响,则是受控组件;否则如果状态都封闭在组件内部,则是非受控组件。

最大区别:受不受外部控制

举个例子:

// 非受控组件,内部有自己的状态,不受外部控制
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,则每次onChangeParent,再触发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了