我理解的前端状态管理:从Flux到Redux再到Pinia说开去

429 阅读23分钟

前言

在前端开发中,状态管理是一个不可忽视的核心主题。随着应用复杂度的提升和用户交互需求的增加,如何高效地管理状态成为了前端开发者必须面对的难题。从最早的 jQuery 操作 DOM,到现代前端框架如 Vue 和 React 中的状态驱动开发,状态管理的思路和实现也在不断进化。

你可能也体会过,当应用变得复杂,组件间的状态共享逐渐增多,甚至需要在不同的页面间传递数据时,光靠 props 和 events 往往捉襟见肘。而一旦状态失控,不仅代码看起来像“意大利面条”,调试起来也会让人崩溃。

本文从基础概念出发,循序渐进地剖析状态管理的内涵与发展。从状态的定义到状态管理的必要性,从经典的 MVC 模式到现代的 Flux 架构,再到 Vuex、Redux 等广为流行的全局状态管理库,我们将全景式地回顾状态管理的演进历程,并探讨不同场景下的最佳实践。

对于前端开发者而言,理解状态管理的本质不仅有助于提升代码的维护性和扩展性,更能够帮助我们应对复杂业务场景下的挑战。希望通过本文的内容,能够为你理清状态管理的脉络,提供实用的参考与启发

文前求赞,写得很辛苦,如果对您有帮助,麻烦点赞、收藏、评论三连。谢谢了!🥲🥲🥲

1. 什么是状态?

在做一件事之前,我们先要搞懂一些基本概念,状态管理亦然。

还记得我们前面写过的前端框架的核心原理公式吗?

基于状态的声明式渲染:UI = f(state)

这里面的 state 就是我们本节要讲的状态。

状态就是系统的一个变化过程,它由数据来做表达。

那什么又是数据呢?我们从后端请求回来的是数据,用户关闭弹窗,showModal=false 也是数据。我们可以理解,我们用数据来表达状态,但是状态不等于数据。

我们讲前端的工作是什么,本质是管理状态。页面的交互或者其他输入导致状态变化,状态变化就要产生响应的副作用操作,例如网络请求、关闭弹窗、跳转页面等等。而框架帮我们做的就是状态变化后的ui渲染自动化。本质上,我们玩来玩去,就是在玩状态。

2. 为什么我们需要状态管理

为什么在jquery时期,不需要状态管理? 因为jq时代只有数据没有状态,数据是直接操作dom塞进去的,而不是有状态的中间层。 而vue和react时期,我们只需要管理好状态,状态变化产生的UI变化由框架来处理。而当你的应用复杂度提升,当你的状态联系的副作用越来越多,你的代码就越来越难维护,质量就非常容易出现问题,我们需要解决方案,需要把状态管理变得简单。

3. 状态管理管的是什么?

从第一层维度讲,主要是2个点:

  • 管理状态变化之前的逻辑:如何更高效和按指定规则的处理
  • 状态变化后的副作用处理:如渲染和网络请求

从容易理解的和结合框架的维度讲:

就是管理组件中间共享的状态,按照统一的管理规则,使得状态的变化是可预测的。

  • 数据的全局共享
  • 数据按特定规则去修改
  • 数据的变化能够触发视图的更新

这里的思路和第一层维度也是互相呼应的。

状态变化之前的逻辑 ---> 数据能够按特定的规则去修改

状态变化后的副作用处理 ----> 数据的变化能够改变相关的视图

我们举一些例子来呼应上面的界定:

vuex中修改数据是不是按特定的规则去修改,mutation、action等,然后数据的变化能够触发视图的更新;redux也同理,也是有一套修改状态的规则,状态的变化触发重新渲染;setstate在改变状态前,内部机制会合并批量执行。vue的响应式本质,也是对状态做了劫持:definepropery和proxy(状态变化前的逻辑),然后状态变化触发watcher的更新。

4. 状态管理的发展

