zoukankan      html  css  js  c++  java
  • redux --(1)核心概念(stateaction educer)、三大原则、todolist with redux

    2019-11-19:

    学习内容:

    Redux 是 JavaScript 状态容器,提供可预测化的状态管理。


    一、介绍:

    在下面的场景中,引入 Redux 是比较明智的:

    • 你有着相当大量的、随时间变化的数据
    • 你的 state 需要有一个单一可靠数据来源
    • 你觉得把所有 state 放在最顶层组件中已经无法满足需要了

    要点:

      应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。

      随着应用不断变大,你应该把根级的 reducer 拆成多个小的 reducers,分别独立地操作 state 树的不同部分,而不是添加新的 stores。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。

    动机:

      太多的state(状态),state 在什么时候,由于什么原因,如何变化已然不受控制。这里的复杂性很大程度上来自于:我们总是将两个难以理清的概念混淆在一起:变化和异步Redux 试图让 state 的变化变得可预测。

    核心概念:(用todolist 举例)

    state:

    {
      todos: [{
        text: 'Eat food',
        completed: true
      }, {
        text: 'Exercise',
        completed: false
      }],
      visibilityFilter: 'SHOW_COMPLETED'
    }

    action:要想更新 state 中的数据,你需要发起一个 action。Action 就是一个普通 JavaScript 对象用来描述发生了什么。action 就像是描述发生了什么的指示器。

    { type: 'ADD_TODO', text: 'Go to swimming pool' }
    { type: 'TOGGLE_TODO', index: 1 }
    { type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }

    reduce:为了把 action 和 state 串起来,开发一些函数,这就是 reducer。reducer 只是一个接收 state 和 action,并返回新的 state 的函数。往往需要好几个reducers组合使用:

    (两个小的reducer)

    // 改可视的筛选器条件:
    function
    visibilityFilter(state = 'SHOW_ALL', action) { if (action.type === 'SET_VISIBILITY_FILTER') { return action.filter } else { return state } }
    // 对state加入action的操作
    function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return state.concat([{ text: action.text, completed: false }]) case 'TOGGLE_TODO': return state.map((todo, index) => action.index === index ? { text: todo.text, completed: !todo.completed } : todo ) default: return state } }

    (再开发一个 reducer 调用这两个 reducer,进而来管理整个应用的 state:)

    function todoApp(state = {}, action) {
      return {
        todos: todos(state.todos, action),
        visibilityFilter: visibilityFilter(state.visibilityFilter, action)
      }
    }

    三大原则:

    (1)单一数据源:

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

        Redux 并不在意你如何存储 state,state 可以是普通对象,不可变对象,或者其它类型。

      来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。

      由于是单一的 state tree ,调试也变得非常容易。在开发中,你可以把应用的 state 保存在本地,从而加快开发速度。  

      以前难以实现的如“撤销/重做”这类功能也变得轻而易举。

    (2)State是只读的,唯一可改变它的只有action:

      action 是一个用于描述已发生事件的普通对象。

      确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心竞态条件(race condition)的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。

    // 上面的action写成dispatch:
    
    store.dispatch({
      type: 'COMPLETE_TODO',
      index: 1
    })
    
    store.dispatch({
      type: 'SET_VISIBILITY_FILTER',
      filter: 'SHOW_COMPLETED'
    })

    (3)使用纯函数来执行修改(就是reducers了)

      随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。

      reducer应该是纯的:不纯的 reducer 会使一些开发特性,如时间旅行、记录/回放或热加载不可实现。此外,在大部分实际应用中,这种数据不可变动的特性并不会带来性能问题,就像 Om 所表现的,即使对象分配失败,仍可以防止昂贵的重渲染和重计算。而得益于 reducer 的纯度,应用内的变化更是一目了然。


    二、基础:

    (1)Action:

      Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。

      我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。除了 type 字段外,action 对象的结构完全由你自己决定。

      这时,我们还需要再添加一个 action index 来表示用户完成任务的动作序列号。因为数据是存放在数组中的,所以我们通过下标 index 来引用特定的任务。而实际项目中一般会在新建数据的时候生成唯一的 ID 作为数据的引用标识。

    i、Action 创建函数:生成 action 的方法

      在 Redux 中的 action 创建函数只是简单的返回一个 action: 这样做将使 action 创建函数更容易被移植和测试。

      Redux 中只需把 action 创建函数的结果传给 dispatch() 方法即可发起一次 dispatch 过程。

    // 创建action
    function addTodo(text) {
      return {
        type: ADD_TODO,
        text
      }
    }
    
    // dispatch
    dispatch(addTodo(text))

      或者创建一个 被绑定的 action 创建函数 来自动 dispatch:

      ⚠️注意:Action 创建函数也可以是异步非纯函数。

    ii、Action 在todolist中的代码:

    (2)Reducer:

       Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state

       ⚠️关于设计state 结构:开发复杂的应用时,不可避免会有一些数据相互引用。建议你尽可能地把 state 范式化,不存在嵌套。把所有数据放到一个对象里,每个数据以 ID 为主键,不同实体或列表间通过 ID 相互引用数据。把应用的 state 想像成数据库。例如,实际开发中,在 state 里同时存放 todosById: { id -> todo } 和 todos: array<id> 是比较好的方式,本文中为了保持示例简单没有这样处理。

      ⚠️ 永远不要在 reducer 里做这些操作:

    • 修改传入参数;
    • 执行有副作用的操作,如 API 请求和路由跳转;
    • 调用非纯函数,如 Date.now() 或 Math.random()

      只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。 

     (a):写一个reducer,接受旧state,action(VisibilityFilters),返回一个新的action:

    注意:i、不要修改 state。 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, { visibilityFilter: action.filter }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对 ES7 提案对象展开运算符的支持, 从而使用 { ...state, ...newState } 达到相同的目的。

    ii、在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state

    i、Redux 首次执行时,state 为 undefined(也就是没有值,可以触发默认值语法),此时我们可借机设置并返回应用的初始 state。

    import { VisibilityFilters } from './actions'
    
    const initialState = {
      visibilityFilter: VisibilityFilters.SHOW_ALL,
      todos: []
    }
    
    function todoApp(state, action) {
      if (typeof state === 'undefined') {
        return initialState
      }
      // 默认值语法,这里还是旧语法
      // 这里暂不处理任何 action,
      // 仅返回传入的 state。
      return state
    }

    ii、现在可以处理 SET_VISIBILITY_FILTER。需要做的只是改变 state 中的 visibilityFilter

    function todoApp(state = initialState, action) {
      switch (action.type) {
        case SET_VISIBILITY_FILTER:
          return Object.assign({}, state, {
            visibilityFilter: action.filter
          })
        default:
          return state
      }
    }

      处理多个action,就在reducer里加多个case。

    (b)避免太多case,把一个reducer拆成多个reducers,然后用函数combineReducers()合并: 

    // 通过export 暴露出每个reducer函数,然后:
    
    import { combineReducers } from 'redux'
    import * as reducers from './reducers'
    
    const todoApp = combineReducers(reducers)

    (c)总结:

    import { combineReducers } from 'redux'
    import {
      ADD_TODO,
      TOGGLE_TODO,
      SET_VISIBILITY_FILTER,
      VisibilityFilters
    } from './actions'
    const { SHOW_ALL } = VisibilityFilters
    
    function visibilityFilter(state = SHOW_ALL, action) {
      switch (action.type) {
        case SET_VISIBILITY_FILTER:
          return action.filter
        default:
          return state
      }
    }
    
    function todos(state = [], action) {
      switch (action.type) {
        case ADD_TODO:
          return [
            ...state,
            {
              text: action.text,
              completed: false
            }
          ]
        case TOGGLE_TODO:
          return state.map((todo, index) => {
            if (index === action.index) {
              return Object.assign({}, todo, {
                completed: !todo.completed
              })
            }
            return todo
          })
        default:
          return state
      }
    }
    
    const todoApp = combineReducers({
      visibilityFilter,
      todos
    })
    
    export default todoApp

    (3)Store:

     Store 就是把action、reduce联系到一起的对象。Store 有以下职责:

      再次强调一下 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。

      根据已有的 reducer 来创建 store 是非常容易的:在前面,我们使用 combineReducers() 将多个 reducer 合并成为一个。现在我们将其导入,并传递 createStore()

       createStore() 的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。

    import { createStore } from 'redux'
    import todoApp from './reducers'
    let store = createStore(todoApp)
    
    // 第二个参数:
    let store = createStore(todoApp, window.STATE_FROM_SERVER)

    (4)数据流:

      严格的单向数据流是 Redux 架构的设计核心。这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。同时也鼓励做数据范式化,这样可以避免使用多个且独立的无法相互引用的重复数据。

    Redux 应用中数据的生命周期遵循下面 4 个步骤:

    1. 调用 store.dispatch(action)

       2.  Redux store 调用传入的 reducer 函数。Store 会把两个参数传入 reducer: 当前的 state 树和 action。reducer 不应做有副作用的操作

       3.  根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。(用combineReducers()

       4.  Redux store 保存了根 reducer 返回的完整 state 树。这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。

      现在,可以应用新的 state 来更新 UI。如果你使用了 React Redux 这类的绑定库,这时就应该调用 component.setState(newState) 来更新。

    (5)搭配 React:

    i、安装: sudo npm install --save react-redux

    ii、容器组件和展示组件:

      让这两个分离:https://juejin.im/post/5a52fe32f265da3e317e008b

      不同点:

     

       大部分的组件都应该是展示型的,但一般需要少数的几个容器组件把它们和 Redux store 连接起来。这和下面的设计简介并不意味着容器组件必须位于组件树的最顶层。如果一个容器组件变得太复杂(例如,它有大量的嵌套组件以及传递数不尽的回调函数),那么在组件树中引入另一个容器。

      技术上讲你可以直接使用 store.subscribe() 来编写容器组件。但不建议这么做的原因是无法使用 React Redux 带来的性能优化。也因此,不要手写容器组件,而使用 React Redux 的 connect() 方法来生成,后面会详细介绍。


    三、Todolist with react-redux:

      目标:显示一个todo项的列表。一个todo项被单击后,会增加一条删除线并标记完成。我们会显示用户添加一个todo段落。在footer里显示一个可切换的显示全部/只显示完成的//显示未完成的待办事项。

      效果: 

     

     

    剩下的笔记记录在示例代码中。

  • 相关阅读:
    在PictureBox中打开图片文件
    while和do-while语句的异同之处
    if选择语句与switch选择语句的比较、区别及应用场景
    二进制数的原码 反码 补码的作用及转换
    一篇文章看懂二进制、八进制、十进制、十六进制数据转换
    ASCII,unicode, utf8 ,big5 ,gb2312,gbk,gb18030等几种常用编码区别(转载)
    百钱买百鸡问题
    鸡兔同笼问题
    用for循环打印九九乘法表(for嵌套循环)
    用*号打印直角三角形(正反)
  • 原文地址:https://www.cnblogs.com/marvintang1001/p/11891348.html
Copyright © 2011-2022 走看看