Redux的不可变力量真的是你渴望的吗?😵😵😵

965 阅读5分钟

这两天被一个需求搞到头都大了,最近在开发一个在线代码编辑器,虽然在写问篇文章的时候我已经解决了这个问题了,那么接下来我就来分享一下我解决这个问题的历程。

项目需求

首先我们先来明确一下项目需求,现在有这样一个功能,在我的编辑器项目中,有一个文件系统,如下图所示:

20230820095147

我要给这个文件系统添加一个修改文件名的功能,如下图所示:

20230820095256

这个功能的大概需求就是我首先有一个 treeData 数据用来渲染文件结构,然后通过对应的 key 获取特定的文件,然后修改其文件名。

一开始用的是 useState 来实现这个功能,并没有发现什么问题,但是现在组件代码太多了,要把两个功能点拆分开来,一个做展示,一个做修改。

方案一

在我的项目中使用的 @reduxjs/toolkit,所以我首先考虑到的也必然是使用它通过全局管理数据的方式来管理这个 treeData 的数据,因为这个数据两个组件都要使用到。通过 dispatch 来修改 treeData。

redux 不仅是一个状态管理工具,其同时也是一种比较优秀的模式,即我们熟知的:

Store -> Dispatch -> Action -> Reducer -> Store

Redux 有三大原则,其中State 是只读的 是我们接下里要了解的。

Redux 是一个用于 JavaScript 应用程序状态管理的库,它的核心思想之一就是不可变性。不可变性是指一旦数据被创建,就不能被修改。它的状态变化是通过纯函数来处理的。这意味着给定相同的输入,总是会得到相同的输出。这种特性使得状态变化可预测,便于调试和测试。唯一改变 state 的方法就是触发 action

也正是因为它的数据不可变让我变得头大了。

接下来我们来看代码,首先我们初始化一个 treeData 数据,并通过 changeTreeData 来修改 treeData 数据:

20230820101457

在项目初始化渲染的时候我们通过 readFileSystem()来获取文件系统的文件目录,并将其返回结果传递给 changeTreeData 这个 reducer 去触发状态更新,如下图所示:

20230820101620

到这里的时候我们已经获取到了 treeData 的值了,也就是这里要展示的值了:

20230820105400

而当我们点击的那个画圈圈的图标之后,或弹错一个消息窗口。

20230820105524

在这里首先我们先从 redux 里获取到数据,查找出当前所点击的文件,并对其文件重新命名,使用 dispatch 来出发更新。这个时候我们回到 changeTreeData 这个 reducers 中来。

20230820105745

在这里我们首先通过 findNodeByKey 获取到当前要修改的文件,并修改当前的 title,这个时候问题就出现了:

20230820105910

意思就是说,这个状态是只读的,不能修改,但是最终我刷新浏览器的时候,效果是生效了的。

那好,既然这样的话,那我用深拷贝的方式对这个 treeData 进行复制:

20230820110111

但是最终还是不行,数据依然是不可变

20230820110214

我去看了一下,好像 redux 全部数据都是不可变的,具体有没有其他方法使它可变的方法我不知道。

在这段代码中,我已经是通过 action 去触发更新,而且要修改的值也不是原来的是 treeData 了,还是以为会有不可变的阻碍。

这个问题产生之后,我一直在想解决办法,其中考虑到三个:

  1. 使用 localStorage:这个好像看起来没有什么问题,通过本地去存和取数据,但是我要做到数据更新,如何实现页面的更新呢,这是一个问题,感觉不太好使,pass 掉。
  2. 切换其他状态管理库:我也有考虑过其他的状态管理库,类似与 zuStand,但是要切换的成本大,我也不想去特意学新的状态管理库,且还是有一点成本的,因为我之前就有其他的状态在管理。
  3. context:使用 context,在展示组件上定义一个 useState,并将 state 和 setState 通过 context 传递给修改组件,并通过 setState 修改 treeData 来触发页面更新。

最终解决方案

一开始想到 context 我是拒绝的,因为我本来就咋写过项目,没有想到 context 是可以传递 setState 的。

首先我们定义一个 context:

import React, { createContext } from "react";
import type { DataNode } from "antd/es/tree";

export interface treeDataContextType {
  treeData: DataNode[];
  setTreeData: React.Dispatch<React.SetStateAction<DataNode[]>>;
}

const TreeDataContext = createContext<treeDataContextType | undefined>(
  undefined
);

export default TreeDataContext;

最终在展示组件中使用该 context 并将 state 传递给修改组件:

20230820112145

最终实现了我想要的效果,如下代码所示:

20230820112305

最终效果如下动图所示:

动画.gif

总结

本人能力有限,以上观点仅仅用于记录本人在开发过程中的心里活动,如果有说错或者有更好的解决方案欢迎指出,有一个可以说的是,换其他状态管理库现在来看应该是不太可能的了,我懒。。。。。。

虽然React官方也在推崇不可变数据,单向数据量,但是有时候我们也要需要有一定的能力去修改这些数据。

项目地址:

如果你也喜欢该项目,欢迎 star 🥰🥰🥰