状态管理的发展和前端的发展息息相关。搞清楚了状态管理的发展也大致理清楚前端的发展。 前端的架构是如何从后端渲染到前后端分离,从jquery到现代前端框架,从 mvp模式走到mvvm模式,从ejs后端的模板填充,走到现在的 vuex redux useState 的?

4.1 服务端渲染

在最早期,不区分前后端,用户发起请求,后端把页面数据填充到模板里返回html页面,这种模式叫服务端渲染。

image.png

4.2 MVC模式

为了降低服务端渲染的复杂度,MVC模式出现了。

Controller: 处理请求,组装这次请求需要的数据

Model:需要用于UI渲染的数据模型

View:视图,用于将模型组装到界面中

image.png

4.3 前后端分离

随着技术发展越来越快,业务越来越复杂,纯后端的开发模式已经不能满足时代的需要,当一个行业发展装大,分工必然是提升效率的历史必然。再者出现了 ajax react vue 等技术迭代,于是前后端开始分离,由前端承担一部分开发工作。

在当前的阶段,中大型项目均使用前端开发框架进行开发,这类框架项目使用的是单向数据流。若需要共享数据,则必须将数据提升到顶层组件,然后数据再一层一层传递,极其繁琐。 虽然可以使用上下文来提供共享数据,但对数据的操作难以监控,容易导致调试错误,以及数据还原的困难。并且,若开发一个大中型项目,共享的数据很多,会导致上下文中的数据变得非常复杂。某个组件去改动了共享数据,会很难定位,难以维护。

针对上述现状,我们如何去做状态的管理呢?

后端的MVC模式能够移植到前端吗?答案是否定的。

原因是前端的 controller 相比服务器端过于复杂。服务端的 controller 只是根据路径的不同做不同的逻辑处理。页面的路径当然是有限的,例如增删查改,因此服务端的控制器相对简单。

而前端 controller 非常复杂,包含了用户的各种操作,鼠标移入鼠标移除,用户点击,settimerout setinterval 等等,所有的这些都要操作数据,在中大型项目中会非常复杂和繁琐。所以后端的mvc模式很难移植到前端,controller 会变得非常庞杂,难以提升效率。

为了解决上述的种种问题,于是出现了redux vuex pinia 等状态管理方案,同时针对不那么复杂的业务,setState useState ref reactive 等方法也足够使用,我们将在下一节,详解介绍,现代是如何实现状态管理的,以及我自己的最佳方案。

5. 状态管理是如何实现的

5.1 思想归纳

状态管理的要达到的目的我们前面说了,这里再重复一下,主要是如下2点:

  • 状态变化之前的逻辑 ---> 数据能够按特定的规则去修改

  • 状态变化后的副作用处理 ----> 数据的变化能够改变相关的视图

实现上述目标有主要两种手段:

  1. 直接修改数据无效,因此封装特定修改数据的API ,来把上面2点目标的处理逻辑(状态变化之前的逻辑&状态变化后的副作用处理)包含在API内。典型代表:reactsetState。状态变化前会批量的异步合并,状态变化后会触发试图渲染和生命周期,hooks等的执行。
  2. 可以直接修改数据,不用调用特定API,原理是对数据进行进行劫持,自动完成上面2点目标的处理逻辑。典型代表: vue2definePropertyvue3Proxy 。在初始化前对数据进行响应式处理(vue2中data写到模板配置项中,内部会对data进行响应式处理),主动对数据进行响应式处理,如 Ref Reactive 的使用。 当数据进行响应式处理后,数据在模板编译或者某些时刻会触发get 请求,从而把对应的状态变化后的副作用处理逻辑包装成 watcher 保存,后续直接修改状态,就会自动触发状态变化后的副作用处理: dep 的 notify

