react-redux 提供了两个重要的对象, Provider
和 connect
,前者使 React 组件可被链接(connectable),后者把 React 组件和 Redux 的 store 真正链接起来。react-redux 的文档中,对 connect
的描述是一段晦涩难懂的英文,在初学 redux 的时候,我对着这段文档阅读了好久,都没有所有弄明白其中的意思(大概就是,单词我都认识,连起来啥意思就不明白了的感受吧)。node
在使用了一段时间 redux 后,本文尝试再次回到这里,给 这段文档 (同时摘抄在附录中)一个靠谱的解读。react
关于react-redux的一个流程图git
流程图github
connect用法介绍redux
connect方法声明:后端
connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])复制代码
做用:链接React组件与 Redux store。api
参数说明:bash
mapStateToProps(state, ownProps) : stateProps复制代码
这个函数容许咱们将 store 中的数据做为 props 绑定到组件上。闭包
const mapStateToProps = (state) => {
return {
count: state.count
}
}复制代码
(1)这个函数的第一个参数就是 Redux 的 store,咱们从中摘取了 count 属性。你没必要将 state 中的数据原封不动地传入组件,能够根据 state 中的数据,动态地输出组件须要的(最小)属性。app
(2)函数的第二个参数 ownProps,是组件本身的 props。有的时候,ownProps 也会对其产生影响。
当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给组件。
mapDispatchToProps(dispatch, ownProps): dispatchProps复制代码
connect 的第二个参数是 mapDispatchToProps,它的功能是,将 action 做为 props 绑定到组件上,也会成为 MyComp 的 props。
[mergeProps],[options]复制代码
无论是 stateProps 仍是 dispatchProps,都须要和 ownProps merge 以后才会被赋给组件。connect 的第三个参数就是用来作这件事。一般状况下,你能够不传这个参数,connect 就会使用 Object.assign 替代该方法。
[options] (Object) 若是指定这个参数,能够定制 connector 的行为。通常不用。
原理解析
首先connect之因此会成功,是由于Provider组件:
那connect作了些什么呢?
它真正链接 Redux 和 React,它包在咱们的容器组件的外一层,它接收上面 Provider 提供的 store 里面的 state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给咱们的容器组件。
关于它的源码
connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,而后返回一个生产Component的函数(wrapWithConnect),而后再将真正的Component做为参数传入wrapWithConnect,这样就生产出一个通过包裹的Connect组件,该组件具备以下特色:
因为connect的源码过长,咱们只看主要逻辑:
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
// 从祖先Component处得到store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = { storeState: null }
// 对stateProps、dispatchProps、parentProps进行合并
this.updateState()
}
shouldComponentUpdate(nextProps, nextState) {
// 进行判断,当数据发生改变时,Component从新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 改变Component的state
this.store.subscribe(() = {
this.setState({
storeState: this.store.getState()
})
})
}
render() {
// 生成包裹组件Connect
return (
<WrappedComponent {...this.nextState} />
)
}
}
Connect.contextTypes = {
store: storeShape
}
return Connect;
}
}复制代码
connect使用实例
这里咱们写一个关于计数器使用的实例:
Component/Counter.js
import React, {Component} from 'react'
class Counter extends Component {
render() {
//从组件的props属性中导入四个方法和一个变量
const {increment, decrement, counter} = this.props;
//渲染组件,包括一个数字,四个按钮
return (
<p>
Clicked: {counter} times
{' '}
<button onClick={increment}>+</button>
{' '}
<button onClick={decrement}>-</button>
{' '}
</p>
)
}
}
export default Counter;复制代码
Container/App.js
import { connect } from 'react-redux'
import Counter from '../components/Counter'
import actions from '../actions/counter';
//将state.counter绑定到props的counter
const mapStateToProps = (state) => {
return {
counter: state.counter
}
};
//将action的全部方法绑定到props上
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increment: (...args) => dispatch(actions.increment(...args)),
decrement: (...args) => dispatch(actions.decrement(...args))
}
};
//经过react-redux提供的connect方法将咱们须要的state中的数据和actions中的方法绑定到props上
export default connect(mapStateToProps, mapDispatchToProps)(Counter)复制代码
首先回顾一下 redux 的基本用法:
connect方法声明以下:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])
做用:链接 React 组件与 Redux store。
链接操做不会改变原来的组件类,反而返回一个新的已与 Redux store 链接的组件类。
返回值
根据配置信息,返回一个注入了 state 和 action creator 的 React 组件。
复制代码
静态属性
WrappedComponent (Component): 传递到 connect() 函数的原始组件类。
静态方法
组件原来的静态方法都被提高到被包装的 React 组件。
实例方法
getWrappedInstance(): ReactComponent
仅当 connect() 函数的第四个参数 options 设置了 { withRef: true } 才返回被包装的组件实例。
备注:
一、 函数将被调用两次。第一次是设置参数,第二次是组件与 Redux store 链接:connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)。
二、 connect 函数不会修改传入的 React 组件,返回的是一个新的已与 Redux store 链接的组件,并且你应该使用这个新组件。
三、 mapStateToProps 函数接收整个 Redux store 的 state 做为 props,而后返回一个传入到组件 props 的对象。该函数被称之为 selector。
const reducer = (state = {count: 0}, action) => {
switch (action.type){
case 'INCREASE': return {count: state.count + 1};
case 'DECREASE': return {count: state.count - 1};
default: return state;
}
}
const actions = {
increase: () => ({type: 'INCREASE'}),
decrease: () => ({type: 'DECREASE'})
}
const store = createStore(reducer);
store.subscribe(() =>
console.log(store.getState())
);
store.dispatch(actions.increase()) // {count: 1}
store.dispatch(actions.increase()) // {count: 2}
store.dispatch(actions.increase()) // {count: 3}
复制代码
经过 reducer
建立一个 store
,每当咱们在 store
上 dispatch
一个 action
, store
内的数据就会相应地发生变化。
咱们固然能够 直接 在 React 中使用 Redux:在最外层容器组件中初始化 store
,而后将 state
上的属性做为 props
层层传递下去。
class App extends Component{
componentWillMount(){
store.subscribe((state)=>this.setState(state))
}
render(){
return <Comp state={this.state}
onIncrease={()=>store.dispatch(actions.increase())}
onDecrease={()=>store.dispatch(actions.decrease())}
/>
}
}
复制代码
但这并非最佳的方式。最佳的方式是使用 react-redux 提供的 Provider
和 connect
方法。
首先在最外层容器中,把全部内容包裹在 Provider
组件中,将以前建立的 store
做为 prop
传给 Provider
。
const App = () => {
return (
<Provider store={store}>
<Comp/>
</Provider>
)
};
复制代码
Provider
内的任何一个组件(好比这里的 Comp
),若是须要使用 state
中的数据,就必须是「被 connect 过的」组件——使用 connect
方法对「你编写的组件( MyComp
)」进行包装后的产物。
class MyComp extends Component {
// content...
}
const Comp = connect(...args)(MyComp);
复制代码
可见, connect
方法是重中之重。
究竟 connect
方法到底作了什么,咱们来一探究竟。
首先看下函数的签名:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect()
接收四个参数,它们分别是 mapStateToProps
, mapDispatchToProps
, mergeProps
和 options
。
mapStateToProps(state, ownProps) : stateProps
这个函数容许咱们将 store
中的数据做为 props
绑定到组件上。
const mapStateToProps = (state) => {
return {
count: state.count
}
}
复制代码
这个函数的第一个参数就是 Redux 的 store
,咱们从中摘取了 count
属性。由于返回了具备 count
属性的对象,因此 MyComp
会有名为 count
的 props
字段。
class MyComp extends Component {
render(){
return <div>计数:{this.props.count}次</div>
}
}
const Comp = connect(...args)(MyComp);
复制代码
固然,你没必要将 state
中的数据原封不动地传入组件,能够根据 state
中的数据,动态地输出组件须要的(最小)属性。
const mapStateToProps = (state) => {
return {
greaterThanFive: state.count > 5
}
}
复制代码
函数的第二个参数 ownProps
,是 MyComp
本身的 props
。有的时候, ownProps
也会对其产生影响。好比,当你在 store
中维护了一个用户列表,而你的组件 MyComp
只关心一个用户(经过 props
中的 userId
体现)。
const mapStateToProps = (state, ownProps) => {
// state 是 {userList: [{id: 0, name: '王二'}]}
return {
user: _.find(state.userList, {id: ownProps.userId})
}
}
class MyComp extends Component {
static PropTypes = {
userId: PropTypes.string.isRequired,
user: PropTypes.object
};
render(){
return <div>用户名:{this.props.user.name}</div>
}
}
const Comp = connect(mapStateToProps)(MyComp);
复制代码
当 state
变化,或者 ownProps
变化的时候, mapStateToProps
都会被调用,计算出一个新的 stateProps
,(在与 ownProps
merge 后)更新给 MyComp
。
这就是将 Redux store
中的数据链接到组件的基本方式。
mapDispatchToProps(dispatch, ownProps): dispatchProps
connect
的第二个参数是 mapDispatchToProps
,它的功能是,将 action 做为 props
绑定到 MyComp
上。
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)),
decrease: (...args) => dispatch(actions.decrease(...args))
}
}
class MyComp extends Component {
render(){
const {count, increase, decrease} = this.props;
return (<div>
<div>计数:{this.props.count}次</div>
<button onClick={increase}>增长</button>
<button onClick={decrease}>减小</button>
</div>)
}
}
const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp);
复制代码
因为 mapDispatchToProps
方法返回了具备 increase
属性和 decrease
属性的对象,这两个属性也会成为 MyComp
的 props
。
如上所示,调用 actions.increase()
只能获得一个 action
对象 {type:'INCREASE'}
,要触发这个 action
必须在 store
上调用 dispatch
方法。 diapatch
正是 mapDispatchToProps
的第一个参数。可是,为了避免让 MyComp
组件感知到 dispatch
的存在,咱们须要将 increase
和 decrease
两个函数包装一下,使之成为直接可被调用的函数(即,调用该方法就会触发 dispatch
)。
Redux 自己提供了 bindActionCreators
函数,来将 action 包装成直接可被调用的函数。
import {bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch, ownProps) => {
return bindActionCreators({
increase: action.increase,
decrease: action.decrease
});
}
复制代码
一样,当 ownProps
变化的时候,该函数也会被调用,生成一个新的 dispatchProps
,(在与 statePrope
和 ownProps
merge 后)更新给 MyComp
。注意, action
的变化不会引发上述过程,默认 action
在组件的生命周期中是固定的。
[mergeProps(stateProps, dispatchProps, ownProps): props]
以前说过,无论是 stateProps
仍是 dispatchProps
,都须要和 ownProps
merge 以后才会被赋给 MyComp
。 connect
的第三个参数就是用来作这件事。一般状况下,你能够不传这个参数, connect
就会使用 Object.assign
替代该方法。
其余
最后还有一个 options
选项,比较简单,基本上也不大会用到(尤为是你遵循了其余的一些 React 的「最佳实践」的时候),本文就略过了。但愿了解的同窗能够直接看文档。
随着WEB应用变得愈来愈复杂,再加上node先后端分离愈来愈流行,那么对数据流动的控制就显得愈加重要。redux是在flux的基础上产生的,基本思想是保证数据的单向流动,同时便于控制、使用、测试。
redux不依赖于任意框架(库),只要subscribe相应框架(库)的内部方法,就可使用该应用框架保证数据流动的一致性。
那么如何使用redux呢?下面一步步进行解析,并带有源码说明,不只作到 知其然 ,还要作到 知其因此然 。
2.1 简单demo入门
先来一个直观的认识:
// 首先定义一个改变数据的plain函数,成为reducer
function count (state, action) {
var defaultState = {
year: 2015,
};
state = state || defaultState;
switch (action.type) {
case 'add':
return {
year: state.year + 1
};
case 'sub':
return {
year: state.year - 1
}
default :
return state;
}
}
// store的建立
var createStore = require('redux').createStore;
var store = createStore(count);
// store里面的数据发生改变时,触发的回调函数
store.subscribe(function () {
console.log('the year is: ', store.getState().year);
});
// action: 触发state改变的惟一方法(按照redux的设计思路)
var action1 = { type: 'add' };
var action2 = { type: 'add' };
var action3 = { type: 'sub' };
// 改变store里面的方法
store.dispatch(action1); // 'the year is: 2016 store.dispatch(action2); // 'the year is: 2017
store.dispatch(action3); // 'the year is: 2016复制代码
2.2 挖掘createStore实现
为了说明主要问题,仅列出其中的关键代码,所有代码,能够点击 这里 阅读。
a首先看createStore到底都返回的内容:
export default function createStore(reducer, initialState) {
...
return {
dispatch,
subscribe,
getState,
replaceReducer
}
}复制代码
每一个属性的含义是: - dispatch: 用于action的分发,改变store里面的state - subscribe: 注册listener,store里面state发生改变后,执行该listener - getState: 读取store里面的state - replaceReducer: 替换reducer,改变state修改的逻辑
b关键代码解析
export default function createStore(reducer, initialState) {
// 这些都是闭包变量
var currentReducer = reducer
var currentState = initialState
var listeners = []
var isDispatching = false;
// 返回当前的state
function getState() {
return currentState
}
// 注册listener,同时返回一个取消事件注册的方法
function subscribe(listener) {
listeners.push(listener)
var isSubscribed = true
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
var index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
// 经过action该改变state,而后执行subscribe注册的方法
function dispatch(action) {
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
listeners.slice().forEach(listener => listener())
return action
}
// 替换reducer,修改state变化的逻辑
function replaceReducer(nextReducer) {
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
// 初始化时,执行内部一个dispatch,获得初始state
dispatch({ type: ActionTypes.INIT })
}复制代码
若是还按照2.1的方式进行开发,那跟flux没有什么大的区别,须要手动解决不少问题,那redux如何将整个流程模板化(Boilerplate)呢?
随着应用愈来愈大,一方面,不能把全部的数据都放到一个reducer里面,另外一方面,为每一个reducer建立一个store,后续store的维护就显得比较麻烦。如何将两者统一块儿来呢?
3.1 demo入手
经过combineReducers将多个reducer合并成一个rootReducer: // 建立两个reducer: count year function count (state, action) { state = state || {count: 1} switch (action.type) { default: return state; } } function year (state, action) { state = state || {year: 2015} switch (action.type) { default: return state; } }
// 将多个reducer合并成一个
var combineReducers = require('./').combineReducers;
var rootReducer = combineReducers({
count: count,
year: year,
});
// 建立store,跟2.1没有任何区别
var createStore = require('./').createStore;
var store = createStore(rootReducer);
var util = require('util');
console.log(util.inspect(store));
//输出的结果,跟2.1的store在结构上不存在区别
// { dispatch: [Function: dispatch],
// subscribe: [Function: subscribe],
// getState: [Function: getState],
// replaceReducer: [Function: replaceReducer]
// }复制代码
3.2 源码解析combineReducers
// 高阶函数,最后返回一个reducer
export default function combineReducers(reducers) {
// 提出不合法的reducers, finalReducers就是一个闭包变量
var finalReducers = pick(reducers, (val) => typeof val === 'function')
// 将各个reducer的初始state均设置为undefined
var defaultState = mapValues(finalReducers, () => undefined)
// 一个总reducer,内部包含子reducer
return function combination(state = defaultState, action) {
var finalState = mapValues(finalReducers, (reducer, key) => {
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
return nextStateForKey
);
return hasChanged ? finalState : state
}
}复制代码
4.1 demo介绍
在2.1中,要执行state的改变,须要手动dispatch:
var action = { type: '***', payload: '***'};
dispatch(action);复制代码
手动dispatch就显得啰嗦了,那么如何自动完成呢?
var bindActionCreators = require('redux').bindActionCreators;
// 能够在具体的应用框架隐式进行该过程(例如react-redux的connect组件中)
bindActionCreators(action)复制代码
4.2 源码解析
// 隐式实现dispatch
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
return mapValues(actionCreators, actionCreator =>
bindAQctionCreator(actionCreator, dispatch)
)
}复制代码
5.1 插件使用demo
一个action能够是同步的,也多是异步的,这是两种不一样的状况, dispatch执行的时机是不同的:
// 同步的action creator, store能够默认实现dispatch
function add() {
return { tyle: 'add' }
}
dispatch(add());
// 异步的action creator,由于异步完成的时间不肯定,只能手工dispatch
function fetchDataAsync() {
return function (dispatch) {
requst(url).end(function (err, res) {
if (err) return dispatch({ type: 'SET_ERR', payload: err});
if (res.status === 'success') {
dispatch({ type: 'FETCH_SUCCESS', payload: res.data });
}
})
}
}复制代码
下面的问题就变成了,如何根据实际状况实现不一样的dispatch方法,也便是根据须要实现不一样的moddleware:
// 普通的dispatch建立方法
var store = createStore(reducer, initialState);
console.log(store.dispatch);
// 定制化的dispatch
var applyMiddleware = require('redux').applyMiddleware;
// 实现action异步的middleware
var thunk = requre('redux-thunk');
var store = applyMiddleware([thunk])(createStore);
// 通过处理的dispatch方法
console.log(store.dispatch);复制代码
5.2 源码解析
// next: 其实就是createStore
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
var store = next(reducer, initialState)
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 // 实现新的dispatch方法
}
}
}
// 再看看redux-thunk的实现, next就是store里面的上一个dispatch
function thunkMiddleware({ dispatch, getState }) {
return function(next) {
return function(action) {
typeof action === 'function' ? action(dispatch, getState) : next(action);
}
}
return next => action =>
typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}复制代码
6.1 基本使用
目前已经有现成的工具 react-redux 来实现两者的结合:
var rootReducers = combineReducers(reducers);
var store = createStore(rootReducers);
var Provider = require('react-redux').Provider;
// App 为上层的Component
class App extend React.Component{
render() {
return (
<Provier store={store}>
<Container />
</Provider>
);
}
}
// Container做用: 1. 获取store中的数据; 2.将dispatch与actionCreator结合起来
var connect = require('react-redux').connect;
var actionCreators = require('...');
// MyComponent是与redux无关的组件
var MyComponent = require('...');
function select(state) {
return {
count: state.count
}
}
export default connect(select, actionCreators)(MyComponent)复制代码
6.2 Provider – 提供store
React经过Context属性,能够将属性(props)直接给子孙component,无须经过props层层传递, Provider仅仅起到得到store,而后将其传递给子孙元素而已:
export default class Provider extends Component {
getChildContext() { // getChildContext: 将store传递给子孙component
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
componentWillReceiveProps(nextProps) {
const { store } = this
const { store: nextStore } = nextProps
if (store !== nextStore) {
warnAboutReceivingStore()
}
}
render() {
let { children } = this.props
return Children.only(children)
}
}复制代码
6.3 connect – 得到store及dispatch(actionCreator)
connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,而后返回一个生产 Component 的函数(wrapWithConnect),而后再将真正的Component做为参数传入wrapWithConnect(MyComponent),这样就生产出一个通过包裹的Connect组件,该组件具备以下特色:
主要的代码逻辑:
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
// 从祖先Component处得到store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = { storeState: null }
// 对stateProps、dispatchProps、parentProps进行合并
this.updateState()
}
shouldComponentUpdate(nextProps, nextState) {
// 进行判断,当数据发生改变时,Component从新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 改变Component的state
this.store.subscribe(() = {
this.setState({
storeState: this.store.getState()
})
})
}
render() {
// 生成包裹组件Connect
return (
<WrappedComponent {...this.nextState} />
)
}
}
Connect.contextTypes = {
store: storeShape
}
return Connect;
}
}复制代码