Redux 入门

Redux思想

严格的单向数据流是 Redux 架构的设计核心,使用 Redux 的一个好处就是让 state 的变化过程变的可预知和透明。javascript

Redux约定使用普通对象object tree来描述应用的 state ,若是想更新 state 中的数据,须要先发起一个 action请求来对更改进行描述,而后调用 reducer函数执行state更改。java

  • object tree:这个对象就像 “Model”,区别是它并无 setter。state 能够是普通对象,不可变对象,或者其它类型。
  • Action:一个普通 JavaScript 对象,用来描述发生了什么。
  • Reducer:一些纯函数,它接收先前的 state 和 action,并返回新的 state。

三大原则

  • 单一数据源:整个应用的 state 被储存在一个 object tree中 ,这个 object tree 只存在于惟一一个 store 中。
  • State 是只读的:改变 store 内 state 的唯一途径是对它 dispatch 一个 action。
  • 使用纯函数来执行修改:只能在 reducers 中执行state的修改。

Store

Store 就是用来维持应用全部的 state 的一个对象。react

  • Store 不是类, 它只是有几个方法的对象;
  • 应用中应有且仅有一个 store;

Store方法

getState()git

返回应用当前的 state 树;github

dispatch(action : object)json

分发 action 改变 store;redux

subscribe(listener : Function): 数组

注册监听器, 每当 dispatch action 的时候就会执行; state 树中的一部分可能已经变化, 能够在回调函数里调用 getState()来拿到当前 state。promise

replaceReducer(nextReducer : Function):架构

用 nextReducer 替换当前 store 的 reducer。在须要实现代码分隔,并且须要当即加载一些 reducer 的时候才可能会用到它,在实现 Redux 热加载机制的时候也可能会用到。

建立store

建立一个 Redux store 来以存放应用中全部的 state:
createStore(reducer, [preloadedState], enhancer)

参数

reducer(): Function

接收两个参数,分别是当前的 state 树和要处理的action,返回新的state 树。

preloadedState: any

初始化state。 若是使用 combineReducers 建立 reducer,它必须是一个普通对象,与传入的 keys 保持一样的结构;不然能够自由传入任何 reducer 可理解的内容。

enhancer: Function

Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。

返回值

Store: 保存了应用全部 state 的对象。

栗子

import React from 'react'
import { createStore } from 'redux'
import reducer from './reducers'

const store = createStore(reducer);

Action

Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的惟一来源。action 内必须使用一个字符串类型的 type 字段来表示将要执行的动做。

  • 多数状况下,type 会被定义成字符串常量。
  • 当应用规模愈来愈大时,建议使用单独的模块或文件来存放 action。
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

Action Creator

Action Creator 就是生成 action 的方法。

function fetchPosts(url) {
  return {
    type: FETCH_POSTS,
    url
  }
}

调用action

store 里直接经过 store.dispatch()调用 action,可是多数状况下会使用 react-redux 提供的 connect()来调用。bindActionCreators()能够自动把多个 action 建立函数 绑定到 dispatch() 方法上。

一个约定俗成的作法是经过建立函数生成 action 对象,而不是在dispatch 的时候内联生成action。
// 1. 内联生成 action
store.dispatch({
  type: ADD_TODO,
  text: 'Build my first Redux app'
})

// 2. Action Creator 生成 action
function fetchPosts(url) {
  return {
    type: FETCH_POSTS,
    url
  }
}
store.dispatch(fetchPosts(url))

Reducer

Redux 的应用程序中最多见的 state 结构是一个简单的 JavaScript 对象,它最外层的每一个 key 中拥有特定域的数据。给这种 state 结构写 reducer 的方式是分拆成多个 reducer,拆分以后的 reducer 都是相同的结构(state, action),而且每一个函数独立负责管理该特定切片 state 的更新。多个拆分以后的 reducer 能够响应一个 action,在须要的状况下独立的更新他们本身的切片 state,最后组合成新的 state。

  • 每一个reducer要保证是纯函数。只要传入参数相同,返回计算获得的下一个 state 就必定相同。
  • 没有特殊状况、没有反作用,没有 API 请求、没有变量修改,单纯执行计算。
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      });
    default:
      return state;
  }
}