5.2 方案总览

  • 组件内部状态管理

    react使用 setState\useState,vue中使用 ref reactive等。

  • 组件间的状态管理

    可以通过 props、provider、inject、context等从上往下传递的方法,遵循单向数据流。 也可以通过EventBus的思路,全局管理。 但是如果业务复杂,数据流转比较复杂,多个组件都有修改同一个状态的需求,就很难通过这些方法来维护状态了。 且上述的方法都比较难做到:状态变化前的逻辑的复用。

    image.png

  • 全局状态管理库 为了能实现状态变化前的逻辑复用,状态变化可按特定规则执行,状态变化的可预测可测试,以及状态变化的全局共享,业内逐步发展除了成熟的全局状态管理库,以便我们进行复杂的组件间的状态管理。

    • react阵营:redux react-redux mobx zustand

    • vue阵营:vuex pinia

6. 状态管理库 flux redux mobx vuex pinia

这一节,我们分别介绍常用的几种状态管理库

6.1 Flux

公共的状态管理的起源在于flux,这是一个由facebook团队剔除的架构思想,旨在解决复杂场景下公共状态管理繁重的问题。

flux将一个应用分成四个部分 视图层View ---> 发出改动状态的消息Action ---> 用来接收Action 执行相应回调函数的Dispatcher -----> Dispacher直接改变状态 状态存储到Store

  • 单向数据流。视图事件或者外部测试用例发出 Action ,经由 Dispatcher 派发给 Store ,Store会触发相应的方法更新数据、更新视图
  • Store 可以有多个
  • Store 不仅存放数据,还封装了处理数据的方法

image.png

可以看到这是一个单向数据流,只能正向不能逆向,用户只能操作action去间接的修改状态,这样我们就可以很容易追踪每一次状态的变化。而redux就是在这个思路上演变而来。

6.2 Redux

