React Hook 初学者最常犯的几个错误

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钩子函数是用于监听值,假如监听的值发生改变的话,就会指定我们制定的函数。而上述的代码中我们监听了firstNamelastName两个变量值,并且两个值当中有一个发生变化后,我们就会重新设置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]);

运行上述的代码后,就会达到我们的预期效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值