重新渲染过多
这个报错就是说重新渲染过多。React 限制渲染的数量以防止无限循环。当组件在很短的时间有太多状态更新时,就可能会发生这种情况。导致无限循环的最常见原因是:
如果遇到这个警告,可以检查组件的这两个方面:
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>
);
}
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]);
}
相邻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 />
</>
);
先来看一个计数器的例子:
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)