useState 和 useRef 函数使用上的权衡
首先我们先来看一个简单的例子,具体代码如下
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
function onSubmit(e: FormEvent) {
e.preventDefault();
console.log({ email, password });
}
return (
<div>
<form onSubmit={onSubmit}>
<div>
<form onSubmit={onSubmit}>
<label htmlFor="email">电子邮件</label>
<input
value={email}
type="email"
id="email"
onChange={(e) => setEmail(e.target.value)}
/>
<label htmlFor="password">密码</label>
<input
value={password}
type="password"
id="password"
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">提交</button>
</form>
</div>
</form>
</div>
);
从上述例子可以看出,上述的表单是采用useState
来进行表单值的跟踪,因为 React 中的useState
钩子函数是当页面重新渲染的时候,就会更新监听的值,而表单元素的每次操作都会重新渲染,这样useState
钩子函数就会监听到变化从而更新值。使用useState
钩子函数也是官方推荐的做法。因为这样做是把表单的值跟踪放给 React 来帮我们实现,并且可以做很多复杂的业务逻辑。
然后假如我们的表单只是简单的把表单数据收集起来,并且不做什么表单校验的话,其实可以使用useRef
来让表单变成不可空组件,这样我们就可以直接通过 DOM 实例直接获取到表单值,从而简化代码。具体代码如下:
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
function onSubmit(e: FormEvent) {
e.preventDefault();
console.log({
email: emailRef.current?.value,
password: passwordRef.current?.value,
});
}
return (
<div>
<form onSubmit={onSubmit}>
<label htmlFor="email">电子邮件</label>
<input ref={emailRef} type="email" id="email" />
<label htmlFor="password">密码</label>
<input ref={passwordRef} type="password" id="password" />
<button type="submit">提交</button>
</form>
</div>
);
但是对于大型表单或者表单有负责行为的不建议使用useRef
来替代useState
钩子函数,因为useRef
让 DOM 而不是 React 来处理跟踪值,这有时会在 React 渲染其虚拟 DOM 时导致意外行为。
useState 函数传入的参数形式不同而导致不同的结果
我们还是首先看一个例子,
const [count, setCount] = useState<number>(0);
function adjustCount(amount: number) {
setCount(count + amount);
}
<div>
<button onClick={() => adjustCount(-1)}>-</button>
<span>{count}</span>
<button onClick={() => adjustCount(1)}>+</button>
</div>;
这是一个简单的例子,我刚开始使用 React 来编写项目的时候都是采用上述这种方式来修改需要监听的值。假如我们在adjustCount
函数中调用两次setCount
,我以前会认为会让 count 累计叠加两次值,代码片段如下:
setCount(count + amount);
setCount(count + amount);
但是运行上述修改后的代码,可以发现并不会和我预想的一样 count 叠加两次值,运算实际只是运行了一次。
因为每次渲染计数器时都会设置计数变量,因此每当我第一次渲染计数器时,count 都会设置为零,所以 count 的所有操作都是零加一,而后续的 setCount 又会重新做一遍。需要说的是在一个函数有连续多个设置状态时,React 所做的就是将他们全部批量一起同时处理所有这些操作。
而为了解决这个问题,我们可以把代码改为如下形式:
setCount((currentCount) => {
return currentCount + amount;
});
setCount((currentCount) => {
return currentCount + amount;
});
我们运行上述的代码后可以发现,上述代码按照我们的预期运行了并得到了对应的结果。因为上述的方式传入了 count 改变后的实际值,所以在后续的开发中建议使用如上的写法来修改监听值。
过渡使用 useEffect 函数和对 useState 函数了解不透彻
首先我们先可以一个简单的例子,
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
return (
<div>
<input value={firstName} onChange={(e) => setFirstName(e.target.value)} />
<input value={lastName} onChange={(e) => setLastName(e.target.value)} />
{fullName}
</div>
);
因为我们知道useEffect
钩子函数是用于监听值,假如监听的值发生改变的话,就会指定我们制定的函数。而上述的代码中我们监听了firstName
和lastName
两个变量值,并且两个值当中有一个发生变化后,我们就会重新设置fullName
的值。
上述的代码不是最好的处理方式,因为过度使用useEffect
钩子函数来监听我们的值,并做相应的业务逻辑处理,我们可以把代码改为如下形式:
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const fullName = `${firstName} ${lastName}`;
return (
<div>
<input value={firstName} onChange={(e) => setFirstName(e.target.value)} />
<input value={lastName} onChange={(e) => setLastName(e.target.value)} />
{fullName}
</div>
);
改为上述的代码后,我们的页面还是返回了预期的效果给我们,因为useState
钩子函数是监听值并且把值的修改绑定到了表单元素上,所以当表单元素有输入时,页面就会重新进行渲染操作,这样所有代码就会重新执行,这样fullName
就会被重新赋值。所以以后在编写项目的时候就需要注意这些细节。从细节决定钩子函数的使用。
函数组件中定义的变量存在隐藏的问题
还是首先看一个简单的例子,具体代码如下:
const [age, setAge] = useState(0);
const [name, setName] = useState("");
const [darkMode, setDarkMode] = useState(false);
const person = { age, name };
useEffect(() => {
console.log(person);
}, [person]);
return (
<div style={{ background: darkMode ? "#333" : "#fff" }}>
Age:{" "}
<input
value={age}
type="number"
onChange={(e) => setAge(+e.target.value)}
/>
<br />
Name:
<input value={name} onChange={(e) => setName(e.target.value)} />
<br />
切换主题: <input
type="checkbox"
value={darkMode + ""}
onChange={(e) => setDarkMode(e.target.checked)}
/>
</div>
);
首先我们先预估一下假如我们点击切换主题
的单选框,会得到什么结果。
以前我肯定会回答不会打印出person
的对象值,但是运行效果是会的,因为当点击表单的单选框时,页面都会重新进行渲染,从而导致所有待会都是重新进行执行,而person
这个变量就会重新创建一个新的对象,所以useEffect
钩子函数会重新执行里面的函数,这里是需要注意的。
假如要解决上述的问题,可以使用useMemo
钩子函数来解决。具体的代码如下:
const person = useMemo(() => {
return { age, name };
}, [age, name]);
运行上述的代码后,就会达到我们的预期效果。