一、参考文章,建议按顺序阅读
1、react-native中使用redux的原理分析及demo
https://www.jianshu.com/p/466639774e01
2、react-native项目中从零开始使用redux
https://www.jianshu.com/p/8fb7df931eea
3、关于react-redux中的connect用法介绍及原理解析【重点推荐】
https://www.jianshu.com/p/9873d4ccb891
4、react利用shouldComponentUpdate钩子函数优化react性能以及引入immutable库的必要性]
https://www.cnblogs.com/penghuwan/p/6707254.html
5、跟着例子一步步学习redux+react-redux【写的很好,重点推荐】
https://segmentfault.com/a/1190000012976767
6、看了我这篇RN你就入门了
https://www.jianshu.com/p/2a20c8485a90
7、【阮一峰】Redux 入门教程(一):基本用法【写的很好,重点推荐】
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
8、【阮一峰】Redux 入门教程(二):中间件与异步操作
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html
9、【阮一峰】Redux 入门教程(三):React-Redux 的用法
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html
以下文字摘自上面文章
一、 context
context就是这么一个东西,某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的 context 只有它的子组件能够访问。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Header from './header';
import Main from './main';
import './App.css';
class App extends Component {
static childContextTypes = {
themeColor :PropTypes.string
}
constructor () {
super()
this.state = {
themeColor : 'red'
}
}
getChildContext () {
return {
themeColor : this.state.themeColor
}
}
render () {
return (
)
}
}
export default App;
构造函数里面的内容其实就很好理解,就是往 state 里面初始化一个 themeColor 状态。getChildContext 这个方法就是设置 context 的过程,它返回的对象就是 context,所有的子组件都可以访问到这个对象。我们用 this.state.themeColor 来设置了 context 里面的 themeColor。
我们看看子组件怎么获取这个状态,修改 App 的孙子组件 Title:
//title.js
class Title extends Component {
static contextTypes = {
themeColor: PropTypes.string
}
render () {
return (
React.js 小书标题
)
}
}
一个组件可以通过 getChildContext 方法返回一个对象,这个对象就是子树的 context,提供 context 的组件必须提供 childContextTypes 作为 context 的声明和验证。
如果一个组件设置了 context,那么它的子组件都可以直接访问到里面的内容,它就像这个组件为根的子树的全局变量。任意深度的子组件都可以通过 contextTypes 来声明你想要的 context 里面的哪些状态,然后可以通过 this.context 访问到那些状态。
二、store
有了 appState 和 dispatch,现在我们把它们集中到一个地方,给这个地方起个名字叫做 store,然后构建一个函数 createStore,用来专门生产这种 state 和 dispatch 的集合,这样别的 App 也可以用这种模式了:
createStore 接受两个参数,一个是表示应用程序状态的 state;另外一个是 stateChanger,它来描述应用程序状态会根据 action 发生什么变化,其实就是相当于本节开头的 dispatch 代码里面的内容。
createStore 会返回一个对象,这个对象包含两个方法 getState 和 dispatch。getState 用于获取 state 数据,其实就是简单地把 state 参数返回。
dispatch 用于修改数据,和以前一样会接受 action,然后它会把 state 和 action 一并传给 stateChanger,那么 stateChanger 就可以根据 action 来修改 state 了。
function createStore (state, stateChanger) {
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
stateChanger(state, action)
listeners.forEach((listener) => listener())
}
return { getState, dispatch, subscribe }
}
我们在 createStore 里面定义了一个数组 listeners,还有一个新的方法 subscribe,可以通过 store.subscribe(listener) 的方式给 subscribe 传入一个监听函数,这个函数会被 push 到数组当中。每当 dispatch 的时候,监听函数就会被调用,这样我们就可以在每当数据变化时候进行重新渲染:
三、reducer
reducer (stateChanger )现在既充当了获取初始化数据的功能,也充当了生成更新数据的功能。如果有传入 state 就生成更新数据,否则就是初始化数据。
//function stateChanger (state, action) {
function reducer (state, action) {
if (!state) {
return {
title: {
text: 'this is title',
color: 'red',
},
content: {
text: 'this is content',
color: 'blue'
}
}
}
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
return {
...state,
title: {
...state.title,
text: action.text
}
}
case 'UPDATE_TITLE_COLOR':
return {
...state,
title: {
...state.title,
color: action.color
}
}
default:
return state
}
}
createStore 内部的 state 不再通过参数传入,而是一个局部变量 let state = null。createStore 的最后会手动调用一次 dispatch({}),dispatch 内部会调用 stateChanger,这时候的 state 是 null,所以这次的 dispatch 其实就是初始化数据了。createStore 内部第一次的 dispatch 导致 state 初始化完成,后续外部的 dispatch 就是修改数据的行为了。
我们给 stateChanger 这个玩意起一个通用的名字:reducer,reducer 是一个函数,细心的朋友会发现,它其实是一个纯函数(Pure Function)。
//function createStore (stateChanger) {
function createStore (reducer) {
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
//state = stateChanger(state, action)
state = reducer(state, action)
listeners.forEach((listener) => listener())
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
四、connect
connect的作用,就是将每个子组件中的从context中获取store的逻辑抽出来,方便任何子组件使用,增强代码复用性。
1、connect高阶函数
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {} // 防止 mapStateToProps 没有传入
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 防止 mapDispatchToProps 没有传入
this.setState({
allProps: {
// {...stateProps} 意思是把从store里面所需要的属性拿出来全部通过 `props` 方式传递进去
...stateProps,
...dispatchProps,
...this.props
}
})
}
render () {
// {... this.state.allProps} 意思是把从store里面的state、dispatch、自身的props合并成新包装的组件的props,全部通过 `props` 方式传递进去
return
}
}
return Connect
}
我们不需要知道 connect 是怎么和 context 打交道的,只要传一个 mapStateToProps 告诉它应该从store里面取哪些数据就可以了。
2、mapStateToProps
这个函数会接受 store.getState() 的结果作为参数,然后返回一个对象,这个对象是根据 state 生成的。mapStateTopProps 相当于告知了 Connect 应该如何去 store 里面取数据,然后可以把这个函数的返回结果传给被包装的组件:
我们在 Connect 组件的 constructor 里面初始化了 state.allProps,它是一个对象,用来保存需要传给被包装组件的所有的参数。生命周期 componentWillMount 会调用调用 _updateProps 进行初始化,然后通过 store.subscribe 监听数据变化重新调用 _updateProps。
为了让 connect 返回新组件和被包装的组件使用参数保持一致,我们会把所有传给 Connect 的 props 原封不动地传给 WrappedComponent。所以在 _updateProps 里面会把 stateProps 和 this.props 合并到 this.state.allProps 里面,再通过 render 方法把所有参数都传给 WrappedComponent。
mapStateToProps 也发生点变化,它现在可以接受两个参数了,我们会把传给 Connect 组件的 props 参数也传给它,那么它生成的对象配置性就更强了,我们可以根据 store 里面的 state 和外界传入的 props 生成我们想传给被包装组件的参数。
3、mapDispatchToProps
和 mapStateToProps 一样,它返回一个对象,这个对象内容会同样被 connect 当作是 props 参数传给被包装的组件。不一样的是,这个函数不是接受 state 作为参数,而是 dispatch,你可以在返回的对象内部定义一些函数,这些函数会用到 dispatch 来触发特定的 action。
在 _updateProps 内部,我们把store.dispatch 作为参数传给 mapDispatchToProps ,它会返回一个对象 dispatchProps。接着把 stateProps、dispatchProps、this.props 三者合并到 this.state.allProps 里面去,这三者的内容都会在 render 函数内全部传给被包装的组件。
这时候我们就可以重构 ThemeSwitch,让它摆脱 store.dispatch:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from './react-redux'
class ThemeSwitch extends Component {
static propTypes = {
themeColor: PropTypes.string,
onSwitchColor: PropTypes.func
}
handleSwitchColor (color) {
if (this.props.onSwitchColor) {
this.props.onSwitchColor(color)
}
}
render () {
return (
)
}
}
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor,
themeName: state.themeName,
fullName: `${state.firstName} ${state.lastName}`
...
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSwitchColor: (color) => {
dispatch({ type: 'CHANGE_COLOR', themeColor: color })
}
}
}
ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)
export default ThemeSwitch
五、Provider
做的事情也很简单,它就是一个容器组件,会把嵌套的内容原封不动作为自己的子组件渲染出来。它还会把外界传给它的 props.store 放到 context,这样子组件 connect 的时候都可以获取到。
如果没有Provider,就需要自己在根组件中写上:store放入context,获取store的逻辑。
export class Provider extends Component {
static propTypes = {
store: PropTypes.object,
children: PropTypes.any
}
static childContextTypes = {
store: PropTypes.object
}
getChildContext () {
return {
store: this.props.store
}
}
render () {
return (
{this.props.children}
)
}
}