zoukankan      html  css  js  c++  java
  • 【重点突破】—— 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);
          }
        }
      )
      

        


    注:项目来自慕课网

    人与人的差距是:每天24小时除了工作和睡觉的8小时,剩下的8小时,你以为别人都和你一样发呆或者刷视频
  • 相关阅读:
    有用的Python模块
    Python中for循环搭配else的陷阱
    MySQL实用操作
    Pycharm常用快捷键
    MySQL基础
    HTML基础
    MySQL基础
    HTTP连接管理
    TCP连接的建立和终止
    TCP数据流
  • 原文地址:https://www.cnblogs.com/ljq66/p/14376745.html
Copyright © 2011-2022 走看看