受控组件与非受控组件的实践与展示前言 在前端开发中,我们经常用到用到输入的一些组件,这些组件大致可分为两大类,受控组件和
前言
在前端开发中,我们经常用到用到输入的一些组件,这些组件大致可分为两大类,受控组件和非受控组件 ,用于处理一些数据的输入。
介绍
受控组件
- 组件的状态是可控制的
- 更新状态是同步的,
每次更新数据就会重新渲染一次视图
- 代码举一个例子
const Index = () => {
const [value, setValue] = useState<string>("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const v = e.target.value;
setValue(v); // 重新渲染数据
};
return <>
<p>{value}</p>
<input type="text" value={value} onChange={handleChange} />
</>;
}
受控组件
- 组件的状态是不可控制的,
可能导致数据不一致
- 更新状态是异步的
- 代码举一个例子
const Index = () => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const v = e.target.value;
console.log("🚀 ~ handleChange ~ v", v);
};
return <>
<p>{value}</p>
<input defaultValue={'1111111'} onChange={handleChange} />
</>;
}
实现
通过上面的例子我们大致知道了什么是受控组件和非受控组件。要是我们在实际开发中使用组件是受控
组件还是用非受控组件嘞,这是一个值得思考的问题,还需要结合实际情况使用。不过毫无疑问的是,封装一个输入的组件,我们还是得将其两者都要兼顾,比如说,Antd Design
就是这样做的,在组件的参数传递的时候都有defaultValue
和value
两个可选值。
我们可以通过传入的参数defaultValue
和value
去判断是否为受控组件
和非受控组件
。
举个例子:
// 封装useControllableValue
import { SetStateAction, useCallback, useRef, useState } from "react";
type Props<T> = {
defaultValue?: T;
value?: T;
onChange?: (e: T) => void;
}
function useControllableValue<T>(props: Props<T>): [T, (v: SetStateAction<T>) => void] {
const { value, defaultValue, onChange } = props;
const isControllable = value !== undefined;
const newValue = value ?? defaultValue ?? '';
const [_, setState] = useState<T>(newValue as T); // 确保在状态变化时正确地触发重新渲染,同时在需要时访问最新的值。
const ref = useRef<T>(newValue as T);
if (isControllable) {
ref.current = value as T;
}
useCallback(() => {
if (isControllable) {
setState(value as T);
}
}, [value, isControllable]);
const setValue = (v: SetStateAction<T>) => {
const t = typeof v === 'function' ? (v as Function)(ref.current) : v;
if (!isControllable) {
ref.current = t;
setState(t);
}
onChange && onChange(t);
};
return [ref.current, setValue];
}
export {useControllableValue};
// 封装一个Input组件
import { useControllableValue } from "./useControllableValue";
type Props = {
defaultValue?: string;
value?: string;
onChange?: (e: string) => void;
// onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
// 非受控组件
// const Index = ( props: Props ) => {
// const { defaultValue, onChange } = props;
// return <input type="text" defaultValue={defaultValue} onChange={onChange} />;
// }
// 受控组件
// const Index = ( props: Props ) => {
// const { defaultValue, value, onChange } = props;
// return <input type="text" defaultValue={defaultValue} value={value} onChange={onChange} />;
// }
const Index = ( props: Props ) => {
const { defaultValue } = props;
const [value, setValue] = useControllableValue<string>(props);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const v = e.target.value;
setValue(v);
};
return <>
<input type="text" defaultValue={defaultValue} value={value} onChange={handleChange} />
</>;
}
效果
受控组件
function App() {
const [value, setValue] = useState<string>('hello world');
const handleChange = (e: string) => {
console.log("🚀 ~ handleChange ~ e:", e)
setValue(e);
};
return <div>
<Input value={value} onChange={handleChange} />
</div>;
}
export default App;
非受控组件
function App() {
const handleChange = (e: string) => {
console.log("🚀 ~ handleChange ~ e:", e)
setValue(e);
};
return <div>
<Input defalutValue={"hello"} onChange={handleChange} />
</div>;
}
export default App;
结语
在封装一个hook函数useControllableValue
的时候,大家可以用ahooks
中的useControllableValue,相关源码,也可以自己手写,主要就是通过defaultValue
和value
这个值去判断是否为受控组件,然后去获取最新的值将传递出去。
转载自:https://juejin.cn/post/7425803259442397223