一个实例教会你如何用redux

152 阅读11分钟

1. redux理解

1.1. 学习文档

  1. 英文文档: redux.js.org/
  2. 中文文档: www.redux.org.cn/
  3. Github: github.com/reactjs/red…

1.2. redux是什么

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。
  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  3. 作用: 集中式管理react应用中多个组件共享的状态。

1.3. 什么情况下需要使用redux

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  2. 一个组件需要改变另一个组件的状态(通信)。
  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

1.4. redux工作流程

image.png

2. redux的三个核心概念

2.1. action

  1. 动作的对象

  2. 包含2个属性

    type:标识属性, 值为字符串, 唯一, 必要属性

    data:数据属性, 值类型任意, 可选属性

  3. 例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

2.2. reducer

  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数。

2.3. store

  1. 将state、action、reducer联系在一起的对象

  2. 如何得到此对象?

    1. import {createStore} from 'redux'
    2. import reducer from './reducers'
    3. const store = createStore(reducer)
  3. 此对象的功能?

    1. getState(): 得到state
    2. dispatch(action): 分发action, 触发reducer调用, 产生新的state
    3. subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

3. redux的核心API

3.1. createstore()

作用:创建包含指定reducer的store对象

3.2. store对象

  1. 作用: redux库最核心的管理对象

  2. 它内部维护着:

    1. state
    2. reducer
  3. 核心方法:

    1. getState()
    2. dispatch(action)
    3. subscribe(listener)
  4. 具体编码:

    1. store.getState()
    2. store.dispatch({type:'INCREMENT', data})
    3. store.subscribe(render)

3.3. applyMiddleware()

作用:应用上基于redux的中间件(插件库),将所有中间件组成一个数组,依次执行。

中间件根本原理:对store.dispatch进行改造。

中间件出现的原因:由于很多时候执行dispatch并不仅仅是立即去更新reducer,这时需要执行其他函数来满足项目需求,这些函数就是中间件,最后执行过一系列中间件后再去执行reducer

3.4. combineReducers()

作用:合并多个reducer函数

4. 使用redux编写应用

效果

redux.gif

5. redux异步编程

5.1理解:

  1. redux默认是不能进行异步处理的,
  2. 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)

5.2. 使用异步中间件

npm install --save redux-thunk

6. react-redux

image.png

6.1. 理解

  1. 一个react插件库
  2. 专门用来简化react应用中使用redux

