Redux入门

127 阅读5分钟

redux

简介

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

什么场景下需要使用redux

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

redux的工作流程

image.png

三个核心概念

Action Creators

action:动作对象,包含两个属性

  • type:动作类型
  • data:数据

dispatch:分发,将action对象向下传递

  • 用法:dispatch(action)

Store

stateactionreducer联系在一起的对象,通俗的说就是自己本身不做处理,接收到动作后,调用Reducers做具体的状态加工。 它会传递两个参数给Reducers

  • previousState:前一个状态
  • action:动作
  1. 如何得到该对象?

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

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

Reducers

  • 初始化状态
  • 加工状态
    • 加工时,根据旧的stateaction,产生新的state纯函数

案例演示

需求:实现一个包含加减乘除的简易版计算器

通过实现该案例来掌握redux的核心API。

基础使用版本

基础代码片段

import React, { Component } from 'react'

export default class Count extends Component {
  state = { count: 0 }
  
  render() {
    return (
      <div>
        <h1>当前求和为:{this.state.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>
      </div>
    )
  }
}

image.png

基础的东西有了,接下来我们再先快速的实现一下几个按钮的功能吧。


    // 加法
    increment = () => {
        const { value } = this.selectNumber
        const { count } = this.state
        this.setState({ count: count + value * 1 })
    }

    // 减法
    decrement = () => {
        const { value } = this.selectNumber
        const { count } = this.state
        this.setState({ count: count - value * 1 })
    }

    // 求和结果为奇数再增加
    incrementIfOdd = () => {
        const { value } = this.selectNumber
        const { count } = this.state
        if (count % 2 !== 0) {
            this.setState({ count: count + value * 1 })
        }
    }

    // 模拟异步增加
    incrementAsync = () => {
        const { value } = this.selectNumber
        const { count } = this.state
        setTimeout(() => {
            this.setState({ count: count + value * 1 })
        }, 1000);
    }

基础已经搭建好了,现在就准备接入Redux吧。

  • 安装redux
yarn add redux
  • 创建store.js
// 引入 createStore,用于创建store对象
import { legacy_createStore as createStore} from 'redux'

// 引入为count组件服务的reducer
import countReducer from './count_reducer'

// 暴露store
export default createStore(countReducer)

这里需要注意的是新版中中createStore已经被弃用,需要使用legacy_createStore来代替。

image.png

  • 创建count_reducer.js
// reducer 本质上一个函数,该文件主要用于创建一个为Count组件服务的reducer

const initState = 0

export default function countReducer(preState = initState, action){
    const {type, data} = action
    switch(type){
        case 'increment':
            return preState + data
        case 'decrement':
            return preState - data
        default:
            return preState
    }
}

redux相关的两个js定义好了,接下来就需要修改我们的Count组件了。

  • 首先引入store
import store from '../../redux/store'
  • 通过getState()API获取状态
<h1>当前求和为:{store.getState()}</h1>
  • 修改increment方法
increment = () => {
    const { value } = this.selectNumber
    store.dispatch({
        type: 'increment',
        data: value * 1
    })
}

image.png

count_reducer.js中打印一下,看到函数有被调用执行,但是页面显示到内容却没有发生变化。

我们再返回去看看redux的工作流程,可以看到redux只负责帮我们处理状态,而页面的更新实际上是React做的事情,我们更新的是redux中的状态,在React这边并没有发生改变,所以页面自然也不会触发render()去帮我们重新渲染。

Count组件中,我们加上勾子函数调用下redux提供的订阅API,来触发下渲染看看行不行。

componentDidMount() {
    // 通过订阅redux的状态变化,调用render重新渲染页面
    store.subscribe(() => {
        this.setState({})
    })
}
// 加法
increment = () => {
    const { value } = this.selectNumber
    store.dispatch({
        type: 'increment',
        data: value * 1
    })
}

// 减法
decrement = () => {
    const { value } = this.selectNumber
    store.dispatch({
        type: 'decrement',
        data: value * 1
    })
}

// 求和结果为奇数再增加
incrementIfOdd = () => {
    const { value } = this.selectNumber
    if (store.getState() % 2 !== 0) {
        store.dispatch({
            type: 'increment',
            data: value * 1
        })
    }
}

// 模拟异步增加
incrementAsync = () => {
    const { value } = this.selectNumber
    setTimeout(() => {
        store.dispatch({
            type: 'increment',
            data: value * 1
        })
    }, 1000);
}

除了组件自身的勾子函数,我们也可以在index.js文件中实现该功能,每次状态发生变化,重新渲染整个App组件。

// 引入React核心库
import React from 'react'
// 引入ReactDOM
import { createRoot } from 'react-dom/client'
// 引入store
import store from './redux/store'

// 引入自定义组件
import App from './App.js'

// 渲染组件到页面
const container = document.getElementById('root')
const root = createRoot(container)
root.render(<App />)

store.subscribe(() => {
    root.render(<App />)
})

React中有diff算法,直接重新渲染整个App组件,也不怕会有太大的效率问题发生。

完整使用版本

在上面的案例中,我们需要自己创建action对象,而且对于type的值也没有一个统一的处理,极容易出错导致状态变化无法识别到。在这里我们可以再定义两个文件:count_action.jsconstant.js

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


import {INCERMENT, DECERMENT} from './constant'

// 创建 incerment action对象
export const createIncrementAction = data => ({type: INCERMENT, data})
// 创建 decerment action对象
export const createDecrementAction = data => ({type: DECERMENT, data})
// 常量定义
export const INCERMENT = 'increment'
export const DECERMENT = 'decrement'

完整代码

import React, { Component } from 'react'
import store from '../../redux/store'
import {createIncrementAction, createDecrementAction} from '../../redux/count_action'

export default class Count extends Component {

    state = {}

    // 加法
    increment = () => {
        const { value } = this.selectNumber
        store.dispatch(createIncrementAction(value * 1))
    }

    // 减法
    decrement = () => {
        const { value } = this.selectNumber
        store.dispatch(createDecrementAction(value * 1))
    }

    // 求和结果为奇数再增加
    incrementIfOdd = () => {
        const { value } = this.selectNumber
        if (store.getState() % 2 !== 0) {
            store.dispatch(createIncrementAction(value * 1))
        }
    }

    // 模拟异步增加
    incrementAsync = () => {
        const { value } = this.selectNumber
        setTimeout(() => {
            store.dispatch(createIncrementAction(value * 1))
        }, 1000);
    }
    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>
            </div>
        )
    }
}

异步action

上面案例展示的都是同步的action,但是我们还有一个按钮是要发送异步请求的,接下来我们就来学习一下异步action该如何创建。

安装redux-thunk

npm add redux-thunk

修改store.js,引入applyMiddlewarethunk

// 引入 createStore,用于创建store对象
import { legacy_createStore as createStore, applyMiddleware} from 'redux'

// 引入为count组件服务的reducer
import countReducer from './count_reducer'

// 引入 thunk 中间件
import thunk from 'redux-thunk'

// 暴露store
export default createStore(countReducer, applyMiddleware(thunk))

创建异步action

// 异步action,就是指action的值为函数
export const createIncrementAsyncAction = (data, time) => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch(createIncrementAction(data))
        }, time)
    }
}

核心API

  • store.getState()
  • store.subscribe()

相关文档

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

本文正在参加「金石计划 . 瓜分6万现金大奖」