combineReducers

这是一个高阶 Reducer ,combineReducers 接收拆分以后的 reducer 函数组成的对象,而且建立出具备相同键对应状态对象的Reducer函数。目前 combineReducers 只能处理普通的 JavaScript 对象。

若是没有给 createStore 提供预加载 state,输出 state 对象的 key 将由输入的拆分以后 reducer 组成对象的 key 决定。

结合Immutable.js 使用

combineReducers 不解决 Immutable.js,Maps等构建的 state tree,也不会把其他部分的 state 做为额外参数传递给 reducer 或者排列 reducer 的调用顺序,它一样不关心 reducer 如何工做。如今有大量提供相似功能的工具,例如 redux-immutable,这个第三方包实现了一个可以处理 Immutable Map 数据而非普通的 JavaScript 对象的 combineReducers

栗子

1.定义reducers

/**
 * reducers.js
 */
function info(state = {}, action) {
  switch (action.type) {
    case 'UPDATE_NAME':
      return { ...state, name: action.text };
    case 'UPDATE_SCHOOL':
      return { ...state, school: action.text };
    default:
      return state
  }
}

function age(state = {}, action) {
  switch (action.type) {
    case 'INCREMENT_AGE':
      return { ...state, age: state.age + 1 };
    case 'DECREMENT_AGE':
      return { ...state, age: state.age - 1 }
    default:
      return state
  }
}

export { info, age };

2.建立store, 引入combineReducers

/**
 * App.js
 */
import { createStore, combineReducers } from 'redux'
import { info, age } from './reducers.js'

let store = createStore(combineReducers({ info, age }), {
  name: '',
  school: '',
  age: 0
});
console.log(store.getState())
// {
//   info: { name: '', school: '' },
//   age: 0
// }

store.dispatch({
  type: 'UPDATE_NAME',
  text: 'Use Redux'
})
console.log(store.getState())
// {
//   info: { name: 'Use Redux', school: '' },
//   age: 0
// }

中间件Middleware

为何出现中间件?

同步: Action 发出之后,Reducer 当即算出 State;
异步:Action 发出之后,过一段时间再执行 Reducer;
默认状况下,createStore()所建立的 Redux store 只支持同步数据流, dispatch只能接收一个普通对象。怎么才能在异步操做结束后Reducer自动执行呢?这时就须要使用中间件。

定义

Redux middleware 提供的是 action 被发起以后,到达 reducer 以前的扩展点,在每一个 action 对象 dispatch 出去以前,注入一个自定义的逻辑来解释 action 对象。

  • 多个 middleware 能够被组合到一块儿使用,造成 middleware 链。
  • 每一个 middleware 都不须要关心链中它先后的 middleware 的任何信息。

使用

中间件能够进行日志记录、建立崩溃报告、调用异步接口或者路由等等;在applyMiddleware方法里把中间件做为参数传入。此方法是 Redux 的原生方法,做用是将全部中间件组成一个数组,依次执行。

applyMiddleware(...middleware)

参数

...middleware

遵循 Redux middleware API 的函数。每一个 middleware 接受 StoredispatchgetState函数做为命名参数,并返回一个函数。该函数会被传入被称为 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数能够直接调用 next(action),或者在其余须要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的 dispatch方法做为 next 参数,并借此结束调用链。因此,middleware 的函数签名是 ({ getState, dispatch }) => next => action

返回值

(Function) :一个应用了 middleware 后的 store enhancer。这个 store enhancer 的签名是 createStore => createStore,可是最简单的使用方法就是直接做为最后一个 enhancer 参数传递给 createStore()函数。

栗子

实现请求先后打日志, 请求失败处理异常的中间件:

import { createStore, combineReducers, applyMiddleware } from 'redux';
import reducers from './reducers'

// 请求先后日志记录middleware
const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

// 请求异常记录middleware
const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}

// createStore引入本身建立的middleware
const store = createStore(
  combineReducers(reducers),
  preloadedState,
  applyMiddleware(logger, crashReporter)
)

compose

从右到左来组合多个函数: compose(...functions)

