在构建基于Redux
的React
应用的时候,我们往往需要使用React-Redux
做数据绑定,当Redux
中的数据刷新的时候,通知所有用到数据的组建刷新,React-Redux
提供了对React
组件的订阅发布的能力。
如果你熟悉Redux
的话,我们知道在Redux
中我们会维护一个全局的store
,store
中存储了当前App
中的数据,如果组件需要使用store
中的数据,有两种办法,第一种是一层一层将store
传下去,组件从props
上去取store
,然后取自己要用到的数据,很显然,这不是一个好的办法,如果存在一个嵌套很深的组件,那么store
会从最顶层传到最下面,这对于代码的维护和可读性并不是很友好;第二种办法就是使用React
提供的context
,在要使用全局状态的时候,从context
上去取,但是在React 16.4.0
之前,React
官方是不推荐使用context
的,原因在于,当context
中的值刷新的时候,是从上到下刷新的,如果中间有组件的shouldComponentUpdate
返回了false
,这个组件下面的组件就收不到更新后的值;而React-Redux
实现了订阅发布的模式,保证使用了store
的组件在数据更新的时候可以得到通知。
在React 16.4.0
之后官方将createContext
暴露出来了,以上的问题不会出现,但是是不是意味着,可以用context
来替代redux
呢?理论上是可以的,但是并不推荐这样做,因为在redux
的发展中,其生态系统是非常繁荣的,用Redux
能避免重复造轮子的窘境。
React
背后的思想是UI组件
是一个函数,一个大的Web App
就是一个由不同函数组成的大的函数;在处理这些组件时,我们遵循这样一个原则,根据职责将组件分为container组件
和presentational
组件,前者负责提供数据,后者接受数据,只负责展示;对于后者是对redux
无感的,只负责从props
上取数据,然后渲染。
react-redux
的connect
函数就提供了生成一个container
组件负责和store
进行交互,我们自己的组件就只用负责渲染就可以,数据的交互不用操心;connect
将数据的来源抽象出来,使得我们的组件复用性更好。
虽然React
在数据更新的时候有着很好的性能,每次React
刷新的时候是从父组件渲染到叶子组件,这里带来的问题就是,在子组件中数据没有发生变化,但是却重复渲染了,这种问题积少成多会导致性能问题,react redux
在其内部提供了对应的性能优化,当组件的数据没有发生变化的时候,这个组件并不会重新渲染。
react-redux
的核心API
就三个,经常使用到的就两个,分别是:
- connect
- Provider
- ConnectAdvanced
这里先来说说Provider
的用法,简单来说就是Provider
是一个容器组件,在你需要使用react redux
管理你的redux store
的时候,将你的App
用Provider
包起来,这样Provider
下的所有组件都可以通过connect
来获取store
上存储的数据了。
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import Counter from './demos/reactReduxDemo/counter';
import { counterStore } from './demos/reactReduxDemo/configStore';
class App extends Component {
render() {
return(
<Provider store={counterStore}> // 将store注入到Provider中
<Counter />
</Provider>
);
}
}
render(<App />, document.getElementById('root'));
如上,我们将store
注入到组件中,当数据发生变化的时候,会通知Provider
下的所有订阅了store
的组件更新。
在我们将store
注入之后,其下的组件如果需要使用store
中的数据,就需要使用connect
这个API
。
connect
其实就是一个高阶组件(传入一个组件,返回一个组件),这里connect
的接口参数如下:
connect(mapStateToProps, mapDispatchToProps, mergeProps, options)
这里的mapStateToProps
主要是用来从store
中获取数据的函数,这里的函数接口如下:
const mapStateToProps = (state, ownProps) => { ... }
如上这里的state
就是store
,在mapStateProps
中我们可以拿到整个应用的数据,在mapStateToProps
中可以拿到我们需要的数据,另外一个参数ownProps
是父组件传给子组件的props
。
另外一个函数mapDispatchToProps
,这里的函数的参数接口如下:
mapDispatchToProps = (dispatch, ownProps) => {}
如上这里的dispatch
就是redux
中的dispatch
,当我们需要改变state
的时候,就需要dispatch action
,在connect
中如果没有传入mapDispatchToProps
的话,会将dispatch
作为props
传入组件;mapDispatchToProps
的函数的返回值是一个object
,函数的value
就是一个action creator
:
const mapDispatchToProps = (dispatch) => {
return ({
foo: (payload) => { dispatch(myAcion(payload)) },
});
}
如上,这里的foo
在组件中可以通过props
访问到,这里最好将组件中的action creator
放在mapDispatchToProps
中,不要暴露dispatch
到组件中,避免将redux
暴露给组件中,这样组件对redux
是无感的,组件的复用性更高。
如下,如果一个组件需要从store
上获取数据,使用connect
将组件包起来:
connect()(MyComponent);
connect(
mapState,
null,
mergeProps,
options)(MyComponent);
如上的方式使用connect
,MyComponent
中的props
就可以拿到dispatch
。
就像上面说的,并不推荐直接将dispatch
直接传给组件,将action creator
包装成一个函数传给组件:
const mapStateToProps = (state, props) => {
......
};
const mapDispatchToProps = (dispatch) => {
return ({
increment: () => dispatch({type: ' INCREASE'}),
decrement: () => dispatch({type: 'DESCREASE'}),
}),
}
connect(mapStateToProps, mapDispatchToProps)(MyComponent);
ConnectAdvanced
是react-redux
提供的最后一个API
,这个API
是5.0之后提供的,这个方法主要是提供定制化的connect
,由开发者自己实现缓存和props check
,connect
内部就是使用connectAdvanced
实现,它也是一个高阶组件,其函数接口如下:
connectAdvanced(selectorFactory, connectOptions?)
如上,其中的selectorFactory
就是产生mapStateToProps + mapDispatchToProps
的工厂函数,起接口及其作用如下:
(dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
我们会在(dispatch, options) => {}
这一层将我们的action creator
函数以及最终传给组件的props
对象创建好,在第二层闭包nextState, nextOwnProps
中会在redux state
刷新的时候拿到最新的state
和props
,在这一层会做缓存的命中,如果最新的props
中变更的数据影响当前组件,会更新在第一层闭包中缓存好的最终需要传给组件的props
。
import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'
function selectorFactory(dispatch) {
let ownProps = {}
let result = {}
const actions = bindActionCreators(actionCreators, dispatch)
const addTodo = text => actions.addTodo(ownProps.userId, text)
return (nextState, nextOwnProps) => {
const todos = nextState.todos[nextOwnProps.userId]
const nextResult = { ...nextOwnProps, todos, addTodo }
ownProps = nextOwnProps
if (!shallowEqual(result, nextResult)) result = nextResult
return result
}
}
export default connectAdvanced(selectorFactory)(TodoApp)
兴趣遍地都是,坚持和持之以恒才是稀缺的