Redux源码分析:附带核心API的代码详解

635 阅读38分钟

🍀 每日思考:“对部分人来说社交焦虑的本质是:和他人的互动是一个不断审视和质问自我的过程,被他人凝视 损害了我们的主体性,无法再维护自我认同。可以稍微尝试改变社交目的:不再是展示自我美好,而是感受世界并遵循和放大自己的生命本能,比如天然对他人的好奇。 关注点从自我放到他人,从小我放到世界。对话中没有凝视只有连接。”——来自彭春花姐姐 哔哩哔哩视频

🍀 本文包括:项目历史/背景/概念/特点/应用/原理总结/API代码详解/生态/学习资源等

🍀 本专栏致力于分析一些网上热门项目,如果本文对你有帮助的话,别忘了点赞 or 关注嗷 🎀。我是Wandra,我们下期再见啦

❗❗❗温馨提示:字数虽多,但是文章从整体到局部都比较有规划有结构,清晰易懂,请放心使用🍖。由于字数过多,建议在电脑上看更合适~💐

项目描述

🍀 github链接:github.com/reduxjs/red…

🍀 官网链接:redux.js.org/

🍀 历史链接:semver.org/

项目背景

🍀 Redux 作为Flux 架构的实现而发明,而 Flux 架构的创建又是为了解决人们在基于事件触发的状态管理(例如 Backbone)中发现的局限性。这就是人们在 2015 年试图解决的问题。

🍀 Redux in ’15, ’16, ’17的阶段,为了解决prop穿透的问题,许多人选择了 Redux。

🍀 React 16.3 起,React 推出了一个新的、改进的 Context API,它从发布之日起就被推荐用于生产使用,Context 的唯一目的是充当作用于子树某些部分的依赖注入机制,该组件子树的任何部分都可以请求读取值。这就是它的全部作用,避免了prop穿透的问题,在穿透问题上可以替代 Redux(但是context并不像redux一样是状态管理系统)

🍀 传统使用rest API的阶段,人们需要使用在他们的应用程序中缓存一些服务器状态,许多人选择了Redux。

🍀 越来越多人开始使用 Graphql API,使用Apollo Client等更加复杂和更加专业而非通用的客户端来管理该数据,可以不再使用 Redux 存储来自服务器的缓存数据。还有几个新库——swrReact Query——它们做同样的事情,但更多地关注 REST API

🍀 最新版已经在2023年更新到v5.0.1。—— GitHubReleases 项目坚持语义版本化

解决问题

❗️开发人员需要准确了解现在正在尝试在自己的应用程序中解决哪些问题,并选择最能解决您的问题的工具。

❗️ 您可以在不使用 Redux 的情况下应用 Redux 的想法。例如,考虑一个具有本地状态的 React 组件

import React, { Component } from 'react';

// reducer
const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

class Counter extends Component {
  state = counter(undefined, {});

  dispatch(action) {
    // “事情如何变化”
    this.setState(prevState => counter(prevState, action));
  }

  increment = () => {
    // “发生了什么”
    this.dispatch({ type: 'INCREMENT' });
  };

  decrement = () => {
    // “发生了什么”
    this.dispatch({ type: 'DECREMENT' });
  };

  render() {
    return (
      <div>
        {this.state.value}
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
      </div>
    )
  }
}

项目概念

🍀 Redux 是一个用于可预测全局状态管理的 用JS语言实现起来的 源码非常简单的 非常通用的 状态管理工具,所以可用于广泛的用例,但是在一些专业的场景下,可能更加专业的工具会比redux做的更多,做的更加适应专业化的场景,比如项目使用Graphql,那么你更有可能会使用更加专业的 Apollo 客户端去替代 Redux 的服务器缓存功能。Redux 涉及到一些函数式编程高阶函数纯函数、不可变等概念,并且不包含框架相关的知识:比如 react框架/vue框架/小程序/Jquery都能使用Redux。Redux 可以运行于不同的环境(客户端、服务器、原生应用),易于测试,并提供超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览。Redux 核心极其简单:Redux 保存当前值,运行单个函数以在需要时更新该值,并通知任何订阅者某些内容已发生更改。

api概念

Store

🍀 store是redux的核心概念,store具体是一个包含dispatch, subscribe, getState等函数的对象,这个store对象被createStore函数返回,而dispatch, subscribe, getState又被包含到store对象,由于闭包的特性,外界可以通过createStore(xxx).dispatch()、createStore(xxx).subscribe()等形式访问createStore上下文中定义的局部变量,比如state以及监听数组。

伪代码
function createStore(reducer) {
    var state;
   // 存储所有需要在state更新后重新执行的回调函数。
    var listeners = []

    // 返回createStore内部的state数据
    function getState() {
        return state
    }

    function subscribe(listener) {
      // 将listener函数加入本地的listeners数组,在state更新后重新执行
        listeners.push(listener)
      // 返回一个 unsubscribe函数,用来取消订阅,将listener从listeners数组中移除,state更新后不会重新执行该listener函数。
        return function unsubscribe() {
            var index = listeners.indexOf(listener)
            listeners.splice(index, 1)
        }
    }

    function dispatch(action) {
        // dispatch函数调用reducer 函数并将它返回的任何值赋值给state对象。
       // 到达dispatch函数内部的action必须是普通对象,并且操作必须具有不是未定义的type字段。
        state = reducer(state, action)
      // 在state更新后重新执行订阅的所有的回调函数
        listeners.forEach(listener => listener())
    }

    dispatch({}) 


    return { dispatch, subscribe, getState } // 返回一个store对象
}

Action

🍀 action 是一个包含type字段的对象, 例如:{ type: 'add', value: 1 },这个action 对象可以理解为 “增加数值 1” 。type字段会匹配使用redux的开发人员在针对不同的type字段所定义的逻辑,在开发人员自定义的逻辑(也就是reducer纯函数)中 采用类似于 case "ADD" : return count++ 的逻辑会被dispatch函数调用,因为dispatch内部会将dispatch函数的入参,比如action和oldState传递给我们开发人员自己编写的reducer函数, reducer函数 取到 old stateaction 后,会通过 action.type 匹配到修改 state 的规则,然后修改并返回新的 state

// action对象的定义和作用,伪代码
const action = {type:'INCREMENT'}

dispatch(action){
  const currentState = counter(currentState,action)
}

dispatch(action)

// action对象的定义和作用,实际使用代码
const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}
dispatch(action)

Reducer

🍀 reducer是一个函数,官方建议写成纯函数, 即接收的参数不变的情况下,返回值也不会发生变化。

🍀 官方不建议的用法:在 reducer函数内部 修改传入参数、执行有副作用的操作,如 API 请求和路由跳转、调用非纯函数,如 Date.now()Math.random()

🍀 该函数接受两个参数:

  • state: 当前的old state。
  • action: 描述了发生什么事情的一个对象,一般带有 type 字段。例如:{ type: 'ADD' }
// reducer函数,伪代码
reducer = (state, action) => {}

// 开发人员实际编写的reducer函数,dispatch内部会调用这个reducer函数
并把reducer函数的返回值赋值给old state.
const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

编程概念