2015 年的时候,Dan Abramov 推出的 Redux 席卷了整个 React 社区,Redux本质就是在 Flux 上做了一些更新:

  • 单向数据流。View发出 Action (store.dispatch(action)),Store调用 Reducer 计算出新的 state ,若 state 产生变化,则调用监听函数重新渲染 View (store.subscribe(render)
  • 单一数据源,只有一个 Store
  • state 是只读的,每次状态更新之后只能返回一个新的 state
  • 没有 Dispatcher ,而是在 Store 中集成了 dispatch 方法,store.dispatch()是 View发出 Action的唯一途径
  • 支持使用中间件(Middleware)管理异步数据流

6.3 react-redux

Redux 是一个独立的第三方库,之后 React 官方在 Redux 的基础上推出了 React-redux:

我们先来回忆一下如何使用,避免后面的论述看不清晰。 最新版的 React-redux,已经全面拥抱了 Hooks,内置了诸如:

  • useSelector
  • useDispatch
  • useStore

另外,Redux官方还推出了 Redux Toolkit,来简化整个 Redux 的使用。

因此现在在 React 应用中,状态管理库的使用一般都是 React-redux + Redux Toolkit

定义

 import { createStore } from "redux";

 // 定义初始状态
 const initialState = {
   count: 0,
 };

 // 定义 reducer
 const counterReducer = (state = initialState, action) => {
   switch (action.type) {
     case "INCREMENT":
       return { ...state, count: state.count + 1 };
     case "DECREMENT":
       return { ...state, count: state.count - 1 };
     case "RESET":
       return { ...state, count: 0 };
     default:
       return state;
   }
 };

 // 创建 Redux store
 const store = createStore(counterReducer);

 export default store;

使用

    import React from "react";
    import { useSelector, useDispatch } from "react-redux";

    const Counter = () => {
      // 从 Redux store 中获取 state
      const count = useSelector((state) => state.count);

      // 获取 dispatch 方法,用于触发 action
      const dispatch = useDispatch();

      return (
        <div style={{ textAlign: "center", marginTop: "50px" }}>
          <h1>Redux Counter</h1>
          <h2>{count}</h2>
          <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
          <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
          <button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
        </div>
      );
    };

    export default Counter;

react-redux的架构如下图,我们逐个解析。

image.png

image.png

如果有中间件那么可以是下面这样:

image.png

关键点
  1. store:全局状态存储,创建时需要传入 reducer
  2. reducer:纯函数,根据当前状态和传入的 action,返回新的状态。
  3. action:描述状态变更的对象,必须包含 type 属性。
  4. Middleware:描述状态变更的对象,必须包含 type 属性。
  5. dispatch:发送 actionstore,触发状态变更。
  6. Provider:React 和 Redux 的桥梁,将 store 提供给整个 React 应用。
  7. useSelector:从 store 中选择所需的状态。
  8. useDispatch:获取 dispatch 方法,用于分发 action

Action

  • action是一个plain-object(平面对象)

    在大型项目,由于操作类型非常多,为了避免硬编码(hard code),会将action的类型存放到一个或一些单独的文件中(样板代码)。

  • action创建函数应为无副作用的纯函数

    • 不能以任何形式改动参数
    • 不可以有异步
    • 不可以对外部环境中的数据造成影响

Reducer

Reducer是用于改变数据的函数

  • 一个数据仓库,有且仅有一个reducer,并且通常情况下,一个工程只有一个仓库,因此,一个系统,只有一个reducer

  • 为了方便管理,通常会将reducer放到单独的文件中。

  • reducer被调用的时机

    • 通过store.dispatch,分发了一个action,此时,会调用reducer

    • 当创建一个store的时候,会调用一次reducer

      1. 可以利用这一点,用reducer初始化状态
      2. 创建仓库时,不传递任何默认状态
      3. 将reducer的参数state设置一个默认值
  • reducer内部通常使用switch来判断type值

  • reducer必须是一个没有副作用的纯函数

    • 为什么需要纯函数

      1. 纯函数有利于测试和调式
      2. 有利于还原数据
      3. 有利于将来和react结合时的优化
    • 具体要求

      1. 不能改变参数,因此若要让状态变化,必须得到一个新的状态
      2. 不能有异步
      3. 不能对外部环境造成影响
  • 由于在大中型项目中,操作比较复杂,数据结构也比较复杂,因此,需要对reducer进行细分。

    • redux提供了方法,可以帮助我们更加方便的合并reducer
    • combineReducers: 合并reducer,得到一个新的reducer,该新的reducer管理一个对象,该对象中的每一个属性交给对应的reducer管理。

Store

Store:用于保存数据

通过createStore方法创建的对象。

该对象的成员:

  • dispatch:分发一个action
  • getState:得到仓库中当前的状态
  • replaceReducer:替换掉当前的reducer
  • subscribe:注册一个监听器,监听器是一个无参函数,该分发一个action之后,会运行注册的监听器。该函数会返回一个函数,用于取消监听

中间件(Middleware)

中间件:类似于插件,可以在不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。在Redux中,中间件主要用于增强dispatch函数。

实现Redux中间件的基本原理,是更改仓库中的dispatch函数。

Redux中间件书写:

  • 中间件本身是一个函数,该函数接收一个store参数,表示创建的仓库,该仓库并非一个完整的仓库对象,仅包含getState,dispatch。该函数运行的时间,是在仓库创建之后运行。

    • 由于创建仓库后需要自动运行设置的中间件函数,因此,需要在创建仓库时,告诉仓库有哪些中间件
    • 需要调用applyMiddleware函数,将函数的返回结果作为createStore的第二或第三个参数。
  • 中间件函数必须返回一个dispatch创建函数

  • applyMiddleware函数,用于记录有哪些中间件,它会返回一个函数

    • 该函数用于记录创建仓库的方法,然后又返回一个函数

我们介绍一下常见的中间件:redux-thunk

在常见的情况中,我们dispatch一个action,state就会被立即更新。这是一个同步的过程。但是如果我们需要异步获取数据后再去更新的时候,我们只能在异步获取数据后再去dispatch一个action修改state。 那有什么办法能让我们,把异步获取数据的逻辑也整合到redux的环节中吗?

中间件 redux-thunk就是这样一个解决方案。 中间件的目的是在dispatch action到reducer的中间,拓展一些功能。

redux-thunk是怎么做到可以把异步的逻辑整合到redux中呢。

  • 默认情况的action是一个扁平的对象。

  • redux-thunkaction可以成为一个函数

  • 该函数会被调用, 并且会传给这个函数两个参数: 一个dispatch函数和getState函数

    • dispatch函数用于我们之后再次派发action
    • getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态

我们来看怎么用 redux-thunk

  1. 定义 Redux 核心逻辑

    文件:store.js

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import axios from "axios";

// 定义初始状态
const initialState = {
  loading: false,
  users: [],
  error: "",
};

// 定义 action 类型
const FETCH_USERS_REQUEST = "FETCH_USERS_REQUEST";
const FETCH_USERS_SUCCESS = "FETCH_USERS_SUCCESS";
const FETCH_USERS_FAILURE = "FETCH_USERS_FAILURE";

// 定义 action 创建函数
const fetchUsersRequest = () => ({ type: FETCH_USERS_REQUEST });
const fetchUsersSuccess = (users) => ({ type: FETCH_USERS_SUCCESS, payload: users });
const fetchUsersFailure = (error) => ({ type: FETCH_USERS_FAILURE, payload: error });

// 定义异步 action(使用 redux-thunk)
export const fetchUsers = () => {
  return async (dispatch) => {
    dispatch(fetchUsersRequest());
    try {
      const response = await axios.get("https://jsonplaceholder.typicode.com/users");
      dispatch(fetchUsersSuccess(response.data));
    } catch (error) {
      dispatch(fetchUsersFailure(error.message));
    }
  };
};

// 定义 reducer
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_USERS_REQUEST:
      return { ...state, loading: true, error: "" };
    case FETCH_USERS_SUCCESS:
      return { ...state, loading: false, users: action.payload };
    case FETCH_USERS_FAILURE:
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
};

