React函数组件中类实例属性未与State同步问题
根本原因
- 你在空依赖数组的
useEffect中初始化UtilityClass实例,这个逻辑只会在组件首次挂载时执行1次。首次挂载时namestate的初始值为undefined,这个值被传入构造函数赋值给this.name,本质是存了一份当时的state值快照。 - React组件state更新时会触发重渲染,函数组件内的
name变量会拿到最新state,但存在useRef中的类实例不会被重新创建,也没有任何逻辑主动更新实例上的this.name属性。你调用setName触发state更新的操作,并不会反向修改类实例上独立存储的this.name,两个值完全没有绑定关系,所以打印时永远是初始化时拿到的undefined。
修复方案
方案1:保留类封装的写法,不存值快照,改为实时读取最新值
不要把state的瞬时值存在类属性上,而是传入可以实时获取最新state的方法,通过ref保证始终能拿到最新值:
- 修改
UtilityClass代码,接收获取name的方法而非name值本身
export default class UtilityClass { constructor(getLatestName, setName) { this.getLatestName = getLatestName this.setName = setName } changeName(newName) { this.setName(newName) } printName() { // 调用时实时读取最新值,而非读取固定的属性快照 console.log(this.getLatestName()) } }
- 修改组件代码,通过ref同步最新state,初始化类时传入取值方法
import { useRef, useState, useEffect } from 'react' import UtilityClass from './UtilityClass' export function App(props) { const utilityClass = useRef() const [name, setName] = useState() // 用ref同步每次渲染的最新name值 const latestNameRef = useRef(name) latestNameRef.current = name useEffect(() => { utilityClass.current = new UtilityClass( () => latestNameRef.current, setName ) }, []) function setViaClass(newName) { utilityClass.current.changeName(newName) } function printViaClass() { utilityClass.current.printName() } return ( <> <button onClick={() => setViaClass('Class')}>Set via class</button> <button onClick={printViaClass}>Print via class</button> </> ); }
方案2:贴合函数组件心智,抽离自定义Hook实现复用
你的复用、代码拆分需求完全可以用React官方推荐的自定义Hook实现,不需要手动维护类实例和state的同步,逻辑更简洁也不容易出闭包问题:
- 抽离复用逻辑为自定义Hook
import { useState, useCallback } from 'react' export function useNameHandler(initialName) { const [name, setName] = useState(initialName) const changeName = useCallback((newName) => { setName(newName) }, []) const printName = useCallback(() => { console.log(name) }, [name]) return { name, changeName, printName } }
- 在任意组件中直接调用该Hook即可
import { useNameHandler } from './useNameHandler' export function App(props) { const { changeName, printName } = useNameHandler() return ( <> <button onClick={() => changeName('Class')}>Set via class</button> <button onClick={printName}>Print via class</button> </> ); }
注意:自定义Hook是函数组件逻辑复用的标准方案,天然和React的渲染、state更新机制对齐,相比手动维护类实例的方案更不容易出现值不同步、闭包陷阱类的问题。
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