❗️Redux没有以任何方式强制执行不变性,可序列化性和纯函数。reducer函数完全有可能改变其状态或触发AJAX调用。应用程序的任何其他部分完全可以调用getState()并直接修改状态树的内容。完全可以将promise、函数、Symbol、类实例或其他不可序列化的值放入action或状态树中。你不应该做这些事情中的任何一件,但这是可能的。

Immutability 不变性

在Redux中,不变性是指状态一旦创建就不能改变。所有的状态更新都是通过返回新对象而非修改现有对象来完成的。这样做可以简化程序的状态管理,因为不需要担心某个地方意外地改变了状态。

const initialState = { count: 0 };

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      // 不直接修改state.count,而是返回一个新的对象
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
}
Pure functions 纯函数

纯函数指的是给定相同的输入总会产生相同输出的函数,并且没有副作用。在Redux中,reducer函数就是一种纯函数,它们仅依赖于传入的参数(state和action),并总是返回相同的结果。

function counterReducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}
Normalization 正常化

🍀 Normalization在Redux中的应用主要是为了优化状态管理和减少冗余。Normalization涉及到将复杂的数据结构转换成一种更加一致和平坦的形式,这种形式有助于避免在状态树中出现重复的数据。当你处理具有关联关系的数据时,例如用户和他们创建的帖子,如果不进行Normalization,就有可能在不同的地方存储相同的数据,这不仅浪费内存,还会增加状态管理的复杂度。

🍀为什么需要Normalization?

  1. 避免冗余:确保每个实体(如用户、帖子)只在状态树中出现一次,即使它们在多个地方被引用。
  2. 简化状态管理:当数据结构更加规范化时,更新状态变得更简单,因为你只需要在一个地方修改数据,而不是到处寻找可能存在的副本。
  3. 提高性能:减少状态树的大小可以提高性能,特别是在大型应用中,更小的状态树意味着更快的状态比较速度,这对于Redux的diff检测非常重要。

🍀如何进行Normalization?

Redux Toolkit提供了一组工具来帮助开发者更轻松地实现Normalization。其中一个主要工具是createEntityAdapter,它可以帮助你构建和管理规范化的状态。

🍀示例:使用Redux Toolkit进行Normalization

假设我们有一个应用,需要展示用户列表和用户创建的帖子。如果我们不进行Normalization,可能会在用户列表中存储用户数据,在帖子列表中也存储用户数据(至少是用户名),这样会导致数据冗余。通过使用createEntityAdapter来管理用户实体这种方式,我们可以确保每个用户只在状态树中出现一次,并且可以通过他们的ID来访问他们的所有相关信息,从而简化了状态管理并提高了应用的整体性能

javascript
深色版本
import { createSlice, createEntityAdapter } from '@reduxjs/toolkit';
// 在这个例子中,我们使用createEntityAdapter来管理用户实体。每个用户都有唯一的ID,我们可以通过这个ID来引用用户而不必复制用户数据。当我们需要更新特定用户的帖子时,我们可以直接更新那个用户的帖子属性,而不需要在其他地方重复用户数据。

// 创建一个适配器来管理用户
const userAdapter = createEntityAdapter({
  selectId: user => user.id,
});

// 定义初始状态
const initialState = userAdapter.getInitialState({
  status: 'idle',
  error: null,
});

// 创建slice
const userSlice = createSlice({
  name: 'users',
  initialState,
  reducers: {
    addUser: userAdapter.addOne,
    removeUser: userAdapter.removeOne,
    updatePostsForUser: (state, action) => {
      const { userId, posts } = action.payload;
      const user = userAdapter.getEntity(state, userId);
      if (user) {
        user.posts = posts;
      }
    },
  },
});

export const { addUser, removeUser, updatePostsForUser } = userSlice.actions;

export default userSlice.reducer;
可序列化

Redux要求状态树必须是可以序列化的,这意味着状态树中的所有数据类型都应该是JSON可以表示的形式。所有基本类型(如字符串、数字)、数组、对象等都可以序列化。但是,函数、Date对象、RegExp对象、Promise、类等则不能直接序列化,因此在Redux中不应将这类对象作为状态的一部分。

Selector

Selectors是用于从Redux store中选择数据的函数。它们不仅可以简单地选择状态的一部分,还可以组合多个状态片段来生成复杂的数据结构。使用selectors的一个好处是它们通常是纯函数,这使得它们易于测试。

import { createSelector } from '@reduxjs/toolkit';

const selectUsers = state => state.users; // 获取users部分的状态

// 创建一个selector来获取活跃用户的列表
export const selectActiveUsers = createSelector(
  [selectUsers],
  users => Object.values(users).filter(user => user.isActive)
);
Thunks

Thunks是一种用于执行异步操作的方法。在Redux中,thunk是一个中间件,允许你在action creator内部写同步或异步逻辑,并且可以调用dispatch和getState方法。

function fetchUsers() {
  return function(dispatch) {
    dispatch({ type: 'FETCH_USERS_REQUEST' });
    fetch('https://api.example.com/users')
      .then(response => response.json())
      .then(users => dispatch({ type: 'FETCH_USERS_SUCCESS', payload: users }))
      .catch(error => dispatch({ type: 'FETCH_USERS_FAILURE', payload: error }));
  };
}
Sagas

sagas是Redux生态系统中用于处理副作用的一种模式,通常与redux-saga库一起使用。Sagas允许你定义一系列的工作流,这些工作流可以在后台执行,例如网络请求、定时任务等,同时允许你取消这些操作。

import { call, put, takeLatest } from 'redux-saga/effects';
import api from '../api';

function* fetchUsers(action) {
  try {
    const users = yield call(api.fetchUsers);
    yield put({ type: 'FETCH_USERS_SUCCESS', payload: users });
  } catch (error) {
    yield put({ type: 'FETCH_USERS_FAILURE', payload: error });
  }
}

export default function* rootSaga() {
  yield takeLatest('FETCH_USERS_REQUEST', fetchUsers);
}

项目特点

技术亮点

1️⃣ 闭包很多:Redux 的 闭包运用很频繁,源代码中基本处处都是闭包

2️⃣ 切面编程思想:redux 中 applyMiddleware 中面向切面编程的思想,这种概念运用比较广泛。

3️⃣ 易于管理状态数据:Redux 将 state 统一管理,会使代码更加有规律,易于维护管理。建议为不同类型的数据提供多个单独的“store”。Redux将这些多个“store”组合到一个状态树中,使调试、状态持久化和撤销/重做等功能更容易使用。redux提供了提供了combineReducers实用程序使得单根reducer函数本身也可以由许多更小的reducer函数组成,在状态树中为每个store创建一个单独的reducer。

4️⃣ 修改数据的过程可监控:Redux 让 state 的变化变得可预测,因为正常手段下修改 state 只能通过 dispatch 一个 action,这个过程是可监控的,在开发环境中,结合 redux-devtools 还可以实现时间旅行、录制、重放等

5️⃣ 可在多种框架中实现通用:Redux与React并非强制绑定,react框架/vue框架/小程序/Jquery都能使用Redux, 不像 Vuex 与 Vue 强耦合,脱离了Vue 则无法使用。

