依赖属性渲染的组件
有时候组件不需要有内部状态,状态完全由外部提供,这样可以进行简单封装。
const Button = ({text} : {
text : string
}) => {
return <button>{text}</button>
}
受控输入组件
const Input = ({value,onChange} : {
value : string,
onChange : Function
}) => {
return <input onChange={onChange} value={value} />
}
有时一个组件的输入完全依赖于属性,这样的组件内部没有状态,完全受到外部的控制,这样的组件是受控组件。 受控组件的优势是设计简单,缺点是增加了更新路径的长度(最小知识原则)。
value的更新需要外部驱动(props.value),而又需要从内部传出去(onChange)。外部需不需要知道这次更新且不说,上面的例子中用户每次的按键都需要尽快通知外部,比如不可以这样写。
const Input = ({value,onChange} : {
value : string,
onChange : Function
}) => {
return <input onChange={debounce(onChange, 300)} value={value} />
}
非受控输入组件
非受控组件会维护内部状态,外部只提供初始值。
const UnControlledInput = ({defaultValue,onChange} : {
defaultValue : string,
onChange? : Function
}) => {
const [value, setValue] = useState<string>(defaultValue)
useEffect(() => {
if(value !== defaultValue) {
onChange && onChange(value)
}
}, [value])
return <input onChange={e => {
setValue(e.target.value)
}} value={value} />
}
非受控组件内部维护了状态,因此外部只需要传入初始值,这样就不需要利用外部属性来驱动组件更新。从最小知识原则来看,这样外部的设计可以得到大大的简化。
兼容受控和非受控组件
当然在封装组件的时候,往往需要同时支持两种行为,那么可以这样做:
const MixedControlInput = ({
defaultValue,
value,
onChange,
}: {
value ? : string,
defaultValue ? : string,
onChange ? : Function
}) => {
const controlled = typeof value !== 'undefined'
const [_value, setValue] = useState<string>(controlled ? value : defaultValue)
useEffect(() => {
if(value !== defaultValue) {
onChange && onChange(value)
}
}, [_value])
return (
<input
onChange={(e) => {
if(controlled) {
onChange && onChange(e.target.value)
return
}
setValue(e.target.value)
}}
value={controlled ? value : undefined}
defaultValue={defaultValue}
/>
)
}
1
|