React useEffect hook设置robot依赖触发无限循环原因排查
React useEffect 无限重渲染问题排查
问题复现代码
import React, { useEffect } from "react"; import CardList from "./ClassList"; import { robots } from './Robots'; import { useState } from "react"; import './App.css' const App = () =>{ const [search , searchfun] = useState(''); const [robot ,setRobot] = useState(['']); console.log(robot) useEffect(()=>{ setRobot(robots.filter((rob)=> rob.name.toLowerCase().includes(search.toLowerCase()))) }, [robot]); return( <div className="tc"> <h1 className="f1">Robofriends</h1> <div> <input className='pa3 ba b--green bg-lightest-blue' type='search' placeholder='search robots' onChange = {(event)=>{ searchfun(event.target.value); }} /> </div> <CardList robots={robot} /> </div> ); } export default App
问题现象
预期逻辑为useEffect执行时根据搜索框输入值过滤机器人数据,初始状态下search为空,过滤后返回全部机器人数据;原本认为将robot加入useEffect依赖数组后,仅当搜索框输入内容触发robot状态变化时才会执行副作用,但实际运行时页面出现无限渲染。
根本原因
无限渲染来自逻辑触发的死循环:
- 组件首次挂载完成后,useEffect感知到初始
robot状态为[''],立刻执行第一次副作用 - 副作用内调用
setRobot,传入robots.filter(...)返回的全新数组引用 - React对状态做浅比较,判定
robot状态发生变化,触发组件重渲染 - 重渲染完成后,useEffect对比依赖项
robot,发现和上一次渲染的引用地址不一致,再次执行副作用 - 副作用再次调用
setRobot传入新生成的数组引用,再次触发重渲染,循环往复不会停止
注意:即便两次过滤返回的内容完全一致,Array.filter()每次执行都会生成独立的数组对象,引用地址和之前的状态完全不同,React会直接判定为状态更新。
除此之外代码还存在逻辑错误:预期输入变化时触发过滤,但依赖项写的是robot,搜索框输入只会更新search状态,根本不会触发当前useEffect执行,搜索功能本身是失效的。
修复方案
将useEffect的依赖项从robot改为search,不要在副作用内部更新自身依赖的状态:
// 修正后的useEffect useEffect(()=>{ setRobot(robots.filter((rob)=> rob.name.toLowerCase().includes(search.toLowerCase()))) }, [search]);
修正后的执行逻辑完全符合预期:
- 组件首次挂载时执行一次,search为空时过滤出全部机器人数据
- 只有搜索框输入导致
search状态变化时,才会重新执行过滤逻辑更新机器人列表 - 不会触发循环更新,因为副作用执行与否和
robot状态无关,setRobot触发重渲染后不会重复调用副作用
额外优化:如果过滤逻辑不涉及异步请求,完全不需要使用useEffect,直接在渲染阶段计算过滤结果即可,可以省掉一次多余的重渲染:
// 更简洁的实现,移除robot状态和对应的useEffect const filteredRobots = robots.filter((rob)=> rob.name.toLowerCase().includes(search.toLowerCase())) // 渲染时直接将filteredRobots传给CardList即可
内容的提问来源于stack exchange,提问作者Midhunkrishnan
相关产品推荐
相关产品推荐

