源码剖析及运用,学习计算

Redux 源码剖析及使用

2018/05/23 · JavaScript
· Redux

初稿出处:
今天头条技术团队   

动用redux+react已有一段时间,刚早先接纳没有深刻领会其源码,近来静下心细读源码,感触颇深~

正文首要含有Redux设计思想、源码解析、Redux应用实例应用八个地点。

参考链接:

1.Redux 统一筹划理念

  Web 应用是3个状态机,视图与气象是逐一对应的

  全部的场所,保存在多少个目的里面

 

Redux 是如今建议的
Flux
思想的一种实施方案,在它此前也有 reflux 、 fluxxor
等高质量的著述,但好景非常长多少个月就在 GitHub 上获近万 star
的成就让那一个后来的超过先前的渐渐变成 Flux 的主流实践方案。

背景:

React 组件 componentDidMount 的时候早先化 Model,并监听 Model 的 change
事件,当 Model 发生变更时调用 React 组件的 setState 方法重复 render
整个组件,最终在组件 component威尔Unmount 的时候撤除监听并销毁 Model。

最发轫完成二个简约实例:例如add加法操作,只要求通过React中 setState
去控制变量扩展的景观,格外简单方便。

唯独当大家供给在品种中扩展乘法/除法/幂等等复杂操作时,就须要规划三个state来支配views的变更,当项目变大,里面含有状态过多时,代码就变得难以维护并且state的变型不可预测。只怕必要追加二个小效率时,就会滋生多处变更,导致支付频率降低,代码可读性不高

譬如说今后利用较多backbone形式:澳门葡京 1

如上海体育场所所示,能够阅览 Model 和 View 之间涉及错综复杂,中期代码难以维护。

为了消除上述难点,在 React 中引入了 Redux。Redux 是
JavaScript 状态容器,提供可预测化的场所管理方案。上面详细介绍~~

  • Redux华语文书档案
  • Redux
    入门教程-阮一峰
  • 看漫画,学
    Redux
  • 在react-native中使用redux
  • [React
    Native]Redux的主干选拔格局
  • Redux管理复杂应用数据逻辑

2.基本概念和API

  Redux 的为主便是 store, action, reducer   store.dispatch(action)
——> reducer(state, action) ——> final state

(1)store 正是保存数据的地点,redux 提供createStore 函数,生成Store

    store = redux.createStore(reducer, []);

        store.getState() //重回store的近来气象

  Store 允许利用store.subscribe方法设置监听函数,一旦 State
爆发变化,就活动执行那个函数。

  store.subscribe(listener);

  store.subscribe 方法再次来到叁个函数,调用那么些函数就能够祛除监听

  let unsubscribe = store.subscribe(() =>

    console.log(store.getState())

  );

  unsubscribe(); //解除监听

  Store 的实现

store.getState() //获取当前状态

store.dispatch() //触发action

store.subscribe() //监听state状态

import { createStore } from ‘redux’;

let { subscribe, dispatch, getState } = createStore(reducer, window.STATE_FORM_SERVER);

window.STATE_FORM_SERVER //是整个应用的初始状态值

 (2)action 是2个惯常的object,必须有1个type属性,证明行为的品类。

  const action = {

    type: ’add_todo’,

    text: ‘read’,

    time

    …

  }

  action描述当前时有发生的事务,改变State的唯一方法就是通过action,会将数据送到store。

  一般用actionCreator
工厂格局发生,View要发出的新闻类型对应action的品类,手写起来很讨厌。

  const ADD_TODO = “添加 todo”;

  function addTodo(text){

    return {

           type: ADD_TODO,

      text 

          }

  }

  const action = addTodo(‘Learn’);

  addTodo 方法正是3个Action Creator

  View 发出Action的绝无仅有路径 store.dispatch(action) //触发事件 

 (3)reducer 其实正是3个平凡函数,首要用来改变state. Store 收到View
发出的Action 未来,必须回到多少个新的State,View 才会爆发变化。
而这几个总括新的State的长河就叫Reducer.

  const reducer = function(state, action){

  switch(state.text){

             case ‘add_todo’:

  return state.contact(‘…’);

              default:

  return state;

         }

  }

   当然实际开发不像下面例子这么简单,要求在开立state的时候就领悟state的总计规则,将reducer传入:

  store = redux.createStore(reducer);

  Reducer
纯函数,只要有相同的输入必然再次回到同样的出口。无法更改原先的state而是通过Reducer再次来到贰个新的state。

//state 是一个对象

function reducer(state, action){

return Object.assign({},state, {thingToChange});

         return {…state, …newState};

}

//state 是一个数组

function reducer(state, action){

return […state, newItem];

}

正如 Redux
官方所称,React
禁止在视图层直接操作 DOM 和异步行为 ( removing both asynchrony and
direct DOM manipulation
),来拆开异步和转变这一对朋友。但它依旧把景况的保管交到了作者们手中。Redux
正是我们的景况管理小管家。

目的:

壹 、深刻领悟Redux的宏图思想

贰 、剖析Redux源码,并结合实际应用对源码有更深层次的驾驭

三 、实际工程选用中所遇到的标题总计,避免重新踩坑

目录

  • 选择场景
  • 应用的三原则
    • 纯净数据源
    • 气象是只读的
    • 源码剖析及运用,学习计算。经过纯函数修改State
  • redux状态管理的流程及相关概念
    • store
    • Action
    • Action 创造函数(Action Creator)
    • Reducer
  • redux怎么着与组件结合
    • 现实示例1
    • 具体示例2

3.Reducer的拆分和合并

在骨子里项目中,reducer 很巨大,不易阅读管理,大家得以将reducer
拆分成小的函数,不一样的函数对应处理区别的性情。然后将其统1/10一个大的reducer

Reducer 提供了三个主意combineReducers方法来归并reducer.

const chatReducer = (state = defaultState, action = {}) => {
  return {
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action)
  }
};

import { combineReducers } from 'redux';

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

export default todoApp;

你能够把具有子reducers 放在二个文本夹里,然后统一引入。

import { combineReducers } from ‘redux’

import * as reducers from ‘./reducers’

const reducer = combineReducers(reducers)

安利的话先暂且说到那,本次大家聊天 React-Redux 在沪江前端团队中的实践。

壹 、Redux设计思想

使用场景

React设计意见之一为单向数据流,那从单向有利于了数据的军管。不过React自个儿只是view,并不曾提供完备的数目管理方案。随着应用的不停复杂化,即使用react创设前端选用的话,就要应对纷纷复杂的数额通讯和管制,js必要维护越多的情状(state),那几个state恐怕包含用户音讯、缓存数据、全局设置意况、被激活的路由、被选中的竹签、是还是不是加载动作效果或许分页器等等。

此时,Flux架构应运而生,Redux是其最优雅的达成,Redux是多个不注重任何库的框架,但是与react结合的最好,个中react-redux等开源组件正是把react与redux组合起来进行调用开发。

备注:

1.只要您不通晓是否须求 Redux,那正是不要求它

2.唯有相逢 React 实在解决不了的题材,你才必要 Redux

Redux使用情形:

  • 有些组件的气象,需求共享
  • 某些状态必要在别的地点都足以得到
  • 2个组件供给转移全局状态
  • 三个组件供给变更另3个组件的情况

比如,论坛应用中的夜间安装、回到顶部、userInfo全局共享等情形。redux最后目的便是让境况(state)变化变得可预测.

4.中间件和异步操作

小编们运用redux
,用户发生action,Reducer算出新的state,然后再次渲染界面。那里Reducer是即时算出state,马上响应的,同步执行的相继。 

唯独假诺大家必要实践异步落成,Reducer执行完事后,自动执行呢?
那里就供给运用middleWare(中间件)。

中间件加在什么地点适度?大家来大约分析下。

  1. Reducer
    是纯函数,用来测算state,相同的输入必然获得相同的输出,理论上纯函数不容许读写操作。

  2. View和state是逐一对应,是state的变现,没有处理能力。

  3. Action 是存放数据的靶子,即音讯的载体,被触发操作。

最后发现,唯有加在store.dispatch() 比较适度。添加日记效用,把 Action 和
State 打字与印刷出来,能够对store.dispatch开始展览如下改造。

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

小结:中间件其实正是一个函数,对store.dispatch方法举行了改造,在产生Action 和实施 Reducer 这两步之间,添加了任何作用。

