在构建基于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)兴趣遍地都是,坚持和持之以恒才是稀缺的