Redux 初探【超详细】

193 阅读5分钟

一、Redux 基本思想

这里给出一个图来说明 redux 的主要三个部分构成。

image.png

1. Action:

用来表示需要对 state 进行什么样操作的数据。主要的数据结构如下:

let action = {
    type: 'xs',
    payload: {
        name: 'xxx',
    },
};

其中,type 主要描述你当前这个 action 的名称,方便后续进行 state 操作的逻辑处理。

Redux 允许我们去在 action 对象中定义其他的数据。比如上面的 action.payload 属性。

也就是说,除了 type 是必须存在的属性(用来描述当前操作的类型),我们可以在 action 中定义其他任意类型的属性。但是 Redux 还是希望我们能够尽量将 action 设计的简洁一些。

2. Reducer:

用来告诉 state,遇到不同类型的 aciton 的时候,应该做什么样的操作。 可以简单概括成是这样一个函数:(currState, action) => nextState。根据当前的 stateaction,获得更改之后得到的 state

一开始我们可以只设计一个 reducer,让这个 reducer 函数来负责整个 state 的变换。比如下面这样:

const reducer = (state, action)=>{
    // 判断是哪种类型的 action
    if(action.type === 'changeName'){
        // 更改姓名 操作
        /* 由于 redux 约定不允许改变原有 state(数据不可变)
               所以使用 Object.assign 来生成一个新的 state */
        return Object.assign({}, state, {
            name: action.payload.name,
          });
    }
    if(action.type === 'xxx'){
        // 进行别的操作
    }
}

随着我们应用的增长,为了方便管理,可以将 reducer 函数拆分出来,设计多个小的 reducer 函数,每个 reducer 函数来控制一部分的 state 的变化。

之后将这些小的 reducer 函数在根 reducer 的函数中进行应用。基本的模式如下所示:

// 对 state.name 负责的 reducer 函数
const nameReducer = (state, action)=>{
    if(action.type === 'changeName'){
         return action.payload.name;
    }
};

// 对 state.country 负责的 reducer 函数
const countryReducer = (state, action)=>{
    if(action.type === 'xxx'){
         // 逻辑操作,返回部分 state
    }
};

// 整体 reducer
const reducer = (state, action)=>{
    return {
        name: nameReducer(state.name, action),
        country: countryReducer(state.country, action),
    };
};

Redux 也给出了自己的 api combineReducers 函数,方便我们去使用:

import { combineReducers } from 'redux';
// 小的 reducer 函数,用来管理部分 state
import { addPeople } from './addPeople.js';
import { setCOuntry } from './setCountry.js';

const reducerObj = {
  people: addPeople,
  country: setCountry,
};

const reducers = combineReducers(reducerObj);

export default reducers;

后面我们会用到 createStore api,这个函数接收一个 reducer 函数作为参数,创建出 store。

这里就会通过传入对象的 key 值来找到 state 中对应的部分。比如,使用到 setCountry 的时候,就会改变 state.country 的值。

后面在描述整个数据流的过程中,会详细说到这里 根 reducer 函数的 key & value 的作用。

3. Store:

主要负责

  • state 的存储
  • 获取部分 state 的值:使用 getState() 函数
  • 更新 state:使用dispatch(action) 函数
  • 注册监听器:使用 subscribe(listener) 函数
  • 注销监听器:使用 subscribe(listener) 返回的函数注销

可以使用 Redux 的 api 来创建 store,来管理 state。需要做的事情是下面几步:

import { createStore } from 'redux';
import reducer from './reducers';

let store = createStore(reducer); // 将 store 和对应的 根 reducer 连接起来

(目前应该先使用前三个api来简单体验一下 redux 的使用)

4. Redux 的基本使用:

目前我们已经知道,使用 Redux 需要创建的基本的三个组成部分。下面来简单看一下如何将 redux 和你已有的 react 组件结合起来:

文件结构:

/src
    index.js // 入口文件,本例子中会是一个简单的 react 组件
    /actions
        index.js // 返回对应的 action 对象的函数
    /reducers
        index.js // 总 reducer,其中调用各种 reducer 函数
        addPeople.js // 子 reducer,管理 state.people 
    /store
        index.js // 存储 store 的位置

具体的代码文件如下所示:

组件

/src/index.js

import React from "react";
import PropTypes from "prop-types";
import { connect } from 'react-redux';

import { store } from './store/index.js';
import { addPeople } from './actions/index.js';

function TestApp(props) {
  const { dispatch } = props; // 获取 dispatch 函数,用来改变 store 中的 state
  
  // 当 button 点击事件发生的时候,改变 state.people 中的信息
  onAddPeopleCLick = () => {
    // 触发 addPeople action
    dispatch(addPeople({
      name: 'jack',
      age: 20,
    }))
  }
  
  return (
    // 注入 store
    <Provider store={store}>
      {/* 显示人数 */}
      <div id='peopleNum'>peopleNum: {props.people.length}</div>
      {/* 增加人数 按钮 */}
      <button onClick={onAddPeopleCLick}>add people</button>
    </Provider>
  );
}

TestApp.propTypes = {};

// 使用 connect 函数,将 store 中保存的 state 注入到 props 中,以便我们使用 store 中的信息
// 第一个箭头函数表示如何将 state 映射到这个新组件的 props 上
// connect 函数返回一个由原有组件,经过注入新的 props之后,包装好的新组件
export default connect((state)=>{
  // 返回的内容就是我们即将注入 props 的内容
  return {
    people: state.people,
  };
})(TestApp);

定义 action

/src/actions/index.js

// 增加人数
export const addPeople = (peopleInfo) => {
   return {
     type: 'ADD_PEOPLE',
     payload: peopleInfo,
   };
}

Reducer 定义

/src/reducer/index.js

// 总 reducer
import { combineReducers } from 'redux';
import { addPeople } from './addPeople.js';
const reducers = combineReducers({
  people: addPeople,
});

export default reducers;

/src/reducer/addPeople.js

export const addPeople = (state, action) => {
  if(action.type === 'ADD_PEOPLE'){
    return [
        ...state,
        action.payload, // 新增人员的信息
    ]
  }
}

store 定义:

/src/store/index.js

import { createStore } from 'redux';
import { reducers } from './reducer/index.js';

export let store = createStore(reducers);

二、补充:redux 中一些api的具体作用

1. combineReducers 函数:

combineReducers(reducerObj)

  • 接收一个对象 reducerObj,会根据这个对象的 key 来寻找 state 中对应的部分,value 中的函数都是负责 state[key] 的对应的内容。
combineReducers({
  people: addPeople,
  country: changeCountry,
});

// 最后得到的state的形式:
/**
{
  people: [],
  country: 'xx',
}
*/


// 增加 个人信息
dispatch({
  type: 'ADD_PEOPLE',
  payload: {
    name: 'jack',
    age: '20',
  }
}); 

// 最后得到的state:
/**
{
  people: [
    {
      name: 'jack',
      age: '20',
    },
  ],
  country: 'xx',
}
*/
2. createStore 函数:

createStore(reducer, initialState)

  • reducer
    • 接收一个 reducer 函数,这个函数告诉 strore,根据现在的 state 和 action,如何得到下一步的 state。
  • initialState:存储的全局 state 的初始值
3. connect 函数:(待更新)

connect(mapStateToProps, mapDispatchToProps,)

  • mapStateToProps: 函数,
4. 从 dispatch action 到 state 变化 发生了什么(切分了多个reducer):

下面给出一张图说明一下,从 dispatch 函数触发的时候,到最后 state 更改的时候,都是怎么进行变化的。 image.png

  • 在 dispatch 函数被调用的时候,根 reducer 函数被调用。
  • 会触发下面每一个 子 reducer 函数
  • 将子 reducer 函数得到的内容进行合并,形成新的 state。每个子 reducer 函数返回的内容,都会作为根 reducer 定义时,子 reducer 函数的 key 下面的内容。

next step: 了解一下 redux 中的异步操作,以及 redux 背后的运作数据流问题。目前我正在使用 redux-toolkit,下次来总结一下这个东西的简单用法