中间件的用法:

const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(logger)
);

createStore方法还可以任何应用的始发状态作为参数,将中间件(logger)放在applyMiddleware主意之中,传入createStore艺术,就大功告成了store.dispatch()的法力增强。

applyMiddleware 是Redux
的原生方法,成效是将持有中间件组成一个数组,依次执行。上面是它的源码:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

能够看看,中间件内部(middlewareAPI)能够获得getState和dispatch那八个主意。

异步操作的三个化解方案,正是让 Action Creator 重返二个 Promise
对象。看一下redux-promise的源码:

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action)
        ? action.then(dispatch)
        : next(action);
    }

    return isPromise(action.payload)
      ? action.payload.then(
          result => dispatch({ ...action, payload: result }),
          error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          }
        )
      : next(action);
  };
}

从地点代码能够见到,假设 Action 本人是七个 Promise,它 resolve
以后的值应该是3个 Action
对象,会被dispatch措施送出(action.then(dispatch)),但 reject
现在不会有此外动作;假诺 Action 对象的payload特性是二个 Promise
对象,那么不论 resolve 和 reject,dispatch方法都会发出 Action。

0. 放弃

您未曾看错,在开班从前我们先是谈谈一下如何状态下不应当用 Redux。

所谓杀鸡焉用宰牛刀,任何技术方案都有其适用场景。作为一个考虑的推行方案,Redux
必然会为落成思想立规矩、铺基础,放在复杂的 React
应用里,它会是“金科玉律”,而身处结构不算复杂的行使中,它只会是“繁文缛节”。

假若大家将要构建的运用无需多层组件嵌套,状态变化不难,数据单一,那么就应遗弃Redux ,选拔单纯的 React 库 或别的 MV*
库。终归,没有人愿意雇佣一个收款比自身收入还高的财务顾问。

背景:

价值观 View 和 Model :一个 view 恐怕和八个 model 相关,1个 model
也可能和几个 view 相关,项目复杂后代码耦合度太高,难以保险。

redux 应运而生,redux 中挑雍州概念reducer,将拥有复杂的 state
集中管理,view 层用户的操作不能一向改动 state从而将view 和 data
解耦。redux 把古板MVC中的 controller 拆分为action和reducer

行使的三口径

  • 单纯性数据源

全总应用的state,存款和储蓄在唯一多个object中,同时也唯有二个store用于存款和储蓄那么些object.

  • 状态是只读的

唯一能更改state的章程,正是触发action操作。action是用来讲述正在爆发的轩然大波的3个指标

  • 经过纯函数修改State

纯函数的难题,也是出自于函数式编制程序思想,大家在中学时学的函数正是纯函数,对于同2个输入,必然有一样的输出。那就确定保证了数额的可控性,那里的纯函数便是reducer

5.React+Redux

redux将兼具组件分为UI组件和容器组件。

UI 组件有以下几天性状。

  1. 只承担 UI 的呈现,不分包别的工作逻辑
  2. 并未动静(即不利用this.state这一个变量)
  3. 享有数据都由参数(this.props)提供
  4. 不行使其余 Redux 的 API

UI 组件又叫做”纯组件”,即它纯函数一样,纯粹由参数决定它的值。

 

容器组件的特征恰恰相反。

  1.负责管理数据和事务逻辑,不负担 UI 的变现

  2.暗含内部景色

  3.使用 Redux 的 API

React-Redux 提供connect方法,用于从 UI
组件生成容器组件。connect的趣味,即是将那二种组件连起来。

import { connect } from ‘react-redux’

const VisibleTodoList = connect(

  mapStateToProps,

  mapDispatchToProps

)(TodoList)

地点代码中,connect方法接受多个参数:mapStateToProps和mapDispatchToProps。

它们定义了 UI 组件的业务逻辑。前者肩负输入逻辑,即将state映射到 UI
组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成
Action。

 

1. 思路

率先,我们回看一下 Redux 的基本思路

澳门葡京 2

redux flow

当用户与界面交互时,交互事件的回调函数会触发 ActionCreators
,它是四个函数,重回二个目的,该目的指导了用户的动作类型和修改 Model
必需的数量,那些目的也被大家誉为 Action 。

以 TodoList 为例,添加二个 Todo 项的 ActionCreator
函数如下所示(借使不熟悉 ES6
箭头函数请移步这里):

const addTodo = text => ({
    type: 'ADD_TODO',
    text
});

在上例中,addTodo 正是 ActionCreator 函数,该函数重临的目的就是 Action

内部 type 为 Redux 中约定的必填属性,它的效率稍后大家会讲到。而 text
则是进行 “添加 Todo 项“ 那个动作必需的数目。

自然,不一样动作所需求的数额也大有不同,如 “删除Todo” 动作,我们就供给了然todo 项的 id,“拉取已有的Todo项” 动作,大家就须求传入一个数组( todos
)。形如 text 、 id 、 todos 那类属性,大家习惯称呼其为 “ payload ” 。

今后,我们获取了二个 “绘身绘色” 的动作。它丰硕简洁,但担任 Model 的
store 暂且还不领会哪些感知这么些动作从而改变数据结构。

为了处理这么些关键难题,Reducer
巧然登场。它还是是几个函数,而且是尚无副效能的纯函数。它只接到三个参数:state
和 action ,重返三个 newState 。

正确,state 正是您在 React 中熟知的 state,但基于 Redux
三原则
之一的 “单一数据源” 原则,Reducer 幽幽地说:“你的 state 被小编承包了。”

于是,单一数据源规则执行起来,是鲜明用 React 的顶层容器组件( Container
Components
)的
state 来囤积单一对象树,同时提交 Redux store 来治本。

那边分别一下 state 和 Redux store:state 是实在储存数据的指标树,而
Redux store 是和谐 Reducer、state、Action 三者的调度核心。

而这么前所说,Reducer
此时手握四个至关主要音讯:旧的数据结构(state),还有改变它所急需的音信(action),然后聪明的 Reducer 算盘一敲,就能交到多少个新的 state
,从而革新数据,响应用户。下边依旧拿 TodoList
举例(不熟悉 “…” ES6 rest/spread
语法请先看这里):

//整个 todoList 最原始的数据结构。
const initState = {
    filter: 'ALL',
    todos: []
};
//Reducer 识别动作类型为 ADD_TODO 时的处理函数
const handleAddTodo = (state, text) => {
    const todos = state.todos;
    const newState = {...state, {
        todos: [
            ...todos, {
            text,
            completed: false
        }]
    }};
    return newState;
};
//Reducer 函数
const todoList = (state = initState, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return handleAddTodo(state, action.text);
        default:
            return state;
    }
}

当接受到1个 action 时,Reducer 从 action.type 识别出该动作是要添加 Todo
项,然后路由到相应的处理方案,接着遵照 action.text 完毕了处理,重临贰个newState 。进度里面,整个应用的 state 就从 state => newState
完结了动静的更动。

以此进度让大家很自然地联想到去银行存取钱的阅历,显明我们应该告诉柜台操作员要存取钱,而不是登高望远着银行的金库自言自语。

Reducer 为大家梳理了颇具变更 state 的法门,那么 Redux store
从无到有,从有到变都应该与 Reducer 强关联。

由此,Redux 提供了 createStore 函数,他的首先个参数正是 Reducer
,用以形容 state 的变动格局。第一个是可选参数 initialState
,在此以前大家精通,那一个 initialState 参数也能够传给 Reducer
函数。放在那里做可选参数的因由是为同构应用提供方便人民群众。

//store.js
import reducer from './reducer';
import { createStore } from 'redux';
export default createStore(reducer);

createStore 函数最终回到四个对象,也正是我们所说的 store
对象。主要提供多少个形式:getState、dispatch 和 subscribe。 当中getState() 获得 state 对象树。dispatch(actionCreator) 用以实践
actionCreators,建起从 action 到 store 的大桥。

偏偏完结景况的更动可不算完,咱们还得让视图层跟上 store 的生成,于是
Redux 还为 store 设计了 subscribe 方法。顾名思义,当 store
更新时,store.subscribe() 的回调函数会更新视图层,以达到 “订阅” 的机能。

在 React 中,有 react-redux 那样的桥接库为 Redux
的融入铺平道路。所以,我们只需为顶层容器组件外包一层 Provider
组件、再合作 connect 函数处理从 store 变更到 view 渲染的有关进度。

import store from './store';
import {connect, Provider} from 'react-redux';
import React from 'react';
import ReactDOM from 'react-dom';
import Page from '../components/page'; //业务组件
// 把 state 映射到 Container 组件的 props 上的函数
const mapStateToProps = state => { 
    return {
        ...state
    }
}
const Container = connect(mapStateToProps)(Page); //顶层容器组件
ReactDOM.render(
    <Provider store={store}>
        <Container />
    </Provider>,
    document.getElementById("root")
);

而顶层容器组件往下的子组件只需依赖 props 就能一层层地拿到 store
数据结构的多寡了。就像这么:

澳门葡京 3

store props

时至前几天,大家走了三回完整的数据流。可是,在实质上项目中,我们面临的须要更是复杂,与此同时,redux
和 react
又是有所强有力扩张性的库,接下去大家将结合以上的主心骨思路,谈谈大家在实际支付中会际遇的一对细节问题。

统一筹划思想:

(1)Web 应用是2个状态机,视图与气象是各样对应的。

(2)全数的状态,保存在贰个指标里面。

Redux 让动用的动静变化变得可预测。假若想更改使用的景况,就亟须
dispatch 对应的
action。而不可能平素改动使用的景况,因为保存那几个情况的地点(称为
store)只有 get方法(getState) 而没有 set方法

只要Redux
订阅(subscribe)相应框架(例如React)内部方法,就足以使用该利用框架保障数据流动的一致性。

redux状态管理的流程及连锁概念

澳门葡京 4

image

  • store

Store
正是保存数据的地点,保存着本程序有所的redux管理的数额,你能够把它作为一个器皿。整个应用只可以有1个Store。(一个store是贰个目的, reducer会改变store中的有些值)

Redux 提供createStore那几个函数,用来生成 Store。

import { createStore } from 'redux';
const store = createStore(fn);

上面代码中,createStore函数接受另五个函数作为参数,再次回到新生成的 Store
对象。这几个fn就是reducer纯函数,平日大家在支付中也会使用中间件,来优化架构,比如最常用的异步操作插件,redux-thunk,如若合作redux-thunk来创制store的话,代码示例:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootReudcer';

let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
let store = createStoreWithMiddleware(rootReducer);

redux-thunk的源码及其简单,如下:

// 判断action是否是函数,如果是,继续执行递归式的操作。所以在redux中的异步,只能出现在action中,而且还需要有中间件的支持。
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

同步action与异步action最大的不一样是:

协助进行只回去八个普通action对象。而异步操作中途会重临二个promise函数。当然在promise函数处理完结后也会再次来到1个普通action对象。thunk中间件便是判定固然回到的是函数,则不传导给reducer,直到检查和测试到是普通action对象,才交由reducer处理。


Store 有以下职分:

  • 提供 getState() 方法获得 state;
  • 提供 dispatch(action) 方法改进 state;
  • 经过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 再次来到的函数注销监听器。

相似情况下,大家只必要getState()和dispatch()方法即可,即能够解决绝大部分题目。

大家得以自定义中间件

例如大家自定义二个得以打字与印刷出当下的触发的action以及出发后的state变化的中间件,代码改动如下:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootReudcer';

let logger = store => next => action => {
    if(typeof action === 'function') {
        console.log('dispatching a function')
    }else{
        console.log('dispatching', action);
    }

    let result = next(action);
    // getState() 可以拿到store的状态, 也就是所有数据
    console.log('next state', store.getState());
    return result;
}

let middleWares = {
    logger, 
    thunk
}
// ... 扩展运算符
let createStoreWithMiddleware = applyMiddleware(...middleWares)(createStore);

let store = createStoreWithMiddleware(rootReducer);

填补:大家自定义的中间件,也有照应的开源插件,redux-logger,人家的更决定。

如果,app中关系到登录难点的时候,可以利用redux-persist其三方插件,这几个第2方插件来将store对象存储到本地,以及从本地恢复生机数据到store中,比如说保存登录音讯,下次开拓应用能够直接跳过登录界面,因为我们当下的施用属于内嵌程序,不登陆的话也进不来,所以并非它。

  • Action

Action 是三个指标,描述了接触的动作,仅此而已。大家约定,action
内必须选用三个字符串类型的 type
字段来代表即将执行的动作。经常它长一下以此样子:

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

除外 type 字段外,action
对象的结构完全由你协调支配,来看三个复杂点的:

{
    type: 'SET_SCREEN_LAST_REFRESH_TIME',
    screenId,
    lastRefreshTime,
    objectId
}

万般我们会添加二个新的模块文件来储存那个actions
types,比如我们新建二个actionTypes.js来保存:

//主页actions
export const FETCH_HOME_LIST = 'FETCH_HOME_LIST';
export const RECEIVE_HOME_LIST = 'RECEIVE_HOME_LIST';
//分类页actions
export const FETCH_CLASS_LIST = 'FETCH_CLASS_LIST';
export const RECEIVE_CLASS_LIST = 'RECEIVE_CLASS_LIST';
//分类详细页actions
export const FETCH_CLASSDITAL_LIST = 'FETCH_CLASSDITAL_LIST';
export const RECEIVE_CLASSDITAL_LIST = 'RECEIVE_CLASSDITAL_LIST';
export const RESET_CLASSDITAL_STATE = 'RESET_CLASSDITAL_STATE'; 
// 设置页actions
export const CHANGE_SET_SWITCH = 'CHANGE_SET_SWITCH';
export const CHANGE_SET_TEXT = 'CHANGE_SET_TEXT';
// 用户信息
export const USER_INFO = 'USER_INFO';

引用的时候,能够:

import * as types from './actionTypes';
  • Action 创造函数(Action Creator)

Action 创设函数 正是生成 action 的点子。“action” 和 “action 创设函数”
那三个概念很简单混在一块,使用时最好注意区分。在 Redux 中的 action
创制函数只是不难的归来一个 action。

import * as types from './actionTypes';
// 设置详情页内容文字主题
let changeText = (theme) => {
    return {
        type: types.CHANGE_SET_TEXT,
        theme
    }
}   

// 函数changeText就是一个简单的action creator。

完整的action文件(setAction.js)

import * as types from './actionTypes';

let setTitle = (value) => {
    return (dispatch, getState) => {
        dispatch(changeValue(value))
    }
}

let setText = (text) => {
    return dispatch => {
        dispatch(changeText(text))
    }
}

// 修改标题颜色主题
let changeValue = (titleTheme) => {
    return {
        type: types.CHANGE_SET_SWITCH,
        titleTheme
    }
}

// 设置详情页内容文字颜色
let changeText = (textColor) => {
    return {
        type: types.CHANGE_SET_TEXT,
        textColor
    }
}

export {
    setText,
    setTitle
};

能够看出上述setTitle、setText函数,重临的并不是三个action对象,而是回到了3个函数,那一个默许redux是没办法处理的,那就需求运用中间件处理了,redux-thunk中间件用于拍卖回来函数的函数,下面也介绍了redux-thunk的选拔基本方式。

  • Reducer

Store 收到 Action 以后,必须交给3个新的 State,那样 View
才会发生变化。那种 State 的测算进度就称为 Reducer。
Reducer 是一个函数,它承受 Action 和当下 State 作为参数,再次来到八个新的
State。

函数签名:

(previousState, action) => newState

Reducer必须保证相对十足,永远不要在 reducer 里做那个操作:

  • 修改传入参数;
  • 实施有副成效的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random();

完整的Reducer方法,(setReducer.js):

import * as types from '../actions/actionTypes';

const initialState = {
    titleTheme: false,
    textColor: false
}
// 这里一个技巧是使用 ES6 参数默认值语法 来精简代码
let setReducer = (state = initialState, action) => {

    switch(action.type){
        case types.CHANGE_SET_SWITCH:
            return Object.assign({}, state, {
                titleTheme: action.titleTheme,
            })

        case types.CHANGE_SET_TEXT:
            return Object.assign({}, state, {
                textColor: action.textColor
            })

        default:
            return state;
    }
}

export default setReducer

注意:

  • 毫无改动 state。 使用 Object.assign() 新建了二个副本。不能够那样使用
    Object.assign(state, {
    titleTheme: action.titleTheme,
    }),因为它会改变第3个参数的值。你不能够不把第三个参数设置为空对象。你也足以拉开对ES7提案对象进行运算符的支撑,
    从而使用 { …state, …newState } 达到平等的指标。
  • 在 default 情形下重返旧的 state。蒙受未知的 action
    时,一定要回到旧的 state

有关拆分Reducer

那里只是比喻了1个简单的效率的reducer,如若有分化的作用,供给规划很多reducer方法,注意各个reducer 只负责管理全局 state 中它负责的一片段。每种 reducer 的 state
参数都差别,分别对应它管理的那有个别 state 数据。

譬如说大家那几个类别的reducer文件结构:

澳门葡京 5

image.png

里面rootReducer.js就是3个根reducer文件,使用了Redux 的
combineReducers() 工具类来展开包装整合。

/**
 * rootReducer.js
 * 根reducer
 */
import { combineReducers } from 'redux';
import Home from './homeReducer';
import Class from './classReducer';
import ClassDetial from './classDetialReducer';
import setReducer from './setReducer';
import userReducer from './userReducer';

export default rootReducer = combineReducers({
    Home,
    Class,
    ClassDetial,
    setReducer,
    userReducer,
})

如此那般依据那一个根reducer,可以生成store,请看上文store的开创进度。

6. react+redux 的 simple 示例

原理图:

澳门葡京 6

<div id="container"></div>

<script type="text/babel">

  //工厂Action

    var addTodoAction = function(text){

      return {

        type: 'add_todo',

        text: text

      }

    };


    var todoReducer = function(state,action){

      if(typeof state === 'undefined') return [];

      switch(action.type){

        case 'add_todo':

        return state.slice(0).concat({

          text: action.text,

          completed:false

        });

        default:

        return state;

      }

    };


    var store = redux.createStore(todoReducer);


    var App = React.createClass({

      //获取初始状态

      getInitialState: function(){

        return {

          items: store.getState()

        };

      },

      componentDidMount: function(){

        var unsubscribe = store.subscribe(this.onChange);

      },

      onChange: function(){

        this.setState({

          items: store.getState()

        });

      },

      handleAdd: function(){

        var input = ReactDOM.findDOMNode(this.refs.todo);

        var value = input.value.trim();

        if(value){

          store.dispatch(addTodoAction(value));

        }

        input.value = '';

      },

      render: function(){

        return(

          <div>

          <input ref="todo" type="text" placeholder="input todo type"/>

          <button onClick ={this.handleAdd}>Add</button>

          <ul>

          {

            this.state.items.map(function(item){

              return <li>{item.text}</li>

            })

          }

          </ul>

          </div>

        );

      }

    });

    ReactDOM.render(

      <App />,

      document.getElementById('container')

    )

  </script>

 

2. 细节

Action Creator:

只好透过dispatch action来改变state,那是唯一的格局

action日常的样式是: action = { type: ‘ … ‘, data: data }
action一定是有二个type属性的目的

在dispatch任何三个 action
时将有所订阅的监听器都实施,文告它们有state的更新澳门葡京 7

redux如何与组件结合

如上部分介绍了Redux 涉及的基本概念,上边介绍与组件交互的工作流程。

梳理一下Redux的办事流程:

澳门葡京 8

image

1.先是,用户发生 Action。

store.dispatch(action);

2.Store 电动调用 Reducer,并且传入多个参数:当前 State 和接到的 Action。
Reducer 会再次回到新的 State 。

let nextState = todoApp(previousState, action);

3.state假如有转变,Store就会调用监听函数,组件能够感知state的转移,更新View。

let newState = store.getState();
component.setState(newState);

具体示例1:

澳门葡京 9

fsdf.gif

安装页面有个switch按钮,能够全局设置标题栏的宗旨。

采取目录

明晰的思路须辅以分工明显的文本模块,才能让我们的采纳达到更佳的履行效果,同时,统一的构造也惠及脚手架生成模板,升高花费效能。

以下的目录结构为公司伙伴数11回商讨和改革而来(限于篇幅,这里只关怀 React
应用的目录。):

appPage
├── components
│   └── wrapper
│       ├── component-a
│       │   ├── images
│       │   ├── index.js
│       │   └── index.scss
│       ├── component-a-a
│       ├── component-a-b
│       ├── component-b
│       └── component-b-a
├── react
│   ├── reducer
│   │   ├── index.js
│   │   ├── reducerA.js
│   │   └── reducerB.js
│   ├── action.js
│   ├── actionTypes.js
│   ├── bindActions.js
│   ├── container.js
│   ├── model.js
│   ├── param.js
│   └── store.js  
└── app.js

Store:

Redux中只有八个store,store中保留应用的保有情形;判断要求转移的景况分配给reducer去处理。

能够有多少个reducer,每一种reducer去负责一小部分效益,最后将三个reducer合并为三个根reducer

作用:

  • 维持state树;
  • 提供 getState() 方法取得 state;
  • 提供 dispatch(action) 方法立异 state;
  • 通过 subscribe(listener) 注册监听器。
代码拆分:

1.设置按钮所在组件:

// SetContainer.js

import React from 'react';
import {connect} from 'react-redux';
import SetPage from '../pages/SetPage';

class SetContainer extends React.Component {
    render() {
        return (
            <SetPage {...this.props} />
        )
    }
}

export default connect((state) => {

    const { setReducer } = state;
    return {
        setReducer
    }

})(SetContainer);

那是容器组件,将SetPage组件与redux结合起来,当中最主要的点子是connect,那几个示例中是将setReducer作为质量传给SetPage组件,关于connect的详解,请移步到connect()。

2.SetPage组件

import React, {
    Component
} from 'react';
import {
    StyleSheet,
    Text,
    Image,
    ListView,
    TouchableOpacity,
    View,
    Switch,
    InteractionManager,
} from 'react-native';

import Common from '../common/common';
import Loading from '../common/Loading';
import HeaderView from '../common/HeaderView';

import {setText,setTitle} from '../actions/setAction';

export default class SetPage extends Component {
    constructor(props){
        super(props);
        this.state = {
            switchValue: false,
            textValue: false
        }

        this.onValueChange = this.onValueChange.bind(this);
        this.onTextChange = this.onTextChange.bind(this);
    }

    componentDidMount() {
        // console.log(this.props)
    }

    onValueChange(bool) {
        const { dispatch } = this.props;
        this.setState({
            switchValue: bool
        })
        dispatch(setTitle(bool));
    }

    onTextChange(bool) {
        const { dispatch } = this.props;

        this.setState({
            textValue: bool
        });

        dispatch(setText(bool));
    }

