【重点突破】—— Redux基础&进阶:用好Redux必备(二)

前言:正在学习react大众点评项目课程的todoList应用阶段,学习react、redux、react-router基础知识。


一、Redux项目结构组织方式

1.按照类型

  • 当项目变大后非常不方便

     

2.按照功能模块

  • 方便开发,易于功能的扩展。
  • 但不同的功能模块的状态可能存在耦合。

      

3.Ducks鸭子(推荐

  

// Action Creators
export const actions = {
  loadWidget: function loadWidget() {
    return { type: LOAD };
  },
  createWidget: function createWidget(widget) {
    return { type: CREATE, widget };
  },
  updateWidget: function updateWidget(widget) {
    return { type: UPDATE, widget };
  },
  removeWidget: function removeWidget(widget) {
    return { type: REMOVE, widget };
  }
}
  • view层和状态管理层比较彻底的解耦
  • 好处:组件引用这些action时,不需要一个一个去引用action的名字,引用时直接引用一个action

 

二、State设计原则

1.常见两种错误

  • 以API为设计State的依据
  • 以页面UI为设计State的依据

2.像设计数据库一样设计StateS

  • 设计数据库基本原则
  1. 数据按照领域(Domain)分类,存储在不同的的表中,不同的表中存储的列数据不能重复
  2. 表中每一列的数据都依赖于这张表的主键。
  3. 表中除了主键以外的其他列,互相之间不能有直接依赖关系。
  • 设计State原则
  1. 数据按照领域把整个应用的状态按照领域(Domain)分成若干个子State,子State之间不能保存重复的数据
  2. 表中State以键值对的结构存储数据,以记录的key/ID作为记录 的索引,记录种的其它字段都依赖于索引
  3. State中不能保存可以通过已有数据计算而来的数据,即State中的字段不互相依赖

3.有序State

{
  "posts": {
    "1": {
      "id": 1,
      "title": "Blog Title",
      "content": "Some really short blog content.",
      "created_at": "2016-01-11T23:07:43.248Z",
      "author": 81,
      "comments": [
        352
      ]
    }
    ...
    "3": {
      ...
    }
  },
  "postIds": [1, ..., 3], //保证博客列表的有序性 posts.id主键
                            避免state对象嵌套层级过深的问题
  "comments": {
    "352": {
      "id": 352,
      "content": "Good article!",
      "author": 41
    },
    ...
  },
  "authors": {
    "41": {
      "id": 41,
      "name": "Jack"
    },
    "81": {
      "id": 81,
      "name": "Mr Shelby"
    },
    ...
  }
}   

4.包含UI状态的State, 合并State节点

{
  "app":{
    "isFetching": false,
  	"error": "",
  },
  "posts":{
    "byId": {
      "1": {
        ...
      },
      ...
    },
    "allIds": [1, ...],
  } 
  "comments": {
    ...
  },
  "authors": {
    ...
  }
}  
  • 补充:
  1. State应该尽量扁平化(避免嵌套层级过深)
  2. UI State:具有松散性特点(可以考虑合并,避免一级节点数量过多)

 

三、Selector函数(选择器函数)

作用一:从Redux的state中读取部分数据,将读取到的数据给Container Compoinents使用

使用原因一:实现Containern Components层和Redux的state层的解耦

问题:AddTodoContainer.js中通过对象属性访问的方式获取text属性;

               如果text要修改为date,所有使用text的地方都要改

const mapStateToProps = state => ({
    text: state.text
});             

使用Selector函数解决:

  • src->selector->index.js
export const getText = (state) => state.text 
  • AddTodoContainer.js中通过函数调用的方式获取text属性
import {getText} from "../selectors"

const mapStateToProps = state => ({
     text: getText(state)
});  

使用原因二:Component代表的view层,和Redux代表的状态层是独立的两个层级;

                      这两个层级的交互应该是通过接口进行通信的,而不是通过state的数据结构通信。

作用二:对读取到的状态进行计算,然后返回计算后的值

  • src->selector->index.js
    export const getVisibleTodos = (state) => {
      const {todos: {data}, filter} = state
      switch (filter) {
        case "all":
          return data;
        case "completed":
          return data.filter(t => t.completed);
        case "active":
          return data.filter(t => !t.completed);
        default:
          return new Error("Unknown filter: " + filter);
      }
    };
  • TodoListContainer.js
    import {getVisibleTodos} from "../selectors"
    
    const mapStateToProps = state => ({
    todos: getVisibleTodos(state)
    });

补充:如果selector函数数量非常多,可以拆分为多个文件

 

四、深入理解前端状态管理思想

 

 

五、Middleware(中间件)

示例:redux-thunk

本质:增强store dispatch的能力

 

默认情况下,dispatch只能处理一个普通对象类型的action

使用redux-thunk后,dispatch可以处理函数类型的action

  • middlewares->logger.js
    /**
     * 打印action、state
     */
    const logger = ({getState, dispatch}) => next => action => {
      console.group(action.type);
      console.log('dispatching:', action);
      const result = next(action);
      console.log('next state:', getState());
      console.groupEnd();
      return result;
    }
    
    export default logger;
    

  

 

六、store enhancer(store增强器)

增强redux store的功能

createStore(reducer, [preloadedState], [enhancer])

  • enhancers->logger.js
    /**
     * 打印actions、state
     */
    const logger = createStore => (...args) => {  //args: reducer、初始state
      const store = createStore(...args);
      const dispatch = (action) => {
        console.group(action.type);
        console.log('dispatching:', action);
        const result = store.dispatch(action);
        console.log('next state:', store.getState());
        console.groupEnd();
        return result;
      }
      return {...store, dispatch}
    }
    
    export default logger;
  • 使用store enhancer:applyMiddleware也是store enhancer,使用compose函数将多个store enhancer连接、组合

    import { createStore, applyMiddleware, compose } from "redux";
    import loggerEnhancer from "./enhancers/logger"
    
    const store = createStore(rootReducer, 
                        compose(applyMiddleware(thunkMiddleware),loggerEnhancer));
    

  

中间件是一种特殊的store enhancer

日常使用中:尽可能的使用middleware,而不是store enhancer,因为middleware是一个更高层的抽象。

慎用store enhancer:  

  • 通过store enhancer不仅可以增强store dispatch的能力,还可以增强其它store中包含的方法,例如getState、subscribe,看似有更大的灵活度对store进行修改,但其实也隐含了一个风险,就是很容易破环store的原有逻辑。
  • 一般选择middleware中间件增强store dispatch的能力:因为中间件约束了我们一个行为,让我们难以去改变store的较底层的逻辑

 

七、常用库集成:Immutable.js

用来操作不可变对象的库

常用的两个不可变的数据结构 

const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 }); //不可变类型的map结构
const map2 = map1.set('b', 50);
map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50
const { List } = require('immutable');
const list1 = List([ 1, 2 ]);
const list2 = list1.push(3, 4, 5);
const list3 = list2.unshift(0);
const list4 = list1.concat(list2, list3);
assert.equal(list1.size, 2);
assert.equal(list2.size, 5);
assert.equal(list3.size, 6);
assert.equal(list4.size, 13);
assert.equal(list4.get(0), 1);

1.在项目中引入immutable  

npm install immutable

2.改造reducer:todo.js  

  • 原本的reducer通过es6扩展对象方式新建一个state
    return {
        ...state,
        isFetching: true
      }
  • 借助immutable的api创建和修改不可变的state

    import Immutable from "immutable";    
    
    const reducer = (state = Immutable.fromJS(initialState), action) => {
      switch (action.type) {
        case FETCH_TODOS_REQUEST:
          return state.set("isFetching", true);
        case FETCH_TODOS_SUCCESS:
          return state.merge({
            isFetching: false,
            data: Immutable.fromJS(action.data)
          });
        case FETCH_TODOS_FAILURE:
          return state.merge({
            isFetching: false,
            error: action.error
          });
        default:
          const data = state.get("data");
          return state.set("data", todos(data, action));
      }
    };
    
    const todos = (state = Immutable.fromJS([]), action) => {
      switch (action.type) {
        case ADD_TODO:
          const newTodo = Immutable.fromJS({
            id: action.id,
            text: action.text,
            completed: false
          });
          return state.push(newTodo);
        case TOGGLE_TODO:
          return state.map(todo =>
            todo.get("id") === action.id
              ? todo.set("completed", !todo.get("completed"))
              : todo
          );
        default:
          return state;
      }
    };
    
  1. Immutable.fromJS(initialState):将一个JS对象转变为一个不可变的对象

  2. state.set("isFetching", true): set方法只能一次修改一个属性

  3. state.merge({
        isFetching: false,
        data: Immutable.fromJS(action.data)   //将数组转化为不可变对象
    })  

    merge方法可以将一个新的JS对象merge到原有的不可变对象中

    注:字符串类型本身就是一个不可变对象,不需要进行转化

  4. const data = state.get("data"):get方法获取某一层级属性的值

  5. const newTodo = Immutable.fromJS({
       id: action.id,
       text: action.text,
       completed: false
    });
    return state.push(newTodo);

    list数据解构的push区别于数组的push,会返回一个新的不可变对象

  6. state.map(todo =>
           todo.get("id") === action.id
               ? todo.set("completed", !todo.get("completed"))
               : todo
    );

    map方法针对不可变对象进行操作,遍历出来的每一个值都是不可变对象类型

    访问属性仍要通过get访问,修改属性仍要通过set修改

3.改变Selector 

export const getText = (state) => state.get("text")

export const getFilter = (state) => state.get("filter")

export const getVisibleTodos = (state) => {
  //修改前:const {todos: {data}, filter} = state
  const data = state.getIn(['todos', 'data']); //获取todos下的data属性值
  const filter = state.get('filter'); //获取第一层级的filter属性值
  switch (filter) {
    case "all":
      return data;
    case "completed":
      return data.filter(t => t.get('completed'));
    case "active":
      return data.filter(t => !t.get('completed'));
    default:
      return new Error("Unknown filter: " + filter);
  }
};    
  1. state.getIn(['todos', 'data']):
    

    getIn()API可以从外层到内层逐层的遍历不可变对象的属性  

4.改变container容器型组件:todoLIstContainer.js

注意:现在获取的todos已经不是一个普通的JS对象,而是Immutable类型的不可变对象,不能直接使用,

           必须转化为普通的JS对象,才能保证展示型组件可以正常使用。

const mapStateToProps = state => ({
     todos: getVisibleTodos(state).toJS()   
})
  1. todos: getVisibleTodos(state).toJS():
    

    toJS()将不可变对象转变为普通的JS对象

5.修改combineReducers   

因为redux提供的combineReducers API只能识别普通的JS对象,而现在每个子reducer返回的state都是一个不可变类型的对象

npm install redux-immutable
import { combineReducers } from 'redux-immutable'

在高阶组件中完成Immutable不可变对象转化为JS对象  

  • src->HOCs->toJS.js
    import React from "react";
    import { Iterable } from "immutable";
    
    export const toJS = WrappedComponent => wrappedComponentProps => {
      const KEY = 0;  //数组的第一个位置:属性名
      const VALUE = 1; //数组的第二个位置:属性值
      const propsJS = Object.entries(wrappedComponentProps).reduce(
        (newProps, wrappedComponentProp) => {
          newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(
            wrappedComponentProp[VALUE]
          )
            ? wrappedComponentProp[VALUE].toJS()
            : wrappedComponentProp[VALUE];
          return newProps;
        },
        {} //初始值是默认的空对象
      );
      return <WrappedComponent {...propsJS} />;
    
  1. Immutable底层存储时仍然通过数组的形式存储

  2. Iterable.isIterable()判断是否是Immutable的不可变对象

 

资料来源:情义w的简书

资料来源:_littleTank_的简书  

容器型组件中对展示型组件用高阶组件包裹

import {toJS} from "../HOCs/toJS"

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(toJS(TodoList));

 

八、常用库集成:Reselect

减少state的重复计算

使用Reselect创建的selector函数,只要这个selector使用到的state没有发生改变,这个selector就不会重新去计算

https://github.com/reduxjs/reselect

  • 使用Reselect
    npm install reselect  
  • src->selectors->index.js
    import { createSelector } from "reselect"
    
    export const getText = (state) => state.text
    
    export const getFilter = (state) => state.filter
    
    const getTodos = state => state.todos.data
    
    export const getVisibleTodos = createSelector(
      //修改前:const {todos: {data}, filter}= state
      [getTodos, getFilter],
      (todos, filter) => {
        switch (filter) {
          case "all":
            return todos;
          case "completed":
            return todos.filter(t => t.completed);
          case "active":
            return todos.filter(t => !t.completed);
          default:
            return new Error("Unknown filter: " + filter);
        }
      }
    )
    

      


注:项目来自慕课网

posted @ 2021-02-05 21:03  柳洁琼Elena  阅读(91)  评论(0编辑  收藏  举报