纵有疾风起
人生不言弃

如何理解Redux

中文文档
英文文档

Redux = Reducer + Flux

Redux 是 React中的状态管理容器,可以理解为存放 公用数据的地方,提供一种更加规范和安全的方式去定义组件之间的公用数据(想象一下兄弟组件传值的过程)。对于初学者来说,它的使用并不简单。

如何理解Redux插图
Redux的理解.png

三大原则

  1. 单一数据源
  2. State 是只读的
  3. 使用纯函数来执行修改

单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中

State 是只读的

唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这里的state是值 redux中的数据,不是组件的state

使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你需要编写 reducers。

redux概念

Action

行为

Action 是把数据从应用。传到 store 的有效载荷。我们的数据只能通过 Action来触发,修改。

Reducer

Reducers 负责接收 action,然后根据action去处理store
不能直接修改原来的state。

Store

store其实是一个仓库,redux应用只有一个store,当需要拆分数据时,不能拆分store,但可以拆分reducer

工作流

如何理解Redux插图(1)
Redux工作流.png

体验步骤

  1. 安装依赖
  2. 创建store
  3. 创建reducer
  4. 将store数据映射到组件中
  5. 组件触发事件 创建action
  6. 将action派发到store
  7. store自己调用reducer

初体验

实现目标

如何理解Redux插图(2)
实现一下以上效果.gif
  1. 可以发请求加载数据
  2. 点击 + - 组件 会修改数据

安装依赖

redux 是核心库 react-redux是负责将react组件连接redux

npm install redux react-redux --save

新建redux配套文件

在src/store/目录下新建 以下文件

  1. index.js store核心文件
  2. reducer/index.js 负责记录操作的reducer文件

reducer.js

// 1 定义默认数据,后期可以从接口中获取const defaultState = {  num: -1};// 2 创建和对外暴露一个函数 返回stateexport default (state = defaultState, action) => {  return state;}

store/index.js

// 1 引入 store生成器import { createStore } from "redux";// 2 引入reducerimport reducer from "./reducer";// 3 创建和对外暴露storeexport default createStore(reducer);

src/index.js

import React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';// 1 引入 react-redux  负责将store和组件连接起来import { Provider } from "react-redux";// 1 引入 storeimport store from "./store";// 2 将App用 Provider 标签包裹起来// 2 将store通过属性的方式传递到App组件上ReactDOM.render(<Provider store={store} ><App /></Provider>, document.getElementById('root'));

App.js

import React, { Component } from 'react';// 1 引入 react-redux 中 链接 组件和store的对象 connectimport { connect } from "react-redux";class App extends Component {  render() {    return (      // 4 使用store中的数据       <div className="App">        {this.props.num}        <hr />        <button> + </button>        <button> - </button>      </div>    );  }}// 2 将 store中的数据传递到 App的props上const mapStateToProps = (state) => {  return {    num: state.num  }}// 3 用 connect 将store中的数据通过props的方式传递到App上export default connect(mapStateToProps, null)(App)

思考

以上代码,“action去了哪里了呢? ”
答:没有操作数据的行为,当然么有action了

抽离组件,绑定事件

编辑App.js文件
将 两个按钮 抽离出来变成两个组件,这才满足组件共享数据的设计理念

// 加class AddBtn extends Component {  render() {    return <button onClick={this.props.numAdd} >+</button>  }} // 减class SubstraBtn extends Component {  render() {    return <button onClick={this.props.numSubStra}>-</button>  }}class App extends Component {  render() {    return (      <div className="App">        {this.props.num}        <hr />        {/* 使用组件 传递props */}        <AddBtn {...this.props}></AddBtn>        {/* 使用组件 传递props*/}        <SubstraBtn {...this.props}></SubstraBtn>      </div>    );  }}

此时,我们发现 两个组件上都绑定了点击事件

<button onClick={this.props.numAdd} >+</button><button onClick={this.props.numSubStra}>-</button>

所以,现在我们需要另外定义 两个事件,在redux中就叫做行为 action
和 mapStateToProps同层级,创建 行为合集 mapDispatchToProps,并且把它传入 connect的第二个参数内。

// 2 将行为action 链接到store和组件上const mapDispatchToProps = (dispatch) => {  return {    // 点击事件中的加    numAdd: () => {      // 创建一个action,负责将行为类型和数据交给reducer      const action = {        // type是一个自定义的字符串        type: "NUM_ADD",        value: 1      };      // 派发行为- 会将action 派发到  reducer中      dispatch(action);    },    // 点击事件中的减    numSubStra: () => {      const action = {        type: "NUM_SUBSTRA",        value: 1      };      dispatch(action);     }  }}// 3 用 connect 将store中的数据通过props的方式传递到App上export default connect(mapStateToProps, mapDispatchToProps)(App)

编辑 reducer逻辑

// 1 定义默认数据,后期可以从接口中获取const defaultState = {  num: -1};// 2 创建和对外暴露一个函数 返回stateexport default (state = defaultState, action) => {  // 当 action被派发时(dispatch),会触发  if (action.type === "NUM_ADD") {    // 复制一份旧的state    let newState = Object.assign({}, state);    newState.num += action.value;    // 将新的state返回,即可触发store的更新    return newState;  }  if (action.type === "NUM_SUBSTRA") {    // 复制一份旧的state    let newState = Object.assign({}, state);    newState.num -= action.value;    return newState;  }  return state;}

优化手段

通过以上步骤,可以把redux的使用流程走完,但是在公司中,还会对以上的代码进行优化,存在以下优化的步骤

  1. 将state中的数据修改为对象形式,因为数据一般不会这么简单。
  2. 将action的type类型提取成常量的形式,避免手写字符串出错
  3. 将action的创建由字面量改为 action生成器来创建,方便后期代码的维护和测试
  4. 拆分和合并reducer,有时候,会根据不同的数据使用不同的reducer
  5. 添加异步action,因为有时候我们的数据是从异步中获取的不是同步的方式。

将state中的数据修改为对象形式

编辑 reducer/index.js

const defaultState = {  // 修改为对象形式  payload: {    num: -1  }};export default (state = defaultState, action) => {  if (action.type === "NUM_ADD") {    let newState = Object.assign({}, state);    // 修改为对象形式    newState.payload.num += action.value;    return newState;  }  if (action.type === "NUM_SUBSTRA") {    let newState = Object.assign({}, state);    // 修改为对象形式    newState.payload.num -= action.value;    return newState;  }  return state;}

修改App.js中使用的state的代码

const mapStateToProps = (state) => {  return {     {/* 修改为对象的形式 */}    num: state.payload.num  }}

将action的type类型提取成常量的形式

新建文件 src/store/actionType/index.js

export const NUM_ADD = "NUM_ADD";export const NUM_SUBSTRA = "NUM_SUBSTRA";

修改 使用到了 NUM_ADD的文件
编辑 src/store/reducer/index.js

// 1 导入 type常量import { NUM_ADD, NUM_SUBSTRA } from "../actionType";export default (state = defaultState, action) => {  // 2 修改为常量的方式  if (action.type === NUM_ADD) {    ......  }  return state;}

编辑 src/App.js

// 1 导入 type 常量import { NUM_ADD, NUM_SUBSTRA } from "./store/actionType";const mapDispatchToProps = (dispatch) => {  return {    numAdd: () => {      const action = {        // 2  使用 type常量        type: NUM_ADD,        value: 1      };      dispatch(action);    }}

使用action生成器来创建action

新建文件 src/store/actionCreator/index.js

import {  NUM_ADD,NUM_SUBSTRA} from "../actionType";export const numAdd = () => ({  type: NUM_ADD,  value: 1})export const numSubstra = () => ({  type: NUM_SUBSTRA,  value: 1})

修改 App.js

// 1 导入actionimport { numAdd, numSubstra } from "./store/actionCreator";const mapDispatchToProps = (dispatch) => {  return {    numAdd: () => {      // 2 修改为 生成器生成的action      dispatch(numAdd());    }  }}

拆分和合并reducer

当需要共享的数据足够多时,一般会拆分多个reducer方便管理
如 拆分成两个 reducer 一个是操作 nums的,一个是操作水果的。

  1. 编辑 actionType/index.js
export const NUM_ADD = "NUM_ADD";export const NUM_SUBSTRA = "NUM_SUBSTRA";// 新增 增加 苹果action typeexport const APPLE_NUM_ADD = "APPLE_NUM_ADD";// 新增 减少 苹果action typeexport const APPLE_NUM_SUBSTRA = "APPLE_NUM_SUBSTRA";
  1. 编辑 actionCreator/index.js
// 新增 添加苹果 action export const appleNumAdd = () => ({  type: APPLE_NUM_ADD,  value: 1})// 新增 减少苹果 actionexport const appleNumSubstra = () => ({  type: APPLE_NUM_SUBSTRA,  value: 1})
  1. 新建文件 reducer/numReducer.js

将 以前 reducer/index.js 全部复制过去即可

import { NUM_ADD, NUM_SUBSTRA } from "../actionType";const defaultState = {  payload: {    num: -1  }};export default (state = defaultState, action) => {  if (action.type === NUM_ADD) {    let newState = Object.assign({}, state);    newState.payload.num += action.value;    return newState;  }  if (action.type === NUM_SUBSTRA) {    let newState = Object.assign({}, state);    newState.payload.num -= action.value;    return newState;  }  return state;}

4.新建文件 reducer/fruitReducer.js

import { APPLE_NUM_ADD, APPLE_NUM_SUBSTRA } from "../actionType";const defaultState = {  payload: {    appleNum: 110  }};export default (state = defaultState, action) => {  if (action.type === APPLE_NUM_ADD) {    let newState = Object.assign({}, state);    newState.payload.appleNum += action.value;    return newState;  }  if (action.type === APPLE_NUM_SUBSTRA) {    let newState = Object.assign({}, state);    newState.payload.appleNum -= action.value;    return newState;  }  return state;}
  1. 编辑 reducer/index.js 用来合并 两个reducer fruitReducernumReducer
// 1 引入 合并reducer的对象import { combineReducers } from "redux";import fruitReducer from "./fruitReducer";import numReducer from "./numReducer";// 2 对象的形式传入 要合并的reducerconst rootReducer = combineReducers({ numReducer, fruitReducer });export default rootReducer;
  1. 修改App.js
import React, { Component } from 'react';import { connect } from "react-redux";// 1 多导入两个action appleNumAdd 和 appleNumSubstraimport { numAdd, numSubstra, appleNumAdd, appleNumSubstra } from "./store/actionCreator";class AddNumBtn extends Component {  render() {    return <button onClick={this.props.numAdd} >+</button>  }}class SubstraNumBtn extends Component {  render() {    return <button onClick={this.props.numSubStra}>-</button>  }}// 2 新增的组件class AddFruitBtn extends Component {  render() {    // 2.1 新绑定的事件    return <button onClick={this.props.appleNumAdd} >+</button>  }}// 2 新增的组件class SubstraFruitBtn extends Component {  render() {    // 2.1 新绑定的事件    return <button onClick={this.props.appleNumSubStra}>-</button>  }}class App extends Component {  render() {    // 3 修改过的页面代码    return (      <div className="App">      数量  {this.props.num}        <hr />        <AddNumBtn {...this.props}></AddNumBtn>        <SubstraNumBtn {...this.props}></SubstraNumBtn>        <hr />      水果数量  {this.props.appleNum}        <br />        {/* 3.1 引入新组件 */}        <AddFruitBtn {...this.props}></AddFruitBtn>        {/* 3.1 引入新组件 */}        <SubstraFruitBtn {...this.props}></SubstraFruitBtn>      </div>    );  }}const mapStateToProps = (state) => {  // 4 修改数据的获取方法  return {    num: state.numReducer.payload.num,    appleNum: state.fruitReducer.payload.appleNum  }}const mapDispatchToProps = (dispatch) => {  return {    numAdd: () => {      dispatch(numAdd());    },    numSubStra: () => {      dispatch(numSubstra());    },    // 5 新增的action    appleNumAdd: () => {      dispatch(appleNumAdd());    },    // 5 新增的action    appleNumSubStra: () => {      dispatch(appleNumSubstra());    },  }}// 3 用 connect 将store中的数据通过props的方式传递到App上export default connect(mapStateToProps, mapDispatchToProps)(App)
如何理解Redux插图(3)
图示.png

添加异步action redux-thunk

想象一下,我们对数据库进行查询,编辑和删除,其实都是异步操作。现在,让我们的应用支持异步action操作。

  1. 安装依赖 redux-thunk
npm install redux-thunk --save
  1. 修改store/index.js
// 1 引入 redux的中间件连接器import { createStore, applyMiddleware } from "redux";import reducer from "./reducer";// 1 引入 redux-thunkimport reduxThunk from "redux-thunk";// 2 使用中间件连接器将redux-thunk传入 store构造器export default createStore(reducer, applyMiddleware(reduxThunk));
  1. 修改 actionCreator/index.js
// 1 修改 减少苹果的action 为异步的形式export const appleNumSubstra = () => {   // 2 返回一个函数  return (dispatch) => {    // 3 开启异步  后期将 setTimeout 替换成异步的方式即可    setTimeout(() => {      const action = {        type: APPLE_NUM_SUBSTRA,        value: 1      };        // 4 开启派发      dispatch(action);    }, 2000);  }}

React的生命周期
React路由
闭包,原型链,继承,AJAX请求步骤等javaScript基础

文章转载于:https://www.jianshu.com/p/d494e5f1baab

原著是一个有趣的人,若有侵权,请通知删除

未经允许不得转载:起风网 » 如何理解Redux

分享到: 生成海报
avatar

评论 抢沙发

评论前必须登录!

立即登录   注册

切换注册

登录

忘记密码 ?

切换登录

注册

我们将发送一封验证邮件至你的邮箱, 请正确填写以完成账号注册和激活