    render() {
        return (
            <View>
                <HeaderView
                  titleView= {'设置'}
                  />

                <View>
                    <View style={styles.itemContainer}>
                        <Text style={{fontSize: 16}}>全局设置标题主题</Text>
                        <Switch 
                            onValueChange={this.onValueChange}
                            value={this.state.switchValue}
                        />
                    </View>

                    <View style={styles.itemContainer}>
                        <Text style={{fontSize: 16}}>设置详情页文字主题</Text>
                        <Switch 
                            onValueChange={this.onTextChange}
                            value={this.state.textValue}
                        />
                    </View>
                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    itemContainer:{
        paddingLeft: 20,
        paddingRight: 20,
        height: 40,
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center'
    }
})

能够只看全局设置标题主题那一个形式,设置详情页文字颜色和她同理。那里可以清晰的看到,用户切换焦点switch按钮的时候,触发的办法:

dispatch(setTitle(bool));

3.大家查阅一下setTitle这些action的源码:

// setAction.js
import * as types from './actionTypes';

let setTitle = (value) => {
    return (dispatch, getState) => {
        dispatch(changeValue(value))
    }
}

let setText = (text) => {
    return dispatch => {
        dispatch(changeText(text))
    }
}

// 修改标题主题
let changeValue = (titleTheme) => {
    return {
        type: types.CHANGE_SET_SWITCH,
        // 这里将titleTheme状态返回
        titleTheme
    }
}

// 设置详情页内容文字主题
let changeText = (textColor) => {
    return {
        type: types.CHANGE_SET_TEXT,
        textColor
    }
}

export {
    setText,
    setTitle
};

澳门葡京 ,4.action只是承担发送事件,并不会回去三个新的state供页面组件调用,它是在reducer中回到的:

// setReducer.js

import * as types from '../actions/actionTypes';

const initialState = {
    titleTheme: false,
    textColor: false
}

let setReducer = (state = initialState, action) => {

    switch(action.type){
        case types.CHANGE_SET_SWITCH:
            return Object.assign({}, state, {
                titleTheme: action.titleTheme,
            })

        case types.CHANGE_SET_TEXT:
            return Object.assign({}, state, {
                textColor: action.textColor
            })

        default:
            return state;
    }
}

export default setReducer

最简易的reducer,就是依据开首值和action对象,重临一个新的state,提须要store,那样,页面里能够从store中获得到这几个全局的state,用于立异组件。

我们只是写了怎么发送action和接收action发出newState的,下边来看这么些标题组件是什么和redux结合的。

5.HeaderView组件

/**
 * Created by ljunb on 16/5/8.
 * 导航栏标题
 */
import React from 'react';
import {
    StyleSheet,
    View,
    Text,
    Image,
    TouchableOpacity,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import Common from '../common/common';
import {connect} from 'react-redux';

class HeaderView extends React.Component {

    constructor(props){
        super(props);

        this.state = {

        }
    }

    render() {
        // 这里,在这里
        const { titleTheme } = this.props.setReducer;
        let NavigationBar = [];

        // 左边图片按钮
        if (this.props.leftIcon != undefined) {
            NavigationBar.push(
                <TouchableOpacity
                    key={'leftIcon'}
                    activeOpacity={0.75}
                    style={styles.leftIcon}
                    onPress={this.props.leftIconAction}
                    >
                    <Icon color="black" size={30} name={this.props.leftIcon}/>
                </TouchableOpacity>
            )
        }

        // 标题
        if (this.props.title != undefined) {
            NavigationBar.push(
                <Text key={'title'} style={styles.title}>{this.props.title}</Text>
            )
        }

        // 自定义标题View
        if (this.props.titleView != undefined) {
            let Component = this.props.titleView;

            NavigationBar.push(
                <Text key={'titleView'} style={[styles.titleView, {color: titleTheme ? '#FFF' : '#000'}]}>{this.props.titleView}</Text>
            )
        }


        return (
            <View style={[styles.navigationBarContainer, {backgroundColor: titleTheme ? 'blue' : '#fff'}]}>
                {NavigationBar}
            </View>
        )
    }
}

const styles = StyleSheet.create({

    navigationBarContainer: {
        marginTop: 20,
        flexDirection: 'row',
        height: 44,
        justifyContent: 'center',
        alignItems: 'center',
        borderBottomColor: '#ccc',
        borderBottomWidth: 0.5,
        backgroundColor: 'white'
    },

    title: {
        fontSize: 15,
        marginLeft: 15,
    },
    titleView: {
        fontSize: 15,
    },
    leftIcon: {
       left: -Common.window.width/2+40,
    },
})


export default connect((state) => {

    const { setReducer } = state;
    return {
        setReducer
    }

})(HeaderView);

以此组件同样选择connect方法绑定了redux,变成了容器组件(container
component)。

connect真的很要紧,请详细查看官方文书档案,上边有链接。

任何不相干的剧情忽略,大旨代码是:

// 拿到全局的state 当有变化的时候,会马上修改
const { titleTheme } = this.props.setReducer;

切实示例2:

澳门葡京 10

image.png

选用redux来请求数据、下拉刷新、上拉加载更加多。

1.首先,封装action。

import * as types from './actionTypes';
import Util from '../common/utils'; 
// action创建函数,此处是渲染首页的各种图片
export let home = (tag, offest, limit, isLoadMore, isRefreshing, isLoading) => {
    let URL = 'http://api.huaban.com/fm/wallpaper/pins?limit=';
    if (limit) URL += limit;
    offest ? URL += '&max=' + offest : URL += '&max=';
    tag ? URL += '&tag=' + encode_utf8(tag) : URL += '&tag='

    return dispatch => {
        // 分发事件  不修改状态   action是 store 数据的唯一来源。
        dispatch(feachHomeList(isLoadMore, isRefreshing, isLoading));
        return Util.get(URL, (response) => {
            // 请求数据成功后
            dispatch(receiveHomeList(response.pins))
        }, (error) => {
            // 请求失败
            dispatch(receiveHomeList([]));
        });

    }

}

function encode_utf8(s) {
    return encodeURIComponent(s);
}

// 我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。
let feachHomeList = (isLoadMore, isRefreshing, isLoading) => {
    return {
        type: types.FETCH_HOME_LIST,
        isLoadMore: isLoadMore,
        isRefreshing: isRefreshing,
        isLoading: isLoading,
    }
}

let receiveHomeList = (homeList) => {
    return {
        type: types.RECEIVE_HOME_LIST,
        homeList: homeList,
    }
}
  • feachHomeList表示正在呼吁数据的动作;
  • receiveHomeList代表请求数据完后的动作;
  • dispatch(feachHomeList(isLoadMore, isRefreshing,
    isLoading));表示分发请求数据的动作;

2.封装reducer函数

import * as types from '../actions/actionTypes';
// 设置初始状态
const initialState = {
    HomeList: [],
    isLoading: true,
    isLoadMore: false,
    isRefreshing: false,
};

let homeReducer = (state = initialState, action) => {

    switch (action.type) {
        case types.FETCH_HOME_LIST:
            return Object.assign({}, state, {
                isLoadMore: action.isLoadMore,
                isRefreshing: action.isRefreshing,
                isLoading: action.isLoading
            })

        case types.RECEIVE_HOME_LIST:
            // 如果请求成功后,返回状态给组件更新数据
            return Object.assign({}, state, {
            // 如果是正在加载更多,那么合并数据
                HomeList: state.isLoadMore ? state.HomeList.concat(action.homeList) : action.homeList,
                isRefreshing: false,
                isLoading: false,
            })

        case types.RESET_STATE: // 清除数据
            return Object.assign({},state,{
                HomeList:[],
                isLoading:true,
            })
        default:
            return state;
    }
}

export default homeReducer;
  • 那边并没有拍卖没有越来越多多少的事态。

3.容器组件

import React from 'react';
import {connect} from 'react-redux';
import Home from '../pages/Home';

class HomeContainer extends React.Component {
    render() {
        return (
            <Home {...this.props} />
        )
    }
}

export default connect((state) => {
    const { Home } = state;
    return {
        Home
    }
})(HomeContainer);
  • 那边根本是利用connect函数将Home
    state绑定到Home组件中,并作为它的props;

4.UI组件

  • 零件挂载请求数据

...
let limit = 21;
let offest = '';
let tag = '';
let isLoadMore = false;
let isRefreshing = false;
let isLoading = true;
...
componentDidMount() {
    InteractionManager.runAfterInteractions(() => {
      const {dispatch} = this.props;
      // 触发action 请求数据
      dispatch(home(tag, offest, limit, isLoadMore, isRefreshing, isLoading));
    })
}
...
  • 下拉刷新

// 下拉刷新
  _onRefresh() {
    if (isLoadMore) {
      const {dispatch, Home} = this.props;
      isLoadMore = false;
      isRefreshing = true;
      dispatch(home('', '', limit, isLoadMore, isRefreshing, isLoading));
    }
  }
  • 上拉加载更加多

// 上拉加载
  _onEndReach() {

    InteractionManager.runAfterInteractions(() => {
      const {dispatch, Home} = this.props;
      let homeList = Home.HomeList;
      isLoadMore = true;
      isLoading = false;
      isRefreshing = false;
      offest = homeList[homeList.length - 1].seq
      dispatch(home(tag, offest, limit, isLoadMore, isRefreshing, isLoading));
    })

  }
  • render方法

render() {
    // 这里可以拿到Home状态
    const { Home,rowDate } = this.props;
     tag = rowDate;

    let homeList = Home.HomeList;
    let titleName = '最新';
    return (
      <View>
        <HeaderView
          titleView= {titleName}
          leftIcon={tag ? 'angle-left' : null}
          />
        {Home.isLoading ? <Loading /> :
          <ListView
            dataSource={this.state.dataSource.cloneWithRows(homeList) }
            renderRow={this._renderRow}
            contentContainerStyle={styles.list}
            enableEmptySections={true}
            initialListSize= {10}
            onScroll={this._onScroll}
            onEndReached={this._onEndReach.bind(this) }
            onEndReachedThreshold={10}
            renderFooter={this._renderFooter.bind(this) }
            style={styles.listView}
            refreshControl={
              <RefreshControl
                refreshing={Home.isRefreshing}
                onRefresh={this._onRefresh.bind(this) }
                title="正在加载中……"
                color="#ccc"
                />
            }
            />
        }
      </View>

    );

  }

由来,二个归纳的Reducer程序完毕了,大家有点计算一下:

  • 整整应用唯有三个store,用来保存全数的场合,视图不供给本身维护状态。
  • 视图通过connect函数绑定到store,当store状态变化后,store会布告视图刷新。
  • 接触四个action之后,会通过只怕N个reducers处理,最终根reducer会将有着reducers处理今后的情形合并,然后交到store,store再布告视图刷新。

正文的源码地址:
案例Demo

进口文件 app.js 与顶层组件 react/container.js

那块大家大多保持和前边思路上的一样,用 react-redux 桥接库提供的
Provider 与函数 connect 达成 Redux store 到 React state 的更动。

密切的你会在 Provider
的源码中发觉,它最后回到的依然子组件(本例中正是顶层容器组件
“Container“ )。星星依然13分简单,Container 还是这几个Container,只是多了七个 Redux store 对象。

而 Contaier 作为 业务组件 Wrapper 的
高阶组件
,负责把 Provider 赋予它的 store 通过 store.getState()
获取数据,转而赋值给 state 。然后又依照大家定义的 mapStateToProps
函数按一定的构造将 state 对接收 props 上。 mapStateToProps
函数大家稍后详说。如下所见,这一步关键是 connect 函数干的劳动。

//入口文件:app.js
import store from './react/store';
import Container from './react/container'; 
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
ReactDOM.render(
    <Provider store={store}>
        <Container />
    </Provider>,
    document.getElementById("root")
);

//顶层容器组件:react/container.js
import mapStateToProps from './param';
import {connect} from 'react-redux';
import Wrapper from '../components/wrapper';
export default connect(mapStateToProps)(Wrapper);

Reducer:

store想要知道二个action触发后怎么转移状态,会履行reducer。reducer是纯函数,根reducer拆分为八个小reducer
,每一个reducer去处理与自小编有关的state更新

注:不直接修改总体应用的情状树,而是将境况树的每一某些举办拷贝并修改拷贝后的变量,然后将这一个片段重新组合成一颗新的事态树。采纳了数码不可变性(immutable),易于追踪数据变动。其它,还是能够扩展例如撤废操作等功能。

事情组件 component/Wrapper.js 与 mapStateToProps

那四个模块是整整应用很重点的事人体模型块。作为贰个扑朔迷离应用,将 state
上的数量和 actionCreator
合理地分发到各种业务组件中,同时要简单维护,是付出的重中之重。

第2,大家设计 mapStateToProps 函数。必要谨记一点:得到的参数是 connect
函数交给我们的根 state,再次回到的靶子是终极 this.props 的构造。

和 Redux
官方示例不等的是,大家为了可读性,将分发
action 的函数也包含进那个结构中。那也是得益于 bindActions
模块,稍后我们会讲到。

//mapStateToProps:react/param.js
import bindActions from './bindActions';
const mapStateToProps = state => {
    let {demoAPP} = state; // demoAPP 也是 reducer 中的同名函数
    // 分发 action 的函数
    let {initDemoAPP, setDemoAPP} = bindActions;
    // 分发 state 上的数据
    let {isLoading, dataForA, dataForB} = demoAPP;
    let {dataForAA1, dataForAA2, dataForAB} = dataForA; 
    // 返回的对象即为 Wrapper 组件的 this.props
    return {
        initDemoAPP, // Wrapper 组件需要发送一个 action 初始化页面数据
        isLoading, //  Wrapper 组件需要 isLoading 用于展示
        paramsComponentA: {
            dataForA, // 组件 A 需要 dataForA 用于展示
            paramsComponentAA: {
                setDemoAPP, // 组件 AA 需要发送一个 action 修改数据
                dataForAA1,
                dataForAA2
            },
            paramsComponentAB: {
                dataForAB
            }
        },
        paramsComponentB: {
            dataForB,
            paramsComponentBA: {}
        }
    }
}
export default mapStateToProps;

诸如此类,大家以此函数就准备好实施它散发数据和组件行为的天职了。那么,它又该怎么
“服役” 呢?

敏感的您势必察觉到刚刚我们安排的构造中,以 “ params ”
起始的性情既没起到给组件展现数据的功能,又从未为组件发送 action
的功力。它们就是大家分发以上二种功用属性的要紧。

咱俩先来探望业务组件 Wrapper :

//业务组件组件:components/wrapper.js
import React, { Component } from 'react';
import ComponentA from '../component-a';
import ComponentB from '../component-b';
export default class Example extends Component {
    constructor(props) {
        super(props);
    }
    componentDidMount() {
        this.props.initDemoAPP(); //拉取业务数据
    }
    render() {
        let {paramsComponentA, paramsComponentB, isLoading} = this.props;

        if (isLoading) {
            return (App is loading ...);
        }
        return (
            <div>
                {/* 为组件分发参数 */}
                <ComponentA {...paramsComponentA}/>
                <ComponentB {...paramsComponentB}/>
            </div>
        );
    }
}

后天,param
属性们为大家来得了它扮演的剧中人物:在组件中实际分发数据和章程的特快专递小哥。那样,尽管类型越变越大,组件嵌套越来越多,我们也能在
param.js
模块中,清晰地看看大家的机件结构。必要变动的时候,我们也能便捷地稳住和改动,而不用对着堆积如山的机件模块梳理父子关系。

相信您应该能猜到剩下的子组件们怎么取到数据了,那里限于篇幅就不贴出它们的代码了。

Views:

容器型组件 Container component 和显示型组件 Presentational component)

建议是只在最顶层组件(如路由操作)里应用
Redux。其他内部零件仅仅是展示性的,全数数据都通过 props 传入。

容器组件 展示组件
Location 最顶层,路由处理 中间和子组件
Aware of Redux
读取数据 从 Redux 获取 state 从 props 获取数据
修改数据 向 Redux 派发 actions 从 props 调用回调函数

Action 模块: react/action.js、react/actionType.js 和 react/bindActions.js

在前头的介绍中,大家提到:二个 ActionCreator 长那样:

const addTodo = text => ({
    type: 'ADD_TODO',
    text
});

而在 Redux 中,真正让其散发二个 action ,并让 store 响应该
action,依靠的是 dispatch 方法,即:

store.dispatch(addTodo('new todo item'));

交互动作一多,就会成为:

store.dispatch(addTodo('new todo item1'));
store.dispatch(deleteTodo(0));
store.dispatch(compeleteTodo(1));
store.dispatch(clearTodos());
//...

而简单想到:抽象出三个公用函数来散发 action
(那里大致写一下自家的思绪,简化格局并不唯一)

const {dispatch} = store;
const dispatcher = (actionCreators, dispatch) => {
    // ...校验参数
    let bounds = {};
    let keys = Object.keys(actionCreators);
    for (let key of keys) {
        bounds[key] = (...rest) => {
            dispatch(actionCreators[key].apply(null, rest));
        }
    }
    return bounds;
}
//简化后的使用方式
const disp = dispatcher({
    addTodo,
    deleteTodo,
    compeleteTodo
    //...
}, dispatch);
disp.addTodo('new todo item1');
disp.deleteTodo(0);
//...

而精心的 Redux 已经为大家提供了那么些法子 ——
bindActionCreator

因此,我们的 bindActions.js 模块就借出了 bindActionCreator 来简化 action
的分发:

// react/bindActions.js
import store from './store.js';
import {bindActionCreators} from 'redux';
import * as actionCreators from './action';
let {dispatch} = store;
export default bindActionCreators({ ...actionCreators}, dispatch);

简单想象,action 模块里正是二个个 actionCreator :

// react/action.js
import * as types from '/actionType.js';
export const setDemoAPP = payload => ({
    type: types.SET_DEMO_APP,
    payload
});
// 其他 actionCreators ...

为了更好地同盟,大家单独为 action 的 type 划分了三个模块 ——
actionTypes.js 里面看起来会比较粗俗:

// react/actionTypes.js
export const SET_DEMO_APP = "SET_DEMO_APP";
// 其他 types ...

Middleware:

中间件是在action被提倡之后,到达reducer此前对store.dispatch方法实行扩展,增强其意义。

比如说常用的异步action => redux-thunk、redux-promise、redux-logger等

react/reducers/ 和 react/store.js

日前我们说到,reducer 的机能就是分别 action type 然后更新 state
,这里不再赘言。可上手实际项目标时候,你会意识 action
类型和对应处理格局多起来会让单个 reducer 飞快庞大。

为此,我们就得想方设法将其按工作逻辑拆分,以防难以维护。可是什么把拆分后的
Reducer 组合起来呢 Redux 再度为大家提供便捷 —— combineReducers 。

唯有纯粹 Reducer 时,想必代码结构你也领略:

import * as actionTypes from '../actionTypes';
let initState = {
    isLoading: true
};
// 对应 state.demoAPP
const demoAPP = (state = initState, action) => {
    switch (action.type) {
        case actionTypes.SET_DEMO_APP:
            return {
                isLoading: false,
                ...action.payload
            };
        default:
            return state;
    }
}
export default demoAPP; // 把它转交给 createStore 函数

大家最后获得的 state 结构是:

  • state
    • demoAPP

当有几个 reducer 时:

import * as actionTypes from '../actionTypes';
import { combineReducers } from 'redux';
let initState = {
    isLoading: true
};
// 对应 state.demoAPP
const demoAPP = (state = initState, action) => {
    switch (action.type) {
        case actionTypes.SET_DEMO_APP:
            return {
                isLoading: false,
                ...action.payload
            };
        default:
            return state;
    }
}
// 对应 state.reducerB
const reducerB = (state = {}, action) => {
    switch (action.type) {
        case actionTypes.SET_REDUCER_B:
            return {
                isLoading: false,
                ...action.payload
            };
        default:
            return state;
    }
}
const rootReducer = combineReducers({demoAPP,reducerB});
export default rootReducer;

我们最后取得的 state 结构是:

  • state
    • demoAPP
    • reducerB

莫不你曾经想到更进一步,把那一个 Reducer 拆分到相应的公文模块下:

// react/reducers/index.js 
import demoAPP from './demoAPP.js';
import reducerB from './reducerB.js';
const rootReducer = combineReducers({demoAPP,reducerB});
export default rootReducer;

继而,大家来看 store 模块:

// react/store.js
import rootReducer from './reducers';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
const initialState = {};
const finalCreateStore = compose(
    applyMiddleware(thunk)
)(createStore);
export default finalCreateStore(rootReducer, initialState);

怎么和设想的不雷同?不应有是如此吧:

// react/store.js
import rootReducer from './reducers';
import { createStore } from 'redux';
export default createStore(rootReducer);

此地引入 redux 中间件的定义,你只需清楚 redux 中间件的服从正是
action 发出以后,给我们三个再加工 action 的火候
就足以了。

缘何要引入 redux-thunk 这当中间件呢?

要清楚,大家原先所谈论的都以联合署名进程。实际项目中,只要蒙受请求接口的场景(当然不惟有那种景观)就要去处理异步进程。

前边大家领略,dispatch 多少个 ActionCreator 会霎时回去三个 action
对象,用以更新数据,而中间件赋予大家再处理 action 的机遇。

试想一下,假诺大家在这么些进程中,发现 ActionCreator 重返的并不是2个action 对象,而是多个函数,然后通过这一个函数请求接口,响应就绪后,我们再
dispatch 七个 ActionCreator ,本次大家确实回到贰个 action
,然后指点接口再次来到的数额去立异 state 。 这样一来不就消除了大家的标题啊?

当然,那只是基本思路,关于 redux
的中间件设计,又是三个妙不可言的话题,有趣味大家能够再开一篇尤其切磋,那里点到结束。

重回大家的话题,经过

const finalCreateStore = compose(
    applyMiddleware(thunk)
)(createStore);
export default finalCreateStore(rootReducer, initialState);

如此包装一次 store 后,大家就足以安心乐意地利用异步 action 了:

// react/action.js
import * as types from './actionType.js';
import * as model from './model.js';
// 同步 actionCreator
export const setDemoAPP = payload => ({
    type: types.SET_DEMO_APP,
    payload
});
// 异步 actionCreator
export const initDemoAPP = () => dispatch => {
    model.getBaseData().then(response => {
        let {status, data} = response;
        if (status === 0) {
            //请求成功且返回数据正常
            dispatch(setDemoAPP(data));
        }
    }, error => {
        // 处理请求异常的情况
    });
}

此地大家用 promise 格局来拍卖请求,model.js 模块如您所想是局地接口请求
promise,就如那样:

export const getBaseData () => {
    return $.getJSON('/someAPI');
}

你也可以参见大家往期介绍的其他格局。

终极,大家再来完善一下事先的流程:

澳门葡京 11

redux flow

Redux中store、action、views、reducers、middleware等数码流程图如下:澳门葡京 12

3.结语

Redux 的 API
三头手都能数得完,源码更是简单,加起来不超越500行。但它给我们带来的,不啻是一套复杂应用消除方案,更是
Flux 思想的凝练表明。其它,你还足以从中体会到函数式编制程序的乐趣。

1000个客官心中有一千个哈姆赖特,你脑海里的又是哪多个呢?

简化数据流程图:澳门葡京 13

Redux核心:

  • 纯净数据源,即:整个Web应用,唯有1个Store,存储着独具的数目【数据结构嵌套太深,数据访问变得繁琐】,保证一切数据流程是Predictable。
  • 将二个个reducer自上而下一流一流地统一起,最终赢得二个rootReducer。
    => Redux通过三个个reducer完毕了对一切数据源(object
    tree)的拆迁访问和修改。 =>
    Redux通过二个个reducer完结了不可变数据(immutability)。
  • 负有数据都以只读的,不可能改改。想要修改只好透过dispatch(action)来改变state。

参考

《Redux
官方文书档案》

《深入 React 技术栈》

② 、Redux源码解析

前记— redux的源码比较直观简洁~

Redux概念和API,请直接查看官方英文API和官方普通话API

Redux目录结构:

|—src   |—applyMiddleware.js   |—bindActionCreators.js  
|—combineReducers.js   |—compose.js   |—createStore.js
定义createStore   |—index.js redux主文件,对外暴光了几个为主API

1
2
3
4
5
6
7
|—src
  |—applyMiddleware.js
  |—bindActionCreators.js
  |—combineReducers.js
  |—compose.js
  |—createStore.js 定义createStore
  |—index.js redux主文件,对外暴露了几个核心API

以下分别是逐一文件源码解析(带普通话批注):

1) combineReducers.js

  • 本质:组合五个分支reducer并回到三个新的reducer,参数也是state和action,进行state的翻新处理
  • 初始化:store.getState()的开端值为reducer(initialState, { type:
    ActionTypes.INIT })
  • Reference:

澳门葡京 14

combineReducers()
所做的只是生成多个函数,那一个函数来调用一三种reducer,每种reducer依照它们的key来筛选出state中的一片段数据并拍卖,然后那个变化的函数再将富有reducer的结果合并成2个尾声的state对象。

在实际利用中,reducer中对于state的处理是后来成多少个state对象(深拷贝):澳门葡京 15

故而在combineReducers中种种小reducers的 nextStateForKey !==
previousStateForKey 一定为 true => hasChange也一定为true

那么难题来了,为何要每回都拷贝贰个新的state,再次回到二个新的state呢?
解释:
  1. Reducer 只是有的纯函数,它接受此前的 state 和 action,并再次回到新的
    state。刚初始容许唯有贰个 reducer,随着应用变大,把它拆成多少个小的
    reducers,分别独立地操作 state tree 的不等部分,因为 reducer
    只是函数,能够控制它们被调用的一一,传入附加数据,甚至编写可复用的
    reducer
    来处理局地通用职分,如分页器等。因为Reducer是纯函数,由此在reducer内部直接改动state是副功效,而回到新值是纯函数,可信赖性增强,便于追踪bug。
  2. 此外由于不可变数据结构总是修改引用,指向同2个数据结构树,而不是平素改动数据,能够保存任意三个历史景况,那样就足以成功react
    diff从而局地刷新dom,约等于react非常的慢速的原故。
  3. 因为严厉限制函数纯度,所以每个action做了什么和平谈判会议做什么总是永恒的,甚至足以把action存到3个栈里,然后逆推出此前的持有state,即react
    dev
    tools的做事原理。再提及react,一般的话操作dom只可以通过副作用,但是react的零件都以纯函数,它们连接被动地直接显示store中得内容,也等于说,好的机件,不受外部环境困扰,永远是可信的,出了bug只万幸外侧的逻辑层。这样写好纯的virtual
    dom组件,交给react处理副成效,很好地分手了关切点。

2) applyMiddleware.js

  • 真相:利用中间件来包装store的dispatch方法,假若有三个middleware则须求接纳compose函数来整合,从右到左依次执行middleware
  • Reference:applymiddleware方法、middleware介绍

澳门葡京 16Reducer有不少很风趣的中间件,能够参照中间件

3) createStore.js

  • 实质:
  1. 若不须要选取中间件,则开创四个分包dispatch、getState、replaceReducer、subscribe八种办法的指标
  2. 若选择中间件,则应用中间件来包装store对象中的dispatch函数来兑现越多的效应
  • createStore.js中代码不难易读,很不难驾驭~

(警告)注:

  1. redux.createStore(reducer, preloadedState,
    enhancer)假设传入了enhancer函数,则赶回
    enhancer(createStore)(reducer,
    preloadedState)假设未传入enhancer函数,则赶回三个store对象,如下:

澳门葡京 17

  1. store对象对外揭发了dispatch、getState、subscribe、replaceReducer方法
  2. store对象通过getState() 获取内部最新state
  3. preloadedState为 store 的开端状态,假如不传则为undefined
  4. store对象通过reducer来修改内部state值
  5. store对象成立的时候,内部会继续努力调用dispatch({ type: ActionTypes.INIT
    })来对内部景色举办开首化。通过断点大概日志打字与印刷就足以看看,store对象创立的还要,reducer就会被调用实行开头化。

Reference:)

考虑实际利用中见惯不惊使用的中间件thunk和logger:

  • thunk源码:

澳门葡京 18

  • logger源码:

澳门葡京 19

全部store包装流程:澳门葡京 20

4) bindActionCreators.js

  • 本质:将拥有的action都用dispatch包装,方便调用
  • Reference:

澳门葡京 21

5) compose.js

  • 实质:组合三个Redux的中间件
  • Reference:

澳门葡京 22

6) index.js

  • 本质:抛出Redux中多少个至关心珍视要的API函数

③ 、实例应用Redux

Redux的宗旨理想:Action、Store、Reducer、UI
View协作来促成JS中复杂的情形管理,详细讲解请查看:Redux基础

React+Redux结合使用的工程目录结构如下:

|—actions    addAction.js    reduceAction.js |—components    |—dialog  
 |—pagination |—constant |—containers    |—add        addContainer.js
       add.less    |—reduce        reduceContainer.js        reduce.less
|—reducers    addReducer.js    reduceReducer.js |—setting    setting.js
|—store    configureStore.js |—entry    index.js |—template  
 index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|—actions
   addAction.js
   reduceAction.js
|—components
   |—dialog
   |—pagination
|—constant
|—containers
   |—add
       addContainer.js
       add.less
   |—reduce
       reduceContainer.js
       reduce.less
|—reducers
   addReducer.js
   reduceReducer.js
|—setting
   setting.js
|—store
   configureStore.js
|—entry
   index.js
|—template
   index.html

优势:明显代码依赖,减弱耦合,降低复杂度~~

下边是事实上中国人民解放军海军事工业程大学业程应用中运用react+redux框架实行重构时,总计运用redux时所波及一些题材&&供给小心的点:

1. Store

在开立异的store即createStore时,须求传入由根Reducer、起始化的state树及运用中间件。

1)根Reducer

重构的工程运用代码很多,不容许让整个state的变更都通过一个reducer来处理。供给拆分为多个小reducer,最终通过combineReducers来将八个小reducer合并为四个根reducer。拆分reducer时,各个reducer负责state中一部分数据,最后将拍卖后的数量统百分之十为全部state。在意每一个reducer只负责管理全局state中它肩负的一片段。各种reducer的state参数都不可同日而语,分别对应它管理的那有些state数据。

事实上工程代码重构中以功能来拆分reducer:澳门葡京 23

是es6中指标的写法,各种reducer所负责的state可以改变属性名。

2)initialState => State树

设计state结构:在Redux应用中,全部state都被保留在二个十足对象中,当中囊括工程全局state,由此对于整个重构工程而言,提前规划state结构显得非凡主要。

尽心尽力把state范式化:大多数程序处理的数码都是嵌套或彼此关联的,开发复杂应用时,尽或然将state范式化,不设有嵌套。可参照State范式化

2、Action

唯一触发更改state的输入,经常是dispatch分裂的action。

API请求尽量都放在Action中,但发送请求成功中回到数据分化处境尽量在Reducer中开始展览拍卖。

  • action.js:

澳门葡京 24

  • reducer.js

澳门葡京 25

注:

1、假设在央浼发送后,要求依据重临数据来判断是或不是必要发送其余请求或然实施一些非纯函数,那么能够将再次回到数据分化情状的处理在Action中开始展览。

贰 、要是遭逢请求错误,须求给用户显示错误原因,如上述reducer代码中errorReason。
要求考虑到是或不是大概会在晋升中追加DOM成分或许有个别互动操作,由此最好是将errorReason在action中赋值,最终在reducer中进行数量处理【reducer是纯函数】。

  • action.js

澳门葡京 26

3、Reducer

reducer是二个收受旧state和action,再次来到新state的函数。 (prevState,
action) => newState

难忘要保全reducer纯净,万一传入参数相同,再次回到总计获得的下2个 state
就自然相同。没有异样情状、没有副功用,没有 API
请求、没有变量修改,单纯执行总结。
永恒不要在reducer中做那几个操作:

a、修改传入参数 b、执行有副功效的操作,如API请求和路由跳转等
c、调用非纯函数,例如Date.now() 或 Math.random()

1
2
3
a、修改传入参数
b、执行有副作用的操作,如API请求和路由跳转等
c、调用非纯函数,例如Date.now() 或 Math.random()

千古不要改动旧state!比如,reducer 里永不选用 Object.assign(state,
newData),应该使用Object.assign({}, state, newData)。那样才不会覆盖旧的
state。

  • reducer.js:

澳门葡京 27

4、View(Container)

渲染界面

a、mapStateToProps

选取mapStateToProps能够得到全局state,可是方今页面只必要该页面包车型大巴所肩负部分state数据,由此在给mapStateToProps传参数时,只需求传当前页面所涉嫌的state。因而在对应的reducer中,接收的旧state也是方今页面所提到的state值。

b、mapDispatchToProps

在mapDispatchToProps中央银行使bindActionCreators让store中dispatch页面全部的Action,以props的样式调用对应的action函数。

富有的 dispatch action 均由 container 注入 props 格局完结。

c、connect ( react-redux )

react-redux 提供的 connect() 方法将零件连接到
Redux,将利用中的任何三个零部件connect()到Redux
Store中。被connect()包装好的机件都能够获得一些办法作为组件的props,并且能够获得全局state中的任何内容。

connect中封装了shouldComponentUpdate方法澳门葡京 28

只要state保持不变那么并不会招致重复渲染的标题,内部零件依然选择mapStateToProps方法选拔该零件所急需的state。要求专注的是:独立的作用模块不可能运用任何模块的state.

d、bind

在constructor中bind全部event handlers =>
bind方法会在每一遍render时都重复回到贰个对准钦定作用域的新函数

  • container.js

澳门葡京 29

四、总结

整篇作品重假如源码精晓和现实性品种应用中全部Redux处理state的流水生产线,作者对Redux有了更深层次的通晓。

Redux+React已广泛应用,期待在以往的运用进度中,有越多更深厚的明亮~

如有错误,欢迎指正 (~ ̄▽ ̄)~

参照链接:

  • redux种类源码解析:
  • redux github:
  • redux剖析:

    1 赞 收藏
    评论

澳门葡京 30

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website