认识状态管理

447 阅读6分钟

前言

主要从以下几个方面分别展开。

  1. 状态管理是什么
  2. 状态管理能做什么
  3. 状态管理带来的好处是什么

状态管理是什么

市面上已经有的状态管理工具有redux、vuex等等,他们有一个共同的特点,主要由三部分组成--状态、同步状态处理函数、异步函数。
对他们的第一印象一定是数据流管理工具,用来组件间数据共享的,避免数据嵌套层级较深的问题。
还有没有其他作用呢?

状态管理能做什么

在介绍状态管理的时候,我们都知道状态管理工具即数据流管理工具,用来管理数据,将数据及相关函数放在一起。那么,这样做的目的是什么呢?是不是将公共逻辑拆分出来?那非公共的数据应不应该抽象出来呢?我认为确实是将公共逻辑(数据)拆分出来,这也是一种状态管理工具的普遍使用的场景。并且我认为非公共数据也应该抽象出来,这里可能会有疑问,公共逻辑(数据)抽象出来可以理解是解耦,那么非公共逻辑(数据)抽象出来是什么目的呢?之后我们再来解答这个问题。
状态管理不应该只是一个数据传递层级深的问题或者代码解耦的一个方案,更应该是高可维护性代码模块划分的方案。
说到这里需要重温一下MVX模型,我们知道最开始有个MVC模型,这个在服务端模板渲染时代,是常见的一种模式,M即数据V则是模板C是业务处理逻辑,这种模式下小明开发的时候把M和C放在一起,开发起来很爽,从头写到尾,但是小明过了几个月维护的时候很头疼,几百上千行代码堆在一起,数据请求搁哪呢,除非command+F搜接口地址,否则就是大海捞针。大明代码素养高一点划分了几个函数,把数据获取与数据处理分离,下次接口新增字段大明只需要去数据处理函数里去改动就ok了。通过这个例子我们看到逻辑拆分的好处。
如今来到了MVVM时代,VM这一层承担了更多V的逻辑,使V变得更薄,那么随时而来的就是VM层变厚,这显然是个不好的消息,由于VM开发起来很丝滑,一不小心就会把view及modal&业务逻辑混在一块开发,带来的负面影响与之前讲到的是一样的,维护起来非常痛苦,模块划分不明确,或者没有划分模块都导致逻辑混乱代码可维护性降低。有一些解决方案比如逻辑拆分、纯函数编程、状态管理。
组件拆分的理念就是抽象公共组件或按照不同模块拆分,效果便是页面组件代码量减少,通过语义化命名组件可以快速了解页面组件的结构与主要逻辑。
纯函数编程的原则是一个函数只做一件事且入参对应唯一出参,效果也是代码逻辑更清晰。
状态管理显式的功能是管理数据,减少数据传递层级,他可以集结合纯函数的理念,让函数更纯粹,想像一下modal的异步effect只负责数据获取,同步reducer只负责数据转换,VM中只负责读取数据并处理视图逻辑,这不正是我们想要的大同社会(代码),说到这里,状态管理的作用就说完了。
回到最开始的一个问题,非公共数据拆到modal里的目的是什么呢?自然是模块划分了,modal作为数据源M,VM作为业务逻辑处理层,V作为视图展示,代码逻辑清晰,可以快速找到目标逻辑所在位置,减少迭代成本。

状态管理带来的好处是什么

讲状态管理带来的好处之前,先聊一下上一节中聊到的三个解决方案:逻辑拆分、纯函数、状态管理。 纯函数处理业务逻辑的好处在于对数据做修改不会影响数据源,使数据源可实现多种处理,且多种处理函数之间互不影响。样例如下:

// page.jsx bad
function Page(props) {
    const [data, setData] = useState([]);
    const fetchData = params => {
        fetch({
            url: '/api/test',
            method: 'GET',
            body: params,
        }).then(res => {
            if (res.success) {
                if (res.data.valueA) {
                    res.data.valueA = res.data.valueA.replace('test', 'test1');
                    setData(res.data);
                }
            }
        });
    };
    
    return <>{data}</>
}
// page.jsx good

function handle(data) {
    const copyData = cloneDeep(data);
    
    copyData.valueA = copyData.valueA.replace('test', 'test1');
    
    return copyData;
}
function Page(props) {
    const [data, setData] = useState([]);
    const fetchData = params => {
        fetch({
            url: '/api/test',
            method: 'GET',
            body: params,
        }).then(res => {
            if (res.success) {
                if (res.data.valueA) {
                    const data = handle(res.data);
                    setData(data);
                }
            }
        });
    };
    
    return <>{data}</>
}

在上述样例中,只有一个数据请求,及简单的展示,若jsx部分比较复杂二三百行,数据请求有五六个,数据处理逻辑有三四百行,那么放到一个文件里将极其臃肿,在我们思考如何划分模块的时候,redux在这个场景下也不失为一个好的解决方案,数据请求与数据处理分离,即异步effect处理数据获取,同步reducer负责数据处理,modal负责数据存储,一下就清晰了起来。reducer与异步effect也可以拆到两个文件中分别维护(数量比较多的情况下),样例如下:

// page.jsx
function Page(props) {
    const { dispatch } = useModal('data');
    const { data } = useSelector(state => state);
    
    useEffect(() => {
        dispatch.fetchData({ a: 1 });
    }, []);
    return <>{data}</>
}
// modal/data.js
import tangdao, { useModel } from '@myfe/tangdao';
import { cloneDeep } from 'lodash';

const td = tangdao();

td.model({
  namespace: 'data',
  state: 0,
  reducers: {
      handle(state, { payload }) {
          const data = cloneDeep(payload);
          
          data.valueA = data.valueA.replace('test', 'test1');
          return {
              ...state,
              data,
          };
      }
  }
  effects: {
      *fetchData({ payload }) {
          return fetch({
            url: '/api/test',
            method: 'GET',
            body: payload,
          }).then(res => {
            if (res.success) {
                this.handle(res.data);
            }
          });
      }
  }
});

利用状态管理我们实现了逻辑拆分,利用纯函数我们可以支持数据多次处理,状态管理使数据可复用,且组件间共享数据无需层层传递,只需利用状态管理分发即可。

总结

当出现新技术的时候,通过思考为什么会诞生这项技术,了解它的前世今生可以知道如何更好地使用它。否则可能会只发挥这项技术的一小部分作用,用不好还会使我们的项目不伦不类。状态管理并不只是数据管理工具,更是高可维护性代码的解决方案。而且状态管理各部分应各司其职,避免effect做了属于reducer的逻辑,保证结构清晰。
tips: @myfe/tangdao是一款猫眼开源状态管理工具