Redux思想
严格的单向数据流是 Redux 架构的设计核心,使用 Redux 的一个好处就是让 state 的变化过程变的可预知和透明。
Redux约定使用普通对象object tree来描述应用的 state ,如果想更新 state 中的数据,需要先发起一个 action请求来对更改进行描述,然后调用 reducer函数执行state更改。
- 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 的一个对象。
- Store 不是类, 它只是有几个方法的对象;
- 应用中应有且仅有一个 store;
Store方法
getState()
返回应用当前的 state 树;
dispatch(action : object)
:
分发 action 改变 store;
subscribe(listener : Function)
:
注册监听器, 每当 dispatch action 的时候就会执行; state 树中的一部分可能已经变化, 可以在回调函数里调用 getState()
来拿到当前 state。
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 接受 Store
的 dispatch
和 getState
函数作为命名参数,并返回一个函数。该函数会被传入被称为 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 默认返回一个对象。- 返回函数的参数是
dispatch
和getState
,而普通的 Action Creator 的参数是 Action 的内容。 - 在返回的函数之中,发出一个 Action
requestPosts(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有三种形式:
- 函数,该函数参数传入的是
action
创建的时候传入的参数,返回结果会作为到生成的action
的payload
的value。 - 数组[payload, meta],payload如上函数, meta 是必需的。
- 一个 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)
);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。