6️⃣ 更新逻辑与应用程序的其余部分分离:使用普通对象action来描述需要发生的更改。也就是添加间接性,以将“发生了什么”与“事情如何变化”分离。例如:

import React, { Component } from 'react';

// reducer
const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

class Counter extends Component {
  state = counter(undefined, {});

  dispatch(action) {
    // “事情如何变化”
    this.setState(prevState => counter(prevState, action));
  }

  increment = () => {
    // “发生了什么”
    this.dispatch({ type: 'INCREMENT' });
  };

  decrement = () => {
    // “发生了什么”
    this.dispatch({ type: 'DECREMENT' });
  };

  render() {
    return (
      <div>
        {this.state.value}
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
      </div>
    )
  }
}

技术扩展

1️⃣ 对开发人员来说可以区分 Redux的实际工作方式和 Redux 的预期工作方式,从概念上讲,Redux 的使用方式几乎有无限多种

技术限制

❗️ 因为 Redux 在实现方面是一个最小的库,所以它在技术层面上实际需要或强制执行的内容非常少

1️⃣ 传递给dispatch函数的action对象必须是一个普通对象,并且action对象必须具有不是未定义的type字段。

2️⃣ 将应用程序 state 描述为普通对象和数组

3️⃣ 将处理更改 state数据的逻辑写成一个纯函数。

技术误区

1️⃣ 著名的Redux 三大原则这些原则并不是关于 Redux 实现的固定规则或文字陈述。 相反,它们只是关于如何使用 Redux 的意图声明。

  • 单一事实来源:整个应用程序的 state 存储在单个 store 中的对象树中。单一事实来源”是错误的,store state不必是一个对象。你甚至不必有一个单一的store。
  • 状态是只读的:改变 state 的唯一方法是 dispatch 出一个 action。“state 是只读的”是错误的,因为没有什么可以真正阻止应用程序的其余部分修改当前状态树。
  • 使用纯函数进行更改:要指定状态树如何通过 action 进行转换,您可以编写纯 reducer 函数。而“改变是由纯函数进行的”是错误的,因为reducer 函数也可以直接改变状态树,或者引发其他副作用。

项目应用

💡 可以将 Redux 与 React 或任何其他视图库,比如vue框架/小程序/Jquery 一起使用

💡 Redux 可能不会成为实际体验中中最好或最有效的工具。

适合的场景

🍀 客户端需要其他复杂的数据管理

🍀从服务器请求数据并缓存数据的状态

🍀组件树庞大,不同的组件节点需要 共享状态

🍀某个组件需要去修改全局的状态或修改其他组件的状态

不适合的场景

🍀 在一些专业化场景,比如项目使用Graphql的时候。其他工具如 React Query 或 Apollo 针对于特定的数据获取情况也许会更加专业,它们比起 Redux 会更加擅长 服务器缓存。

原理总结

文字版

🍀 本质上:应用的整个全局 state 在创建store对象的函数createStore的函数上下文中定义,由于闭包的特性,dispatch,subscribe,getState函数在createStore函数内部定义,所以我们可以通过store.dispatch,store.subscribe,store.getState访问函数上一级作用域也就是createStore上下文中的变量。从正常流程来讲,更改state唯一方法是在 react (也可以是其他框架)组件中调用 dispatch(action),派发一个 action。action 是一个包含type字段的对象, 例如:{ type: 'add', value: 1 },这个action 对象可以理解为 “增加数值 1” 。type字段会匹配使用redux的开发人员在针对不同的type字段所定义的逻辑,在开发人员自定义的逻辑(也就是reducer纯函数)中 采用类似于 case "ADD" : return count++ 等逻辑会被dispatch函数调用,因为dispatch内部会将dispatch函数的入参,比如action和oldState传递给我们开发人员自己编写的reducer函数, reducer函数 取到 old stateaction 后,会通过 action.type 匹配到修改 state 的规则,然后修改并返回新的 state ,因为dispach函数是一个闭包函数,可以访问或者修改上一作用域也就是createStore函数上下文的state数据,reducer函数返回的新state值,比如count++会被dispatch函数逻辑 赋值给 上一级作用域的state,这样就完成了state的更新。 然后在dispatch函数中调用所有通过 store.subscribe()订阅了state数据变化的监听函数,监听函数可以通过 store.getState() (和dispatch函数一样也是一个闭包函数,可以访问上一作用域的state数据) 获取新的 state,重新渲染组件。流程类似于如下:

// 伪代码
import React, { Component } from 'react';

// reducer
const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

class Counter extends Component {
  state = counter(undefined, {});

  dispatch(action) {
    // “事情如何变化”
    this.setState(prevState => counter(prevState, action));
  }

  increment = () => {
    // “发生了什么”
    this.dispatch({ type: 'INCREMENT' });
  };

  decrement = () => {
    // “发生了什么”
    this.dispatch({ type: 'DECREMENT' });
  };

  render() {
    return (
      <div>
        {this.state.value}
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
      </div>
    )
  }
}

🍀 Redux 只是纯粹的状态管理器,默认只支持同步,实现异步任务比如延迟、网络请求等,需要中间件的支持。store 是 redux 的核心内容,除了 store 相关内容,redux 还提供了其他一些 api,用于扩展、接入其他库,实现更强大的功能。其中 applyMiddlewares 用于中间件的接入应用。

使用redux

创建store

import { createStore } from 'redux';
// 定义 reducer 函数
const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'add':
      return state + 1;
    case 'minus':
      return state - 1;
    default:
      return state;
  }
};
// 使用 createStore ,传入 reducer 函数,生成一个 store
const store = createStore(reducer);

export default store;

使用store

import React, { Component } from 'react';
import store from '../store';

