如何使用React—Redux

在构建基于ReduxReact应用的时候,我们往往需要使用React-Redux做数据绑定,当Redux中的数据刷新的时候,通知所有用到数据的组建刷新,React-Redux提供了对React组件的订阅发布的能力。

我们为什么需要React-Redux

如果你熟悉Redux的话,我们知道在Redux中我们会维护一个全局的storestore中存储了当前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 Redux给我们提供了哪些能力?

  • Container 组件和Presentational组件

React背后的思想是UI组件是一个函数,一个大的Web App就是一个由不同函数组成的大的函数;在处理这些组件时,我们遵循这样一个原则,根据职责将组件分为container组件presentational组件,前者负责提供数据,后者接受数据,只负责展示;对于后者是对redux无感的,只负责从props上取数据,然后渲染。

react-reduxconnect函数就提供了生成一个container组件负责和store进行交互,我们自己的组件就只用负责渲染就可以,数据的交互不用操心;connect将数据的来源抽象出来,使得我们的组件复用性更好。

  • React-Redux的性能优化

虽然React在数据更新的时候有着很好的性能,每次React刷新的时候是从父组件渲染到叶子组件,这里带来的问题就是,在子组件中数据没有发生变化,但是却重复渲染了,这种问题积少成多会导致性能问题,react redux在其内部提供了对应的性能优化,当组件的数据没有发生变化的时候,这个组件并不会重新渲染。

如何使用React Redux

react-redux的核心API就三个,经常使用到的就两个,分别是: - connect - Provider - ConnectAdvanced

这里先来说说Provider的用法,简单来说就是Provider是一个容器组件,在你需要使用react redux管理你的redux store的时候,将你的AppProvider包起来,这样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这个APIconnect其实就是一个高阶组件(传入一个组件,返回一个组件),这里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);

如上的方式使用connectMyComponent中的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);

ConnectAdvancedreact-redux提供的最后一个API,这个API是5.0之后提供的,这个方法主要是提供定制化的connect,由开发者自己实现缓存和props checkconnect内部就是使用connectAdvanced实现,它也是一个高阶组件,其函数接口如下:

	connectAdvanced(selectorFactory, connectOptions?)

如上,其中的selectorFactory就是产生mapStateToProps + mapDispatchToProps的工厂函数,起接口及其作用如下:

	(dispatch, options) => (nextState, nextOwnProps) => nextFinalProps

我们会在(dispatch, options) => {}这一层将我们的action creator函数以及最终传给组件的props对象创建好,在第二层闭包nextState, nextOwnProps中会在redux state刷新的时候拿到最新的stateprops,在这一层会做缓存的命中,如果最新的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)

兴趣遍地都是,坚持和持之以恒才是稀缺的


Written by@wang yao
I explain with words and code.

GitHub