Redux 背后的架构思想——认识 Flux 架构

Redux 的设计在很大程度上受益于 Flux 架构,可以认为 Redux 是 Flux 的一种实现形式(虽然它并不严格遵循 Flux 的设定),理解 Flux 将帮助更好地从抽象层面把握 Redux。

  Flux 并不是一个具体的框架,它是一套由 Facebook 技术团队提出的应用架构,这套架构约束的是应用处理数据的模式。在 Flux 架构中,一个应用将被拆分为以下 4 个部分。

  • View(视图层):用户界面。该用户界面可以是以任何形式实现出来的,React 组件是一种形式,Vue、Angular 也完全 OK。Flux 架构与 React 之间并不存在耦合关系。
  • Action(动作):也可以理解为视图层发出的“消息”,它会触发应用状态的改变。
  • Dispatcher(派发器):它负责对 action进行分发。
  • Store(数据层):它是存储应用状态的“仓库”,此外还会定义修改状态的逻辑。store的变化最终会映射到 view层上去。

这 4 个部分之间的协作将通过下图所示的工作流规则来完成配合:

一个典型的 Flux 工作流是这样的:用户与 View之间产生交互,通过 View发起一个 ActionDispatcher 会把这个 Action派发给 Store,通知 Store进行相应的状态更新。Store状态更新完成后,会进一步通知 View去更新界面。

值得注意的是,图中所有的箭头都是单向的,这也正是 Flux 架构最核心的一个特点——单向数据流

Flux 架构到底解决了什么问题 

Flux 的核心特征是单向数据流,要想完全了解单向数据流的好处,我们需要先了解双向数据流带来了什么问题。

MVC 模式在前端场景下的局限性

双向数据流最为典型的代表就是前端场景下的 MVC 架构,该架构的示意图如下图所示:

除了允许用户通过 View 层交互来触发流程以外,MVC 架构还有另外一种形式,即允许用户通过直接触发 Controller逻辑来触发流程,这种模式下的架构关系如下图所示:

在 MVC 应用中,会涉及这 3 个部分:

  • Model(模型),程序需要操作的数据或信息;
  • View(视图),用户界面;
  • Controller(控制器),用于连接 View 和 Model,管理 Model与 View之间的逻辑。

原则上来说,三者的关系应该像上图一样,用户操作 View后,由 Controller来处理逻辑(或者直接触发 Controller的逻辑),经过 Controller将改变应用到 Model中,最终再反馈到 View上。在这个过程中,数据流应该是单向的。

  事实上,在许多服务端的 MVC 应用中,数据流确实能够保持单向。但是在前端场景下,实际的 MVC 应用要复杂不少,前端应用/框架往往出于交互的需要,允许 View 和 Model 直接通信。此时的架构关系就会变成下图这样:

这就允许了双向数据流的存在。当业务复杂度较高时,数据流会变得非常混乱,出现类似下图这种情况:

图中我们的示例只有一个 Controller,但考虑到一个应用中还可能存在多个 Controller,实际的情况应该比上图还要复杂得多(尽管图示本身已经够复杂了)。

  在如此复杂的依赖关系下,再小的项目变更也将伴随着不容小觑的风险——或许一个小小的改动,就会对整个项目造成“蝴蝶效应”般的巨大影响。如此混乱的修改来源,将会使得我们连 Bug 排查都无从下手,因为你很难区分出一个数据的变化到底是由哪个 Controller或者哪个 View 引发的

  此时再回头看下 Flux 的架构模式,你应该多少能感受到其中的妙处。这里再来回顾一下 Flux 中的数据流模式,请看下图:

Flux最核心的地方在于严格的单向数据流,在单向数据流下,状态的变化是可预测的。如果 store中的数据发生了变化,那么有且仅有一个原因,那就是由 Dispatcher派发 Action来触发的。这样一来,就从根本上避免了混乱的数据关系,使整个流程变得清晰简单。

  不过这并不意味着 Flux 是完美的。事实上,Flux 对数据流的约束背后是不可忽视的成本:除了开发者的学习成本会提升外,Flux 架构还意味着项目中代码量的增加。

  Flux 架构往往在复杂的项目中才会体现出它的优势和必要性。如果项目中的数据关系并不复杂,其实完全轮不到 Flux 登场,这一点对于 Redux 来说也是一样的。