export default class ReduxPage extends Component {
  componentDidMount() {
    // 到目前为止,通过 dispath 一个 action 修改 state 后,页面并不会自动更新
    // 在没有使用其他库配合前,暂时使用这种方式更新界面,订阅 store 变化,然后强制更新页面
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  add = () => {
    store.dispatch({ type: 'ADD' });
  };

  minus = () => {
    store.dispatch({ type: 'MINUS' });
  };

  render() {
    return (
      <div>
        ReduxPage
        <div>count: {store.getState()}</div>
        <button onClick={this.add}>add</button>
        <button onClick={this.minus}>minus</button>
        <button onClick={()=> this.unsubscribe()}>unsubscribe</button>
      </div>
    );
  }
}
import React from 'react';
import ReduxPage from './page/ReduxPage';

function App() {
  return (
    <div className="App">
      <ReduxPage />
    </div>
  );
}

export default App;

项目API

顶级暴露的API

createStore(reducer, [preloadedState], [enhancer]) : Redux 功能的核心。

🍀createStore函数传入一个 reducer 函数,createStore函数传入的 reducer 函数开发人员自定义的用来修改state数据的纯函数 定义了修改 state 的规则 createStore函数内部,会定义一个state数据,还会定义一个dispatch函数,dispatch函数会被传递给store对象并暴露给开发人员,供开发人员想要修改state数据的时候调用,开发人员可以根据store.dispatch函数调用该dispatch方法,dispatch函数接收带有type 字段的action对象,然后将state数据(也可以理解为old state)以及action对象传递给reducer函数,dispatch函数调用reducer函数,reducer函数会返回一个新的state数据, dispatch内部会讲reducer返回的新state直接赋值给old state。在更新完state数据之后,dispatch函数内部会通知订阅者,调用订阅者传递的回调函数,回调函数一般是包含一些更新页面重新获取state的逻辑。开发人员在项目中可以通过store.subscribe方法订阅state的更新,传递一个回调函数,dispatch操作中更新完state,dispatch内部就会一个一个调用我们传递过去的所有订阅回调。最终createStore函数会返回一个 store 实例对象,这个实例包含了 getStatesubscribedispatch 等方法。

🍀 伪代码

function createStore(reducer) {
    var state;
   // 存储所有需要在state更新后重新执行的回调函数。
    var listeners = []

    // 返回createStore内部的state数据
    function getState() {
        return state
    }

    function subscribe(listener) {
      // 将listener函数加入本地的listeners数组,在state更新后重新执行
        listeners.push(listener)
      // 返回一个 unsubscribe函数,用来取消订阅,将listener从listeners数组中移除,state更新后不会重新执行该listener函数。
        return function unsubscribe() {
            var index = listeners.indexOf(listener)
            listeners.splice(index, 1)
        }
    }

    function dispatch(action) {
        // dispatch函数调用reducer 函数并将它返回的任何值赋值给state对象。
       // 到达dispatch函数内部的action必须是普通对象,并且操作必须具有不是未定义的type字段。
        state = reducer(state, action)
      // 在state更新后重新执行订阅的所有的回调函数
        listeners.forEach(listener => listener())
    }

    dispatch({}) 


    return { dispatch, subscribe, getState } // 返回一个store对象
}

🍀 精简源代码:(人为精简了注释,删除了TS类型,删除了参数判断边界错误处理(已经增加了注释对删掉的边界情况进行说明))

import $$observable from './utils/symbol-observable'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
import { kindOf } from './utils/kindOf'

export function createStore(reducer, preloadedState, enhancer) {
  // reducer必须是函数,否则会报错

  // preloadedState和enhancer同时是函数 或者 enhancer是函数以及arguments[3]是函数 这两种情况任意一种情况发生都会报错

  // preloadedState是函数以及enhancer是undenfined的情况,会将preloadedState和enhancer变量交换

  // enhancer不为undefined也不为函数的情况,会直接报错

  // enhancer不为undefined但为函数的情况,直接返回enhancer(createStore(reducer,preloadedState)
  // 调用`createStore`: 包含有2种情况,这里属于其中一种情况:传入了增强器`enhancer`时,会将`createStore`传入`enhancer`,也就是在`enhancer`里创建了`store`后,把`store`的方法增强后再返回


  // 调用`createStore`: 包含有2种情况,这里属于另外其中一种情况,没有enhancer且preloadedState也不是函数,直接创建store
  let currentReducer = reducer

  let currentState = preloadedState 

  let currentListeners = new Map()
  let nextListeners = currentListeners

  let listenerIdCounter = 0

  let isDispatching = false

  // createStore函数内部使用:判断nextListeners 是否全等于 currentListeners,如果是的话,将currentListeners复制一份到nextListeners
  // 这是一个对currentListeners的浅复制,所以我们可以将nextListeners当作一个临时的list在dispatch的过程中使用
  // 这样做的目的是可以防止在dispatch调用过程中,调用subscribe/unsubscribe产生错误
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = new Map()
      currentListeners.forEach((listener, key) => {
        nextListeners.set(key, listener)
      })
    }
  }

  // 供外部开发人员通过sotre.getState的形式使用:通过闭包的形式访问createStore函数内部声明的变量state,也就是currentState。在正在执行dipatch函数的时候不能调用store.getState函数。
  function getState() {
    if (isDispatching) {
      throw new Error('You may not call store.getState() while the reducer is executing.')
    }

    return currentState
  }



  // 供外部开发人员通过sotre.subscribe(()=>{})的形式使用:将开发人员传递的listener回调函数以listenerId为key,listener为value的形式存入nextListeners map中,dipatch函数调用的时候会遍历nextListeners map并挨个调用每一个listener函数。
  // 返回一个取消订阅的函数:通过map的delete方法根据 上次subcribe函数调用因为闭包保存的listenerId 从map中删除对应的listener回调
  function subscribe(listener) {
  // listener必须是一个函数,否则会报错
  // 如果外部环境正在调用dispatch函数的情况下调用subscribe,那么会在subcribe函数直接return,不做任何事情,unsubscribe函数同理。
    if (isDispatching) {
      throw new Error('You may not call store.subscribe() while the reducer is executing'
      )
    }

    let isSubscribed = true

    // createStore函数内部使用:判断nextListeners 是否全等于 currentListeners,如果是的话,将currentListeners复制一份到nextListeners
    ensureCanMutateNextListeners()

    const listenerId = listenerIdCounter++
    nextListeners.set(listenerId, listener)

    return function unsubscribe() {
      // 如果已经退订了,就return,防止多次调用函数
      if (!isSubscribed) {
                return;
            }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      // 删除掉订阅的函数
      nextListeners.delete(listenerId)
      // currentListeners设置为null的原因是防止内存泄露
      // 见https://github.com/reduxjs/redux/issues/3474
      currentListeners = null 
    }
  }

  //  供外部开发人员通过store.dispatch({type:xxx,value:xxx})的形式调用
  // dispatch一个action,这是触发state改变的唯一方式
  // 它只实现了基础的字面量对象action操作,如果你想要dispatch一个Promise、Observable、thunk获取其他的,你需要将创建store的函数放进响应的中间件,比如redux-thunk包
  function dispatch(action) {
  // 如果传递给dispatch函数的action参数不是一个plain object,那么会直接报错
  // 如果传递给dispatch函数的action参数内的type类型为undefined,那么会直接报错
  // 如果传递给dispatch函数的action参数内的type类型不为string类型,那么会直接报错
  // 如果正在执行dispatch的时候再次调用dispatch,那么会直接报错

    try {
      // isDispatching用来标识是否正在执行diaptch函数,如果是则不能:dispatch、subscribe、unsubscribe、getState等等
      isDispatching = true
      // currentReducer = reducer,currentState = preloadedState 由于闭包,dispatch内部可以访问和操作上一作用域的currentState对象,dispatch将当前的state和用户传递给dispatch函数的action再传递给用户自定义编写的用来操作state修改逻辑的reducer函数,在用户自定义编写的reducer函数中,一般会返回具有和原来state不同地址的新state对象,,reducer返回的新state对象直接赋值给当前的state,即currentState
      currentState = currentReducer(currentState, action)
    } finally {
      // 修改完闭包的 currentState对象,标识 isDispatching 为 false
      isDispatching = false
    }

    // 遍历所有的nextListeners,执行所有的监听回调
    const listeners = (currentListeners = nextListeners)
    listeners.forEach(listener => {
      listener()
    })
    // 返回用户传递的action对象
    // 为了方便返回值为相同的action对象
    // 如果你使用来自定义的中间件,可能会返回其他的东西,比如Promise
    return action
  }


    // 替换store当前使用的reducer来计算state
    // 如果你的app实现了代码分割,并且你想动态的加载某些reducers,或者实现来redux的热重载,就需要这个方法
  function replaceReducer(nextReducer) {
    // 如果nextReducer不是function类型,那么就直接报错

    // 将nextReducer 复制给 currentReducer
    currentReducer = nextReducer

    // 调用dispatch:在reducer函数中做REPLACE类型的逻辑,将reduce函数的返回值赋值给当前的state,并执行所有的监听回调。
    dispatch({ type: ActionTypes.REPLACE })
  }

  // 提供给observable/reactive库的接口
  function observable() {
    const outerSubscribe = subscribe
    return {

      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError(
            `Expected the observer to be an object. Instead, received: '${kindOf(
              observer
            )}'`
          )
        }

        function observeState() {
          const observerAsObserver = observer
          if (observerAsObserver.next) {
            observerAsObserver.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

  // 调用dispatch:在reducer函数中做INIT类型的逻辑,将reduce函数的返回值赋值给当前的state,并执行所有的监听回调,可以理解为初始化一下store中的state数据。
  // 执行一次初始化的 dispatch,这样可以保证整个 state 树都拥有初始状态值,这样在定义 reducer 函数时定义都初始 state 才会生效。
  // 内部会dispatch一个type: ActionTypes.INIT的action,因为我们的开发人员的自定义的reducer不会有这个type的,所以会返回reducer函数中定义的默认值,这一步就算是给整个state tree赋初始值了
  dispatch({ type: ActionTypes.INIT })
    // 当store创建好了,会派发一个INIT的action,这样所有的reducer都会返回它们的初始值
    // 有效填充了初始的state tree

  const store = {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  } 
  // 最终createStore函数会返回一个 store 实例对象,这个实例包含了 getState、subscribe、dispatch、replaceReducer、observable 等方法。
  return store
}

🍀 流程图

image.png

legacy_createStore(reducer, preloadedState, enhancer)

🍀 精简源代码

// 返回createStore函数调用的返回值,也就是返回一个store对象。
export function legacy_createStore(reducer, preloadedState, enhancer) {
  return createStore(reducer, preloadedState, enhancer)
}
combineReducers(reducers)

🍀 由于开发人员通常有定义多个reducer的需求,所以需要使用redux提供的合并reducer的函数combineReducers来将开发人员定义的多个reducer函数进行合并,与其说是合并成一个reducer,不如说是combineReducers返回一个了combination函数,该函数可以传入当前的statey和一个代表你想执行某类操作的action,在combination函数遍历所有的reducers键,通过传入state和action去调用每一个reducer函数,通过前面的学习我们可以知道,reducer函数会返回一个新的state,combination函数内部呢,就把reducer函数返回一个新的state给添加到总的大的那个state对象中。这里说明一下,因为reducer有很多,每个reducer负责的state一般都不同,所以一般我们会将这些reducer函数返回的子state对象都添加到那个全局唯一的大的state对象上面。

伪代码

// 传入
const reducers = {
  count: state => state,
  string: state => state
};

// 函数里处理

const keys = ["count", "string"];
// 新state
const state = {};
for (const key of keys) {
  // 通过上一次key对应的state,调用对应的reducer函数,得到新的state
  state[key]=reducers[key](prevKeyState)
}
return state;

原代码

import ActionTypes from "./utils/actionTypes";

// combineReducers函数接受一个reducers对象,该对象包括多个以key,value形式定义的由开发人员自定义的reducer函数
export default function combineReducers(reducers) {
    // 获取传入的reducers对象的keys
    const reducerKeys = Object.keys(reducers);

    // 实际使用的reducers对象
    const finalReducers = {};

   // 将传入的reducers对象拷贝到finalReducers对象
    for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i];
        finalReducers[key] = reducers[key];
    }
    // 获取reducers的key
    const finalReducerKeys = Object.keys(finalReducers);

    // 返回一个可以以此遍历和调用reducers对象中的所有reducer函数,并依次将reducer的返回的新state挂到总的state对象的函数
    return function combination(state = {}, action) {
        // 标示state有没有改变
        let hasChanged = false;
        // 经过reducer处理的下一次state
        const nextState = {};
        // 循环调用每一个reducer函数
        for (let i = 0; i < finalReducerKeys.length; i++) {
            // 当前reducer的key
            const key = finalReducerKeys[i];
            // 当前reducer的函数
            const reducer = finalReducers[key];
            // 当前key对应的state的值
            const previousStateForKey = state[key];
            // 经过reducer函数后的下一此state值
            // state 和reducer函数对象中的键之间存在精确的对应关系
            // 期望给定的每个reducer函数将通过返回其default state 来“正确”响应未知的action.type,并且永远不会实际返回undefined 
            const nextStateForKey = reducer(previousStateForKey, action);
            // 当前key的值赋值给state对象
            nextState[key] = nextStateForKey;
            // 它进行引用相等比较,以查看所有reducer函数是否返回其先前的值。。
            // 如果当前key的state和上一次的state不同,说明state就已经改变了
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
        }
        // 如果replace了reducers,可能会需要判断key的length
        // 见https://github.com/reduxjs/redux/issues/3488
        // 如果所有返回的值看起来都相同,则它假设任何地方实际上都没有发生任何变化,并且它返回原始根状态对象作为潜在的优化
        hasChanged =
            hasChanged || finalReducerKeys.length !== Object.keys(state).length;
        return hasChanged ? nextState : state;
    };
}

图示版

image.png

image.png

applyMiddleware(…middlewares)

🍀 applyMiddlewareredux自带的增强器,主要是用来增强dispatch函数的功能,通过dispatch函数的中间件来增强dispatch函数,在 dispatch 外面又包了一层函数,执行中间件本身的功能,执行完中间件本身的功能后,再去调用原始的 dispatch。可以使得原来只支持同步逻辑的redux也能支持异步逻辑,我们可以动态获取数据之后异步更新state到目前为止。

🍀中间件的执行在调用 dispatch 到最终 reducer 函数修改 state 的这个过程之间。中间件是按照一定的顺序执行的,调整中间件的位置,可能会得到不同的执行结果。 🍀 中间件可能是一个,也可能是无数个,要保证这些中间件都能有效并且有序的执行,就需要一个好的方法将中间件串联起来,这里用到的是compose函数连接中间件。

🍀 伪代码

import React, { Component } from 'react';
import store from '../store';



export default class ReduxPage extends Component {
  componentDidMount() {
    store.subscribe(() => {
      this.forceUpdate();
    });
  }

  add = () => {
    store.dispatch({ type: 'ADD' });
  };

  minus = () => {
    store.dispatch({ type: 'MINUS' });
  };
    // 传入 action 为函数,,在函数内部进行异步操作
  asyncAdd = () => {
    store.dispatch(() => {
      setTimeout(() => {
        store.dispatch({ type: 'ADD' });
      });
    });
  };

  render() {
    return (
      <div>
        <div>count: {store.getState()}</div>
        <button onClick={this.add}>add</button>
        <button onClick={this.minus}>minus</button>
        <button onClick={this.asyncAdd}>asyncAdd</button>
      </div>
    );
  }
}

🍀 源代码

import compose from './compose';

export default function applyMiddleware(...middlewares) {
  // 返回一个函数,这个函数
 // applyMiddleware 函数传入中间件,并返回一个增强函数enhancer, 该enhancer函数式会接收 createStore 作为参数,并对其进行增强,返回一个增强后的 createStore 函数

  return (createStore) => (reducer) => {
    // 使用原始的 createStore 函数,创建好 store
    let store = createStore(reducer);
    let dispatch = () => {
      throw new Error(
        '不允许在构建中间件时调用 dispatch,其他中间件不会应用此 dispatch。'
      );
    };
    // 提供给中间件的 API,中间件在构建时可以用到这些 api
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args),
    };
    // 依次遍历和调用所有的中间件函数(对所有中间件进行构建),调用时传入 middlewareAPI,中间件在构建时可以用到这些 api
    const chain = middlewares.map((middleware) => middleware(middlewareAPI));
    // 使用 compose 将中间件数组串起来
    // compose返回的函数接受store对象上的原dispatch方法,返回一个新的被增强过的dispatch方法。
    // 这里 let dispatch 定义了一个函数,最后才给 dispatch 赋值为“增强” 后的 dispatch, 是为了防止在构建中间件的时候就调用 dispath
    dispatch = compose(...chain)(store.dispatch);

    // 返回原store对象的内容+新的dispatch函数组成一个新的store对象。
    return {
      ...store,
      dispatch,
    };
  };
}

// createStore函数中会使用到applyMiddleware(也可以是其他)返回的enhancer函数
export default function createStore(reducer, enhancer) {
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('enhancer 必须是一个函数');
    }
    return enhancer(createStore)(reducer);
  }

}

🍀 流程图

image.png

🍀 一般我们会使用redux-thunk,以及react-redux来对store做功能增强,如支持异步操作。

🍀 middleware的实现要求:

  • 第一层函数,是中间件的构建函数,构建时会传入 getState 和 dispatch 方法,中间件的初始化也会在这里执行,使中间件在执行时可以用到这两个方法。

  • 第二层函数,主要用于将中间件串连起来

  • 第三层函数,是该中间件要扩展的功能的主要逻辑代码实现

🍀 实现redux-thunk

// 构建中间件的时候,传入 getState, dispatch,使中间件可以用这两个方法
const thunk = ({ getState, dispatch }) => {
  // 第二层是 compose 的时候生成的一层包一层的函数,其中 next 就是下一层中间件,最后一个 next 就是原始的 dispatch
  return (next) => {
    // 中间件主要逻辑代码
    return (action) => {
      if (typeof action === 'function') {
        return action(dispatch, getState);
      }
      return next(action);
    };
  };
};

🍀 实现react-redux

  • 单纯依靠 redux 的话,只能通过 subscribe 订阅数据的变化,手动更新界面,并且每次都需要通过 getState 获取最新的数据。这样操作起来比较麻烦。要更好的将 react 跟 redux 结合起来,就需要借助像 react-redux 这样的库。
  • react-redux 提供了两个 api:Providerconnect
import Provider from './Provider';
import connect from './connect';
// 暴露 Provider,connect API
export { Provider, connect };
import React from 'react';

export const ReactReduxContext = React.createContext(null);

export default ReactReduxContext;
import React, { Component } from 'react';
import { ReactReduxContext } from './Context';

export default class Provider extends Component {
  render() {
    // 使用 Context.Provider 为组件提供 store
    return (
      <ReactReduxContext.Provider value={this.props.store}>
        {this.props.children}
      </ReactReduxContext.Provider>
    );
  }
}
import React, { useLayoutEffect, useReducer, useContext } from 'react';
// import { bindActionCreators } from 'redux';
import { bindActionCreators } from '../redux';
import { ReactReduxContext } from './Context';

// connect 接受 mapStateToProps 和 mapDispatchToProps 参数,返回一个高阶组件。“连接” 需要使用 store 的组件,把 store 中的值传递给组件
// connect 顾名思义,就是连接 Redux store 与组件。它是一个高阶组件(HOC),传入一个组件,并且返回一个新的组件,扩展原来的组件使原来组件可以获取到 sotre 中的数据与变更数据的方法。连接操作不会改变原来的组件类。而是返回一个新的已与 Redux store 连接的组件类。


const connect = (mapStateToProps = (state) => state, mapDispatchToProps) => (
  WrappedComponent
) => (props) => {
  const store = useContext(ReactReduxContext);
  const { getState, dispatch, subscribe } = store;

  // mapStateToProps 是在使用 connect 时传入的第一个参数,
  // getState 获取到 state 到值,然后传递给 mapStateToProps 使用
  // mapStateToProps 执行完成后返回需要传递给组件的 stateProps
  const stateProps = mapStateToProps(getState());

  // connect 的第二个参数 mapDispatchToProps 可以是对象或者函数
  let dispatchProps;
  if (typeof mapDispatchToProps === 'object') {
    // 如果 mapDispatchToProps 是一个对象,则使用 bindActionCreators 将该对象包装成可以直接调用的函数对象
    dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
  } else if (typeof mapDispatchToProps === 'function') {
    // 如果 mapDispatchToProps 是一个函数,则调用函数并传入 dispatch
    dispatchProps = mapDispatchToProps(dispatch);
  } else {
    // 默认传递 dispatch
    dispatchProps = { dispatch };
  }

  function storeStateUpdatesReducer(state, action) {
    return state + 1;
  }
  // 使用 useReducer 实现强制更新页面
  // useReducer 返回的数组包含两个项 [state, dispatch],调用 dispatch 返回新的 state 时,组件会重新渲染
  const [, forceComponentUpdateDispatch] = useReducer(
    storeStateUpdatesReducer,
    0
  );

  useLayoutEffect(() => {
    // 订阅 store 中数据更新,强制刷新页面
    const ubsubscribe = subscribe(() => {
      forceComponentUpdateDispatch({ type: 'STORE_UPDATED' });
    });
    return () => {
      // 卸载组件取消订阅
      ubsubscribe && ubsubscribe();
    };
  }, [store]);

  // 将需要“连接”的组件返回,并传递给组件需要的数据
  return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />;
};

export default connect;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import { Provider } from './react-redux';
import store from './store'

// 使用 Provider 为后代组件提供 store,使组件层级中的 connect() 方法都能够获得 Redux store
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
import React, { Component } from 'react';
import { connect } from '../react-redux';

class ReactReduxPage extends Component {
  add = () => {
    this.props.dispatch({ type: 'ADD' });
  };

  minus = () => {
    this.props.dispatch({ type: 'MINUS' });
  };

  asyncAdd = () => {
    this.props.dispatch((dispatch) => {
      setTimeout(() => {
        dispatch({ type: 'ADD' });
      }, 1000);
    });
  };

  render() {
    return (
      <div>
        <div>count: {this.props.count}</div>
        {/* <button onClick={this.add}>add</button> */}
        <button onClick={this.props.add}>add</button>
        <button onClick={this.minus}>minus</button>
        <button onClick={this.asyncAdd}>asyncAdd</button>
      </div>
    );
  }
}

// mapStateToProps 用于将 redux 的 state 传给 组件
// mapDispatchToProps 用于将 dispatch 与 action 封装成方法,再传给组件,方便组件里修改 state,而不用写大量 dispatch(xxxx)

const mapStateToProps = (state) => ({ count: state });
// mapDispatchToProps 传一个对象的使用方式
const mapDispatchToProps = {
  add: () => ({ type: 'ADD' }),
};

// mapDispatchToProps 传一个函数的使用方式,下面代码与上面传对象的方式实现同样的功能
// const mapDispatchToProps = (dispatch) => {
//   return {
//     dispatch,
//     add: () => dispatch({ type: 'ADD' }),
//   };
// };

// 使用 connect 方法连接组件与 store
// 传入的 mapStateToProps 和 mapDispatchToProps 定义了需要传递给组件的 state 与 dispatch
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage);

Context 的使用

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

function Button({ theme }) {
  return <button style={{ backgroundColor: theme }}>Test Button</button>;
}

// 也可以使用 Consumer 获取 context
// function ThemedButton () {
//   return (
//     <ThemeContext.Consumer>
//       {value => <Button theme={value} />}
//     </ThemeContext.Consumer>
//   )
// }

// 或者使用 hooks,useContext()
import { createStore, applyMiddleware } from 'redux';
// import { createStore } from '../redux';

// 引入中间件
import thunk from 'redux-thunk';
import logger from 'redux-logger';

const reducer = function (state = 0, action) {
  switch (action.type) {
    case 'ADD':
      return state + 1;
    case 'MINUS':
      return state - 1;
    default:
      return state;
  }
};

// 使用 applyMiddleware 接入 logger,thunk 中间件
const store = createStore(reducer, applyMiddleware(logger, thunk));

export default store;

例如上面例子,logger 放在 thunk 前面,可以打印到异步的action 的日志,如果 logger 放到 thunk 后面,就无法打印出异步的action 的信息,因为 thunk 的逻辑代码中,如果 action 是函数,就会执行该函数,直接跳过后续中间件。

bindActionCreators(actionCreators, dispatch)

🍀 作用是在 mapDispatchToProps 为对象时,使对象 { add: () => ({type: 'ADD'})} 相当于 add = () => dispatch({type: 'ADD'})

// bindActionCreator 会对 action creator 进行包装,加上 dispatch 调用
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args));
}

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  const boundActionCreators = {};
  for (let key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
    }
  }
  return boundActionCreators;
}

🍀 流程图

image.png react-redux用到的bindActionCreators的作用是简化操作,可以把dispatch包装进我们的action creator函数

bindActionCreators 在 connect 时传 mapDispatchToProps 为对象时会用到,用于将对象中每个 key 对应的 value(action creator) 绑定一层 dispatch 调用。然后通过 props 传给组件,使组件中可以更方便的修改 redux 的 state,减少组件中写很多 dispatch(xxx)。

bindActionCreators 把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象,同时使用 dispatch对每个 action creator 进行包装,以便可以直接调用它们。

作用大概是下面这样的效果:

{
  add: () => ({ type: 'ADD' }),
};
// 变成
{
    add: () => dispatch({ type: 'ADD' })
}
  1. actionCreators : 一个 action creator,或者一个 value 是 action creator 的对象。

  2. ActionCreator 是指用来生成 action 的函数,Action 是一个信息的负载,而 action creator 是一个创建 action 的工厂。例如:

function ADD_TODO(text) {
    return { type: 'ADD', text}
}
dispatch(ADD_TODO('待办事项1')) // dispatch({ type: 'ADD', text: '待办事项1'})

上例中 ADD_TODO 用来生成 Action { type: 'ADD', text}ADD_TODO 就是一个 action creator 函数

compose(…functions)

🍀 compose函数是中间件applyMiddleware的核心功能,能将多个单参数函数从右到左嵌套调用

🍀 compose 函数实现将中间件串起来,一个接一个执行,并将上一个中间件的执行结果,传过下个中间件。

🍀 实现这样的效果 compose(f1, f2, f3)(...args) => f1(f2(f3(...args)))

🍀 compose 的实现使用到 [Array.prototype.reduce()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) 方法。

原代码

export default function compose(...funcs) {
  // funcs 长度为 0 的话,返回一个默认的函数
  if(funcs.length === 0) {
    return arg => arg
  }
  // funcs 长度为 1 的话,直接返回该函数
  if(funcs.length === 1) {
    return funcs[0]
  }
  // 使用 reduce 将函数串联起来
  return funcs.reduce((a,b) => (...args) => a(b(...args)))
  // 下面是将上面这行代码的箭头函数拆开,方便理解,要想理解这段代码,必须先理解 reduce 的使用。
  // 例如:funcs 是 [logger,thunk],则最终会得到的是 logger(thunk(..args))
  // return funcs.reduce(function(a, b) {
  //      return function(...args) {
  //     return a(b(...args))
  //   }
    // })
}

🍀 流程图

image.png

Store暴露的API

store.getState()

🍀 store.getSate()返回state 数据,state 数据在外部无法直接访问,必须通过 getState 方法获取。

🍀 伪代码

export default function createStore(reducer) {
  // 定义 currentState,保存当前的 state
  let currentState;

  function getSate() {
    // 调用 getState 时,返回当前的 state
    return currentState;
  }
  function subscribe() {}
  function dispatch() {}

  return {
    getSate,
    subscribe,
    dispatch,
  }
}

🍀 原代码

  // 供外部开发人员通过sotre.getState的形式使用:通过闭包的形式访问createStore函数内部声明的变量state,也就是currentState。在正在执行dipatch函数的时候不能调用store.getState函数。
  function getState() {
    if (isDispatching) {
      throw new Error('You may not call store.getState() while the reducer is executing.')
    }

    return currentState
  }
store.dispatch(action)

🍀 想要正常修改 state 中的数据,必须通过 dispatch,他是唯一的方法。

🍀store.dispatch() 函数接受一个action对象作为入参,action 是一个对象,包含一个 type 字段,例如:{ type: 'add' },除了 type 字段外,也可以包含其他数据,type 用于匹配 reducer 中的修改规则,其他数据用于更新 state。

🍀 dispatch 方法会调用创建 store 时传入的 reducer 函数,并把当前的 state 和 action 传入。最后会循环调用 subscribe 方法收集的回调函数。

🍀 伪代码

export default function createStore(reducer) {
  let currentState;
  let lisenters = [];

  function getSate() {
    return currentState;
  }

  function subscribe(listener) {
    lisenters.push(listener);

    return function unsubscribe() {
      const index = lisenters.indexOf(lisenter);
      lisenters.splice(index, 1);
    };
  }

  function dispatch(action) {
    // 调用 reducer 函数,修改当前的 state
    currentState = reducer(currentState, action)
    // 循环调用回调函数
    for (let i = 0; i < lisenters.length; i++) {
      const lisenter = lisenters[i];
      lisenter()
    }
  }

  return {
    getSate,
    subscribe,
    dispatch,
  }
}

