React常见错误及处理(二)

1. Too many re-renders. React limits the number of renders to prevent an infinite loop

重新渲染过多

这个报错就是说重新渲染过多。React 限制渲染的数量以防止无限循环。当组件在很短的时间有太多状态更新时,就可能会发生这种情况。导致无限循环的最常见原因是:

  • 直接在渲染中执行状态更新;
  • 未向事件处理程序提供适当的回调。

如果遇到这个警告,可以检查组件的这两个方面:

  1. 渲染中的状态更新
  2. 触发事件是否有正确的回调
const Component = () => {
  const [count, setCount] = useState(0);

  setCount(count + 1); // 渲染中的状态更新

  return (
    <div className="App">
      {/* onClick 没有正确的回调 */}
      <button onClick={setCount((prevCount) => prevCount + 1)}>
        Increment that counter
      </button>
    </div>
  );
}

2. React Hook useEffect has a missing dependency: ‘XXX’. Either include it or remove the dependency array

useEffect 缺少依赖

先来看看 React 官网给出的例子:

function Example({ someProp }) {
  function doSomething() {
    console.log(someProp);
  }

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

useEffect中定义空的依赖数组是不安全,因为它调用的 doSomething 函数使用了 someProp。这时就会报错:React Hook useEffect has a missing dependency: 'XXX'. Either include it or remove the dependency array**。**当props中的someProp发生变化时,函数doSomething的结果就会发生变化,然而useEffect的依赖数组为空,所以就不会执行回调中的内容。

有两种方式来解决这个问题:

  • useEffect中声明其所需函数,这种方式适用于只需要调用一次的函数,比如初始化函数:
function Example({ someProp }) {
  useEffect(() => {
    function doSomething() {
      console.log(someProp);
    }	
    doSomething();
  }, [someProp]); 
}
  • 使用useCallback来定义依赖项,确保当自身依赖发生改变时函数主体也会改变:
function Example({ someProp }) {
  const doSomething = useCallback(() => {
    console.log(someProp);
  }, [someProp])

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

3. Adjacent JSX elements must be wrapped in an enclosing tag

相邻JSX元素没有包装在封闭标记中

这个报错就是说相邻JSX元素必须包装在封闭标记中,也就是必须要有一个根元素:

const Component = () => (
  <Nice />
  <Bad />
);

从 React 开发人员的角度来看,这个组件只会在另一个组件内部使用。 因此,在他们的心智模型中,从一个组件返回两个元素是有意义的,因为生成的 DOM 结构将是相同的,无论外部元素是在此组件中定义还是在父组件中定义。但是,React 无法做出这种假设。该组件可能会在根目录中使用并破坏应用,因为它会导致无效的 DOM 结构。

所以,应该始终将组件返回的多个 JSX 元素包装在一个封闭标记中。可以是一个元素、一个组件或者 React Fragment:

const Component = () => (
  <React.Fragment>
    <Nice />
  	<Bad />
  </React.Fragment>
);

或者直接使用一个空标签来包装两个 JSX 元素:

const Component = () => (
  <>
    <Nice />
  	<Bad />
  </>
);

4. 如果要使用当前状态来计算下一个状态,就要使用函数的式方式来更新状态

先来看一个计数器的例子:

const Increaser = () => {
  const [count, setCount] = useState(0);
  
  const increase = useCallback(() => {
    setCount(count + 1);
  }, [count]);
  
  const handleClick = () => {
    increase();
    increase();
    increase();
  };
  
  return (
    <>
      <button onClick={handleClick}>+</button>
      <div>Counter: {count}</div>
    </>
  );
}

这里的handleClick方法会在点击按钮后执行三次增加状态变量count的操作。那么点击一次是否会增加3呢?事实并非如此。点击按钮之后,count只会增加1。问题就在于,当我们点击按钮时,相当于下面的操作:

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

当第一次调用setCount(count + 1)时是没有问题的,它会将count更新为1。接下来第2、3次调用setCount时,count还是使用了旧的状态(count为0),所以也会计算出count为1。发生这种情况的原因就是状态变量会在下一次渲染才更新。

解决这个问题的办法就是,使用函数的方式来更新状态:

const Increaser = () => {
  const [count, setCount] = useState(0);
  
  const increase = useCallback(() => {
    setCount(count => count + 1);
  }, [count]);
  
  const handleClick = () => {
    increase();
    increase();
    increase();
  };
  
  return (
    <>
      <button onClick={handleClick}>+</button>
      <div>Counter: {count}</div>
    </>
  );
}

这样改完之后,React就能拿到最新的值,当点击按钮时,就会每次增加3。所以需要记住:如果要使用当前状态来计算下一个状态,就要使用函数的式方式来更新状态:

setValue(prevValue => prevValue + someResult)

你可能感兴趣的:(react.js,javascript,前端)