前端状态管理入门

学习脉络:了解历史,了解Flux,了解Redux、Vuex,了解基于hooks的状态管理方案。

历史

前端的发展历程可以追溯到最早的静态HTML页面,那时任何微小的改动都需要整个页面的重绘。随后,iframe和XMLHttpRequest(XHR)的出现让开发者能够实现异步局部加载,进一步丰富了网页的动态性。然后,jQuery成为了一个里程碑,它提供了一套简单易用的API来操纵DOM和实现Ajax操作。

进入到框架时代,Angular、React和Vue等前端框架成为主流。这一时代的两大特点是组件化数据驱动。组件化意味着将用户界面拆分为多个可重用和独立的小块,而数据驱动则意味着所有的界面渲染都是由底层数据模型控制的。

对于这两个特点,一方面,组件化让我们面临一个问题:在复杂应用中,不同组件如何进行有效的通信? 另一方面,数据驱动方式提出了另一个问题:如何高效地管理多个组件依赖的公共数据?

这个时候就有聪明的Facebook攻城狮提出了Flux架构思想,打开了前端状态管理的大门。

Flux

Flux 是一种架构思想,专门解决软件的结构问题。它跟MVC 架构是同一类东西,但是更加简单和清晰。

flux四部分(View Action Dispatcher Store)

Flux将一个应用分成四部分:

View 视图层

Action 动作 (视图层发来的消息)

Dispacher 派发器(用来接收

Store 数据层(存储应用状态

单向流动

Flux最大特点是数据单向流动,举个「点击按钮数字+1」的例子:

用户点击按钮 (View发出用户的Action

Action 将时间传递给Dispacher

Dispacher改变Store里的状态(Store里计数器的值+1

Store状态改变出发View渲染(视图上的计数器+1

Flux 是前端状态管理的基础架构模式,而 Redux、Vuex 等库可以被视为 Flux 思想的具体实现和优化。这些库采用了 Flux 的核心概念,并加入了各自独特的特性和优化,以提供更高效、可扩展的状态管理解决方案。

Redux

三大原则(单一、只读、纯函数)

  1. 单一数据源(一个Redux应用只有一个Store),也是单向的数据流;
  2. state只读(state只可整体替换,不可直接修改);
  3. 使用纯函数(Reducer)来修改state。

Redux代码示例

js 复制代码
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';

// reducer.js
const initialState = { count: 0 };
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    default:
      return state;
  }
};

const store = createStore(reducer);

// App.js
const App = ({ count, dispatch }) => (
  <div>
    <p>Count: {count}</p>
    <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
  </div>
);

const mapStateToProps = (state) => ({
  count: state.count,
});

const ConnectedApp = connect(mapStateToProps)(App);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedApp />
  </Provider>,
  document.getElementById('root')
);

纯函数

纯函数有以下特点:

  • 相同的输入总是返回相同的输出。
  • 不产生副作用。
  • 不依赖于外部状态。

纯函数示例

js 复制代码
// 纯函数,接受一个参数,返回一个新的对象,原参数没有被改变
function addOneToEachElement(arr) {
  return arr.map(element => element + 1);
}
// 非纯函数,改变了输入参数
function addOneToEachElementImpure(arr) {
  for(let i = 0; i < arr.length; i++) {
    arr[i] += 1;
  }
  return arr;
}
// 非纯函数,使用了系统 I/O
function readFileAndDoSomething(filename) {
  const data = fs.readFileSync(filename);  // 假设 fs 是文件系统模块
  // do something with data
}
// 非纯函数,使用了 Math.random()
function getRandomNumber() {
  return Math.random();
}

在Redux中,reducer 必须是纯函数,主要有以下几个原因:

  1. 确保可预测性:纯函数的输出完全取决于输入,不会有副作用,从而使得State的变化可预测。
  2. 确保可测试性:纯函数更容易测试,因为给定相同的输入,输出总是相同的。
  3. 确保可维护性:纯函数由于没有副作用,更容易维护和理解。
  4. 时间回溯:纯函数使得开发者工具能实现时间旅行等高级功能,这些功能能帮助你更好地调试应用。

举个简单例子 假设我们有一个简单的计数器应用,其中的reducer可能是这样的(纯函数):

js 复制代码
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

如果我们使用非纯函数,可能会是这样:

js 复制代码
function nonPureCounterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      state.count = state.count + 1; // 直接修改了state
      return state;
    case 'DECREMENT':
      state.count = state.count - 1; // 直接修改了state
      return state;
    default:
      return state;
  }
}

在这个非纯函数nonPureCounterReducer的例子中,我们直接修改了传入的state。这会导致以下问题:

  1. 这样的reducer可能会引起组件无法正确重新渲染,因为React 的 PureComponent / React.memo 是浅比较,它们可能不会意识到 state 已经改变
  2. 当你尝试回溯或者查看历史state时,你会发现所有的历史state都是相同的,因为他们都是对同一个对象的引用。

使用非纯函数会让你的应用变得难以维护和调试,因此建议总是使用纯函数作为你的 reducer。

React-Redux

React-Redux 是一个专为 React 设计的库,旨在简化 Redux 在 React 中的使用。它并没有改变 Redux 的基础原理,如单一状态树、Action 和 Reducer,而是提供了一系列 React 组件和 Hooks,让你能更轻松地在 React 应用中实现状态管理。

通过使用 React-Redux 提供的 Provider 组件和 connect 高阶组件或者新引入的 Hooks(useSelectoruseDispatch 等),可以避免编写大量样板代码,同时也可以更方便地将 Redux store 的状态和 dispatch 方法传递给 React 组件。

Vuex(单一、mutation、action)

三大原则

  1. 应用层级的状态应该集中到单个 Store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。
js 复制代码
// 导入 Vue 和 Vuex
import Vue from 'vue';
import Vuex from 'vuex';

// 使用 Vuex
Vue.use(Vuex);

// 创建一个新的 store 实例
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    }
  }
});

// 创建一个新的 Vue 实例
new Vue({
  el: '#app',
  store,
  computed: {
    count() {
      return this.$store.state.count;
    }
  },
  methods: {
    incrementAsync() {
      this.$store.dispatch('incrementAsync');
    }
  },
  template: `
    <div>
      <p>{{ count }}</p>
      <button @click="incrementAsync">Increment After 1 Second</button>
    </div>
  `
});

Vuex与Redux的区别

二者核心区别在于响应式 。Vuex有getter/setter,知道组件何时重新渲染,所以Vuex可以直接修改State内容,而React需要替换整个State

Hooks时代的react状态管理

使用React原生的Hooks(如useStateuseReducer等)或者基于Hooks的库(如zustandhox等)通常更加简洁和直观。这些方案通常没有Redux和Vuex那样的"仪式感",例如不需要定义action types、actions和reducers,因此对新手更友好。

常见的方案有unstated-nexthoxzustand等,详见EllieSummer大佬的文章:React hooks 状态管理方案

总结

参考:

阮一峰-flux架构入门

我所理解的前端状态管理

前端状态管理

阮一峰 redux入门

从零实现redux

vuex与redux的区别

React hooks 状态管理方案

fe-tool.com/awesome-rea...

相关推荐
涔溪39 分钟前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun1 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇1 小时前
ES6进阶知识一
前端·ecmascript·es6
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
fakaifa2 小时前
CRMEB Pro版v3.1源码全开源+PC端+Uniapp前端+搭建教程
前端·小程序·uni-app·php·源码下载
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript