源码剖析及采纳,Redux从入门到跳楼

Redux 源码剖析及利用

2018/05/23 · JavaScript
· Redux

初稿出处:
博客园技术公司   

选拔redux+react已有一段时间,刚初阶选拔没有深刻摸底其源码,近年来静下心细读源码,感触颇深~

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

参考链接:

1.Redux 设计理念

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

  全部的动静,保存在二个指标里面

 

Redux 是近期提出的
Flux
思想的一种实施方案,在它前边也有 reflux 、 fluxxor
等高品质的小说,但短短几个月就在 GitHub 上获近万 star
的实际业绩让这么些后来居上逐步改为 Flux 的主流实践方案。

背景:

React 组件 componentDidMount 的时候开始化 Model,并监听 Model 的 change
事件,当 Model 产生变动时调用 React 组件的 setState 方法重复 render
整个组件,最终在组件 component威尔Unmount 的时候废除监听并销毁 Model。

最初阶完毕3个简单实例:例如add加法操作,只须求经过React中 setState
去控制变量扩大的状态,分外不难方便。

唯独当我们需求在类型中扩充乘法/除法/幂等等复杂操作时,就必要规划三个state来控制views的变动,当项目变大,里面包涵状态过多时,代码就变得难以维护并且state的变动不可预测。只怕要求追加2个小功用时,就会引起多处改动,导致支出频率下跌,代码可读性不高

比如说以后利用较多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 是三个常见的object,必须有3个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 方法正是一个Action Creator

  View 发出Action的唯一途径 store.dispatch(action) //触发事件 

 (3)reducer 其实正是七个一般函数,重要用来改变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/103个大的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 放在2个文本夹里,然后统一引入。

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使用境况:

  • 某些组件的情形,需求共享
  • 有个别状态须要在任哪个地点方都足以获得
  • 一个组件须求变更全局状态
  • 二个零件供给改变另一个组件的情景

比如,论坛应用中的夜间设置、回到顶部、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源码剖析及采纳,Redux从入门到跳楼。格局,就马到功成了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那多少个办法。

异步操作的1个缓解方案,正是让 Action Creator 重回2个 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属性是1个 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是用来描述正在产生的轩然大波的三个目的

  • 经过纯函数修改State

纯函数的题材,也是来源于于函数式编制程序思想,大家在中学时学的函数正是纯函数,对于同二个输入,必然有同一的输出。那就有限协助了数额的可控性,那里的纯函数正是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 ” 。

现今,大家赢得了1个 “绘影绘声” 的动作。它丰硕简洁,但担任 Model 的
store 临时还不知底怎么着感知这么些动作从而改变数据结构。

为了处理那些关键难点,Reducer
巧然登场。它还是是三个函数,而且是没有副成效的纯函数。它只收到多少个参数:state
和 action ,重临2个 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;
    }
}

当接过到2个 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)全体的图景,保存在三个对象里面。

Redux 让使用的情况变化变得可预测。假设想更改使用的状态,就亟须
dispatch 对应的
action。而不能够从来改动使用的景况,因为保存这一个意况的地方(称为
store)只有 get方法(getState) 而没有 set方法

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

redux状态管理的流程及相关概念

美高梅开户网址 4

image

  • store

Store
便是保存数据的地点,保存着本程序有所的redux管理的数目,你能够把它看成二个容器。整个应用只能有2个Store。(三个store是一个目的, reducer会改变store中的有个别值)

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

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

地点代码中,createStore函数接受另3个函数作为参数,再次来到新生成的 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函数处理完结后也会回来2个普通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其三方插件,那几个第贰方插件来将store对象存款和储蓄到地头,以及从本地苏醒数据到store中,比如说保存登录消息,下次开拓应用能够一向跳过登录界面,因为我们当前的利用属于内嵌程序,不登陆的话也进不来,所以不用它。

  • Action

Action 是1个对象,描述了接触的动作,仅此而已。大家约定,action
内必须运用3个字符串类型的 type
字段来代表即将执行的动作。平时它长一下以此样子:

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

而外 type 字段外,action
对象的结构完全由你协调决定,来看1个复杂点的:

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

普通大家会添加1个新的模块文件来囤积那一个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对象,而是回到了1个函数,那么些私下认可redux是迫不得已处理的,那就供给动用中间件处理了,redux-thunk中间件用于拍卖回来函数的函数,上边也介绍了redux-thunk的使用基本办法。

  • Reducer

Store 收到 Action 现在,必须付出三个新的 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() 新建了1个副本。不能如此使用
    Object.assign(state, {
    titleTheme: action.titleTheme,
    }),因为它会改变第1个参数的值。你必须把第叁个参数设置为空对象。你也足以打开对ES7提案对象进行运算符的扶助,
    从而使用 { …state, …newState } 达到同等的指标。
  • 在 default 情况下回到旧的 state。碰着未知的 action
    时,一定要回来旧的 state

至于拆分Reducer

此地只是比喻了二个简短的效果的reducer,倘若有两样的效果,需求规划很多reducer方法,注意各类reducer 只负责管理全局 state 中它担负的一部分。每一种 reducer 的 state
参数都不比,分别对应它管理的那某个 state 数据。

譬如大家以此类别的reducer文件结构:

美高梅开户网址 5

image.png

其间rootReducer.js正是三个根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按钮,能够全局设置标题栏的主旨。

使用目录

清晰的思路须辅以分工分明的文书模块,才能让大家的运用达到更佳的施行效果,同时,统一的布局也方便脚手架生成模板,提升开发功能。

以下的目录结构为团体伙伴数十四回探索和创新而来(限于篇幅,那里只关切 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“ )。星星依然不行不难,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
合理地分发到各类业务组件中,同时要便于维护,是支付的主要性。

先是,大家安顿 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 划分了2个模块 ——
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 再次回到的并不是3个action 对象,而是三个函数,然后经过这些函数请求接口,响应就绪后,大家再
dispatch 3个 ActionCreator ,这一次大家确实回到三个 action
,然后引导接口再次回到的多少去立异 state 。 那样一来不就消除了大家的难点啊?

理所当然,那只是基本思路,关于 redux
的中间件设计,又是2个妙趣横生的话题,有趣味大家得以再开一篇特别探究,那里点到结束。

回到我们的话题,经过

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
3只手都能数得完,源码更是简单,加起来不超过500行。但它给大家带来的,不啻是一套复杂应用化解方案,更是
Flux 思想的简洁表明。其它,你还足以从中体会到函数式编程的意趣。

一千个客官心中有一千个哈姆赖特,你脑公里的又是哪三个吧?

简化数据流程图:美高梅开户网址 13

Redux核心:

  • 单纯性数据源,即:整个Web应用,唯有二个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的结果合并成一个尾声的state对象。

在事实上行使中,reducer中对于state的拍卖是新兴成2个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个数据结构树,而不是一向改动数据,能够保存任意1个历史气象,那样就足以做到react
    diff从而局地刷新dom,也正是react非凡高效的由来。
  3. 因为严苛限制函数纯度,所以每种action做了哪些和平谈判会议做什么总是永恒的,甚至能够把action存到1个栈里,然后逆推出在此以前的具有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. 若不须要利用中间件,则开创3个分包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中一部分数据,最后将处理后的数额统1/10为整个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

注:

① 、如若在呼吁发送后,要求基于再次来到数据来判断是还是不是须求发送别的请求或许进行一些非纯函数,那么能够将再次回到数据差异意况的处理在Action中展开。

二 、即使蒙受请求错误,必要给用户呈现错误原因,如上述reducer代码中errorReason。
须要考虑到是或不是只怕会在提示中扩充DOM成分只怕有个别互相操作,由此最棒是将errorReason在action中赋值,最后在reducer中展开数量处理【reducer是纯函数】。

  • action.js

美高梅开户网址 26

3、Reducer

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

纪事要保证reducer纯净,一经传入参数相同,重临总结得到的下1个 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时都重新回来3个针对钦赐效能域的新函数

  • container.js

美高梅开户网址 29

四、总结

整篇作品首若是源码精晓和求实项目采纳中漫天Redux处理state的流水生产线,我对Redux有了更深层次的知晓。

Redux+React已广泛应用,期待在以往的选取进程中,有更加多更深入的通晓~

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

参考链接:

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

    1 赞 收藏
    评论

美高梅开户网址 30

发表评论

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

网站地图xml地图