Redux的使用

一、redux是什么

  1. redux是专门用于管理状态的JS库,可以集中式管理react应用中的多个组件共享的状态;
  2. redux不是react插件库,它可以用在react、angular、vue中,但基本与react配合使用;
  3. 使用:在多个组件需要共享状态时,或者一个组件需要改变另一个组件的状态时使用。

二、redux工作流程

image.png
  1. action:动作对象,包含两个属性:
  • type: 动作标识,string,唯一且必填,初始化时默认为@@redux/INI*
  • data:数据,any, 可选属性,初始化不传;
    eg:{type: 'ADD', data: {name: 'tom'}}
  1. reducer:用于初始化状态和加工状态的 纯函数;

纯函数,是指对与同样的参数,返回值始终一样的函数。
纯函数必须遵守以下约束:
(1)函数内部不得改写参数;
(2)不得存在网络请求等操作;
(3)不得调用Date.now()或者Math.round等不纯的方法;

  1. store: 将state、action、reducer连在一起的对象;

三、使用

实现下述操作:点击运算按钮,显示下拉框的选中值与当前求和值的运算结果。


1. 不用redux实现

Count.js

import React, { Component } from "react";

export default class Count extends Component {

  state = {count: 0}

  increment = () => {
    const {value} = this.selectNumber;
  // const {count} = this.state;
  //   this.setState({
  //       count: count + value * 1
  //   })
    this.setState((state) => ({
      count: state.count + value * 1
    }))
  }
  decrement = () => {
      const {value} = this.selectNumber;
      this.setState((state) => ({
          count: state.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;
      setTimeout(() => {
          this.setState((state) => ({
              count: state.count + value * 1
          }))
      }, 500)
  }

  render() {
    return (
      <div>
        <p>当前求和为: {this.state.count}</p>
        <select ref={dom => this.selectNumber = dom}>
          {
            [1,2,3].map((data, i) => <option value={data} key={i}>{data}</option>)
          }
        </select>
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
        <button onClick={this.incrementAsync}>异步加和</button>
      </div>
    )
  }
}

2. 使用redux实现

第一步:准备好store

  • yarn add redux
  • 创建src/redux/store.js:
import {createStore} from 'redux'; 

import countReducer from './count-reducer';

export default createStore(countReducer); 
  • 引入apicreateStore, 用于创建store,第一个参数传入需要的reducers

第二步:准备 reducer

  • redux文件夹下创建reducer/count.js
  • reducer是一个函数,接收previousStateaction,返回newState
import {INCREMENT, DECREMENT} from "./constants";

const initState = 0;

export default function countReducer(preState = initState, action) {
    console.log({preState, action});
    const { type, data } = action;
    switch (type) {
        case INCREMENT:
            return preState + data;
        case DECREMENT:
            return preState - data;
        default:
            return preState;
    }
}
  • 初次展示状态时,store会自动调用一次reducer,之后为dispatch通知触发;
  • preState默认为undefined,可以自定义默认值。之后该值自动传为上次的数据。action中的type初始值为@@redux/INI*;
  • action中的type最好用switch...case...来分类,最好用常量表示,定义在固定的文件中;
  • reducer的返回值会展示在store.getState()中;
  • reducer可以在组件中直接封装参数对象通过dispatch触发,也可以通过action得到参数对象再触发;
  // 组件中直接触发reducer
    increment = () => {
      const {value} = this.selectNumber;
      store.dispatch({type: 'increment', data: value * 1});
    }
  • reducer中处理的state的类型是引用类型,修改时必须修改引用地址值,如下方法,在数组前面推送一个对象,不能对原数组使用unshift方法,需要返回一个新的数组:
const initState = [
  {
    name: 'ad',
    age: 11,
    id: '123esd'
  },
]
export default function addPerson(preState = initState, action) {
  const { type, data} = action;
  switch (type) {
    case ADD_PERSON:
      return [data, ...preState];
    default:
      return preState;
  }
}
  • 如果有多个子组件的reducer,需要借助redux中的combineReducers合并传入store:
    src/redux/reducers/index
import {combineReducers} from "redux";

import count from './count';
import person from './person';

// 使用combineReducers把所有reducer合并返回
export default combineReducers({count, person})

第三步: 准备action

  • 创建redux/actions/count.js文件;
import { INCREMENT, DECREMENT } from "./constants";

// 同步action,返回值为一般对象
export const increment = (data) => ({type: INCREMENT, data});

export const decrement = (data) => ({type: DECREMENT, data})

// 异步action,返回值为函数。异步action中一般都会调用同步action,所以有默认参数dispatch。
// 但是直接使用redux不认,需要引入中间件`redux-thunk`;
// 异步action不是必须要用,可以在组件内部处理异步函数。
export const incrementAsync = (data, time) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(increment(data));
    }, time)
  }
}
  • action分为同步和异步。同步action返回值为一般对象, 异步action,返回值为函数;
  • 异步action中一般都会调用同步action,所以有默认参数dispatch;
  • reducer不认返回值为函数的action,需要在store中安装并引入中间件redux-thunk,使用applyMiddleware应用到store中:
    store.js
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';

