一、从一个计数器开始
1.1 用 class 声明一个计数器
import React, { Component } from 'react'import ReactDOM from 'react-dom'import Computed from './Computed'class Counter extends Component {constructor (props, context) {super()this.state = {num: 0}}render () {return (<div className="container"><button onClick={() => this.setState({num: this.state.num + 1})}>+</button><span>{this.state.num}</span><button onClick={() => this.setState({num: this.state.num - 1})}>-</button><Computed num={this.state.num} /></div>)}}ReactDOM.render(<Counter />, document.getElementById('root'))
1.2 声明一个计算当前计数器的数据是奇数还是偶数的 Computed 组件
import React, { Component } from 'react'export default class Computed extends Component {render () {return (<div><h2>{this.props.num % 2 === 0 ? '偶数' : '奇数'}</h2></div>)}}
父子组件来回传,而且兄弟组件无能为力,所以我们需要使用 redux 全局托管数据
二、使用 redux
redux 是一种数据管理模式,它不仅可以用于 react 还可以配合其他库或者框架使用;我们把整个应用的状态交给 redux 托管,redux 会导出一个 store,其中包含获取状态的方法,以及变更状态的方法
2.1 安装 redux
yarn add redux --save
2.2 创建 store
2.2.1. 创建 store 需要使用 redux 的 createStore 方法,使用前需要导入:
import { createStore } from 'redux'
2.2.2. createStore 创建 store 需要 reducer函数
使用 redux 我们不能直接修改状态,我们需要定义修改状态的函数,这个函数称为 reducer
reducer 函数都会接收两个参数
- state 就是当前 redux 托管的数据对象,在创建 reducer 时给 state 设置的默认值就是 state 的初始值
 - action 是修改状态具体的动作以及修改状态需要的参数,是一个带有 type 字段的对象 { type: ‘ADD’, …payload } ,而 reducer 的作用就是根据不同的 action.type 返回一个新的状态对象
 
在定义 reducer 时,我们需要初始化需要交给 redux 托管的状态设置初始值;在创建reducer时给 state 设置的默认值就是 state 的初始值
- 示例:
 
