十三、Redux

一、Redux简介

网址:https://redux.js.org/introduction/getting-started

1、如果UI层非常简单,没有很多互动,Redux就是不必要的,用了反而增加复杂性

2、如果项目的迭代变得越来越复杂,组件的数量和层级也变得越来越多、越来越深,此时组件间的数据通信就变得异常的复杂和低效,就需要使用Redux

操作原理图

 

1、store通过reducer创建了初始状态

2、组件通过store.getState()获取到store中保存的state挂载在了自己的状态上

3、用户产生了操作,调用了actions 的方法

4、actions的方法被调用,创建了带有标示性信息的action(描述对象)

5、actions将action通过调用store.dispatch方法发送到了reducer中

6、reducer接收到action并(根据标识信息判断之后)返回了新的state

7、store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知组件去重新获取state

二、三个核心概念

1、action

  • 动作的对象
  • 包含两个属性
    • type:标识属性,值为字符串,唯一(必要属性)
    • data:数据属性,值类型任意(可选属性)
1 {type: 'add', data: {name: '张三'}}

2、reducer

  • 用于初始化状态,加工状态
  • 纯函数,根据旧的state和action加工成新的state,两个参数
    • 参数一:preState,之前的状态
    • 参数二:action,动作对象

3、store

  • 将state、action、reducer联系在一起的对象
  • 如何得到此对象
    • import {createStore} from 'redux'
    • import reducer from './reducer'
    • const store = createStore(reducer)
  • 此处的功能
    • getState():得到state
    • dispatch(action):分发action,触发reducer调用,产生新的state
    • subscribe(listener):注册监听,产生新的state时自动调用

三、三大原则

1、单一数据源

整个应用的state(这个state不是组件中的state)被储存在一棵对象结构树中,并且这个对象结构只存在于唯一一个store中

2、state是只读的

唯一改变state的方法就是触发action,action是一个用于描述已发生事件的普通对象

 

3、使用纯函数来进行修改

  • 纯函数:一个函数的返回结果只受到其形参的影响
  • 为了描述action如何改变state tree ,我们需要编写reducer,reducer必须是纯函数,它接收先前的state和action,并返回新的state
  • 纯函数是函数式编程的概念,必须遵守以下一些约束。
    • 不得改写参数
    • 不能调用系统 I/O 的API
    • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

注意:由于reducer被要求是纯函数,所以reducer函数里面不能改变State,必须返回一个全新的数据

四、求和案例

一、纯react版

1、新建src/components/Count/index.jsx文件

 1 import React, { Component } from 'react'
 2 
 3 export default class Count extends Component{
 4   // 组件私有状态
 5   state = { count: 0 }
 6 
 7   //
 8   increment = () => {
 9     const {value} = this.selectNumber
10     const {count} = this.state
11     this.setState({count: count + value*1})
12   }
13   
14   //
15   decrement = () => {
16     const {value} = this.selectNumbr
17     const {count} = this.state
18     this.setState({count: count - value*1})
19   }
20   
21   // 奇数加
22   incrementOdd = () => {
23     const {value} = this.selectNumber
24     const {count} = this.state
25     if(count % 2 !== 0){
26         this.setState({count: count + value*1})   
27     }
28   }
29   
30   // 异步加
31   incrementAsync = () => {
32     const {value} = this.selectNumber
33     const {count} = this.state
34     setTimeout(() => {
35       this.setState({count: count + value*1})
36     },500)
37   }
38   
39   render(){
40     return (
41         <div>
42           <h2>当前求和为:{this.state.count}</h2>
43         <select ref={c => this.selectNumber = c}>
44           <option value="1">1</option>
45           <option value="2">2</option>
46           <option value="3">3</option>
47         </select>
48         <button onClick={this.increment}>+</button>
49         <button onClick={this.dcrement}>-</button>
50         <button onClick={this.incrementOdd}>和为奇数+</button>
51         <button onClick={this.incrementAsync}>异步+</button>
52       </div>
53     )
54   }
55 }

2、在App.jsx根组件中引入使用

 1 import React, { Component } from 'react'
 2 import Count from './component/Count'
 3 export default class App extends Component{
 4   render(){
 5     return(
 6       <div>
 7           <Count/>
 8       </div>
 9     )
10   }
11 }

二、redux精简版

1、安装redux

npm i -S redux

2、新建src/redux/store.js文件

 1 /**
 2  * 该文件专门用于暴露一个store对象,整个应用只有一个store对象
 3  */
 4 
 5 // 引入createStore,用于创建redux中的store对象
 6 import { createStore } from 'redux'
 7 // 引入为Count组件服务的reducer
 8 import countReducer from './count_reducer'
 9 // 暴露store
10 export default createStore(countReducer)

提示:为了方便调试redux(可选安装),建议去谷歌商店安装redux dev tools,在使用的时候需要参考其说明页面,参考其使用说明给代码做对应的调整:

1 // 创建仓库
2 const store = createStore(
3   reducer,
4   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
5 );
6 
7 // 如果需要使用安装的浏览器插件则需要添加
8 // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

打开浏览器调试工具就可以看到类似如下界面:

3、新建src/redux/count_reducer.js

 1 /**
 2  * 1、为Count服务的reducer,本质是个函数
 3  * 2、reducer函数接到两个参数,一个是之前的状态(preState),一个是动作对象(action)
 4  */
 5 const initState = 0
 6 
 7 export default function conuntReducer(preState = initState, action){
 8   // 从action对象中获取type,data
 9   const { type, data } = action
10   // 根据type决定如何加工数据
11   switch (type) {
12     case 'incrment':
13       return preState + data
14     case 'decrement':
15       return preState - data
16     default:
17       return preState
18   }
19 }

4、组件内使用src/components/Count/index.jsx

 1 import React, { Component } from 'react'
 2 // 引入store,用于获取redux中保存的状态
 3 import store from '../../redux/store'
 4 export default class Count extends Component{
 5   // 组件私有状态
 6   state = { carName: 'BMW' }
 7 
 8     // 组件挂载时监测redux中数据的变化,手动调用render
 9     componentDidMount(){
10     store.subscribe(() => {
11       this.setState({})
12     })
13   }
14     // 也可用下面这种监测方式
15   // constructor(props){
16   //   super(props)
17   //   // 获取初始数据
18   //   this.state = store.getState()
19   //   // 订阅数据(获取更新)
20   //   store.subscribe(() => {
21   //     this.setState(() => store.getState())
22   //     // this.setState((state) => (state = store.getState()))
23   //   });
24   // }
25 
26   //
27   increment = () => {
28     const {value} = this.selectNumber
29     store.dispatch({type: 'increment', data: value*1})
30   }
31   
32   //
33   decrement = () => {
34     const {value} = this.selectNumbr
35     store.dispatch({type: 'decrement', data: value*1})
36   }
37   
38   // 奇数加
39   incrementOdd = () => {
40     const {value} = this.selectNumber
41     const count = store.getState()
42     if(count % 2 !== 0){
43         store.dispatch({type: 'increment', data: value*1})   
44     }
45   }
46   
47   // 异步加
48   incrementAsync = () => {
49     const {value} = this.selectNumber
50     setTimeout(() => {
51       store.dispatch({type: 'increment', data: value*1})
52     },500)
53   }
54   
55   render(){
56     return (
57         <div>
58           <h2>我是Count组件</h2>
59         <h4>当前求和为:{store.getState()}</h4>
60         <select ref={c => this.selectNumber = c}>
61           <option value="1">1</option>
62           <option value="2">2</option>
63           <option value="3">3</option>
64         </select>
65         <button onClick={this.increment}>+</button>
66         <button onClick={this.dcrement}>-</button>
67         <button onClick={this.incrementOdd}>和为奇数+</button>
68         <button onClick={this.incrementAsync}>异步+</button>
69       </div>
70     )
71   }
72 }

5、简化Count组件(组件内监测改为入口文件index.js中监测,Count组件中删除componentDidMount订阅)

 1 import React, { Component } from 'react'
 2 // 引入store,用于获取redux中保存的状态
 3 import store from '../../redux/store'
 4 
 5 export default class Count extends Component{
 6   // 组件私有状态
 7   state = { carName: 'BMW' }
 8 
 9   //
10   increment = () => {
11     const {value} = this.selectNumber
12     store.dispatch({type: 'increment', data: value*1})
13   }
14   
15   //
16   decrement = () => {
17     const {value} = this.selectNumbr
18     store.dispatch({type: 'decrement', data: value*1})
19   }
20   
21   // 奇数加
22   incrementOdd = () => {
23     const {value} = this.selectNumber
24     const count = store.getState()
25     if(count % 2 !== 0){
26         store.dispatch({type: 'increment', data: value*1})   
27     }
28   }
29   
30   // 异步加
31   incrementAsync = () => {
32     const {value} = this.selectNumber
33     setTimeout(() => {
34       store.dispatch({type: 'increment', data: value*1})
35     },500)
36   }
37   
38   render(){
39     return (
40         <div>
41         <h2>我是Count组件</h2>
42         <h4>当前求和为:{store.getState()}</h4>
43         <select ref={c => this.selectNumber = c}>
44           <option value="1">1</option>
45           <option value="2">2</option>
46           <option value="3">3</option>
47         </select>
48         <button onClick={this.increment}>+</button>
49         <button onClick={this.dcrement}>-</button>
50         <button onClick={this.incrementOdd}>和为奇数+</button>
51         <button onClick={this.incrementAsync}>异步+</button>
52       </div>
53     )
54   }
55 }

在入口文件index.js进行订阅

 1 import React from 'react'
 2 import ReactDom from 'react-dom'
 3 import App from './App'
 4 import store from './redux/store'
 5 
 6 ReactDom.render(
 7     <App/>,
 8   document.getElementById('root')
 9 )
10 
11 // 监测redux中状态的改变,状态发生变化,重新渲染App组件
12 store.subscribe(() => {
13   ReactDom.render(
14     <App/>,
15     document.getElementById('root')
16   )
17 })

三、redux完整版

1、新建src/redux/count_action.js文件

 1 /**
 2  * 该文件专门为Count组件生成action对象
 3  */
 4 export const createIncrementAction = (data) => {
 5   return {type: 'increment', data}
 6 }
 7 // 上方简写
 8 // export const createIncrementAction = data => ({type: 'increment', data})
 9 