import countReducer from './count-reducer';

export default createStore(countReducer, applyMiddleware(thunk));
  • 异步action不是必须要用,可以在组件内部处理完异步操作,再调用同步action替代;

第四步:组件中的使用

import React, {Component} from "react";
import store from "../../redux/store";
import {
  increment,
  incrementAsync,
  decrement
} from "../../redux/actions/count";

export default class Count extends Component{
  state = {
    // count: 0
  }
  /**
   * 通过在componentDidMount中调用store.subscribe,监听store的变化。
   * 并且在每次变化时,通过this.setState的合并方法来触发页面重新渲染
   * */
  // componentDidMount() {
  //   store.subscribe(() => {
  //     this.setState({})
  //   })
  // }

  increment = () => {
    const {value} = this.selectDom;
    /**
     * 组件中直接生成对象,使用dispatch触发reducer
     * */
    store.dispatch({
      type: 'increment',
      data: value*1,
    })
  }
  decrement = () => {
    const {value} = this.selectDom;
    /**
     * 通过action触发reducer
     * */
    store.dispatch(decrement(value*1))
  }
  incrementIfOdd = () => {
    const {value} = this.selectDom;
    if(store.getState().count %2 !== 0) {
      store.dispatch(increment(value*1))
    }
  }

  /**
   * reducer只接受一个普通函数作为action参数;
   * 如果action要异步返回一个方法,需要下载依赖 redux-thunk,作为中间件在store中引入
   * */
  incrementAsync = () => {
    const {value} = this.selectDom;
    store.dispatch(incrementAsync(value*1, 500))
  }

  render() {
    return (
      <div>
        {/**
         通过store.getState()获取状态数据
         */}
        <h2>当前求和为{store.getState().count}</h2>
        <h2>下面人数为{store.getState().person.length}</h2>
        <select ref={c=>this.selectDom = c}>
          {[1,2,3].map((number, i) => <option value={number} key={i}>{number}</option>)}
        </select>
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
        <button onClick={this.incrementIfOdd}>求和为奇数则加</button>
        <button onClick={this.incrementAsync}>异步求和</button>
      </div>
    )
  }
}

第五步:渲染
通过上述步骤,点击按钮,可以触发reducer,修改store中的值,但是页面并没有改变。这是因为改变store后,需要自己监听store变化,调用渲染的方法。

  • 方法一: 在每个组件内部的componentDidMount生命周期钩子中,通过store.subscribe监听store中状态数据的变化,并调用this.setState({}),通过调用setState合并空对象,触发react渲染机制;
    Count.js
export default class Count extends Component {

    state = {}

    componentDidMount() {
        // 检测store中状态的变化,只要变化就触发
        store.subscribe(() => {
            // this.render(); // 无法触发渲染
            this.setState({}); // 通过调用`setState`合并空对象,触发react渲染机制
        })
    }

    increment = () => {
        const {value} = this.selectNumber;
        store.dispatch({type: 'increment', data: value * 1});
    }
  • 方法二: 在 入口文件中引入store,通过store.subscribe监听并重新渲染App
import React from "react";
import ReactDom from 'react-dom';

import store from "./redux/store";
import App from "./App";

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

store.subscribe(() => {
    ReactDom.render(<App/>, document.getElementById('root'));
})

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容