// 创建 store 并应用 thunk 中间件
const store = createStore(reducer, applyMiddleware(thunk));

export default store;

  1. 在 React 中使用 Redux 和 Redux-Thunk

    文件:UsersList.js

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchUsers } from "./store";

const UsersList = () => {
  const dispatch = useDispatch();

  // 从 store 中获取状态
  const { loading, users, error } = useSelector((state) => state);

  // 在组件挂载时触发异步操作
  useEffect(() => {
    dispatch(fetchUsers());
  }, [dispatch]);

  // 渲染状态
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <h1>User List</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};

export default UsersList;

文件:App.js

import React from "react";
import { Provider } from "react-redux";
import store from "./store";
import UsersList from "./UsersList";

const App = () => {
  return (
    <Provider store={store}>
      <UsersList />
    </Provider>
  );
};

export default App;
  • fetchUsers 是一个异步 action,通过 redux-thunk 支持的函数式 action 创建器。
  • 在函数中,首先 dispatch FETCH_USERS_REQUEST 设置加载状态为 true,然后进行 API 请求。
  • 请求成功后,dispatch FETCH_USERS_SUCCESS 并将数据存储到 store 中;失败则 dispatch FETCH_USERS_FAILURE

redux-thunk:使得 action 可以返回一个函数,而不是对象,从而支持异步操作。

redux-thunk的源码其实很简单,具体如下,总结就是如果action是一个对象,那就交给下一步,如果action是一个函数,那就执行它。 这样,在dispatch到reducer的过程中,就可以把异步的逻辑添加进去了。

//redux-thunk简要源码
const thunk = ({ dispatch, getState }) => (next) => (action) => {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }

  return next(action);
};
export default thunk

6.4 mobx

mobx也是一个react状态管理库,相对于繁琐的redux,不需要再写模板代码,可以直接处理异步等优点,但其实说实话,mobx非常Vue,它的内部机制也是从defineProperty到proxy,用法也类似。

总体来说mobx是响应式代理的方案,对全局state做劫持,状态的get手机依赖,状态的set触发更新。很自然的,全局state就是一个class,这是一个面向对象的思想,可以通过继承实现逻辑复用。

