React-Redux笔记

156 阅读10分钟

Redux

介绍

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览

Redux 除了和 React 一起用外,还支持其它界面库。 它体小精悍(只有2kB,包括依赖)。

img

流程和使用

安装

 npm i redux 

store.js

import { createStore } from "redux";

// 创建reducer
// 传入参数,第一个是默认状态,第二个是派发的动作
function counter(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

// 创建store
// 将counter包装到createStore,store内就会包含状态和操作
const store = createStore(counter);
export default store;

在React入口文件index.js注册到整个应用中

import React from "react";
import ReactDOM from "react-dom";
import ReduxTest from "./ReduxTest";

// 导入
import store from "./store";

// 将ReactDOM.render包裹导函数内
function render() {
  ReactDOM.render(<ReduxTest />, document.querySelector("#root"));
}

render();

// 注册订阅
// 监听state,每次state更新时就会调用render函数重新渲染
// 调用函数时不能加()
store.subscribe(render);

组件中使用

import React, { Component } from "react";
// 导入sotre
import store from "./store";
import { Button } from "antd";

export default class ReduxTest extends Component {
  increment = () => {
    // 通过dispatch传递action参数,它是一个对象,内包含type属性
    // 个值经过switch语句判断
    store.dispatch({
      type: "INCREMENT",
    });
  };
  decrement = () => {
    store.dispatch({
      type: "DECREMENT",
    });
  };
  render() {
    return (
      <div>
        <h3>{store.getState()}</h3>
        <Button onClick={this.increment}>
          +1
        </Button>
        <Button onClick={this.decrement}>
          -1
        </Button>
      </div>
    );
  }
}
image-20220130225655184

React-Redux

在React应用使用redux状态渲染组件,每次state更新都会重新render,这造成一些不必要的浪费渲染,这就需要换用react-redux

安装依赖

npm i react-redux

具体步骤

React Redux 提供了 <Provider />,使得Redux store都被应用到应用程序内

修改index.js,不再需要订阅,而是通过Provider将store作为props传入组件使用

import React from "react";
import ReactDOM from "react-dom";
import store from "./store";
import ReduxTest from "./ReduxTest";

import { Provider } from "react-redux";

function render() {
  ReactDOM.render(
    <Provider store={store}>
      <ReduxTest />
    </Provider>,
    document.querySelector("#root")
  );
}

render();
// 订阅不需要了
// store.subscribe(render)

React-Redux提供了 connect 将组件连接到 store

import React, { Component } from "react";

import { Button } from "antd";
import { connect } from "react-redux";
// import store from './store'

// connect的第一个函数参数取得store.js内的state赋值给num用于在组件内获取
const mapStateToProps = (state) => {
  return {
    num: state,
  };
};
// connect的第二个参数取得dispatch来触发store.js内的事件
const mapDispatchToProps = (dispatch) => {
  return {
    increment: () => {
      dispatch({ type: "INCREMENT" });
    },
    decrement: () => {
      dispatch({ type: "DECREMENT" });
    },
  };
};

class ReduxTest extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.num}</h3>
        <Button onClick={() => this.props.increment()}>
          +1
        </Button>
        <Button onClick={() => this.props.decrement()}>
          -1
        </Button>
      </div>
    );
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(ReduxTest);

装饰器写法

抛出原组件,并将connect写在组件上方

@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.num}</h3>
        <Button onClick={() => this.props.increment()}>
          +1
        </Button>
        <Button onClick={() => this.props.decrement()}>
          -1
        </Button>
      </div>
    );
  }
}

export default ReduxTest;

redux中间件

Redux 入门教程(二):中间件与异步操作 - 阮一峰的网络日志 (ruanyifeng.com)

介绍了 Redux 的基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染

但是,一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。

怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。

利用redux中间件机制可以在实际action响应前执行其他额外的业务逻辑,特点是可以自由组合,自由插拔的插件机制

中间件概念

为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?