10 export const createDecrementAction = (data) => {
11   return {type: 'decrement', data}
12 }

2、Count组件内使用action

 1 import React, { Component } from 'react'
 2 // 引入store,用于获取redux中保存的状态
 3 import store from '../../redux/store'
 4 // 引入actionCreater,用于创建action对象
 5 import {
 6   createIncrementAction,
 7   createDecrementAction
 8 } from '../../redux/count_action'
 9 
10 export default class Count extends Component{
11   // 组件私有状态
12   state = { carName: 'BMW' }
13 
14     // componentDidMount(){
15   //   // 监测redux中数据的变化,手动调用render
16   //   store.subscribe(() => {
17   //     this.setState({})
18   //   })
19   // }
20 
21   //
22   increment = () => {
23     const {value} = this.selectNumber
24     store.dispatch(createIncrementAction(value*1))
25   }
26   
27   //
28   decrement = () => {
29     const {value} = this.selectNumbr
30     store.dispatch(createDecrementAction(value*1))
31   }
32   
33   // 奇数加
34   incrementOdd = () => {
35     const {value} = this.selectNumber
36     const count = store.getState()
37     if(count % 2 !== 0){
38         store.dispatch(createIncrementAction(value*1))   
39     }
40   }
41   
42   // 异步加
43   incrementAsync = () => {
44     const {value} = this.selectNumber
45     setTimeout(() => {
46       store.dispatch(createIncrementAction(value*1))
47     },500)
48   }
49   
50   render(){
51     return (
52         <div>
53         <h2>我是Count组件</h2>
54         <h4>当前求和为:{store.getState()}</h4>
55         <select ref={c => this.selectNumber = c}>
56           <option value="1">1</option>
57           <option value="2">2</option>
58           <option value="3">3</option>
59         </select>
60         <button onClick={this.increment}>+</button>
61         <button onClick={this.dcrement}>-</button>
62         <button onClick={this.incrementOdd}>和为奇数+</button>
63         <button onClick={this.incrementAsync}>异步+</button>
64       </div>
65     )
66   }
67 }

3、常量独立成单独文件src/redux/constant.js

1 /**
2  * 定义action对象中type类型的常量值
3  */
4 export const INCREMENT = 'increment'
5 export const DECREMENT = 'decrement'

4、在src/redux/count_reducer.js中使用

 1 /**
 2  * 1、为Count服务的reducer,本质是个函数
 3  * 2、reducer函数接到两个参数,一个是之前的状态(preState),一个是动作对象(action)
 4  */
 5 import { INCREMENT, DECREMENT } from './constant'
 6 
 7 const initState = 0
 8 
 9 export default function conuntReducer(preState = initState, action){
10   // 从action对象中获取type,data
11   const { type, data } = action
12   // 根据type决定如何加工数据
13   switch (type) {
14     case INCREMENT:
15       return preState + data
16     case DECREMENT:
17       return preState - data
18     default:
19       return preState
20   }
21 }

5、在src/redux/count_action.js中使用

 1 /**
 2  * 该文件专门为Count组件生成action对象
 3  */
 4 import { INCREMENT, DECREMENT } from './constant'
 5 
 6 export const createIncrementAction = (data) => {
 7   return {type: INCREMENT, data}
 8 }
 9 // 上方简写
10 // export const createIncrementAction = data => ({type: INCREMENT, data})
11 
12 export const createDecrementAction = (data) => {
13   return {type: DECREMENT, data}
14 }

四、redux异步action版

  • 通常情况下,action只是一个对象,不能包含异步操作

  • 这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用

  • 同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰

  • 使用中间件了之后,可以通过actionCreator异步编写action

  • 这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用

  • 同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量

 

常见异步库:

  • Redux-thunk

  • Redux-saga

  • Redux-effects

  • Redux-side-effects

  • Redux-loop

  • Redux-observable

  • ...

基于Promise的异步库:

  • Redux-promise

  • Redux-promises

  • Redux-simple-promise

  • Redux-promise-middleware

Redux官方出品的中间件库:redux-thunk

1、安装redux-thunk

1 npm i -S redux-thunk

2、修改src/redux/store.js文件

 1 /**
 2  * 该文件专门为用于暴露一个store对象,整个应用只有一个store对象
 3  */
 4 
 5 // 引入createStore,用于创建redux中的store对象
 6 import { createStore, applyMiddleware } from 'redux'
 7 // 引入为Count组件服务的reducer
 8 import countReducer from './count_reducer'
 9 // 引入redux-thunk,用于支持异步action
10 import thunk from 'redux-thunk'
11 // 暴露store
12 export default createStore(countReducer, applyMiddleware(thunk))

3、修改src/redux/count_action.js文件

 1 /**
 2  * 该文件专门为Count组件生成action对象
 3  */
 4 import { INCREMENT, DECREMENT } from './constant'
 5 
 6 // 同步action,就是指action的值为Object类型的一般对象
 7 export const createIncrementAction = data => ({type: INCREMENT, data})
 8 export const createDecrementAction = data => ({type: DECREMENT, data})
 9 