image.png

Mobx和Redux的对比,可以归结为 面向对象 vs 函数式。

  • 相对redux的广播遍历dispatch,然后遍历判断引用来决定组件是否更新,mobx和vue一样,可以劫持数据,精确收集依赖,只更新局部组件,理论上性能更好。

  • Mobx的数据只有一份引用,不具备回溯的能力,难以调试。而redux每次更新都相当于打了一个快照,调试时使用redux-logger中间件,可以直接看到数据流的变化,利于维护调试。

  • Mobx的学习成本更低,更容易上手。

  • Mobx如何在更新有深层嵌套属性的state更有优势,直接赋值就好了

reduxmobx
有严格的工作流程,需要写大量的模板代码没有模板代码,简洁
需要保证数据不可变数据是响应式的,可以直接修改数据(defineProperty proxy)
redux需要中间件处理异步mobx直接处理异步
redux约束强,适合大型多人协作适合简单规模不大的应用
简单易用,精准更新,性能更好适合简单规模不大的应用
  • 核心思想
  1. observable 定义一个存储 state 的可追踪字段(Proxy)
  2. action 将一个方法标记为可以修改 state 的 action
  3. computed 标记一个可以由 state 派生出新值并且缓存其输出的计算属性
  4. 工作流程

  • 使用

    创建 store

    • 新建文件 store/Counter.ts, 通过 class 创建一个 Counter 类
    • 使用 makeObservable 将类的方法 和属性变成响应式的
    • 导出 counter 实例
    // store/Counter.ts
    import {action, makeObservable, observable} from 'Mobx'
    class Counter {
      constructor(){
        // 参数1:target,把谁变成响应式(可观察)
        // 参数2:指定哪些属性或者方法变成可观察
        makeObservable(this, {
           count: observable,
           increment: action,
           decrement: action,
           reset: action,
         })
      }
      count = 0
      increment(){
        this.count++
      }
      decrement(){
        this.count--
      }
      reset(){
        this.count = 0
      }
    }
    const counter = new Counter()
    export default counter
    

    在组件中使用

    • 从 Mobx-react 库中引入 observer 高阶组件函数
    • 使用 observer 高阶组件函数包裹需要使用 store 的组件(触发组件更新)
    • 引入 store 对象
    • 使用 store 对象中的属性和方法即可
    // App.tsx
    import counter from './store/Counter';
    // observer 是一个高阶组件函数,需要包裹一个组件,这样组件才会更新
    import { observer } from 'Mobx-react'
    
    function App() {
      const {cart, counter} = useStore()
      return (
        <div className="App">
          <h3>计数器案例</h3>
          <div>点击次数:{counter.count}</div>
          <button onClick={()c=> ounter.increment()}>加1</button>
          <button onClick={()c=> ounter.decrement()}>减1</button>
          <button onClick={() => counter.reset()}>重置</button>
        </div>
      );
    }
    export default observer(App);
    

6.5 Vuex 和 Pinia

vuex的实现思路其实更像mobx和redux的结合,内部也是响应式的代理。get收集依赖,set触发更新。但是暴露的api跟redux更像,而pinia是一个升级版的vuex。

image.png

我们回顾vuex和pinia的使用,再来对比它们之间的差异

  • vuex和pinia的使用

    1. vuex和pinia的全局注册。

    从注册方式可以看到 vuex 全局共享一个store,而pinia并不是,是多仓库的。

    
    import { createApp } from 'vue'
    import App from './App.vue'
    
    import { createPinia } from 'pinia'
    const Pinia=createPinia()
    
    import store from'./store'//vux状态仓库
    
    const app = createApp(App)
    app
        .use(store)
        .use(Pinia)
        .mount('#app')
    
    
    1. 定义仓库
    //vuex定义仓库
    import { createStore } from "vuex";//只有一个store
    
    //全局状态
    const state={
        count:0,//计数状态
        user:'张三',
        price:20
    
    }
    
    const getters={
        //计算属性 不会修改状态
        allPrice:state=>{
            return state.price*state.count;
        }
    }
    
    const actions={
        increment:({commit})=>{
            //commit 提交一项修改 提交给mutations
            commit('increment')
        },
        decrement:({commit})=>{
            commit('decrement')
        }
    }
    //所有的状态修改都要经过mutations
    //只有mutations可以修改状态
    const mutations={
        increment(state){
            state.count++;
        },
        decrement(state){
            state.count--;
        }
    }
    //除了读操作,对写操作非常严格
    const store=createStore({
        state,
        getters,
        actions,
        mutations
    })
    
    export default store;
    
    
    //pinia定义仓库
    import { defineStore } from "pinia";
    
    export const useAgeStore=defineStore('age',{
        state:()=>{
            return {
                year:2002
            }
        },
        getters: {
            age: (state) => {
                return new Date().getFullYear() - state.year;
            }
        }
        ,actions: {
            increment() {
                this.year++;
            },
            decrement() {
                this.year--;
            }
        }
    })
    
    
  • 对比vuex和pinia

Vuex: StateGettesMutations(同步)、Actions(异步)

Pinia: StateGettesActions(同步异步都支持)

vuexpinia
有mutation没有mutaion,语法更简洁,直接操作actions
全局单一store,子模块嵌套不嵌套子模块,各模快扁平化且独立
更好的typescript支持
redux约束强,适合大型多人协作适合简单规模不大的应用
简单易用,精准更新,性能更好适合简单规模不大的应用

引用下pinia的官方文档

对比 Vuex

Pinia 起源于一次探索 Vuex 下一个迭代的实验,因此结合了 Vuex 5 核心团队讨论中的许多想法。最后,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分功能,所以决定将其作为新的推荐方案来代替 Vuex。

与 Vuex 相比,Pinia 不仅提供了一个更简单的 API,也提供了符合组合式 API 风格的 API,最重要的是,搭配 TypeScript 一起使用时有非常可靠的类型推断支持。

对比 Vuex 3.x/4.x

Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的。

Pinia API 与 Vuex(<=4) 也有很多不同,即:

  • mutation 已被弃用。它们经常被认为是极其冗余的。它们初衷是带来 devtools 的集成方案,但这已不再是一个问题了。
  • 无需要创建自定义的复杂包装器来支持 TypeScript,一切都可标注类型,API 的设计方式是尽可能地利用 TS 类型推理。
  • 无过多的魔法字符串注入,只需要导入函数并调用它们,然后享受自动补全的乐趣就好。
  • 无需要动态添加 Store,它们默认都是动态的,甚至你可能都不会注意到这点。注意,你仍然可以在任何时候手动使用一个 Store 来注册它,但因为它是自动的,所以你不需要担心它。
  • 不再有嵌套结构的模块。你仍然可以通过导入和使用另一个 Store 来隐含地嵌套 stores 空间。虽然 Pinia 从设计上提供的是一个扁平的结构,但仍然能够在 Store 之间进行交叉组合。你甚至可以让 Stores 有循环依赖关系
  • 不再有可命名的模块。考虑到 Store 的扁平架构,Store 的命名取决于它们的定义方式,你甚至可以说所有 Store 都应该命名。

7. 总结

不管前端的状态管理是 setState Ref 还是 vuex pinia redux mobx ,它们本质都离不开我们前面说的2点:

  • 状态变化之前的逻辑 ---> 数据能够按特定的规则去修改

  • 状态变化后的副作用处理 ----> 数据的变化能够改变相关的视图

实现上述两点都脱离不了2种方式

1. 直接修改数据无效,因此封装特定修改数据的API。

2. 可以直接修改数据,不用调用特定API,对数据进行劫持。

只不过它们使用了不同的实现方式。

最后,状态管理就是前端开发的核心,理解了状态变化前的逻辑和状态变化后的副作用联动,也就理解了前端的一大半。