compose(funcA, funcB, funcC) 形象为 compose(funcA(funcB(funcC())))

参数
(arguments): 须要合成的多个函数。预计每一个函数都接收一个参数。它的返回值将做为一个参数提供给它左边的函数,以此类推

返回值
(Function): 从右到左把接收到的函数合成后的最终函数。

React-redux

react-redux是使用redux开发react时使用的一个插件。react-redux提供了两个重要的API:Provider、connect

<Provider> API

<Provider> 使组件层级中的 connect() 方法都可以得到 Redux store。
正常状况下,你的根组件应该嵌套在 <Provider> 中才能使用 connect() 方法。若是不想把根组件嵌套在 <Provider> 中,你能够把 store 做为 props 传递到每个被 connect() 包装的组件。

属性

  • store : 应用程序中惟一的 Redux store 对象;
  • children: ReactElement : 组件层级的根组件;

栗子

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';

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

connect API

链接 React 组件与 Redux store。链接操做不会改变原来的组件类, 返回一个新的已与 Redux store 链接的组件类。
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

参数

mapStateToProps(state, [ownProps]): Function
stateProps。

  • 若是定义该参数,组件将会监听 Redux store 的变化;若是省略,则组件将不会监听 Redux store的变化;
  • 只要 Redux store 发生改变,mapStateToProps 函数若是定义了就会被调用;
  • 若是定义了第二个参数 ownProps,则该参数的值为传递到组件的 props。只要组件接收新的 props,mapStateToProps 也会被调用。(例如,当父组件从新渲染子组件props改变,那么 ownProps 参数,mapStateToProps 都会被从新计算)

该函数必须返回一个纯对象,这个对象会与组件的 props 合并。

mapDispatchToProps(dispatch, [ownProps]):(Object / Function)
dispatchProps。

  • 若是传递的是一个对象,那么每一个定义在该对象的函数都将被看成 Redux action creator,对象所定义的方法名将做为属性名。每一个方法将返回一个新的函数,函数中dispatch方法会将 action creator 的返回值做为参数执行。这些属性会被合并到组件的 props 中。
  • 若是传递的是一个函数,该函数参数默认是 dispatch 函数,返回值是一个对象。返回对象经过 dispatch 函数与 action creator 以某种方式绑定在一块儿。
  • 若是省略 mapDispatchToProps ,默认状况下,dispatch 会注入到你的组件 props 中。
  • 若是指定了该回调函数中第二个参数 ownProps,该参数的值为传递到组件的 props,并且只要组件接收到新 props,mapDispatchToProps 也会被调用。