6.2. react-Redux将所有组件分成两大类

  1. UI组件

    1. 只负责 UI 的呈现,不带有任何业务逻辑
    2. 通过props接收数据(一般数据和函数)
    3. 不使用任何 Redux 的 API
    4. 一般保存在components文件夹下
    import React, { Component } from 'react'
    
    export default class Count extends Component {
    
        increment=()=>{
            const{value}=this.selectNumber
            this.props.jia(value*1)
        }
        decrement=()=>{
            const{value}=this.selectNumber
            this.props.jian(value*1)
        }
        incrementIfOdd=()=>{
            const{value}=this.selectNumber
            if(this.props.count % 2 !==0){
                this.props.jia(value*1)
            }
        }
        incrementAsync=()=>{
            const{value}=this.selectNumber
            this.props.jiaAsync(value*1,3000)
        }
        render() {
            return (
                <div>
                    <h1>当前求和为:{this.props.count}</h1>
                    <select ref={c=>this.selectNumber=c}>
                        <option value="1">1</option>
                        <option value="2">2</option>
                        <option value="3">3</option>
                    </select>&nbsp;
                    <button onClick={this.increment}>+</button>&nbsp;
                    <button onClick={this.decrement}>-</button>&nbsp;
                    <button onClick={this.incrementIfOdd}>当前求和为奇数时+</button>&nbsp;
                    <button onClick={this.incrementAsync}>异步+</button>&nbsp;
                </div>
            )
        }
    }
    
  2. 容器组件

    1. 负责管理数据和业务逻辑,不负责UI的呈现
    2. 使用 Redux 的 API
    3. 一般保存在containers文件夹下
    // 引入Count的UI组件
    import CountUI from '../../components/Count';
    //引入action
    import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/count_action';
    
    //引入connect用于链接UI组件与redux
    import {connect} from 'react-redux';
    
    /*
        mapStateToProps函数返回的是一个对象
        返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
        mapStateToProps用于传递状态
    */
    
    function mapStateToProps(state){     //react-redux自动调用store.getState所以此处直接接收state
        return {count:state}
    }
    
    /*
        mapDispatchToProps函数返回的是一个对象
        返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
        mapDispatchToProps用于传递操作状态的方法
    */
    function mapDispatchToProps(dispatch){    //react-redux自动调用store.dispatch所以此处直接接收state
        return {
            jia:(number)=>{
                //通知redux执行加法
                dispatch(createIncrementAction(number))
            },
            jian:(number)=>{
                //通知redux执行减法
                dispatch(createDecrementAction(number))
            },
            jiaAsync:(number,time)=>{
                //通知redux执行异步加
                dispatch(createIncrementAsyncAction(number,time))
            }
        }
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    export default connect(mapStateToProps,mapDispatchToProps)(CountUI)   //固定的写法
    

6.3. 相关API

  1. Provider:让所有组件都可以得到state数据

    **<Provider** **store**=**{store**}>
    **<App />
    </Provider>**
    
  2. connect:用于包装 UI 组件生成容器组件

    **import { connect } from 'react-redux'
      connect(
        mapStateToprops,
        mapDispatchToProps
      )(Counter)**
    
  3. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性

    **const mapStateToprops = function (state) {
      return {
        value: state
      }
    }**
    
  4. mapDispatchToProps:将分发action的函数转换为UI组件的标签属性

7. 使用redux调试工具

7.1. 安装chrome浏览器插件

image.png

7.2. 下载工具依赖包

npm install --save-dev redux-devtools-extension

8. 纯函数和高阶函数

8.1. 纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

  2. 必须遵守以下一些约束

    1. 不得改写参数数据
    2. 不会产生任何副作用,例如网络请求,输入和输出设备
    3. 不能调用Date.now()或者Math.random()等不纯的方法
  3. redux的reducer函数必须是一个纯函数

8.2. 高阶函数

  1. 理解: 一类特别的函数

    1. 情况1: 参数是函数
    2. 情况2: 返回是函数
  2. 常见的高阶函数:

    1. 定时器设置函数
    2. 数组的forEach()/map()/filter()/reduce()/find()/bind()
    3. promise
    4. react-redux中的connect函数
  3. 作用: 能实现更加动态, 更加可扩展的功能

9.redux实例—加法器

redux.gif

index.js

  • 只要store的状态有变化就刷新app,此做法会调用所有组件的render,
  • 虽然当只有一个组件状态变化时有点多余,但是react-dom的diff算法不会使此做法效率变地太低
  • 如果写在组件的componentDidMount里,假设有3000个组件就得写3000次
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './redux/store'

ReactDom.render(<App/>,document.getElementById('root'))

//只要store的状态有变化就刷新app,此做法会调用所有组件的render,
//虽然当只有一个组件状态变化时有点多余,但是react-dom的diff算法不会使此做法效率变地太低
//如果写在组件的componentDidMount里,假设有3000个组件就得写3000次
store.subscribe(()=>{
    ReactDom.render(<App/>,document.getElementById('root'))
})

App.jsx

import React, { Component } from 'react'
import Count from './components/Count';

export default class App extends Component {
    render() {
        return (
            <div>
                <Count/>
            </div>
        )
    }
}

store.js

//该文件用于暴露一个store对象,整个应用只有一个store对象

//引入createStore,专门用于创建redux中最为核心的store对象
//applyMiddleware将所有中间件组成一个数组,依次执行。
import {createStore,} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'

export default createStore(countReducer,applyMiddleware(thunk))

action.js

//该文件专门为Count组件生成action对象

import {INCREMENT,DECREMENT} from './constant'

// function createIncrementAction(data){
//     return {type:'increment',data}
// }
// function createDecrementAction(data){
//     return {type:'decrement',data}
// }

//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction=(data)=>({type: INCREMENT,data})
export const createDecrementAction=(data)=>({type: DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的
export const createIncrementAsyncAction=(data,time)=>{
    return (dispatch)=>{
       setTimeout(()=>{
            dispatch(createIncrementAction(data))
       },time)
    }
}

reducer.js

//用于创建一个为count组件服务的reducer,reducer的本质是一个函数
//函数会接收到两个参数,分别为之前的状态prestate和动作对象action

import {INCREMENT,DECREMENT} from './constant'

//这么写是为了增加代码可读性
const initState=0 ///初始化状态
export default function countReducer(preState=initState,action){  //当没传preState或preState=undefined时赋值initState
    // if(preState===undefined) preState=0
    //从action对象中获取type和data
    const{type,data}=action
    switch (type) {
        case INCREMENT: return preState + data  
        case DECREMENT: return preState - data   
        default: return preState  //即初始化
    }

}

constants.js

//该模块用于定义action对象中type类型的常量值
//目的只有一个,便于管理的同时防止程序员单词写错

export const INCREMENT='increment'
export const DECREMENT='decrement'

component-Count.js

import React, { Component } from 'react'
//引入store,用于获取redux中保存状态
import store from '../../redux/store';
//引入actionCreator,专门用于创建action对象
import {createDecrementAction,createIncrementAction,createIncrementAsyncAction} from '../../redux/count_action';

export default class Count extends Component {
    // state={count:0}

    //若有3000组件则要写3000遍,所以写在入口文件index里,虽然写在index里每次会调用所有组件的render,但是react-dom的diff函数作用,并不会使效率非常低
    // componentDidMount(){
        // 检测rudux状态是否改变,只要变化就调用render以刷新页面(this.setState会自动调用render)
    //     store.subscribe(()=>{
    //         this.setState({})  //借助setState自动调用render的机制
    //     })
    // }
    increment=()=>{
        const{value}=this.selectNumber
        // store.dispatch({type:'increment',data:value*1})
        store.dispatch(createIncrementAction(value*1))
    }
    decrement=()=>{
        const{value}=this.selectNumber
        store.dispatch(createDecrementAction(value*1))
    }
    incrementIfOdd=()=>{
        const{value}=this.selectNumber
        const count=store.getState()
        if(count%2!==0){
            store.dispatch(createIncrementAction(value*1))
        }        
    }
    incrementAsync=()=>{
        const{value}=this.selectNumber
        // setTimeout(() => {
            store.dispatch(createIncrementAsyncAction(value*1,3000))
        // }, 3000);
    }
    render() {
        return (
            <div>
                <h1>当前求和为:{store.getState()}</h1>
                <select ref={c=>this.selectNumber=c}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>&nbsp;
                <button onClick={this.increment}>+</button>&nbsp;
                <button onClick={this.decrement}>-</button>&nbsp;
                <button onClick={this.incrementIfOdd}>当前求和为奇数时+</button>&nbsp;
                <button onClick={this.incrementAsync}>异步+</button>&nbsp;
            </div>
        )
    }
}

10.react-redux实例

index.js

  • react-redux自动监测store的状态变化,不需要调用store.subscribe()了
  • 用Provider包裹App,目的是让App所有的后代容器组件都能直接接收到store
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'

ReactDom.render(
    //此处用Provider包裹App,目的是让App所有的后代容器组件都能直接接收到store
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root')
)


//react-redux自动监测  不需要写下面代码了
//只要store的状态有变化就刷新app,此做法会调用所有组件的render,虽然当只有一个组件状态变化时有点多余,但是react-dom的diff算法不会使此做法效率变地太低
// store.subscribe(()=>{
//     ReactDom.render(<App/>,document.getElementById('root'))
// })

App.jsx

import React, { Component } from 'react'
import Count from './containers/Count';
import Person from './containers/Person';
// import store from './redux/store';

export default class App extends Component {
    render() {
        return (
            <div>
                {/* 给容器组件传递store */}
                {/* <Count store={store}/>  */}
                {/* 优化:在App里传 */}
                <Count/>
                <br />
                <Person/>
            </div>
        )
    }
}

store.js

  • composeWithDevTools:redux的可视化工具,谷歌的应用商城工具
//该文件用于暴露一个store对象,整个应用只有一个store对象

//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为Count组件服务的reducer
// import countReducer from './reducers/count'
//引入为Person组件服务的reducer
// import personReducer from './reducers/person'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'

//引入汇总后的reducer
import allReducer from './reducers'

//汇总所有reducer
// const allReducer=combineReducers({
//     count:countReducer,
//     persons:personReducer
// })

export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

actions-count.js

//该文件专门为Count组件生成action对象

import {INCREMENT,DECREMENT} from '../constant'

// function createIncrementAction(data){
//     return {type:'increment',data}
// }
// function createDecrementAction(data){
//     return {type:'decrement',data}
// }

//同步action,就是指action的值为Object类型的一般对象
export const increment=(data)=>({type: INCREMENT,data})
export const decrement=(data)=>({type: DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的
export const incrementAsync=(data,time)=>{
    return (dispatch)=>{
       setTimeout(()=>{
            dispatch(increment(data))
       },time)
    }
}

actions-person.js

import {ADD_PERSON} from '../constant'

export const addPerson = (personObj)=>({type:ADD_PERSON,data:personObj})

reducers-index.js

  • 该文件用于汇总所有reducer为一个总的reducer
  • combineReducers,用于汇总多个reducer
//该文件用于汇总所有reducer为一个总的reducer

//引入为Count组件服务的reducer
import count from './count'
//引入为Person组件服务的reducer
import persons from './person'
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'

//汇总所有reducer
const allReducer=combineReducers({
    // count:countReducer,
    // persons:personReducer
    count,
    persons
})
export default allReducer

reducers-count.js

//用于创建一个为count组件服务的reducer,reducer的本质是一个函数
//函数会接收到两个参数,分别为之前的状态prestate和动作对象action

import {INCREMENT,DECREMENT} from '../constant'

//这么写是为了增加代码可读性
const initState=0 ///初始化状态
export default function countReducer(preState=initState,action){  //当没传preState或preState=undefined时赋值initState
    // if(preState===undefined) preState=0
    //从action对象中获取type和data
    const{type,data}=action
    switch (type) {
        case INCREMENT: return preState + data  
        case DECREMENT: return preState - data   
        default: return preState  //即初始化
    }

}

reducers-person.js

import {ADD_PERSON} from '../constant'

// 初始化人列表
const initState=[{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){
    const{type,data}=action
    switch (type) {
        case ADD_PERSON: return [data,...preState]
        // preState.unshift(data)  //此处不能这么写,这样会导致preState被改写,personReducer就不是纯函数了
        // return preState
        default:
           return preState
    }

}

constant.js

//该模块用于定义action对象中type类型的常量值
//目的只有一个,便于管理的同时防止程序员单词写错

export const INCREMENT='increment'
export const DECREMENT='decrement'
export const ADD_PERSON='add_person'

containers-Count.js

//把ui组件和容器组件合并为一个文件
// 引入Count的UI组件
// import CountUI from '../../components/Count';
//引入action
import {increment,decrement,incrementAsync} from '../../redux/actions/count';
//引入connect用于链接UI组件与redux
import {connect} from 'react-redux';
import React, { Component } from 'react'

class Count extends Component {

    increment=()=>{
        const{value}=this.selectNumber
        this.props.increment(value*1)
    }
    decrement=()=>{
        const{value}=this.selectNumber
        this.props.decrement(value*1)
    }
    incrementIfOdd=()=>{
        const{value}=this.selectNumber
        if(this.props.count % 2 !==0){
            this.props.increment(value*1)
        }
    }
    incrementAsync=()=>{
        const{value}=this.selectNumber
        this.props.incrementAsync(value*1,500)
    }
    render() {
        return (
            <div>
                <h1>我是Count组件</h1>
                <h4>当前求和为:{this.props.count},人数为:{this.props.personCount}</h4>
                <select ref={c=>this.selectNumber=c}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>&nbsp;
                <button onClick={this.increment}>+</button>&nbsp;
                <button onClick={this.decrement}>-</button>&nbsp;
                <button onClick={this.incrementIfOdd}>当前求和为奇数时+</button>&nbsp;
                <button onClick={this.incrementAsync}>异步+</button>&nbsp;
            </div>
        )
    }
}

//映射状态
// const mapStateToProps=state=>({count:state})  //返回一个对象可以用()包裹


//映射操作状态的方法
// const mapDispatchToProps=dispatch=>(   
//     {
//         increment:number=>dispatch(createIncrementAction(number)),
//         incrementn:number=>dispatch(createDecrementAction(number)),
//         incrementAsync:(number,time)=>dispatch(createIncrementAsyncAction(number,time))
//     }
// )

//使用connect()()创建并暴露一个Count的容器组件
export default connect(
    state=>({count:state.count,personCount:state.persons.length}),

    //mapDispatchToProps的一般写法
    // dispatch=>({
    //     increment:number=>dispatch(createIncrementAction(number)),
    //     incrementn:number=>dispatch(createDecrementAction(number)),
    //     incrementAsync:(number,time)=>dispatch(createIncrementAsyncAction(number,time))
    // })

    //mapDispatchToProps的简写(react-redux自动分发了dispatch)
    {
        increment,
        decrement,
        incrementAsync
    }
)(Count)   //固定的写法

containers-Person.js

import React, { Component } from 'react'
import {nanoid} from 'nanoid';
import {connect} from 'react-redux';
import {addPerson} from '../../redux/actions/person';

class Person extends Component {
    addPerson=()=>{
        const name=this.nameNode.value
        const age=this.ageNode.value*1
        const personObj={id:nanoid(),name,age}
        this.props.addPerson(personObj)
        this.nameNode.value=''
        this.ageNode.value=''
    }
    render() {
        return (
            <div>
                <h1>我是Person组件,上方和为: {this.props.count}</h1>
                <input ref={c=>this.nameNode=c} type="text" placeholder="输入你的名字"/>
                <input ref={c=>this.ageNode=c} type="text" placeholder="输入你的年龄"/>
                <button onClick={this.addPerson}>添加</button>
                <ul>
                    {
                        this.props.persons.map((p)=>{
                            return <li key={p.id}>{p.name}---{p.age}</li>
                        })
                    }
                    
                </ul>
            </div>
        )
    }
}

export default connect(
    state=>({
        persons:state.persons,
        count:state.count
    }),
    {addPerson}
)(Person)