你应该避免的常见 React Hooks 错误

React Hook 功能首次在 React 16.8 更新中引入,并因其功能而在开发人员中广受欢迎。它允许您挂钩 React 状态和生命周期功能。而且,有时,将其用于高级用例时可能具有挑战性。

在本文中,我将讨论每个开发人员在构建健壮的 React 应用程序时应该避免的 4 个常见的 React Hooks 错误。

1.改变Hooks调用顺序

不应在循环、条件或嵌套函数中调用钩子,因为条件执行的钩子会导致意外错误。

避免这种情况可确保每次组件渲染时以正确的顺序调用 Hook。此外,它有助于保持挂钩的状态在众多的一致useStateuseEffect电话。

在下面的示例中,我创建了一个基本的 React 组件来从下拉列表中选择一个人并显示所选人的数据。

SelectPerson 组件接收要检索的人的 id 作为 propsperson使用useEffect()Hook将个人数据保存在状态变量中。

import { useState, useEffect } from "react";

function SelectPerson({ id }) {
  if (!id) {
    return <div>Please Select a Person </div>;
  }

  const [person, setPerson] = useState({
    name: "",
    role: ""
  });

  useEffect(() => {
    const selectPerson = async () => {
      const response = await fetch(`/api/persons/${id}`);
      const selectedPerson = await response.json();
      setPerson(selectedPerson);
    };
    selectPerson();
  }, [id]);

  return (
    <div>
      <div>Name: {person.name}</div>
      <div>Description: {person.role}</div>
    </div>
  );
}

export default function IndexPage() {
  const [personId, setPersonId] = useState("");
  return (
    <div>
      <select onChange={({ target }) => setPersonId(target.value)}>
        <option value="">Select a person</option>
        <option value="1">David</option>
        <option value="2">John</option>
        <option value="3">Tim</option>
      </select>
      <SelectPerson id={personId} />
    </div>
  );
}
复制代码

乍一看,一切看起来都很好,但您会收到控制台错误,因为useState(),并且useEffect()并非在每个渲染中都可用。

image.png

为了避免这种情况,您可以简单地在 Hooks 调用移动 return 语句之前。

因此,请始终记住在 React 组件的顶层任何早期返回之前使用 Hooks。


import { useState, useEffect } from "react";

function SelectPerson({ id }) {

  const [person, setPerson] = useState({
    name: "",
    role: ""
  });

  useEffect(() => {
    const selectPerson = async () => {
      const response = await fetch(`/api/persons/${id}`);
      const selectedPerson = await response.json();
      setPerson(selectedPerson);
    };
    if (id) { 
      selectPerson(); 
    }
  }, [id]);
  if (!id) {
    return 'Please Select a Person';
  }

  return (
    <div>
      <div>Name: {person.name}</div>
      <div>Description: {person.role}</div>
    </div>
  );
}

....
...
...
复制代码

2. 不需要 Re-Render 时使用 useState

在功能组件中,您可以使用useStateHook 进行状态处理。虽然它非常简单,但如果使用不当,可能会出现意想不到的问题。

例如,假设一个组件有两个按钮。第一个按钮将触发计数器,第二个按钮将根据当前计数器状态发出请求。

import { useState } from "react";

function CounterCalc() {
  const [counter, setCounter] = useState(0);

  const onClickCounter = () => {
    setCounter((c) => c + 1);
  };

  const onClickCounterRequest = () => {
    apiCall(counter);
  };

  return (
    <div>
      <button onClick={onClickCounter}>Counter</button>
      <button onClick={onClickCounterRequest}>Counter Request</button>
    </div>
  );
}

export default function IndexPage() {
  return (
    <div>
      <CounterCalc />
    </div>
  );
}
复制代码

上述组件将正常工作。但是,由于我们没有state在渲染阶段使用,每次点击计数器按钮时都会有不需要的重新渲染。

因此,如果您需要在组件内使用一个变量来在渲染中保持其状态而不触发重新渲染,那么useRefHook 将是更好的选择。

import { useRef } from "react";

function CounterCalc() {
  const counter = useRef(0);

  const onClickCounter = () => {
    counter.current++;
  };

  const onClickCounterRequest = () => {
    apiCall(counter.current);
  };

  return (
    <div>
      <button onClick={onClickCounter}>Counter</button>
      <button onClick={onClickCounterRequest}>Counter Request</button>
    </div>
  );
}

export default function IndexPage() {
  return (
    <div>
      <CounterCalc />
    </div>
  );
}
复制代码

3. 通过 useEffect 处理 Actions

useEffect 挂钩是用于处理方法中的propstate修改相关的任务。但是,在某些情况下,它会使事情变得更加复杂。

假设有一个组件接受一个数据列表并将其呈现给 DOM。

import { useState, useEffect } from "react";

function PersonList({ onSuccess }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  const fetchPersons = () => {
    setLoading(true);
    apiCall()
      .then((res) => setData(res))
      .catch((err) => setError(err))
      .finally(() => setLoading(false));
  };

  useEffect(() => {
    fetchPersons();
  }, []);

  useEffect(() => {
    if (!loading && !error && data) {
      onSuccess();
    }
  }, [loading, error, data, onSuccess]);

  return <div>Person Data: {data}</div>;
}

export default function IndexPage() {
  return (
    <div>
      <PersonList />
    </div>
  );
}
复制代码

第一个useEffectHook 处理apiCall()初始渲染。如果所有条件都满足,则第二个useEffect将调用该onSuccess()函数。

但是,操作和调用的函数之间没有直接联系。因此,我们不能保证只有请求成功才会出现这种情况。

为了处理这种情况,我们应该在onSucces()函数内部移动apiCall()函数而不使用单独的 Hook。

4. 使用过时状态

如果您useState在代码的后续行中多次修改,结果可能会让您大吃一惊。

import { useState } from "react";

function Counter({ onSuccess }) {
  const [count, setCount] = useState(0);
  console.log(count);

  const increaseCount = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };

  return (
    <>
      <button onClick={increaseCount}>Increase</button>
      <div>Counter: {count}</div>
    </>
  );
}

export default function IndexPage() {
  return (
    <div>
      <Counter />
    </div>
  );
}
复制代码

在上面的例子中,Counter组件增加了状态变量的值count。由于increaseCount()函数调用了setCount()3 次函数,您可能认为单击一次按钮会将count 值增加3。但是,每次单击按钮只会增加 1。

初始调用适当地setCount(count + 1)增加了count,如 count + 1 = 0 + 1 = 1。类似地,setCount(count + 1)too的后续两次调用将计数设置为 1,因为它使用了count.

发生这种情况是因为状态值只会在下一次渲染中更新。

这个过时的状态问题可以通过以函数式方法更新状态来解决。

import { useState } from "react";

function Counter({ onSuccess }) {
  const [count, setCount] = useState(0);
  console.log(count);

  const increaseCount = () => {
    setCount(count => count + 1);
    setCount(count => count + 1);
    setCount(count => count + 1);
  };

  return (
    <>
      <button onClick={increaseCount}>Increase</button>
      <div>Counter: {count}</div>
    </>
  );
}

export default function IndexPage() {
  return (
    <div>
      <Counter />
    </div>
  );
}
复制代码

猜你喜欢

转载自juejin.im/post/7041388241848647711