(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。

(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。

(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

想来想去,只有发送 Action 的这个步骤,即 store.dispatch() 方法,可以添加功能

bg2016092002(UpRGB)(auto_scale)(Level3)(x2.000000)

介绍两款比较成熟的中间件

  • redux-logger:处理日志记录的中间件
  • Redux-thunk:处理异步action
npm i redux-logger redux-thunk

redux-logger

redux-logger的使用早store.js加入

import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";

// 创建reducer
// 传入参数,第一个是默认状态,第二个是派发的动作
function counter(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

// 创建store
// 将counter包装到createStore,store内就会包含状态和操作
// 导入applyMiddleware和logger之后作为第二个参数传入
const store = createStore(counter, applyMiddleware(logger));
// 抛出
export default store;
image-20220130235622352

redux-thunk

使用redux-logger来执行异步操作,导入插件并添加到applyMiddleware

import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";

function counter(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counter, applyMiddleware(logger, thunk));
export default store;

组件内使用,通过声明一个函数来返回一个执行异步操作的

import React, { Component } from "react";
import { Button } from "antd";
import { connect } from "react-redux";

const mapStateToProps = (state) => {
  return {
    num: state,
  };
};

// 声明一个函数,这个函数会返回一个函数作为参数传入asyncIncrement方法,内部模拟异步请求
// 返回的函数内传入参数,在请求数据返回后通过dispatch赋值给store
const asyncAdd = () => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch({ type: "INCREMENT" });
    }, 1000);
  };
};
const mapDispatchToProps = (dispatch) => {
  return {
    increment: () => {
      dispatch({ type: "INCREMENT" });
    },
    decrement: () => {
      dispatch({ type: "DECREMENT" });
    },
    // action默认接收一个对象,执行下一个任务
    // 如果是一个函数,则需要使用react-thunk进行异步处理
    asyncIncrement: () => {
      dispatch(asyncAdd());
    },
  };
};

@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.num}</h3>
        <Button onClick={() => this.props.asyncIncrement()}>+1</Button>
        <Button onClick={() => this.props.decrement()}>-1</Button>
      </div>
    );
  }
}

export default ReduxTest;
image-20220131001442231

模块化

项目代码重构和状态模块划分

在一个大型应用项目中,reducer会有很多,都不会直接写在store中,需要对store进行模块划分

代码划分

store/counter.reducer.js,将reducer的代码放到里面并抛出,reducer需要触发这些方法来修改state,原有定义的mapStateToProps、mapSispatchToProps 和异步方法放在组件内就显得非常冗余了,把这些代码放到counter.reducer.js中后export抛出

function counter(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

export const mapStateToProps = (state) => {
  return {
    num: state,
  };
};

// 异步操作
const asyncAdd = () => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch({ type: "INCREMENT" });
    }, 1000);
  };
};

export const mapDispatchToProps = (dispatch) => {
  return {
    increment: () => {
      dispatch({ type: "INCREMENT" });
    },
    decrement: () => {
      dispatch({ type: "DECREMENT" });
    },
    asyncIncrement: () => {
      dispatch(asyncAdd());
    },
  };
};

export default counter

组件内导入 mapStateToProps 和 mapDispatchToProps

import React, { Component } from "react";

import { Button } from "antd";
import { connect } from "react-redux";
import {mapStateToProps, mapDispatchToProps} from './store/counter.reducer'

@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.num}</h3>
        <Button onClick={() => this.props.asyncIncrement()}>+1</Button>
        <Button onClick={() => this.props.decrement()}>-1</Button>
      </div>
    );
  }
}

export default ReduxTest;

store.js内剩余代码放入store/index.js,导入counter.reducer.js

import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counter from "./counter.reducer";

const store = createStore(counter, applyMiddleware(logger, thunk));
export default store;

reducer复合

但仅仅是这样,也只能创建一个reducer的store,redux提供了一个combineReducers方法,它接收一个reduicer为成员的对象,对全部的ruducer进行复合,实现状态的模块化

import { createStore, applyMiddleware, combineReducers } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counter from "./counter.reducer";

const store = createStore(
  combineReducers({
    counter,
  }),
  applyMiddleware(logger, thunk)
);
export default store;

mapStateToProps调用时需加上reducer的这个key来读取,实现模块化的标识

export const mapStateToProps = (state) => {
  return {
    num: state.counter,
  };
};

Mobx

概念

MobX 中文文档

MobX 是一个更加轻量级的状态管理库,它通过透明的函数响应式编程使得状态管理变得简单和可扩展,在小型应用项目中更推荐Mobx。

MobX背后的哲学很简单:

  • 任何源自应用状态的东西都应该自动地获得
  • 其中包括UI、数据序列化、服务器通讯,等等
MobX unidirectional flow

React是一个消费者,将应用状态state渲染成组件树对其渲染

Mobx是一个提供者,用于存储和更新状态state

安装依赖

npm i mobx mobx-react
  • mobx —— 创建观察者和对应的事件回调函数,以及相关的状态
  • mobx-react —— 包装普通组件,让普通组件拥有状态的一些能力

使用

创建store/mobx.js

  • observable - 创建观察者对象保存共享状态
  • actions - 创建事件回调的方法,修改当前的状态
  • computed - 计算属性,本次没有用到,
import { observable, action, computed } from "mobx";

// 创建观察者
const appState = observable({
  // 创建一个num属性,默认值为0
  num: 0,
});

// actions
appState.increment = action(() => {
  appState.num += 1;
});
appState.decrement = action(() => {
  appState.num -= 1;
});


export default appState;

组件内使用

import React, { Component } from "react";
import { Button } from "antd";
import { observer } from "mobx-react";

@observer
class MobxTest extends Component {
  render() {
    return (
      <div>
        <h2>{this.props.appState.num}</h2>
        <Button onClick={() => this.props.appState.increment()}>+1</Button>
        <Button onClick={() => this.props.appState.decrement()}>-1</Button>
      </div>
    );
  }
}

export default MobxTest;

导入mobx.js和组件,将appState挂载到组件

import React from "react";
import ReactDOM from "react-dom";
import "./App.less";

import appState from "./store/mobx"
import MobxTest from "./components/MobxTest";

function render() {
  ReactDOM.render(
    <MobxTest appState={appState} />,
    document.querySelector("#root")
  );
}

render();

mobx要比redux简单很多,但这只限于小型应用使用

mobx.js也可以换另一种,装饰器写法,observable方法在mobx6.0换为了makeAutoObservable

import { makeAutoObservable, action } from "mobx";

// 创建观察者
class AppState {
  num = 0;
  constructor() {
    makeAutoObservable(this);
  }
  @action
  increment() {
    this.num++;
    console.log(this.num);
  }
  @action
  decrement() {
    this.num--;
    console.log(this.num);
  }
}
export default new AppState();

对比Redux 和 Mobx

相比于Mobx的轻量级,Redux:

  • 学习难度高
  • 工作量大
  • 内存开销大
  • 状态管理集中
  • 需遵守样板代码规范

结论:使用Mobx入门简单,构建应用迅速,但是当项目足够大时,还是Redux更适合,开启严格模式,更规范的代码管理

Redux-saga完美方案

redux-saga是一个用于管理应用程序 side effect ( 副作用: 例如异步获取数据,访问浏览器缓存等 ) 的库,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易

redux-saga 使用了 ES6 的 Generator 功能,让一步的流程更易于读取、写入和测试

通过这样的方式,这些异步的流程看起来就像是便准同步的js代码

不同于 redux-thunk,不会再遇到回调地狱问题,可以更容易的测试一步流程并保持action的代码整洁

npm i redux-saga

store/sagas.js

import { call, put, takeEvery } from "redux-saga/effects";

// 模拟登陆的api
const api = {
  login() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > 0.5) {
          resolve({ id: 1, name: "Max" });
        } else {
          reject("用户名或密码错误");
        }
      }, 1000);
    });
  },
};

// work saga
function* login(action) {
  try {
    // 异步代码同步化 (call方法调用上方api对象内的login方法发起请求)
    // 返回的result进入put,触发login的action
    const result = yield call(api.login);
    yield put({ type: "login", result });
  } catch (error) {
    yield put({ type: "loginError", message: error.message });
  }
}

// 将login和saga关联起来,类似监听
function* mySaga() {
  yield takeEvery('login_request', login)
}

export default mySaga

index.js

import { createStore, applyMiddleware, combineReducers } from "redux";
import logger from "redux-logger";
import counter from "./counter.reducer";
// 3.1 导入sagas
import mySaga from "./sagas";

// 1.导入并创建中间件
import createSagaMiddleware from "redux-saga";
const mid = createSagaMiddleware();

const store = createStore(
  combineReducers({
    counter,
  }),
  // 2.使用中间件
  applyMiddleware(logger, mid)
);

// 3.2运行当前中间件
mid.run(mySaga);

export default store;

user.reducer.js内的写法改变

const initState = {
  isLogin: false,// 表示用户未登录
  userInfo: {}
}
function user(state = initState, action) {
  switch (action.type) {
    case 'login':
      return (isLogin: true)
    default:
      return initState
  }
}

// 异步操作 for redux-thunk
const login = () => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch({ type: "login" });
    }, 1000);
  };
};

// 异步操作 for redux-saga 纯对象的action
const login = () => {
  return { type: "login_request" };
};

export const mapDispatchToProps = dispatch => {
  return {
    //action默认接收一个对象,执行下一个任务,如果是一个函数,则需要异步处理
    login: () => {
      dispatch(login())
    }
  }
}

saga的中间件已经关联到了store中,请求会走到sagas.js,找到takeEvery内的login_request,调用login函数,执行api对象中的login方法发起请求并返回数据,

function* login的result,如果返回正确请求则触发login派发动作并传递登录数据,如果请求错误则触发loginError派发动作并传递错误信息

redux-thunk 和 redux-saga的不同

  • thunk可以接受function类型的action,saga则是纯对象的action的解决方案
  • saga使用的是generator解决异步问题,非常容易用同步代码编写异步代码,是异步代码同步化写法
  • 如果是大型的复杂应用项目,更建议使用redux-saga,反之则是redux-thunk