mergeProps(stateProps, dispatchProps, ownProps): Function]
props。

  • 若是指定了这个参数,mapStateToProps()mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。
  • 该回调函数返回的对象将做为 props 传递到被包装的组件中。
  • 若是你省略这个参数,默认状况下返回 (Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。

options: Object
若是指定这个参数,能够定制 connect 的行为;

  • [pure = true] (Boolean): 若是为 true,connector 将执行 shouldComponentUpdate 而且浅对比 mergeProps 的结果,避免没必要要的更新,前提是当前组件是一个“纯”组件,它不依赖于任何的输入或 state 而只依赖于 props 和 Redux store 的 state。默认值为 true
  • [withRef = false] (Boolean): 若是为 true,connector 会保存一个对被包含的组件实例的引用,该引用经过 getWrappedInstance() 方法得到。默认值为 false

返回值
根据配置信息,返回一个注入了 state 和 action creator 的 React 组件。

redux-thunk 中间件

redux-thunk是redux解决异步的中间件, 可让 Action Creator 返回函数(普通的 Action Creator 默认返回一个对象)。若是action creator 返回的是一个函数,就执行它,若是不是,就按照原来的next(action)执行。

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

const store = createStore(
  reducer,
  applyMiddleware(thunk)
);

redux-thunkexport default的是createThunkMiddleware(),这个函数返回的是一个柯里化过的函数。

function createThunkMiddleware(extraArgument) {
  return function({ dispatch, getState }) {
    return function(next){
      return function(action){
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
        return next(action);
      };
    }
  }
}

栗子

  • fetchPosts返回了一个函数,而普通的 Action Creator 默认返回一个对象。
  • 返回函数的参数是dispatchgetState,而普通的 Action Creator 的参数是 Action 的内容。
  • 在返回的函数之中,发出一个 ActionrequestPosts(title),表示操做开始。
  • 异步操做结束以后,发出一个 ActionreceivePosts(title, json)`,表示操做结束。
const fetchPosts = title => (dispatch, getState) => {
  dispatch(requestPosts(title));
  return fetch(url)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(title, json)));
  };
};

store.dispatch(fetchPosts('test'));

redux-promise 中间件

既然使用redux-thunk中间件可让 Action Creator 返回函数,固然也能够返回其余值。另外一种异步操做的解决方案是让 Action Creator 返回一个 Promise 对象。这就须要使用redux-promise中间件。

import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import reducer from './reducers';

const store = createStore(
  reducer,
  applyMiddleware(promiseMiddleware)
);

栗子

const fetchPosts = (dispatch, title) => new Promise((resolve, reject)=> {
  dispatch(requestPosts(title));
  return fetch(url).then(response => {
      type: 'FETCH_POSTS',
      payload: response.json()
  });
});

redux-actions

Redux 让状态管理变得很冗长,大量的action、actionCreator、reducer让开发者不断在写重复的代码。redux-actions就解决了这个问题,让编写redux状态管理变得简单起来。

主要API有createAction(s)handleAction(s)combineActions

createAction(s) Api

建立一个action: createAction(type)

  • type必须实现toString()。
import { createAction } from 'redux-actions';

export const increment = createAction('INCREMENT');
export const decrement = createAction('DECREMENT');

increment(); // { type: 'INCREMENT' }
decrement(); // { type: 'DECREMENT' }
increment(10); // { type: 'INCREMENT', payload: 10 }
decrement([1, 42]); // { type: 'DECREMENT', payload: [1, 42] }

建立多个action: createActions(actionMap, ...identityActions[, options])

  • 第一个参数 actionMap 是一个对象,以 action type 为键名,键值value有三种形式:

    1. 函数,该函数参数传入的是action建立的时候传入的参数,返回结果会做为到生成的actionpayload的value。
    2. 数组[payload, meta],payload如上函数, meta 是必需的。
    3. 一个 actionMap 对象;
  • 第二个参数identityActions,可选参数;
createActions({
  ADD_TODO: todo => ({ todo }), // payload creator
  REMOVE_TODO: [
    todo => ({ todo }), // payload creator
    (todo, warn) => ({ todo, warn }) // meta
  ]
});

handleAction(s) API

处理action,返回一个reducer,处理一种类型的action type

处理一个action: handleAction(type, reducer, defaultState)

  • 若是传递了reducer函数,则该函数将用于处理正常操做和失败操做。
  • 若是未定义reducer参数(reducer),那么将使用identity函数。
  • 第三个参数defaultState是必需的,而且在将undefined传递给reducer时使用。
import { handleAction } from 'redux-actions';

handleAction(
  'APP/COUNTER/INCREMENT',
  (state, action) => ({
    counter: state.counter + action.payload.amount
  }),
  defaultState
);

处理多个action: handleActions(reducerMap, defaultState[, options])

  • defaultState是必需的,将undefined传递给reducer 时使用;
const reducer = handleActions(
  {
    INCREMENT: (state, action) => ({
      counter: state.counter + action.payload
    }),

    DECREMENT: (state, action) => ({
      counter: state.counter - action.payload
    })
  },
  { counter: 0 }
);

禁止Redux DevTools

React Developer Tools、Redux DevTools 能够给开发人员在研发阶段调试程序带来极大的方便。 可是上了生产环境后,应该将禁止 DevTools。

Redux DevTools的做者已经给出了标准的解决方案。具体实现步骤以下:

  • 设置process.env.NODE_ENV = JSON.stringify('production')
  • 使用redux-devtools-extension/developmentOnly引入方法
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';

const store = createStore(
  rootReducer,
  composeWithDevTools(middlewareEnhancer)
);
相关文章
相关标签/搜索