FITURE

If you can fight, fight.

首页 >> 分享>>Javascript>>Redux在React APP里面的应用技巧分享

Redux在React APP里面的应用技巧分享

Posted by fiture / 2017年03月24日 / Javascript」「分享

提到Redux,让我们先来回顾一下目前在前端框架风靡的React,在过去的一年中(2016)React可谓是在前端技术里面最为热门JavaScript框架之一,React是一种单纯的UI框架,提供十分简洁的API接口调用。它以Virtual DOM、组件化和组件生命周期,数据驱动视图变化的设计的核心理念,赢得了一大批开发者的簇拥和支持,以react为核心的各种辅助类库如雨后春笋般相继出现。

React是以数据来驱动界面试图的变更,提倡单向数据流的概念数据通过父子属性来来进行传递,每个组件都有自己的一个内部的状态管理,当内部的状态发生变化时,react会自动的进行界面的重新渲染,这样让广大的前端开发从传统的DOM操作中解脱出来,把更多的关注放在设计数据和视图之前的关系即可,由此我们引出今天的主角,应用状态(APP数据)管理器——Redux。

Redux 的介绍和使用

当我们项目日趋壮大和复杂时,管理这些组件的状态变得越来越困难,redux可以很好的解决这种问题。Redux是JavaScript应用里面的一种可预测的状态管理容器,可以用Redux进行无痛的状态管理应用,通过其三大原则,试图让应用的状态变更变得可预测。

  • 单一数据源(Single source of truth)
  • State是只读的(State is read-only)
  • 使用纯函数来进行修改(Changes are made with pure functions)

redux本身代码也2K大小,API设计的接口也非常简洁,通过action、reducer、store来触发/生成/管理整个应用的数据,站在巨人的肩膀上,redux基于flux的设计理念又不同于flux,采用Elm纯函数方式编程,(action, state) => state 的设计也被应用于此。

Redux的使用技巧分享

Reudx 希望通过一定的规则来规避一些在改变State的时候的未知异常的发生,所以当我们在reducer处理数据时应该同时注意上面的三大原则。其中最重要的一条就是不能改变原来的数据,而是总是生成新的数据。

(prevState) (reducer)=> newState

Reducer 对 Array 类型的数据处理

在处理这种类型数据的时候还是尊循不要改变原数据的基本原则,所以我们在生成新的数组时就要避免使用这些会导致数组变化的操作方法,如:pushunshiftsplice等。

  • 新增一条数据:
      function addItem(arr, item) {
        return [
          item,
          arr
        ]
      }

    使用数组展开语法,快速复制和新增数据,如果需要再数组之后插入,把item放到,展开语法过后即可

  • 根据索引删除一条数据
    function removeItem(arr, idx) {
      return [
        arr.slice(0, idx),
        arr.slice(idx + 1)
      ]
    }
  • 根据数组中的元素字段删除数据
    function removeItem(arr, condition) {
      return arr.filter(item => condition !== item.condition)
    }
  • 在指定位置插入一条数据:
    function insertItem(arr, idx, item) {
      return [
        arr.slice(0, idx),
        item,
        arr.slice(idx)
      ]
    }

上面列举了一些常用的reducer里面对数组的操作,这里只是做个抛砖引玉的说明。我们还可以通过先 arr.slice() 对数组先做个复制,然后再去对复制出来的数组做操作等等。

Reducer 对 Object 类型的数据处理

对Object类型的数据通常我们通过对象复制来更新数据对象,常用的复制方法如:

  const updateObj = (obj) => {
    return Object.assign({}, obj, {name: newName})
  }
 
  //或者用最新的对象展开操作符 
  const updateObj = (obj) => {
    return {
      obj,
      name: newName
    }
  }

需要值得注意的是下面两种典型的错误例子:

 
  //1、不要通过key直接更新原对象 
  const updateObj = (obj) => {
    obj.name = newName
    return obj;
  }
 
  //2、深层嵌套的对象,就算一级做了复制,二级对象也不能通过key来直接更新 
  const updateObj = (obj) => {
    let newObj = { obj };
    newObj.sub.name = new Name;
    return newObj;
  }

可以看出上面的第2种例子,通常不容易被发现有问题。虽然对一级对象的值做了复制,但二级对象还是引用了原始值的地址,然后通过key直接操作对象时,就对原始值做了变更。针对这种情况,我们只能去做一个更深层次的拷贝:

  const updateObj = (obj) => {
    return {
      obj,
      [sub]: {
        obj[sub],
        name: newName
      }
    }
 
    return newObj;
  }

我们用对象展开语法,精简了一些代码量,但嵌套的object越深,代码可读性会成倍的降低,想想要是用Object.assign,那代码就没什么可读性了,这里就要求我们尽可能扁平的组织和处理Object的数据结构,不要嵌套太多Object数据。可能会有人会问,扁平数据结构后我们整个应用下面就会有很多的二级Object,这里后面再做说明,通过combineReducer组织我们的整个应用数据结构。

