React中const声明useMemo返回值触发'is constant'linter警告
这个警告和React Hooks本身没有关系,是JavaScript基础语法规则触发的:const 声明的变量禁止被重新赋值,你平时用const声明各类Hooks返回值不会报警告,是因为绝大多数场景下你只会读取变量值、调用Hook提供的更新方法,从来不会直接对变量做重新赋值操作。
这次linter报警告,就是因为扫到了你在clearLoadingProcess函数中写了loadingList = []——这行代码是直接给const声明的loadingList变量重新赋值,完全违反了const的语法约束。
这里要注意一个容易混淆的点:const 仅限制变量的重新赋值,不限制对引用类型值内部内容的修改。所以你代码里写的loadingList.push()、loadingList.splice()操作不会触发报错,因为这些操作只是修改了loadingList指向的数组本身的内容,没有改变loadingList变量的指向;但loadingList = []是让变量指向一个全新的空数组,属于重新赋值,必然触发规则校验。
你现在觉得功能运行正常,只是没触发对应重渲染场景的巧合,这段代码本身有明显的逻辑漏洞:useMemo的设计用途是缓存计算开销大的派生值,从来不是用来存储跨渲染周期的可变数据的。你直接修改useMemo返回的数组、甚至在函数内给变量重新赋值的操作,在组件触发重渲染时会直接失效——重渲染会重新执行组件内的代码,const loadingList = useMemo(() => [], [])会重新执行,你之前在clearLoadingProcess里给loadingList赋的新空数组会直接被覆盖,完全达不到清空的预期。
就算你把const改成let消掉了linter警告,这个逻辑bug依然存在。
根据你的使用场景选对应方案即可:
- 如果这个loading列表不需要触发UI更新,只是用来存跨渲染周期的临时可变数据,用
useRef替代useMemo:
const loadingListRef = useRef([]); function addLoadingProcess(process) { loadingListRef.current.push(process) } function clearLoadingProcess() { loadingListRef.current = [] setAppIsLoaded(true) } function removeLoadingProcess(process) { const index = loadingListRef.current.indexOf(process) if (index > -1) { loadingListRef.current.splice(index, 1) } if (loadingListRef.current.length === 0) { setAppIsLoaded(true) } }
- 如果这个loading列表的变化需要同步驱动UI渲染,直接用
useState管理状态,所有修改都通过生成新数组的方式做不可变更新,不要直接修改原数组。
内容的提问来源于stack exchange,提问作者Simon Nazarenko

