为了账号安全,请及时绑定邮箱和手机立即绑定

如何在EventEmitter的on('change')函数中使用钩子设置状态?

如何在EventEmitter的on('change')函数中使用钩子设置状态?

斯蒂芬大帝 2023-09-07 17:01:01
为了学习的目的,我试图通过从头开始实现它来分解通量模式的基础知识。我知道我可以使用类组件解决下面的问题,但为什么它可以与类组件一起使用setState({ ... }),而不可以与钩子的setValues功能一起使用?正如您所猜测的,问题在于该setValues函数似乎没有触发新状态的重新渲染。我有一种感觉这与闭包有关,但我不太了解它们,所以我将不胜感激任何指导和帮助。店铺// store.jsimport EventEmitter from "events";class StoreClass extends EventEmitter {    constructor() {        super();        this.values = []    }    addNow() {        this.values.push(Date.now());        this.emit('change')    }    getAll() {        return this.values;    }}const store = new StoreClass();export default store;成分// App.jsimport store from './store';function App() {    const [values, setValues] = useState([]);    const handleClick = () => {        store.addNow();    };    useEffect(() => {        const onChangeHandler = () => {            console.log("Change called");            setValues(() => store.getAll())        };        store.on('change', onChangeHandler);        return () => store.removeAllListeners('change')    }, [values]);    return (        <div>            <button onClick={handleClick}>Click to add</button>            <ul>                {values.map(val => <li key={val}>{val}</li>)}            </ul>        </div>    );}
查看完整描述

2 回答

?
ibeautiful

TA贡献1993条经验 获得超5个赞

经过几个小时的修补并再次阅读反应文档后,我终于可以回答我自己的问题了。简而言之,问题在于商店中的状态并不是一成不变地更新的。


Redux 是 Flux 架构的实现,在其文档中说得非常清楚:


Redux 期望所有状态更新都是不可变地完成


因此,上面代码中的罪魁祸首就在addNowstore的函数中。该Array.push()方法通过附加一个项目来改变现有数组。执行此操作的不可变方法是使用该Array.concat()方法或 ES6 扩展语法(解构)[...this.values],然后将新数组分配给this.values.


addNow() {

  this.values.push(Date.now()); // Problem - Mutable Change

  this.values = this.values.concat(Date.now()) // Option 1 - Immutable Change

  this.values = [...this.values, Date.now()] // Option 2 - Immutable Change

  this.emit('change')

}

要查看 React 组件中每个更改的效果,请将钩子setValues中的函数更改useEffect为以下内容:


setValues((prev) => {

  const newValue = store.getAll()


  console.log("Previous:", prev)

  console.log("New value:", newValue)


  return newValue

})

在控制台中,很明显,当存储中的状态可变地更改时,单击第一个按钮后,先前的状态和新的状态将始终相同并引用相同的数组。但是,当存储中的状态不可改变地更改时,先前的状态和新的状态不会引用相同的数组。此外,在控制台中可以清楚地看到先前的状态数组比新的状态数组少一个值。


为什么使用类组件时状态更改会触发重新渲染,而使用钩子时则不会?

根据类组件的 setState 函数的React 文档。


setState() 总是会导致重新渲染,除非 shouldComponentUpdate() 返回 false。


这意味着无论存储中的状态是可变的还是不可变的,当触发更改事件并setState调用该函数时,React 都会重新渲染组件并显示“新”状态。然而,钩子的情况并非如此useState。


根据useState hook的 React 文档:


如果将 State Hook 更新为与当前状态相同的值,React 将退出而不渲染子项或触发效果。(React 使用 Object.is 比较算法。)


这意味着当通过改变原始对象来完成对存储中数组的更改时,react 在决定再次渲染之前会检查新状态和先前状态是否相同。由于先前状态和当前状态的引用是相同的,因此 React 不执行任何操作。为了解决这个问题,商店中的更改必须一成不变地完成。


查看完整回答
反对 回复 2023-09-07
?
梵蒂冈之花

TA贡献1900条经验 获得超5个赞

useEffct 监听值的变化,但 setValues 只存在于 useEffect 中,我认为你根本不需要 useEffect


你可以尝试这样的事情:


    // App.js

import store from './store';


function App() {

    const [values, setValues] = useState([]);



    const onChangeHandler = () => {

            console.log("Change called");

            const storeValues = store.getAll();

            setValues(storeValues);

        };


    const handleClick = () => {

        store.addNow();

        onChangeHandler();

        };



    return (

        <div>

            <button onClick={handleClick}>Click to add</button>

            <ul>

                {values.map(val => <li key={val}>{val}</li>)}

            </ul>

        </div>

    );

}


查看完整回答
反对 回复 2023-09-07
  • 2 回答
  • 0 关注
  • 69 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信