function reducer(state = { num: 0 }, action) {// state 就是当前 redux 托管的数据对象,在创建 reducer 时给 state 设置的默认值就是 state 的初始值// action 是修改状态具体的动作以及修改状态需要的参数,是一个带有 type 字段的对象 { type: 'ADD', ...payload } ,而 reducer 的作用就是根据不同的 action.type 返回一个新的状态对象switch (action.type) {case 'ADD':return {num: state.num + action.amount}case 'MINUS':return {num: state.num - action.amount}}// 使用 reducer 首先需要返回一个默认状态return state}
2.2.3. 创建一个 store 的完整示例:
import { createStore } from 'redux'// 使用 redux 我们不能直接修改状态,我们需要定义修改状态的函数,这个函数称为 reducer// 一个用来管理状态的函数function reducer(state = { num: 0 }, action) {// state 就是当前 redux 托管的数据对象,在创建 reducer 时给 state 设置的默认值就是 state 的初始值// action 是修改状态具体的动作以及修改状态需要的参数,是一个带有 type 字段的对象 { type: 'ADD', ...payload } ,而 reducer 的作用就是根据不同的 action.type 返回一个新的状态对象switch (action.type) {case 'ADD':return {num: state.num + action.amount}case 'MINUS':return {num: state.num - action.amount}}// 使用 reducer 首先需要返回一个默认状态return state}// 创建 store,只需要把 reducer 传递给 createStorelet store = createStore(reducer)export default store
2.3 在组件中导入 store
有了 store,你可以:
- store.getState() 初始化 state
 - 当修改数据时需要 派发行动 dispatch action; store.dispatch({type: ‘ADD’, amount: 12}),dispatch 执行时传递的对象就是 action,action 对象会传递给 reducer 函数的第二个参数
 - 如果需要数据修改后更新视图,需要订阅这个数据发生变化后的事件,如果想修改视图就更新 state,在组件挂载的钩子中订阅
 - 订阅后会有取消订阅的需要,订阅函数会返回取消订阅的函数,在组件将要销毁的钩子中执行取消订阅的操作
 
2.3.1 导入
import store from '../store'
2.3.2 使用 store.getState() 初始化 state
....constructor (props, context) {super()this.state = {num: store.getState().num}}....
2.3.3 store.dispatch(actionObj) 修改状态
<button onClick={() => store.dispatch({type: 'ADD', amount: 1})}>+</button>
2.3.4 store.subscribe(callback) store 订阅状态更新后执行的回调,以便更新视图
componentDidMount () {// 订阅状态变化后做的事情this.unsub = store.subscribe(() => {this.setState({num: store.getState().num})})}componentWillUnmount () {// 组件销毁时取消订阅this.unsub()}
2.4 使用 redux 后的 Counter.js
import React, { Component } from 'react'import ReactDOM from 'react-dom'import Computed from './Computed'import store from '../store'window.__store = storeclass Counter extends Component {constructor (props, context) {super()this.state = {num: store.getState().num}}componentDidMount () {// 订阅状态变化后做的事情this.unsub = store.subscribe(() => {this.setState({num: store.getState().num})})}componentWillUnmount () {this.unsub()}render () {return (<div className="container"><button onClick={() => store.dispatch({type: 'ADD', amount: 1})}>+</button><span>{this.state.num}</span><button onClick={() => store.dispatch({type: 'MINUS', amount: 1})}>-</button><Computed num={this.state.num} /></div>)}}ReactDOM.render(<Counter />, document.getElementById('root'))
2.5 使用 redux 后的 Computed.js
import React, { Component } from 'react'import store from '../store'export default class Computed extends Component {constructor (props, context) {super()this.state = {num: store.getState().num}}componentDidMount () {this.unsub = store.subscribe(() => {this.setState({num: store.getState().num})})}componentWillUnmount () {this.unsub()}render () {return (<div><h2>{this.state.num % 2 === 0 ? '偶数' : '奇数'}</h2></div>)}}
三、提取 action 的 type
把 action 的 type 提取出来,作为宏
let ADD = 'ADD'let MINUS = 'MINUS'
对应修改 store 和 Counter.js
四、reducer 合并
- 现在有两个拥有独立功能的组件:Counter.js 和 Todo.js
 - redux 是一个单一的状态树,整个应用内所有的数据都要保存到一个 store 中,所以如果多个组件的状态,那么就会有多个 reducer,而 createStore 只能接收一个 reducer,此时就需要使用 reducer 合并
 - redux 中的 combineReducers 是用来整合状态的方法,它接收一个对象作为参数,会把多个 reducer 整合到一个中。
整合后返回一个新的 reducer ,我们在创建 store 的时候传入这个整合后的 reducer; - 同时状态也被整合,例如上面的 counter 和 todo 整合后的状态对象: { todo: {list: [], filter}, counter: {num: 0}}
 
4.1 整合后示例
import { createStore, combineReducers } from 'redux'// 把 action 的 type 提取出来,作为宏let ADD = 'ADD'let MINUS = 'MINUS'// 使用 redux 我们不能直接修改状态,我们需要定义修改状态的函数,这个函数称为 reducer// 一个用来管理状态的函数function counter(state = { num: 0 }, action) {// state 就是当前 redux 托管的数据对象,在创建reducer时给 state 设置的默认值就是 state 的初始值// action 是修改状态具体的动作以及修改状态需要的参数,是一个带有 type 字段的对象 { type: 'ADD', ...payload } ,而 reducer 的作用就是根据不同的 action.type 返回一个新的状态对象switch (action.type) {case ADD:return {num: state.num + action.amount}case MINUS:return {num: state.num - action.amount}}// 使用 reducer 首先需要返回一个默认状态return state}let todoState = {list: [],filter: 'all'}function todo(state = todoState, action) {switch (action.type) {case 'ADD_TODO':return {...state,list: [ // 这个 list 会覆盖上面 ... 出来的 list...state.list,action.content]}}return state}// combineReducers 是用来整合状态的方法,它接收一个对象作为参数,会把多个 reducer 整合到一个中// 整合后的 返回一个新的 reducer ,同时 状态也被整合,例如上面的 counter 和 todo 整合后的状态对象: { todo: {list: [], filter}, counter: {num: 0}}let combinedReducer = combineReducers({todo: todo,counter: counter})let store = createStore(combinedReducer)export default store
4.2 使用整合后的 store
整合后的 store 带有命名空间,在组件中使用的时候需要通过对应的命名空间获取状态;
4.2.1 Counter.js
import React, { Component } from 'react'import ReactDOM from 'react-dom'import Computed from './Computed'import Todo from './Todo'import store from '../store'// 把 action 的 type 提取出来,作为宏let ADD = 'ADD'let MINUS = 'MINUS'window.__store = storeclass Counter extends Component {constructor (props, context) {super()this.state = {num: store.getState().counter.num}}componentDidMount () {// 订阅状态变化后做的事情this.unsub = store.subscribe(() => {this.setState({num: store.getState().counter.num})})}componentWillUnmount () {this.unsub()}render () {return (<div className="container"><button onClick={() => store.dispatch({type: ADD, amount: 1})}>+</button><span>{this.state.num}</span><button onClick={() => store.dispatch({type: MINUS, amount: 1})}>-</button><Computed /><Todo /></div>)}}ReactDOM.render(<Counter />, document.getElementById('root'))
4.2.2 Computed.js
import React, { Component } from 'react'import store from '../store'export default class Computed extends Component {constructor (props, context) {super()this.state = {num: store.getState().counter.num}}componentDidMount () {this.unsub = store.subscribe(() => {this.setState({num: store.getState().counter.num})})}componentWillUnmount () {this.unsub()}render () {return (<div><h2>{this.state.num % 2 === 0 ? '偶数' : '奇数'}</h2></div>)}}
4.2.3 Todo.js
import React, { Component } from 'react'import store from '../store'export default class Todo extends Component {constructor (props, context) {super()this.state = {todos: store.getState().todo}}componentDidMount () {store.subscribe(() => {this.setState({todos: store.getState().todo})})}render () {return (<div className="container"><p><input type="text" onKeyDown={(e) => {if (e.keyCode === 13) {store.dispatch({type: 'ADD_TODO',content: e.target.value})e.target.value = ''}}}/></p><ul>{this.state.todos.list.map((item, index) => <li key={index}>{item}</li>)}</ul></div>)}}
五、actionCreator 动作创建器
写一个函数专门生成 dispatch 需要的 action 对象,这个函数称为 action-creator
示例:
import * as Types from '../action-type'function add(amount) {return {type: Types.ADD,amount}}function minus(amount) {return {type: Types.MINUS,amount}}
- 有了 action-creator 以后,在 dispatch 时只需要调用 action-creator 并且传入 payload 即可;
 - 示例:
 
// add 和 minus 就是 action-creator,而 1 是传递给 上面的 amount,即 payload<button onClick={() => store.dispatch(add(1))}>+</button><span>{this.state.num}</span><button onClick={() => store.dispatch(minus(1))}>-</button>
六、文件拆分
React 的模块化特性很强,对应着 redux 的使用也需要拆分成模块化达到代码解耦的目的,上例中的 store 我们拆分文件后的结构如下;
6.1 文件结构
store│ index.js 导出 store│├─action action-creator│ counter.js│ todo.js│├─action-type action 定义│ index.js│└─reducer 存放 reducercounter.jstodo.js
/store/index.js
import {createStore, combineReducers} from 'redux'import { counter } from './reducer/counter'import { todo } from './reducer/todo'let combinedReducer = combineReducers({todo: todo,counter: counter})let store = createStore(combinedReducer)export default store
store/action/counter.js
import * as Types from '../action-type'function add(amount) {return {type: Types.ADD,amount}}function minus(amount) {return {type: Types.MINUS,amount}}export { add, minus }
store/action/todo.js
import * as Types from '../action-type'function addTodo(content) {return {type: Types.ADD_TODO,content}}export { addTodo }
store/action-types/index
export const ADD = 'ADD'export const MINUS = 'MINUS'export const ADD_TODO = 'ADD_TODO'
store/reducer/counter
import * as Types from '../action-type'export function counter(state = {num: 0}, action) {switch (action.type) {case Types.ADD:return {num: state.num + action.amount}case Types.MINUS:return {num: state.num - action.amount}}// 使用 reducer 首先需要返回一个默认状态return state}
/store/reducer/todo.js
import * as Types from '../action-type'let todoState = {list: [],filter: 'all'}export function todo(state = todoState, action) {switch (action.type) {case Types.ADD_TODO:return {...state,list: [ // 这个 list 会覆盖上面 ... 出来的 list...state.list,action.content]}}return state}
七、react-redux
上面虽然实现了数据的统一管理,但是代码组织很繁琐,为了解决这个问题我们使用一个另一个工具,react-redux;
7.1 安装 react-redux
yarn add react-redux --save
7.2 react-redux 的 Provider
react-redux 提供了一个 Provider 组件,通过 Provider 组件将 store 引入到组件树中,可以简化在组件里初始化状态,修改组件需要派发事件,然后还需要订阅更新;
- 使用 Provider 在主入口 index.js 引入 store,通过 prop 把 store 作为属性,传给 Provider 组件
 
7.3 react-redux 的 connect 方法
react-redux 提供了一个 connect 方法;通过 connect 改造组件,经过 connect 改造,组件中的数据以及派发数据的方法全部通过 props 来实现;
7.4 示例:
7.4.1 src/index.js
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import { Provider } from 'react-redux'import store from './store'import Counter from './components/Counter'ReactDOM.render(<Provider store={store}><Counter /></Provider>, document.getElementById('root'))
7.4.2 使用 connect 导出一个连接后的组件
- 7.3.2.1 使用 connect 首先从 react-redux 导出 connect;
 
import { connect } from 'react-redux'
- 7.3.2.2 使用 connect 导出的是连接后的组件,connect 可以执行两次:
 
connect 是一个高阶函数,形如let connect = > (m1, m2) => (component) => {}
- 第一次执行传入两个回调函数:
- mapStateToProps 把 store 的状态映射成为组件的 props
 - mapDispatchToProps 把 dispatch 映射为 prop
 
 - 第二次执行,传入组件名 Counter
 
使用 connect 后,对应组件内的使用数据和修改数据的方式也需要调整,方法和数据都要从 prop 上获取
- Counter.js 示例:
 
import React, { Component } from 'react'import ReactDOM from 'react-dom'import Computed from './Computed'import Todo from './Todo'import { add, minus} from '../store/action/counter'import { connect } from 'react-redux'// 使用 react-redux 需要导出一个连接后的组件class Counter extends Component {render () {return (<div className="container"><button onClick={() => this.props.add(1)}>+</button><span>{this.props.num}</span><button onClick={() => this.props.minus(1)}>-</button><Computed /><Todo /></div>)}}// react-redux 的 connect 方法接收两个参数,let connect = > (m1, m2) => (component) => {}// 第一次执行传入两个回调函数:// 1. mapStateToProps 把 store 的状态映射称为组件的 props// 2. mapDispatchToProps 把 dispatch 映射为 prop// 第二次执行,传入组件名 Counterlet mapStateToProps = (state) => {// mapStateToProps 的参数 state 就是合并后的状态对象// 在 mapStateToProps 函数中要返回一个对象,这些对象中是数据都会成为对应组件的 propsreturn {num: state.counter.num}}let mapDispatchToProps = (dispatch) => {// mapDispatchToProps 的参数是 dispatch 就是派发行为的 store.dispatch 方法// mapDispatchToProps 需要返回一个对象,这个对象中return {add: (amount) => dispatch(add(amount)),minus: (amount) => dispatch(minus(amount))}}export default connect(mapStateToProps, mapDispatchToProps)(Counter)
7.5 mapStateToProps 和 mapDispatchToProps 有简便写法
- mapStateToProps 可以写成一个 箭头函数,在箭头函数中使用 … 运算符展开某个状态对象
 - mapDispatchToProps 可以传入一个 actionCreator 对象,示例
 
export default connect(state => ({...state.counter}), {add, minus})(Counter)
【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】