Redux 是 JavaScript 状态容器,它提供可预测的状态管理。

Redux 关键要素与工作流回顾
  虽然 Redux 在实现层面并没有按照 Flux 那一套来(比如 Flux 中允许多个 Store 存在,而 Redux 中只有一个 Store 等),但 Redux 在设计思想上确实和 Flux 一脉相承。

  接下来介绍 Redux 的实现原理之前,先简单回顾一下它的关键要素与工作流。Redux 主要由 3 部分组成:Store、Reducer和 Action。

  • Store:它是一个单一的数据源,而且是只读的。
  • Action:是“动作”的意思,它是对变化的描述。
  • Reducer:它负责对变化进行分发和处理,最终将新的数据返回给 Store。

StoreAction和 Reducer三者紧密配合,便形成了 Redux独树一帜的工作流,如下图所示:

在 Redux的整个工作过程中,数据流是严格单向的。如果你想对数据进行修改,只有一种途径:派发 Action。Action会被 Reducer读取,Reducer将根据 Action内容的不同执行不同的计算逻辑,最终生成新的 state(状态),这个新的 state会更新到 Store对象里,进而驱动视图层面作出对应的改变。

  对于组件来说,任何组件都可以以约定的方式从 Store读取到全局的状态,任何组件也都可以通过合理地派发 Action来修改全局的状态。Redux通过提供一个统一的状态容器,使得数据能够自由而有序地在任意组件之间穿梭。

Redux 是如何工作的

  先来看一下 Redux 的源码文件夹结构,如下图所示:

其中,utils是工具方法库;index.js作为入口文件,用于对功能模块进行收敛和导出。真正“干活”的是功能模块本身,也就是下面这几个文件:

  • applyMiddleware.js
  • bindActionCreators.js
  • combineReducers.js
  • compose.js
  • createStore.js

  applyMiddleware是中间件模块,它的独立性较强。

bindActionCreators(用于将传入的 actionCreator与 dispatch方法相结合,揉成一个新的方法)、combineReducers(用于将多个 reducer合并起来)、compose(用于把接收到的函数从右向左进行组合)这三个方法均为工具性质的方法。

  不用急着去搜索这三个工具方法,因为它们均独立于 Redux 主流程之外,属于“非必须使用”的辅助 API,不熟悉这些 API 并不影响你理解 Redux 本身。理解 Redux 实现原理,真正需要我们关注的模块其实只有一个——createStore。

  createStore方法是我们在使用 Redux 时最先调用的方法,它是整个流程的入口,也是 Redux 中最核心的 API。

故事的开始:createStore

  使用 Redux 的第一步,我们就需要调用 createStore方法。单纯从使用感上来说,这个方法做的事情似乎就是创建一个 store对象出来,像这样:

// 引入 redux
import { createStore } from 'redux'
// 创建 store
const store = createStore(reducer,initial_state,applyMiddleware(middleware1, middleware2, ...)
);

createStore方法可以接收以下 3 个入参:

  • reducer
  • 初始状态内容
  • 指定中间件

从拿到入参到返回出 store的过程中,到底都发生了什么呢?这里我为你提取了 createStore 中主体逻辑的源码(解析在注释里):