🍀 源代码

  //  供外部开发人员通过store.dispatch({type:xxx,value:xxx})的形式调用
  function dispatch(action) {
  // 如果传递给dispatch函数的action参数不是一个plain object,那么会直接报错
  // 如果传递给dispatch函数的action参数内的type类型为undefined,那么会直接报错
  // 如果传递给dispatch函数的action参数内的type类型不为string类型,那么会直接报错
  // 如果正在执行dispatch的时候再次调用dispatch,那么会直接报错

    try {
      // isDispatching用来标识是否正在执行diaptch函数,如果是则不能:dispatch、subscribe、unsubscribe、getState等等
      isDispatching = true
      // currentReducer = reducer,currentState = preloadedState 由于闭包,dispatch内部可以访问和操作上一作用域的currentState对象,dispatch将当前的state和用户传递给dispatch函数的action再传递给用户自定义编写的用来操作state修改逻辑的reducer函数,在用户自定义编写的reducer函数中,一般会返回具有和原来state不同地址的新state对象,,reducer返回的新state对象直接赋值给当前的state,即currentState
      currentState = currentReducer(currentState, action)
    } finally {
      // 修改完闭包的 currentState对象,标识 isDispatching 为 false
      isDispatching = false
    }

    // 遍历所有的nextListeners,执行所有的监听回调
    const listeners = (currentListeners = nextListeners)
    listeners.forEach(listener => {
      listener()
    })
    // 返回用户传递的action对象
    return action
  }
store.subscribe(listener)

🍀store.subscribe 用于订阅 store 的数据变化。

🍀subscribe函数内部将订阅的回调函数保存到回调函数数组中,订阅的回调函数会在 dispatch 调用期间,state的数据修改后执行

🍀 伪代码

export default function createStore(reducer) {
  let currentState;
  // 定义 currentListeners,保存订阅的回调函数
  let lisenters = [];

  function getSate() {
    return currentState;
  }
  function subscribe(listener) {
    // 将回调函数保存到回调函数数组中
    lisenters.push(listener)
    // 返回一个取消订阅的函数
    return function unsubscribe() {
      const index = lisenters.indexOf(lisenter);
      lisenters.splice(index, 1);
    }
  }
  function dispatch() {}

  return {
    getSate,
    subscribe,
    dispatch,
  }
}

🍀 源代码

  // 供外部开发人员通过sotre.subscribe(()=>{})的形式使用:将开发人员传递的listener回调函数以listenerId为key,listener为value的形式存入nextListeners map中,dipatch函数调用的时候会遍历nextListeners map并挨个调用每一个listener函数。
  // 返回一个取消订阅的函数:通过map的delete方法根据 上次subcribe函数调用因为闭包保存的listenerId 从map中删除对应的listener回调
  function subscribe(listener) {
  // listener必须是一个函数,否则会报错
  // 如果外部环境正在调用dispatch函数的情况下调用subscribe,那么会在subcribe函数直接return,不做任何事情,unsubscribe函数同理。
    if (isDispatching) {
      throw new Error('You may not call store.subscribe() while the reducer is executing'
      )
    }

    let isSubscribed = true

    // createStore函数内部使用:判断nextListeners 是否全等于 currentListeners,如果是的话,将currentListeners复制一份到nextListeners
    ensureCanMutateNextListeners()

    const listenerId = listenerIdCounter++
    nextListeners.set(listenerId, listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      nextListeners.delete(listenerId)
      currentListeners = null // xxxxxxxxxxxxxxxxxxxxxxxxxxxx x x
    }
  }
store.replaceReducer(nextReducer)
  function replaceReducer(nextReducer) {
    // 如果nextReducer不是function类型,那么就直接报错

    // 将nextReducer 复制给 currentReducer
    currentReducer = nextReducer

    // 调用dispatch:在reducer函数中做REPLACE类型的逻辑,将reduce函数的返回值赋值给当前的state,并执行所有的监听回调。
    dispatch({ type: ActionTypes.REPLACE })
  }

项目生态

Redux

  • redux系列的核心,包括依赖项在内共2KB,体积很小。

@reduxjs/toolkit

  • 官方推荐的编写Redux逻辑的方法围绕Redux核心,包含我们认为构建Redux应用程序必不可少的包和函数
  • Redux Toolkit构建了官方建议的最佳实践,Redux Toolkit简化了编写Redux逻辑和设置存储的过程,允许我们编写更短的逻辑,更容易阅读,同时仍然遵循原始的核心Redux行为和数据流,并防止了开发中的常见错误。
  • 使用Redux Toolkit的案例代码:
import { createSlice, configureStore } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    incremented: state => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decremented: state => {
      state.value -= 1
    }
  }
})

export const { incremented, decremented } = counterSlice.actions

const store = configureStore({
  reducer: counterSlice.reducer
})

// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()))

// Still pass action objects to `dispatch`, but they're created for us
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}

React-redux

  • React-Redux 是 React 和 Redux 之间的一个桥梁,它提供了一套工具,让 React 组件能够更方便地访问和操作 Redux 状态管理库中的状态。React-Redux 的主要作用包括以下几个方面

Redux Devtool

Redux DevTool 是一个chrome 插件,可用来调试redux逻辑和观察数据的变化以及流转。Redux DevTool 由两个主要部分组成:存储增强器(通过dispatch分派操作列表来实现时间旅行行为)以及允许您查看和操作历史记录的 UI。

插件系统

  • redux有一个丰富的插件生态系统。与vuex实现扩展的插件形式不同,redux实现扩展是通过是中间件( AOP ( 面向切面编程 )) 的形式来实现,由于reduxRedux.applyMiddleware()是一个增强函数,所以可以由用户来实现增强器,进一步将[redux](https://link.juejin.cn/?target=https%3A%2F%2Fwww.redux.org.cn%2Fdocs%2Fintroduction%2FEcosystem.html)生态引向繁荣。
  • 使用插件系统的案例代码
import { createStore, applyMiddleware } from 'redux';
// 引入 composeWithDevTools
import { composeWithDevTools } from 'redux-devtools-extension';

import thunk from 'redux-thunk';
import logger from 'redux-logger';

const reducer = function (state = 0, action) {
  switch (action.type) {
    case 'ADD':
      return state + 1;
    case 'MINUS':
      return state - 1;
    default:
      return state;
  }
};

const store = createStore(
  reducer,
  composeWithDevTools(applyMiddleware(logger, thunk))
);
export default store;

学习资源

  • Redux Fundamentals tutorial 是一个“自下而上”的教程,它从第一原理和没有任何抽象来教授“Redux如何工作”,以及为什么标准的Redux使用模式存在。
  • Redux Essentials tutorial 是一个“自上而下”的教程,使用我们最新推荐的API和最佳实践来教授“如何正确使用Redux”。我们建议从那里开始。

参考链接

关于作者

作者:Wandra, 全能型人才🐶

内容:算法 | 趋势 |源码|Vue | React | CSS | Typescript | Webpack | Vite | GithubAction | GraphQL | Uniqpp。

专栏:欢迎关注呀🌹

🍀 本专栏致力于分析一些网上热门项目,如果本文对你有帮助的话,别忘了点赞 or 关注嗷 🎀。我是Wandra,我们下期再见啦