10 // 异步action,就是指action的值为函数,异步action中一般包含调用同步action
11 export const createIncrementAsyncAction = (data, time) => {
12   return (dispatch) => {
13     setTimeout(() => {
14       dispatch(createIncrementAction(data))
15     }, time)
16   }
17 }

4、组件内使用src/components/Count/index.jsx

 1 import React, { Component } from 'react'
 2 // 引入store,用于获取redux中保存的状态
 3 import store from '../../redux/store'
 4 // 引入actionCreater,用于创建action对象
 5 import {
 6   createIncrementAction,
 7   createDecrementAction,
 8   createIncrementAsyncAction
 9 } from '../../redux/count_action'
10 
11 export default class Count extends Component{
12   // 组件私有状态
13   state = { carName: 'BMW' }
14 
15     // componentDidMount(){
16   //   // 监测redux中数据的变化,手动调用render
17   //   store.subscribe(() => {
18   //     this.setState({})
19   //   })
20   // }
21 
22   //
23   increment = () => {
24     const {value} = this.selectNumber
25     store.dispatch(createIncrementAction(value*1))
26   }
27   
28   //
29   decrement = () => {
30     const {value} = this.selectNumbr
31     store.dispatch(createDecrementAction(value*1))
32   }
33   
34   // 奇数加
35   incrementOdd = () => {
36     const {value} = this.selectNumber
37     const count = store.getState()
38     if(count % 2 !== 0){
39         store.dispatch(createIncrementAction(value*1))   
40     }
41   }
42   
43   // 异步加
44   incrementAsync = () => {
45     const {value} = this.selectNumber
46     // setTimeout(() => {
47       store.dispatch(createIncrementAsyncAction(value*1, 500))
48     // },500)
49   }
50   
51   render(){
52     return (
53         <div>
54         <h2>我是Count组件</h2>
55         <h4>当前求和为:{store.getState()}</h4>
56         <select ref={c => this.selectNumber = c}>
57           <option value="1">1</option>
58           <option value="2">2</option>
59           <option value="3">3</option>
60         </select>
61         <button onClick={this.increment}>+</button>
62         <button onClick={this.dcrement}>-</button>
63         <button onClick={this.incrementOdd}>和为奇数+</button>
64         <button onClick={this.incrementAsync}>异步+</button>
65       </div>
66     )
67   }
68 }

五、react-redux基本使用

使用react-redux之后不需要在每个组件中手动的订阅数据的更新了,容器组件连接UI组件和redux

网址:https://react-redux.js.org/

1、安装react-redux(react-redux依赖于redux,使用react-redux需要先安装redux)

npm i -S react-redux

2、根组件App.jsx引入store

 1 import React, { Component } from 'react'
 2 import Count from './component/Count'
 3 import store from './redux/store'
 4 
 5 export default class App extends Component{
 6   render(){
 7     return(
 8       <div>
 9           <Count store={store}/>
10       </div>
11     )
12   }
13 }

3、新建Count组件的容器组件src/containers/Count/index.jsx

 1 /**
 2  * Count组件的容器组件
 3  */
 4 
 5 // 引入Count组件的UI组件
 6 import CountUI from '../../components/Count'
 7 import {
 8   createIncrementAction,
 9   createDecrementAction,
10   createIncrementAsyncAction
11 } from '../../redux/count_action'
12 
13 // 引入connect用于连接UI组件与redux
14 import { connect } from 'react-redux'
15 
16 /**
17  * 1、mapStateToProps函数返回一个对象
18  * 2、函数返回对象中的key作为传递给UI组件props中的key
19  * 3、mapStateToProps用于传递状态
20  */
21 function mapStateToProps(state){
22   return {count: state}
23 }
24 
25 /**
26  * 1、mapDispatchToProps函数返回一个对象
27  * 2、函数返回对象中的key作为传递给UI组件props中的key
28  * 3、mapDispatchToProps用于传递操作状态的方法
29  */
30 function mapDispatchToProps(dispatch){
31   return {
32     jia: number => dispatch(createIncrementAction(number)),
33     jian: number => dispatch(createDecrementAction(number)),
34     jiaAsync: (number, time) => dispatch(createIncrementAsyncAction(number,time)),
35   }
36 }
37 
38 // 使用connect()()创建并暴露一个Count的容器组件
39 export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

4、修改Count的UI组件src/components/Count/index.jsx

 1 import React, { Component } from 'react'
 2 
 3 export default class Count extends Component{
 4   // 组件私有状态
 5   state = { carName: 'BMW' }
 6 
 7   //
 8   increment = () => {
 9     const {value} = this.selectNumber
10     this.props.jia(value*1)
11   }
12   
13   //
14   decrement = () => {
15     const {value} = this.selectNumbr
16     this.props.jian(value*1)
17   }
18   
19   // 奇数加
20   incrementOdd = () => {
21     const {value} = this.selectNumber
22     if(count % 2 !== 0){
23         this.props.jia(value*1)   
24     }
25   }
26   
27   // 异步加
28   incrementAsync = () => {
29     const {value} = this.selectNumber
30     this.props.jiaAsync(value*1, 500)
31   }
32   
33   render(){
34     return (
35         <div>
36         <h2>我是Count组件</h2>
37         <h4>当前求和为:{this.props.count}</h4>
38         <select ref={c => this.selectNumber = c}>
39           <option value="1">1</option>
40           <option value="2">2</option>
41           <option value="3">3</option>
42         </select>
43         <button onClick={this.increment}>+</button>
44         <button onClick={this.dcrement}>-</button>
45         <button onClick={this.incrementOdd}>和为奇数+</button>
46         <button onClick={this.incrementAsync}>异步+</button>
47       </div>
48     )
49   }
50 }

