【Redex】使用及案例分析

  • Post author:
  • Post category:其他

一、基本使用

在组件中展示一个按钮,点按钮后给redux中的数字+9,数字初始为0,。实现一个计数器的效果

步骤:
1. 创建store
2. 创建视图组件(展示store中的数据)
3. 修改
4. 回显示数据到视图组件
  • src下的目录结构:

在这里插入图片描述

  • src/store/index.js
/*
    redux的注意点:
    1. 安装好redux之后,系统不会给我们生成redux对应的src/store文件夹,需要自建
    2. src/store下的index.js文件也是需要自建的
    3. 创建仓库的配置文件index.js中的代码
        步骤:
        a. 创建默认的数据源 defaultState 变量,其值形式是一个对象
        b. 先创建简单的reducer,简单的返回state即可(供页面组件使用),此时不具备修改数据的功能
        c. 创建store对象
        d. 导出store对象

*/ 
// 导入需要的依赖成员,如果传递多个reducer,则需要导入combineReducers合并reducer
import {combineReducers, createStore}from "redux"

// a. 创建默认的数据源(类似于vuex的state)
const defaultState = {
    count : 0,
    // 演示使用
    age:20,
    username:"Roze"
};

// b. 先创建简单的reducer,要求纯函数
// state一般需要进行一个赋值操作
function reducer(state = defaultState,action){
    // 主要是进行数据修改
    // 在修改操作的时候需要使用分支判断
    if(action.type==="mod_count"){
        // 修改count数据(注意数据的合并,...state展开在赋值)
        return{...state,count:state.count+action.payload}
    }
    // 如果有乘除操作
    if(action.type==="cheng_count"){
        // 。。。。。  
    }
    // 返回数据给页面使用
    return state;
}

// 一个reducer负责一个数据的修改
function reducer2(state = defaultState,action){
    if(action.type==="mod_age"){
        return{...state,age:state.age+action.payload}
    }
    return state;
}

// 合并reducer
const reducers = combineReducers({reducer,reducer2})

// c. 创建store对象(通过createState方法),目前(后续有变)其参数就是reducer
const store = createStore(reducers);


// d. 导出store对象
export default store;
  • src/components/Counter.jsx
import React, { Component } from 'react';
// 需要导入store
import store from "../store/index";
class Counter extends Component {
    // 在constructor中获取store中的数据
    constructor(props){
        super(props);
        // 获取store数据(一次性,不具备响应式)
        this.state = store.getState();
        // 订阅数据的更新
        store.subscribe(()=>this.setState(()=>store.getState()));
    }
    render() {
        return (
            <div>
                <div>当前Store中的数据是:{this.state.reducer.count}</div>
                <button onClick={this.addCount.bind(this)}>点击+9</button>
                <hr/>
                <div>当前Store中的数据是:{this.state.reducer2.age}</div>
                <button onClick={this.addAge.bind(this)}>点击+1</button>
            </div>
        );
    }
    // 点击+9
    addCount(){
        // 描述数据如何更改的对象,其必须有type属性
        let action ={type:"mod_count",payload:9}
        // 通过storethis.$store.dispatch去派发action(会将该action派发给所有的reducer,每个reducer都会被执行,因此一定要注意type的取值)
        store.dispatch(action);
    }
    // 点击+1
    addAge(){
        let action ={type:"mod_age",payload:1}
        store.dispatch(action);
    }
}

export default Counter;
  • src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/Counter';


ReactDOM.render(
    <App />,
  document.getElementById('root')
);
  • 运行结果:

在这里插入图片描述

在这里插入图片描述

  • 针对redux的模块化,在一个常规项目中会将其代码拆分成以下几个部分:
1. States:
建立同名目录,存放模块化之后的state(默认数据源)

2. Reducers:
建立同名目录,存放模块化之后的reducer
3. Actions:
建立同名目录,存放模块化之后的action
4. Type(可选):
建立同名目录,存放独立的type声明
  - 注意:在整个项目中,对于不同数据源的更改时使用的type名称不能重复,这个一定要注意。
  - 原因:redux在做修改数据的时候,其原理是依据type的值去循环每个reducer,找到匹配的去执行,为了避免出现同名,建议type集中书写
  • 具体实现,以项目的代码为准。

由于代码已经经过模块化,在获取redux中的数据的时候需要更改获取方式,比如说之前获取count是写成:this.state.count,模块化之后需要写成:this.state.counter.count,比之前多了一个模块化的模块名称(等同于vuex中命名空间)

二、案例分析:

  • 我们写一个计数器
  • +:点击后 +1
  • -:点击后 -1
  • ++:点击后延时后 +1

在这里插入图片描述

2.1、不用 Redex

  • App.jsx 文件
import React ,{Component} from 'react';
class App extends Component{
  constructor(props) {
    super(props);
    this.state = {
        count:0
    }
  }
  increment=()=>{
    const {count} = this.state; 
    this.setState({
        count :count+1
    })
  }
  decrement=()=>{
    const {count} = this.state; 
    this.setState({
        count :count-1
    })
  }
  incrementAsync=()=>{
    const {count} = this.state; 
    setTimeout(()=>{
        this.setState({
            count :count+1
        })
    },1000)
  }

  render() {

    return (<>
        <h2>count:{this.state.count}</h2>
        <p>
            <button onClick={this.increment}>+</button>
            <button onClick={this.decrement}>-</button>
            <button onClick={this.incrementAsync}>+</button>
        </p>
        
    </>)
  }
}
export default App;
  • index.js 文件
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

2.2、使用 Redex

  • src 下的文件目录结构:
    在这里插入图片描述
  • store.js 文件
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象

// 引入createStore,创建Store
import {createStore} from 'redux';
// 引入为Count组件服务的reducer
import countReducer from './count_reducer';

// 暴露store
export default createStore(countReducer);
/* 
const store = createStore(countReducer);
export default store;
*/
  • count_reducer.js 文件
/* 
    该文件是用于创建一个为Count组件服务的reducer
    reducer的本质就是一个函数,reducer函数会接收到两个参数
    分别是:之前的状态(preState),动作对象(action)
*/
// 引入constant文件,改写type值
import { INCREMENT, DECREMENT } from './constant';


// 初始化状态
const initState = 0;
//  定义好reducer函数后并暴露出去
export default function countReducer(preState=initState,action){
    console.log(preState,action);
    // 从action对象中取出:type、data
    const {type,data} = action;
    // 根据type决定如何加工数据
    switch(type){
        // case 'increment': // 原始的
        case INCREMENT: // 改写后
            return preState+data;   // 如果是加
        // case 'decrement': // 原始的
        case DECREMENT: // 改写后
            return preState-data;   // 如果是减
        // reducer是个纯函数,不能处理什么异步加+1什么的
        default:
            return preState;  
    }
}
  • constant.js 文件
/* 
    该模块是用于定义action对象中的type类型的常量值
*/
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
  • count_action.js 文件
/* 
    该文件专门为 Count 组件生成 action 对象
*/

// 引入constant文件,改写type值
import { INCREMENT, DECREMENT } from './constant';

// 改写后
export const createIncrementAction = (data)=>{
    return {type:INCREMENT,data:data}
}
export const createDecrementAction = data=>({type:DECREMENT,data:data})


/*
// 原始数据
export const createIncrementAction = (data)=>{
    return {type:'increment',data:data}
}
export const createDecrementAction = data=>({type:'decrement',data:data})
*/
  • Count.jsx 组件
import React ,{Component} from 'react';
// 引入store,用于获取redux中保存的状态
import store from './redux/store';

// 引入actionCreator,专门用于创建action对象
import {createIncrementAction,createDecrementAction} from './redux/count_action';

class Count extends Component{
  constructor(props) {
    super(props);
    this.state = {
        text:'自己独有的text'
    }
  }
/*  
  如果写在这里,如果有多个组件就要写多个这个,十分麻烦
  为了方便,我们把改写index.js文件,一劳永逸
  componentDidMount(){
      // 检测redux中状态的变化,只要变化,就检测render
      store.subscribe(()=>{
        this.setState({})
      })
  }
*/
  // 加法
  increment=()=>{
    // 通知redux
    // store.dispatch({type:'increment',data:1}); // 原来的写法
    // 使用action改写
    store.dispatch(createIncrementAction(1));
  }
  // 减法
  decrement=()=>{
    // store.dispatch({type:'decrement',data:1}); // 原来的写法
    // 使用action改写
    store.dispatch(createDecrementAction(1));
  }
  // 延时加法
  incrementAsync=()=>{
    setTimeout(()=>{
        // store.dispatch({type:'increment',data:1}); // 原来的写法
        // 使用action改写
        store.dispatch(createIncrementAction(1));
    },1000)
  }

  render() {
    
    return (<>
        {/* 获取store中的状态值 */}
        <h2>count:{store.getState()}</h2>
        <p>
            <button onClick={this.increment}>+</button>
            <button onClick={this.decrement}>-</button>
            <button onClick={this.incrementAsync}>++</button>
        </p>
        
    </>)
  }
}
export default Count;
  • index.js 文件
import React from 'react';
import ReactDOM from 'react-dom';
import Count from './Count';

// 为了store.subscribe调用store
import store from './redux/store';

// 简单写法:

let render = () =>
  ReactDOM.render(
    <Count />,
    document.getElementById('root')
  );
render();
//检测 store是否有变化,如果变化,自动调用render
store.subscribe(render);

/*
复杂写法:
ReactDOM.render(
  <Count />,
  document.getElementById('root')
);
// 检测 store是否有变化,如果变化,自动调用render
store.subscribe(()=>{
  ReactDOM.render(
    <Count />,
    document.getElementById('root')
  );   
})
*/

2.3、知识点总结:

store.js

1. 引入 redux  中的 createStore() 函数,创建一个 store
2. createStore() 调用时要传入一个为其服务的 reducer
3. 记得暴露 store 对象(export default

count_reducer.js

1. reducer 的本质是一个函数,接收:preState,action。返回加工后的状态
2. reducer 有两个作用:初始化状态,加工状态
3. reducer 被第一次调用时,是store自动触发
	传递的preState是undefined
	传递的action是 {type:'@@REDUX/INIT_a.w.e.4'}

index.js

- 检测store中状态的改变,一旦发生改变重新渲染<Count/>组件
备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写

后续完善

1. 新增 count_action.js
- 专门用于创建 action 对象
2. 新增 constant.js 
- 放置由于写错的 action 对象中的 type 值

2.4、Redux 异步操作

通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量

  • 当 action 为对象时(Object)则是同步的
  • 当 action 为函数时(function)就是异步的

常见的异步库

  • Redux-thunk【推荐使用】
  • Redux-saga
  • Redux-effects
  • Redux-side-effects
  • Redux-loop
  • Redux-observable

基于Promise的异步库

  • Redux-promise
  • Redux-promises
  • Redux-simple-promise
  • Redux-promise-middleware

2.5、改写计数器


  1. 安装 redux-thunk
yarn add redux-thunk
或:
npm i -S redux-thunk
  1. 修改 store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象

// 引入createStore,创建Store
// 为了实现异步action,需要额外引入applyMiddleware
import {createStore,applyMiddleware} from 'redux';
// 引入为Count组件服务的reducer
import countReducer from './count_reducer';
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk';

// 暴露store
export default createStore(countReducer,applyMiddleware(thunk));
  1. 修改 count_action.js
/* 
    该文件专门为 Count 组件生成 action 对象
*/

// 引入constant文件里面的常量
import { INCREMENT, DECREMENT } from './constant';

import store from './store';

// 同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = (data)=>{
    return {type:INCREMENT,data:data}
}
export const createDecrementAction = data=>({type:DECREMENT,data:data})
// 异步action,就是指action的值为函数,异步action中一般会调用同步action,异步action不是必须要用的
export const createIncrementAsyncAction =(data,time)=>{
    return ()=>{
        setTimeout(()=>{
            // store.dispatch({type:'increment',data:1}); // 原来的写法
            // 使用action改写
            store.dispatch(createIncrementAction(1));
        },time)
    }
}

  1. 修改 Count.js
import React ,{Component} from 'react';
// 引入store,用于获取redux中保存的状态
import store from './redux/store';

// 引入actionCreator,专门用于创建action对象
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from './redux/count_action';

class Count extends Component{
  constructor(props) {
    super(props);
    this.state = {
        text:'自己独有的text'
    }
  }
  // 加法
  increment=()=>{
    // 通知redux
    // store.dispatch({type:'increment',data:1}); // 原来的写法
    // 使用action改写
    store.dispatch(createIncrementAction(1));
  }
  // 减法
  decrement=()=>{
    // store.dispatch({type:'decrement',data:1}); // 原来的写法
    // 使用action改写
    store.dispatch(createDecrementAction(1));
  }
  // 延时加法
  incrementAsync=()=>{
    store.dispatch(createIncrementAsyncAction(1,1000));
    /* 
    // 把下面这个放到action里面
    setTimeout(()=>{
        // store.dispatch({type:'increment',data:1}); // 原来的写法
        // 使用action改写
        store.dispatch(createIncrementAction(1));
    },1000) 
    */
  }

  render() {
    
    return (<>
        {/* 获取store中的状态值 */}
        <h2>count:{store.getState()}</h2>
        <p>
            <button onClick={this.increment}>+</button>
            <button onClick={this.decrement}>-</button>
            <button onClick={this.incrementAsync}>++</button>
        </p>
        
    </>)
  }
}
export default Count;

2.6、知识点总结

  • 明确需求:延时的动作不想交给组件自身,想交给action
  • 何时需要异步action:想要对自身状态进行操作,但是具体的数据靠异步任务返回
  • 具体编码
1. 安装:npm i -S redux-thunk
2. 配置:在store中进行配置
- import {createStore,applyMiddleware} from 'redux';
- import thunk from 'redux-thunk';
- export default createStore(countReducer,applyMiddleware(thunk));
3. 创建action的函数不再返回一个一般对象,而是一个函数,该函数中写异步任务
export const createIncrementAsyncAction =(data,time)=>{
    return ()=>{
        setTimeout(()=>{
          store.dispatch(createIncrementAction(1));
        },time)
    }
}
4. 异步任务有结果后,派发一个同步的action去真正操作数据
store.dispatch(createIncrementAction(1));

备注

  • 异步action不是必须要写的
  • 完全可以自己等待异步任务的结果再去派发同步action
  • (就是在组件里面写,不单独拿到action去做)

版权声明:本文为qq_45677671原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。