function createStore(reducer, preloadedState, enhancer) {// 这里处理的是没有设定初始状态的情况,也就是第一个参数和第二个参数都传 function 的情况if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {// 此时第二个参数会被认为是 enhancer(中间件)enhancer = preloadedState;preloadedState = undefined;}// 当 enhancer 不为空时,便会将原来的 createStore 作为参数传入到 enhancer 中if (typeof enhancer !== 'undefined') {return enhancer(createStore)(reducer, preloadedState);}// 记录当前的 reducer,因为 replaceReducer 会修改 reducer 的内容let currentReducer = reducer;// 记录当前的 statelet currentState = preloadedState;// 声明 listeners 数组,这个数组用于记录在 subscribe 中订阅的事件let currentListeners = [];// nextListeners 是 currentListeners 的快照let nextListeners = currentListeners;// 该变量用于记录当前是否正在进行 dispatchlet isDispatching = false// 该方法用于确认快照是 currentListeners 的副本,而不是 currentListeners 本身function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice();}}// 我们通过调用 getState 来获取当前的状态function getState() {return currentState;}// subscribe 订阅方法,它将会定义 dispatch 最后执行的 listeners 数组的内容function subscribe(listener) {// 校验 listener 的类型if (typeof listener !== 'function') {throw new Error('Expected the listener to be a function.')}// 禁止在 reducer 中调用 subscribeif (isDispatching) {throw new Error('You may not call store.subscribe() while the reducer is executing. ' +'If you would like to be notified after the store has been updated, subscribe from a ' +'component and invoke store.getState() in the callback to access the latest state. ' +'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}// 该变量用于防止调用多次 unsubscribe 函数let isSubscribed = true;// 确保 nextListeners 与 currentListeners 不指向同一个引用ensureCanMutateNextListeners(); // 注册监听函数nextListeners.push(listener); // 返回取消订阅当前 listener 的方法return function unsubscribe() {if (!isSubscribed) {return;}isSubscribed = false;ensureCanMutateNextListeners();const index = nextListeners.indexOf(listener);// 将当前的 listener 从 nextListeners 数组中删除 nextListeners.splice(index, 1);};}// 定义 dispatch 方法,用于派发 action function dispatch(action) {// 校验 action 的数据格式是否合法if (!isPlainObject(action)) {throw new Error('Actions must be plain objects. ' +'Use custom middleware for async actions.')}// 约束 action 中必须有 type 属性作为 action 的唯一标识 if (typeof action.type === 'undefined') {throw new Error('Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?')}// 若当前已经位于 dispatch 的流程中,则不允许再度发起 dispatch(禁止套娃)if (isDispatching) {throw new Error('Reducers may not dispatch actions.')}try {// 执行 reducer 前,先"上锁",标记当前已经存在 dispatch 执行流程isDispatching = true// 调用 reducer,计算新的 state currentState = currentReducer(currentState, action)} finally {// 执行结束后,把"锁"打开,允许再次进行 dispatch isDispatching = false}// 触发订阅const listeners = (currentListeners = nextListeners);for (let i = 0; i < listeners.length; i++) {const listener = listeners[i];listener();}return action;}// replaceReducer 可以更改当前的 reducerfunction replaceReducer(nextReducer) {currentReducer = nextReducer;dispatch({ type: ActionTypes.REPLACE });return store;}// 初始化 state,当派发一个 type 为 ActionTypes.INIT 的 action,每个 reducer 都会返回// 它的初始值dispatch({ type: ActionTypes.INIT });// observable 方法可以忽略,它在 redux 内部使用,开发者一般不会直接接触function observable() {// observable 方法的实现}// 将定义的方法包裹在 store 对象里返回return {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable}
}

通过阅读源码会发现,createStore从外面看只是一个简单的创建动作,但在内部却别有洞天,涵盖了所有 Redux 主流程中核心方法的定义。

  接下来将 createStore内部逻辑总结进一张大图中,这张图涵盖了每个核心方法的工作内容,它将帮助快速把握 createStore的逻辑框架。

在 createStore导出的方法中,与 Redux主流程强相关的,同时也是我们平时使用中最常打交道的几个方法,分别是:

  • getState
  • subscribe
  • dispatch

其中 getState的源码内容比较简单,在逐行分析的过程中已经对它有了充分的认识。而 subscribe和 dispatch则分别代表了 Redux独有的“发布-订阅”模式以及主流程中最为关键的分发动作。

针对 dispatch和 subscribe这两个具体的方法进行分析,分别认识 Redux工作流中最为核心的dispatch动作,以及 Redux自身独特的 “发布-订阅”模式。

Redux 工作流的核心:dispatch 动作

dispatch应该是大家在使用 Redux的过程中最为熟悉的 API 了。结合前面对设计思想的解读,我们已经知道,在 Redux中有这样 3 个关键要素:

  • action
  • reducer
  • store

     之所以说 dispatch是 Redux 工作流的核心,是因为dispatch 这个动作刚好能把 action、reducer和 store这三位“主角”给串联起来。dispatch的内部逻辑,足以反映了这三者之间“打配合”的过程。

  这里把 dispatch的逻辑从 createStore中提取出来,请看相关源码:

function dispatch(action) {// 校验 action 的数据格式是否合法if (!isPlainObject(action)) {throw new Error('Actions must be plain objects. ' +'Use custom middleware for async actions.')}// 约束 action 中必须有 type 属性作为 action 的唯一标识 if (typeof action.type === 'undefined') {throw new Error('Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?')}// 若当前已经位于 dispatch 的流程中,则不允许再度发起 dispatch(禁止套娃)if (isDispatching) {throw new Error('Reducers may not dispatch actions.')}try {// 执行 reducer 前,先"上锁",标记当前已经存在 dispatch 执行流程isDispatching = true// 调用 reducer,计算新的 statecurrentState = currentReducer(currentState, action)} finally {// 执行结束后,把"锁"打开,允许再次进行 dispatchisDispatching = false}// 触发订阅const listeners = (currentListeners = nextListeners);for (let i = 0; i < listeners.length; i++) {const listener = listeners[i];listener();}return action;
}

结合源码,将 dispatch的工作流程提取如下:

在这段工作流中,有两个点值得细细回味。

1. 通过“上锁”避免“套娃式”的 dispatch

  dispatch工作流中最关键的就是执行 reducer这一步,它对应的是下面这段代码:

try {// 执行 reducer 前,先“上锁”,标记当前已经存在 dispatch 执行流程isDispatching = true// 调用 reducer,计算新的 state currentState = currentReducer(currentState, action)
} finally {// 执行结束后,把"锁"打开,允许再次进行 dispatch isDispatching = false
}

reducer 的本质是 store的更新规则,它指定了应用状态的变化如何响应 action并发送到 store。这段代码中调用 reducer,传入 currentState和 action,对应的正是 action → reducer → store 这个过程,如下图标红处所示:        

在调用 reducer之前,Redux首先会将 isDispatching变量置为 true,待 reducer执行完毕后,再将 isDispatching变量置为 false。这个操作应该不陌生,因为在“setState” 的“批处理”也是用类似的“上锁”方式来实现的。

  这里之所以要用 isDispatching将 dispatch的过程锁起来,目的是规避“套娃式”的 dispatch。更准确地说,是为了避免开发者在 reducer中手动调用 dispatch。

 “禁止套娃”用意何在?首先,从设计的角度来看,作为一个“计算 state 专用函数”,Redux 在设计 reducer 时就强调了它必须是“纯净”的,它不应该执行除了计算之外的任何“脏操作”,dispatch调用显然是一个“脏操作”;其次,从执行的角度来看,若真的在 reducer中调用 dispatch,那么 dispatch又会反过来调用 reducer,reducer又会再次调用 dispatch…这样反复相互调用下去,就会进入死循环,属于非常严重的误操作。

  因此,在 dispatch的前置校验逻辑中,一旦识别出 isDispatching为 true,就会直接 throw Error(见下面代码),把死循环扼杀在摇篮里:

if (isDispatching) {throw new Error('Reducers may not dispatch actions.')
}

2. 触发订阅的过程

  在 reducer执行完毕后,会进入触发订阅的过程,它对应的是下面这段代码:

// 触发订阅
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {const listener = listeners[i];listener();
}

在阅读这段源码的过程中,疑问点主要在两个方面:

  1. 之前并没有介绍 subscribe这个 API,也没有提及 listener相关的内容,它们到底是如何与 Redux 主流程相结合的呢?
  2. 为什么会有 currentListeners和 nextListeners这两个 listeners数组?这和我们平时见到的“发布-订阅”模式好像不太一样。

  要弄明白这两个问题,我们需要先了解 subscribe这个 API。
Redux 中的“发布-订阅”模式:认识 subscribe
  dispatch中执行的 listeners数组从订阅中来,而执行订阅需要调用 subscribe。在实际的开发中,subscribe并不是一个严格必要的方法,只有在需要监听状态的变化时,我们才会调用 subscribe。

  subscribe接收一个 Function类型的 listener作为入参,它的返回内容恰恰就是这个 listener对应的解绑函数。你可以通过下面这段示例代码简单把握一下 subscribe的使用姿势:

function handleChange() {// 函数逻辑
}
const unsubscribe = store.subscribe(handleChange)
unsubscribe()

     subscribe在订阅时只需要传入监听函数,而不需要传入事件类型。这是因为 Redux 中已经默认了订阅的对象就是“状态的变化(准确地说是 dispatch 函数的调用)”这个事件。

  到这里,就可以回答上面提出的第一个关于 subscribe的问题了:subscribe 是如何与 Redux 主流程结合的呢?首先,可以在 store 对象创建成功后,通过调用 store.subscribe来注册监听函数,也可以通过调用 subscribe的返回函数来解绑监听函数,监听函数是用 listeners数组来维护的;当dispatch action 发生时,Redux 会在 reducer执行完毕后,将 listeners数组中的监听函数逐个执行。这就是 subscribe与 Redux 主流程之间的关系。

  接下来我们结合源码来分析一下 subscribe的内部逻辑,subscribe源码提取如下:

function subscribe(listener) {// 校验 listener 的类型if (typeof listener !== 'function') {throw new Error('Expected the listener to be a function.')}// 禁止在 reducer 中调用 subscribeif (isDispatching) {throw new Error('You may not call store.subscribe() while the reducer is executing. ' +'If you would like to be notified after the store has been updated, subscribe from a ' +'component and invoke store.getState() in the callback to access the latest state. ' +'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}// 该变量用于防止调用多次 unsubscribe 函数let isSubscribed = true;// 确保 nextListeners 与 currentListeners 不指向同一个引用ensureCanMutateNextListeners(); // 注册监听函数nextListeners.push(listener); // 返回取消订阅当前 listener 的方法return function unsubscribe() {if (!isSubscribed) {return;}isSubscribed = false;ensureCanMutateNextListeners();const index = nextListeners.indexOf(listener);// 将当前的 listener 从 nextListeners 数组中删除 nextListeners.splice(index, 1);};
}

结合这段源码,可以将 subscribe的工作流程提取如下:

这个工作流中有一个步骤让人很难不在意,那就是对 ensureCanMutateNextListeners的调用。结合前面整体源码的分析,我们已经知道 ensureCanMutateNextListeners的作用就是确保 nextListeners不会和 currentListener指向同一个引用。 那么为什么要这样做呢?这里就引出了之前提出的关于 subscribe的第二个问题:为什么会有 currentListeners 和 nextListeners 两个 listeners 数组?

  要理解这个问题,首先要搞清楚 Redux 中的订阅过程和发布过程各自是如何处理 listeners数组的。

1. 订阅过程中的 listeners 数组

  两个 listeners之间的第一次“交锋”发生在 createStore的变量初始化阶段,nextListeners会被赋值为 currentListeners(见下面代码),这之后两者确实指向同一个引用。

 let nextListeners = currentListeners

但在 subscribe第一次被调用时,ensureCanMutateNextListeners就会发现这一点,然后将 nextListeners纠正为一个内容与 currentListeners一致、但引用不同的新对象。对应的逻辑如下面代码所示:

function ensureCanMutateNextListeners() {// 若两个数组指向同一个引用if (nextListeners === currentListeners) {// 则将 nextListeners 纠正为一个内容与 currentListeners 一致、但引用不同的新对象nextListeners = currentListeners.slice()}
}

在 subscribe的逻辑中,ensureCanMutateNextListeners每次都会在 listener注册前被无条件调用,用以确保两个数组引用不同。紧跟在 ensureCanMutateNextListeners之后执行的是 listener的注册逻辑,我们可以对应源码中看到 listener最终会被注册到 nextListeners数组中去:

nextListeners.push(listener);

2. 发布过程中的 listeners 数组

  触发订阅这个动作是由 dispatch来做的,相关的源码如下:

// 触发订阅
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {const listener = listeners[i];listener();
}

这段源码告诉我们,在触发订阅的过程中,currentListeners会被赋值为 nextListeners,而实际被执行的 listeners数组又会被赋值为 currentListeners。因此,最终被执行的 listeners 数组,实际上和当前的 nextListeners 指向同一个引用。

  注册监听也是操作 nextListeners,触发订阅也是读取 nextListeners(实际上,取消监听操作的也是 nextListeners 数组)。既然如此,要 currentListeners 有何用

3. currentListeners 数组用于确保监听函数执行过程的稳定性

  正因为任何变更都是在 nextListeners上发生的,我们才需要一个不会被变更的、内容稳定的 currentListeners,来确保监听函数在执行过程中不会出幺蛾子。

举个例子,下面这种操作在 Redux 中完全是合法的:

// 定义监听函数 A
function listenerA() {}
// 订阅 A,并获取 A 的解绑函数
const unSubscribeA = store.subscribe(listenerA)
// 定义监听函数 B
function listenerB() {// 在 B 中解绑 AunSubscribeA()
}
// 定义监听函数 C
function listenerC() {}
// 订阅 B
store.subscribe(listenerB)
// 订阅 C
store.subscribe(listenerC)

在这个 Demo 执行完毕后,nextListeners数组的内容是 A、B、C 3 个 listener

[listenerA,  listenerB, listenerC]

接下来若调用 dispatch,则会执行下面这段触发订阅的逻辑:

// 触发订阅
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {const listener = listeners[i];listener();
}

当 for 循环执行到索引 i = 1 处,也就是对应的 listener为 listenerB时,问题就会出现:listenerB中执行了 unSubscribeA这个动作。而结合我们前面的分析,监听函数注册、解绑、触发这些动作实际影响的都是 nextListeners。为了强化对这一点的认知,复习一下 unsubscribe的源码:

return function unsubscribe() {// 避免多次解绑if (!isSubscribed) {return;}isSubscribed = false;// 熟悉的操作,调用 ensureCanMutateNextListeners 方法ensureCanMutateNextListeners();// 获取 listener 在 nextListeners 中的索引const index = nextListeners.indexOf(listener);// 将当前的 listener 从 nextListeners 数组中删除 nextListeners.splice(index, 1);
};

假如说不存在 currentListeners,那么也就意味着不需要 ensureCanMutateNextListeners这个动作。若没有 ensureCanMutateNextListeners,unsubscribeA() 执行完之后,listenerA会同时从 listeners数组和 nextListeners数组中消失(因为两者指向的是同一个引用),那么 listeners数组此时只剩下两个元素 listenerB和 listenerC,变成这样:

[listenerB, listenerC]

listeners数组的长度改变了,但 for 循环却不会感知这一点,它将无情地继续循环下去。之前执行到 i = 1处,listener = listeners[1] ,也就是说 listener === listenerB;下一步理应执行到 i = 2处,但此时 listeners[2]已经是 undefined了,原本应该出现在这个索引位上的 listenerC,此时因为数组长度的变化,被前置到了 i = 1处!这样一来,undefined 就会代替 listenerC 被执行,进而引发函数异常

  这可怎么办呢?答案当然是将 nextListeners 与当前正在执行中的 listeners 剥离开来,将两者指向不同的引用。这也正是 ensureCanMutateNextListeners所做的事情。

  在示例的这种场景下,ensureCanMutateNextListeners执行前,listeners、currentListeners和 nextListeners之间的关系是这样的:

listeners === currentListeners === nextListeners

    而 ensureCanMutateNextListeners执行后,nextListeners就会被剥离出去:

nextListeners = currentListeners.slice()
listeners === currentListeners !== nextListener

这样一来,nextListeners上的任何改变,都无法再影响正在执行中的 listeners。currentListeners 在此处的作用,就是为了记录下当前正在工作中的 listeners 数组的引用,将它与可能发生改变的 nextListeners 区分开来,以确保监听函数在执行过程中的稳定性。

React:Redux 设计思想相关推荐

  1. 应用数据流状态管理框架Redux简介、设计思想、核心概念及工作流

    tip:有问题或者需要大厂内推的+我脉脉哦:丛培森 ٩( 'ω' )و [本文源址:http://blog.csdn.net/q1056843325/article/details/54784109 ...

  2. 一个 react+redux 工程实例

    在前几天的一篇文章中总结部分提到了学习过程中基础的重要性.当然,并不是不支持大家学习新的框架,这篇文章就分享一下react+redux工程实例. 一直在学习研究react.js,前前后后做了几次分享. ...

  3. React+Redux打造“NEWS EARLY”单页应用 一步步让你理解最前沿技术栈的真谛

    之前写过一篇文章,分享了我利用闲暇时间,使用React+Redux技术栈重构的百度某产品个人中心页面.您可以参考这里,或者参考Github代码仓库地址. 这个工程实例中,我采用了厂内的工程构建工具-F ...

  4. [Redux/Mobx] Mobx的设计思想是什么

    [Redux/Mobx] Mobx的设计思想是什么 依赖收集.在Mobx中,定义了observable的属性,mobx会自动跟踪这个属性值的变化:在用了mobx与react的桥接库mobx-react ...

  5. React/React Native框架的设计思想

    React Native框架的编程思想 (一)React Native框架的设计思想 基于响应式编程范式 从其全局刷新的机制以及flux架构可以得出,react native是基于响应式编程范式的产物 ...

  6. 前端React教程第二课 React生命周期设计思想

    02 为什么 React 16 要更改组件的生命周期?(上) React 生命周期已经是一个老生常谈的话题了,几乎没有哪一门 React 入门教材会省略对组件生命周期的介绍.然而,入门教材在设计上往往 ...

  7. React 设计思想

    React 设计思想 译者序:本文是 React 核心开发者.有 React API 终结者之称的 Sebastian Markbåge 撰写,阐述了他设计 React 的初衷.阅读此文,你能站在更高 ...

  8. React+Redux+中间件

    MVVM是Model-View-ViewModel的缩写.mvvm是一种设计思想.Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑:View 代表UI 组件,它负责将数据模 ...

  9. React组件设计实践总结05 - 状态管理

    今天是 520,这是本系列最后一篇文章,主要涵盖 React 状态管理的相关方案. 前几篇文章在掘金首发基本石沉大海, 没什么阅读量. 可能是文章篇幅太长了?掘金值太低了? 还是错别字太多了? 后面静 ...

最新文章

  1. ylb:使用sql语句实现添加、删除约束
  2. layer iframe层的使用,传参
  3. Binary classification - 聊聊评价指标的那些事儿【实战篇】
  4. java压缩传输_简单实现字符串的压缩,减轻传输压力
  5. hibernate mysql 配置文件_hibernate 框架的配置文件和映射文件以及详解
  6. ExtJs使用自定义插件动态保存表头配置(隐藏或显示)
  7. linux 6.4 multipath.conf跟其他版本的区别,宏杉与其他厂商存储共用multipath的配置方法...
  8. python构建电商用户画像(1)
  9. 猫盘onespace x3p系统使用
  10. 开淘宝店怎么注册公司?开淘宝店是否需要去工商局登记注册公司
  11. 系统分析与控制_多智能体协同控制研究中各定位系统分析
  12. Linux常用命令——mysqladmin命令
  13. Python爬虫基础-mysql数据库
  14. Shell知识点(一)基本语法
  15. 常用链接ssh服务器的工具(推荐)
  16. ultra fast lane detection数据集制作
  17. Debian11(Bullseye)系统安装docker及启动失败问题解决
  18. c语言位段实现字节异或,C语言-位运算-小结
  19. 读书笔记(被讨厌的勇气一)
  20. Q3净收入创单季新高,每日优鲜靠什么增速增效?

热门文章

  1. 母函数详解(转 侵删)
  2. 程序设计之HardCoding
  3. 告诉你一个朴素的上海(中/食)
  4. Win10自带的SSH客户端
  5. 《工作前5年,决定你一生的财富》三公子TXT,PDF,epub,mobi,azw3,kindle电子书下载
  6. java压测服务器_Java简单模拟设备压测服务器(Rabbitmq)
  7. 滑铁卢计算机专业qs排名,新鲜出炉 2021年滑铁卢大学世界综合及专业排名 很强势有木有!...
  8. C++项目新冠疫苗预约系统
  9. 安装mechanize
  10. Android小程序之音乐播放列表