常见的服务端列表数据 State 结构设计

数据列表,并且需要操作单条数据的,不要单独存成数组形式,为什么这样说呢,我们先来看个例子:

日常开发中我们经常会拿到类似这样的服务端返回的JSON数据:

  {
    code: 0,
    data: [
      {
        id: 1,
        name: foo
      },
      {
        id: 2,
        name: bar
      }
    ]
  }

最开始开发时,为了方便一般我们会存储成(默认选中第一条):

  Store = {
    contactList: [
      { id: 1, name: foo, selected: true},
      { id: 2, name: bar}
      //… 
    ]
  }

类似上面的结构,实际应用中,我们可能需要:

  • 更新其中某条数据
  • 选中某条数据

依次来实现以上几种操作的reducer:

  1. 更新contactList:
  (state=initState.contactList, action) => {
    case UPDATE:
      return state.map( person => {
        if ( person.id == action.id ) {
          return {
            person,
            name: action.name
          }
        }
        return person;
      })
  }
  1. 选中某条数据
   (state=initState.contactList, action) => {
     case SELECTED:
       return state.map( person => {
         //先去掉默认选中的那条数据 
         if ( person.selected ) {
           return {
             person,
             selected: false
           }
         }
 
         //选中当前 
         if ( person.id == action.selectedId ) {
           return {
             person,
             selected: true
           }
         }
 
         return person;
       })
   }

上面仅仅实现了两种简单常见的需求,但当随着应用复杂度慢慢增加,维护这些数组会变得异常的困难。我们对上面的Store存储结构做个优化:

  Store = {
    contactListById: {
      1: {
        id: 1,
        name: foo
        //..other props 
      },
      2: {
        id: 2,
        name: bar
        //..other props 
      }
    },
    contactListIds: [1, 2], //… 
    selectedId: 1
  }

其中contactListById,以单个数据对象的id作为key数据对象作为值,这样我们可以contactListById[id]方便快捷高效的获取到想要的对象,contactListIds,仅仅存储这些数据对象的id,作为对列表排序使用,selectedId, 保存当前选中的id,这样我们要实现刚刚上面两种需求的reducer代码可能就是是下面这样:

  1. 更新contactList:
  (state=initState.contactListById, action) => {
    case UPDATE:
      {
        //获取到需要更新的目标对象 
        const target = state[action.id];
        return {
          state,
          [action.id]: {
            target,
            name: action.name
          }
        }
      }
  }
  1. 选中某条数据
   (state=initState.selectedId, action) => {
     case SELECTED:
       return action.selectedId
   }

由此可以看出良好的数据结构设计,有利于代码的组织和优化,我们巧妙运用Object保存数据再通过 obj[key]的原生js支持取出想要的对象,需要更新选中值时,在reducer里面直接更新即可, 减少了数组的循环运算。

不要为了redux而使用redux

  • 不需要持续存储的数据,不要保存到redux的store里面,如个别的ajax请求。很多情况下,我们组件的内部状态是不需要做持续存储的,而且内部状态不会和外面的组件关联,拿个常用的例子举例:倒计时的按钮,这个组件我们只有两个事件回调,倒计时开始,倒计时完毕,倒计时完毕后才能点击某个按钮,这个组件里面的倒计时就不需要进行持久性保存,就没必要把当前倒计时的秒数存入Redux的Store。另外一些需要时时获取数据的页面,直接用react 的State即可,ajax.success( (data)=> this.setState({data}) )

在action里面获取store

在react应用里面,我们通过react-redux组件隔离了action访问store的访问,但实际应用中需要根据另外一个状态去做一些操作,这里就可以推荐 redux-thunk 插件去做类似的事情

  //引入redux-thunk 
  applyMiddleware(thunk /*, … */)
 
  //action Creator获取Store 
  function doSomethingIfNeeded() {
    return (dispatch, getState) => {
      const store = getState();
      if ( needDoSomething(store.xx) ) {
        dispatch( doSomething() )
      }
    }
  }

巧用combineReducer 组合相关的子级reducer

上文已经提到 单个reducer 处理的数据应该尽量扁平,避免过多的嵌套,多个reducer可以使用函数组合起来或者使用combineReducer方法组合

小结

Redux确实给我们开发带了了方便,但带来方便的同时也因为其本身固有规则,给很多初入Redux的开发者带了一许多困惑,它仅仅是一种设计模式和理念,怎么使用,使用好还得我们自己继续探索和发现,本文分享一些典型的例子,希望能为大家解答一些疑惑。

发表评论

电子邮件地址不会被公开。 必填项已用*标注