需要注意:让redux调试工具正常使用,需要调整store代码,也可参考Redux开发者工具引入使用redux-devtools-extension(使用方法参考第七小节)

 1 /**
 2  * 1、compose用于redux调试工具
 3  */
 4 import { createStore, applyMiddleware, compose } from 'redux'
 5 import thunk from 'redux-thunk'
 6 
 7 // 引入reducer
 8 import countReducer from './reducers/count_reducer'
 9 
10 // 使redux调试工具生效
11 const composeEnhancers =
12  (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
13 
14 export default createStore(
15   countReducer,
16   composeEnhancers(applyMiddleware(thunk))
17 )

六、react-redux使用优化

1、react-redux优化一

修改src/containers/Count/index.jsx文件

 1 /**
 2  * Count组件的容器组件
 3  */
 4 
 5 // 引入Count组件的UI组件
 6 import CountUI from '../../components/Count'
 7 import {
 8   createIncrementAction,
 9   createDecrementAction,
10   createIncrementAsyncAction
11 } from '../../redux/count_action'
12 
13 // 引入connect用于连接UI组件与redux
14 import { connect } from 'react-redux'
15 
16 /**
17  * 1、mapStateToProps函数返回一个对象
18  * 2、函数返回对象中的key作为传递给UI组件props中的key
19  * 3、mapStateToProps用于传递状态
20  */
21 const mapStateToProps = state => ({count: state})
22 
23 /**
24  * 1、mapDispatchToProps函数返回一个对象
25  * 2、函数返回对象中的key作为传递给UI组件props中的key
26  * 3、mapDispatchToProps用于传递操作状态的方法
27  */
28 const mapDispatchToProps = dispatch => ({
29   jia: number => dispatch(createIncrementAction(number)),
30   jian: number => dispatch(createDecrementAction(number)),
31   jiaAsync: (number, time) => dispatch(createIncrementAsyncAction(number,time)),
32 })
33 
34 // 使用connect()()创建并暴露一个Count的容器组件
35 export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

2、redux-redux优化二

修改src/containers/Count/index.jsx文件

 1 /**
 2  * Count组件的容器组件
 3  */
 4 
 5 // 引入Count组件的UI组件
 6 import CountUI from '../../components/Count'
 7 import {
 8   createIncrementAction,
 9   createDecrementAction,
10   createIncrementAsyncAction
11 } from '../../redux/count_action'
12 
13 // 引入connect用于连接UI组件与redux
14 import { connect } from 'react-redux'
15 
16 // 使用connect()()创建并暴露一个Count的容器组件
17 export default connect(
18   state => ({count: state}),
19   
20   // mapDispatchToProps的一般写法
21   // dispatch => ({
22   //   jia: number => dispatch(createIncrementAction(number)),
23   //   jian: number => dispatch(createDecrementAction(number)),
24   //   jiaAsync: (number, time) => dispatch(createIncrementAsyncAction(number,time)),
25   // })
26   
27   // mapDispatchToProps的简写
28   {
29       jia: createIncrementAction,
30       jian: createDecrementAction,
31       jiaAsync: createIncrementAsyncAction
32   }
33 )(CountUI)

3、react-redux优化三

1、修改入口文件index,js

 1 import React from 'react'
 2 import ReactDom from 'react-dom'
 3 import App from './App'
 4 import store from './redux/store'
 5 import { Provider } from 'react-redux'
 6 
 7 ReactDom.render(
 8   <Provider store={store}>
 9         <App/>
10   </Provider>,
11   document.getElementById('root')
12 )

2、将Count的UI组件整合到Count的容器组件中

 1 /**
 2  * Count的UI组件+容器组件
 3  */
 4 
 5 import React, { Component } from 'react'
 6 import {
 7   createIncrementAction,
 8   createDecrementAction,
 9   createIncrementAsyncAction
10 } from '../../redux/count_action'
11 // 引入connect用于连接UI组件与redux
12 import { connect } from 'react-redux'
13 
14 class Count extends Component{
15   // 组件私有状态
16   state = { carName: 'BMW' }
17 
18   //
19   increment = () => {
20     const {value} = this.selectNumber
21     this.props.jia(value*1)
22   }
23   
24   //
25   decrement = () => {
26     const {value} = this.selectNumbr
27     this.props.jian(value*1)
28   }
29   
30   // 奇数加
31   incrementOdd = () => {
32     const {value} = this.selectNumber
33     if(count % 2 !== 0){
34         this.props.jia(value*1)   
35     }
36   }
37   
38   // 异步加
39   incrementAsync = () => {
40     const {value} = this.selectNumber
41     this.props.jiaAsync(value*1, 500)
42   }
43   
44   render(){
45     return (
46         <div>
47         <h2>我是Count组件</h2>
48         <h4>当前求和为:{this.props.count}</h4>
49         <select ref={c => this.selectNumber = c}>
50           <option value="1">1</option>
51           <option value="2">2</option>
52           <option value="3">3</option>
53         </select>
54         <button onClick={this.increment}>+</button>
55         <button onClick={this.dcrement}>-</button>
56         <button onClick={this.incrementOdd}>和为奇数+</button>
57         <button onClick={this.incrementAsync}>异步+</button>
58       </div>
59     )
60   }
61 }
62 
63 // 使用connect()()创建并暴露一个Count的容器组件
64 export default connect(
65     state => ({count: state}),
66   {
67     jia: createIncrementAction,
68       jian: createDecrementAction,
69       jiaAsync: createIncrementAsyncAction
70   }
71 )

七、实现两个组件的数据共享

1、action和reducer集中管理

1、将src/redux/count_action.jscount_action.js更名为count.js并放入新建的src/redux/actions目录下

2、将src/redux/count_action.jscount_reducer.js更名为count.js并放入新建的src/redux/reducers目录下

2、新建Person组件

1、新建Person组件src/containers/Person/index.jsx

1 import React, { Component } from 'react'
2 
3 export default class Person extends Component{
4   render(){
5     return (
6         Person...
7     )
8   }
9 }

2、在根组件App.jsx中使用

 1 import React, { Component } from 'react'
 2 import Count from './containers/Count'
 3 import Person from './containers/Person'
 4 
 5 export default class App extends Component{
 6   render(){
 7     return (
 8         <div>
 9           <Count/>
10           <hr/>
11         <Person/>
12       </div>
13     )
14   }
15 }

3、新建Person组件的常量src/redux/constant.js

1 /**
2  * 定义action对象中type类型的常量值
3  */
4 export const INCREMENT = 'increment'
5 export const DECREMENT = 'decrement'
6 export const ADD_PERSON = 'add_person'

4、新建Person组件的reducer文件src/redux/reducers/person.js

 1 import { ADD_PERSON } from '../constant'
 2 
 3 const initState = [{id: '001', name: 'tom', age: 18}]
 4 
 5 esport default function personReducer(preState = initState, action){
 6   const { type, data } = action
 7   switch(type){
 8     case ADD_PERSON:
 9       return [data, ...preState]
10     default:
11       return preState
12   }
13 }

5、新建Person组件的action文件src/redux/actions/person.js

1 import { ADD_PERSON } from '../constant'
2 
3 export const createAddPersonAction = personObj => ({type: ADD_PERSON, data: personObj})

6、修改store文件,将reducer进行合并src/reudx/store.js

 1 /**
 2  * 该文件专门为用于暴露一个store对象,整个应用只有一个store对象
 3  */
 4 
 5 // 引入createStore,用于创建redux中的store对象
 6 import { createStore, applyMiddleware, combineReducers } from 'redux'
 7 // 引入为Count组件服务的reducer
 8 import countReducer from './reducers/count'
 9 // 引入为Person组件服务的reducer
10 import personReducer from './reducers/person'
11 // 引入redux-thunk,用于支持异步action
12 import thunk from 'redux-thunk'
13 
14 // 暴露store
15 export default createStore(
16   combineReducers({
17     he: countReducer,
18     rens:personReducer
19   }),
20   applyMiddleware(thunk)
21 )

引入使用redux-devtools-extension,使redux开发者工具生效

 1 /**
 2  * 该文件专门为用于暴露一个store对象,整个应用只有一个store对象
 3  */
 4 
 5 // 引入createStore,用于创建redux中的store对象
 6 import { createStore, applyMiddleware, combineReducers } from 'redux'
 7 // 引入为Count组件服务的reducer
 8 import countReducer from './reducers/count'
 9 // 引入为Person组件服务的reducer
10 import personReducer from './reducers/person'
11 // 引入redux-thunk,用于支持异步action
12 import thunk from 'redux-thunk'
13 // 引入redux-devtools-extension
14 import { composeWithDevTools } from 'redux-devtools-extension'
15 
16 // 暴露store
17 export default createStore(
18   combineReducers({
19     he: countReducer,
20     rens:personReducer
21   }),
22   composeWithDevTools(applyMiddleware(thunk))
23 )

3、实现数据共享

1、修改Person组件src/containers/Person/index.jsx

 1 import React, { Component } from 'react'
 2 // 安装并使用nanoid
 3 import { nanoid } from 'nanoid'
 4 import { createAddPersonAction } from '../../redux/actions/person'
 5 import { connect } from 'react-redux'
 6 
 7 class Person extends Component{
 8   addPerson = () => {
 9     const name = this.nameNode.value
10     const age = this.ageNode.value
11     const personObj = {id: nonoid(), name, age}
12     this.props.jiayiren(personObj)
13     this.nameNode.value = ''
14     this.ageNode.value = ''
15   }
16   render(){
17     return (
18         <div>
19           <h2>我是Person组件,上方和为:{this.props.he}</h2>
20         <input ref={c => this.nameNode = c} type="text" placeholder="输入名字"/>
21         <input ref={c => this.ageNode = c} type="text" placeholder="输入年龄"/>
22         <button onClick={this.addPerson}>添加</button>
23         <ul>
24             {
25             this.props.person.map(p => {
26               return <li key={p.id}>{p.name}--{p.age}</li>
27             })
28           }
29         </ul>
30       </div>
31     )
32   }
33 }
34 export default connect(
35     state = ({
36     person: state.rens,
37     he: state.he
38   }),
39   { jiayiren: createAddPersonAction }
40 )(Person)

2、修改Count组件src/containers/Count/index.jsx

 1 /**
 2  * Count的UI组件+容器组件
 3  */
 4 
 5 import React, { Component } from 'react'
 6 import {
 7   createIncrementAction,
 8   createDecrementAction,
 9   createIncrementAsyncAction
10 } from '../../redux/actions/count'
11 // 引入connect用于连接UI组件与redux
12 import { connect } from 'react-redux'
13 
14 class Count extends Component{
15   // 组件私有状态
16   state = { carName: 'BMW' }
17 
18   //
19   increment = () => {
20     const {value} = this.selectNumber
21     this.props.jia(value*1)
22   }
23   
24   //
25   decrement = () => {
26     const {value} = this.selectNumbr
27     this.props.jian(value*1)
28   }
29   
30   // 奇数加
31   incrementOdd = () => {
32     const {value} = this.selectNumber
33     if(count % 2 !== 0){
34         this.props.jia(value*1)   
35     }
36   }
37   
38   // 异步加
39   incrementAsync = () => {
40     const {value} = this.selectNumber
41     this.props.jiaAsync(value*1, 500)
42   }
43   
44   render(){
45     return (
46         <div>
47         <h2>我是Count组件</h2>
48         <h4>当前求和为:{this.props.count},下方人数是:{this.props.renshu}</h4>
49         <select ref={c => this.selectNumber = c}>
50           <option value="1">1</option>
51           <option value="2">2</option>
52           <option value="3">3</option>
53         </select>
54         <button onClick={this.increment}>+</button>
55         <button onClick={this.dcrement}>-</button>
56         <button onClick={this.incrementOdd}>和为奇数+</button>
57         <button onClick={this.incrementAsync}>异步+</button>
58       </div>
59     )
60   }
61 }
62 
63 // 使用connect()()创建并暴露一个Count的容器组件
64 export default connect(
65     state => ({
66     count: state.he,
67     renshu: state.rens.length
68   }),
69   {
70     jia: createIncrementAction,
71       jian: createDecrementAction,
72       jiaAsync: createIncrementAsyncAction
73   }
74 )(Count)

使用redux调试工具,也可在src/redux/store.js加入如下代码

 1 /**
 2  * 1、combineReducers用于合并多个reducer
 3  * 2、compose用于redux调试工具
 4  */ 
 5 import {
 6   createStore, applyMiddleware, combineReducers, compose
 7 } from 'redux'
 8 import thunk from 'redux-thunk'
 9 
10 // 引入reducer
11 import countReducer from './reducers/count_reducer'
12 import personReducer from './reducers/person_reducer'
13 
14 // 使redux调试工具生效
15 const composeEnhancers =
16  (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
17 
18 export default createStore(
19   combineReducers({
20     he: countReducer,
21     rens: personReducer
22   }),
23   composeEnhancers(applyMiddleware(thunk))
24 )

五、redux最终版代码封装

1、项目效果和文件路径

项目效果

文件路径

2、项目入口文件和根组件

项目入口文件index.js

 1 import React from 'react';
 2 import ReactDOM from 'react-dom';
 3 import App from './App';
 4 import store from './redux/store'
 5 import { Provider } from 'react-redux'
 6 
 7 ReactDOM.render(
 8   <Provider store={store}>
 9     <App />
10   </Provider>,
11   document.getElementById('root')
12 );

App.js

 1 import React, { Component } from 'react'
 2 import Count from './containers/Count'
 3 import Person from './containers/Person'
 4 
 5 export default class App extends Component {
 6   render() {
 7     return (
 8       <div>
 9         <Count />
10         <hr />
11         <Person/>
12       </div>
13     )
14   }
15 }

3、redux根文件

redux根文件:src/redux/store.js

 1 /**
 2  * 1、combineReducers用于合并多个reducer
 3  * 2、compose用于redux调试工具
 4  */ 
 5 import {
 6   createStore, applyMiddleware, combineReducers, compose
 7 } from 'redux'
 8 import thunk from 'redux-thunk'
 9 
10 // 引入reducer
11 import count from './reducers/count_reducer'
12 import persons from './reducers/person_reducer'
13 
14 // 使redux调试工具生效
15 const composeEnhancers =
16  (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
17 
18 export default createStore(
19   combineReducers({
20     count,
21     persons
22   }),
23   composeEnhancers(applyMiddleware(thunk))
24 )

4、action对象的常量文件

action对象的常量文件src/redux/constant.js

1 /**
2  * 定义action对象中的type类型常量值
3  */ 
4 export const INCREMENT = 'increment'
5 export const DECREMENT = 'decrement'
6 export const ADD_PERSON = 'add_person'

5、reducer文件

Count的reducer文件:src/redux/reducers/count_reducer.js

 1 /**
 2  * count的reducer文件
 3  */
 4 import { INCREMENT, DECREMENT } from '../constant'
 5 
 6 // 初始化状态
 7 const initState = 0
 8 
 9 // 加工状态
10 export default function countReducer(preState = initState, action) {
11   //获取action里的type,data
12   const { type, data } = action
13   // 根据type决定如何加工数据
14   switch (type) {
15     case INCREMENT:
16       return preState + data
17     case DECREMENT:
18       return preState - data
19     default:
20       return preState
21   }
22 }

Person的reducer文件:src/redux/reducers/person_reducer.js

 1 import { ADD_PERSON } from '../constant'
 2 
 3 const initState = [{ id: '01', name: '张三', age: 18 }]
 4 
 5 export default function personReducer(preState = initState, action) {
 6   const { type, data } = action
 7   switch (type) {
 8     case ADD_PERSON:
 9       return [data, ...preState]
10     default:
11       return preState
12   }
13 }

6、action文件

Count的action文件:src/redux/actions/count_action.js

 1 /**
 2  * action文件:为action生成action对象,定义修改规则
 3  */
 4 import { INCREMENT, DECREMENT } from '../constant'
 5 
 6 export const increment = (data) => {
 7   return { type: INCREMENT, data }
 8 }
 9 
10 export const decrement = (data) => {
11   return { type: DECREMENT, data }
12 }
13 
14 // 异步action,就是指action的值为函数
15 // 异步action一般会调用同步action
16 export const incrementAsync = (data, time) => {
17   return (dispatch) => {
18     setTimeout(() => {
19       dispatch(increment(data))
20     }, time);
21   }
22 }

Person的action文件:src/redux/actions/person_action.js

1 import { ADD_PERSON } from '../constant'
2 
3 // 导出任务清单
4 export const addPerson = (personObj) => ({
5   type: ADD_PERSON, data: personObj
6 })

7、组件内使用

Count组件内使用redux:src/containers/Count/index.jsx

 1 /**
 2  * Count容器组件+UI组件
 3  */
 4 import React, { Component } from 'react'
 5 // 引入action
 6 import {
 7   increment,
 8   decrement,
 9   incrementAsync
10 } from '@/redux/actions/count_action'
11 
12 // 引入connect,连接容器组件与UI组件
13 import { connect } from 'react-redux'
14 
15 // 定义UI组件
16 class CountUI extends Component {
17   state = {
18     carName: '组件自己的state'
19   }
20   increment = () => {
21     const { value } = this.selectNumber
22     this.props.increment(value*1)
23   }
24   decrement = () => {
25     const { value } = this.selectNumber
26     this.props.decrement(value*1)
27   }
28   incrementOdd = () => {
29     const { value } = this.selectNumber
30     if (this.props.count % 2 !== 0) {
31       this.props.increment(value*1)
32     }
33   }
34   incrementAsync = () => {
35     const { value } = this.selectNumber
36     this.props.incrementAsync(value*1, 1000)
37   }
38   render() {
39     return (
40       <div>
41         <h2>当前求和为:{this.props.count},下方总人数为{ this.props.personCount }</h2>
42         <select ref={c => this.selectNumber = c} name="" id="">
43           <option value="1">1</option>
44           <option value="2">2</option>
45           <option value="3">3</option>
46         </select>&nbsp;
47         <button onClick={this.increment}>+</button>&nbsp;
48         <button onClick={this.decrement}>-</button>&nbsp;
49         <button onClick={this.incrementOdd}>和为奇数加</button>&nbsp;
50         <button onClick={this.incrementAsync}>异步加</button>
51       </div>
52     )
53   }
54 }
55 
56 export default connect(
57   // 传递状态
58   state => ({
59     count: state.count,
60     personCount: state.persons.length
61   }),
62   // 传递操作状态的方法
63   {
64     increment,
65     decrement,
66     incrementAsync
67   }
68 )(CountUI)

Person组件内使用redux:src/containers/Person/index.jsx

 1 import React, { Component } from 'react'
 2 import { nanoid } from 'nanoid'
 3 import { connect } from 'react-redux'
 4 import {
 5   addPerson
 6 } from '@/redux/actions/person_action'
 7 
 8 class Person extends Component {
 9 
10   addPerson = () => {
11     const name = this.nameNode.value
12     const age = this.ageNode.value*1
13     const personObj = { id: nanoid(), name, age }
14     this.props.addPerson(personObj)
15   }
16 
17   render() {
18     return (
19       <div>
20         <h2>上方组件和为{ this.props.count }</h2>
21         <input ref={c => this.nameNode = c} type="text" placeholder="输入名字"/>
22         <input ref={c => this.ageNode = c} type="text" placeholder="输入年龄" />
23         <button onClick={this.addPerson}>填加</button>
24         <ul>
25           {
26             this.props.persons.map((p) => {
27               return <li key={ p.id }>{ p.name }---{ p.age }</li>
28             })
29           }
30         </ul>
31       </div>
32     )
33   }
34 }
35 
36 export default connect(
37   state => ({
38     persons: state.persons,
39     count: state.count
40   }),
41   {
42     addPerson
43   }
44 )(Person)

 

posted @ 2021-07-12 16:02  大米饭盖饭  阅读(107)  评论(0)    收藏  举报