2018-05-25 Redux

三个原则

1.store是数据的唯一来源
2.state是只读的
3.只能通过纯函数改变state。

基于上述三点,下面来分析下这三个特点给我们编程过程中带来的便利和麻烦。

1. store是数据的唯一来源

便利:类比于观察者模式。项目的各个部分只需要监听store 的变化,就可以获得相关信息。
麻烦:react中,数据流是自上而下单向的从父节点到子节点。如果子节点要监听store的变化,就需要从顶级父元素一层层传递到对应的子节点,增加了代码的复杂性,降低了可读性。
解决方案:React—Redux里的Provider组件解决了这个问题。Provider组件使得子元素都可以获得store。connect组件将React组件和Redux store连接在一起。

2.state是只读的

具体来说,只有通过发送action,才能改变state,这样将state的改变途径限定了。而且action是一个纯粹的对象,可以打印,或者存储起来。

3.只能通过纯函数改变state

首先说明下纯函数的定义,纯函数的返回值只会由输入值决定。一样的输入,一样的输出,state 的变化成为可预见的。并且输入结果不会带来其他变化,例如,输入值是一个对象,纯函数会返回一个新对象,而不会改变输入的对象。
好处:可实现时间旅行。
解决方案:可以利用spread语法,或者Immutable库来返回新对象。

关键概念

  1. store
  2. state
  3. action
  4. dispatch

store

store 保存应用的state,并且提供了四个方法。

  1. store.getState()获得state。
  2. store.dispatch(action)更新state。
  3. store.subscribe() 订阅监听事件的发生。
  4. store.replaceReducer 替换reducer。

action

action 必须是plain object, 并且type是必传项

// https://github.com/reduxjs/redux/blob/master/src/createStore.js
if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

dispatch

// https://github.com/reduxjs/redux/blob/master/src/createStore.js
   currentState = currentReducer(currentState, action)
// https://github.com/reduxjs/redux/blob/master/src/combineReducers.js
for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }

dispatch 一个action时,所有的reducer都会执行一次,然后reducer内部有判断action对应的是否是当前reducer。

例子

// action.js
export const ADD_PROJECT = 'ADD_PROJECT'
export const DELETE_PROJECT = 'DELETE_PROJECT'

export function addProject(data) {
  return { type: ADD_PROJECT, data } // action 是 a plain object
}

export function deleteProject(index) {
  return { type: DELETE_PROJECT, index }
}
// reducers.js

import {
  ADD_PROJECT, 
  DELETE_PROJECT,
} from './actions'

 
function projects(state = [], action) {
  switch (action.type) {
    case ADD_PROJECT:
      return [
        ...state,
        {
          id: state.length,
          data: action.data,
        }
      ]
    case DELETE_PROJECT:
      return state.filter((project) => {
        if (project.id !== action.index) {
          return project
        }
        return ''
      }).filter(v=>v)
     
    default:
      return state
  }
}
const rootReducers = combineReducers({
  projects
})

export default rootReducers
//store.js
import { createStore } from 'redux'
import rootReducers from './reducers'
 
const store = createStore(rootReducers)

优化

一个项目只有一个store。随着项目越来越大,reducers越来越多,可以参考flux的命名空间的方式,将reducers按照功能分组。 reducers可以分为两级,第一级判断是否是当前命名空间的方法,第二级在当前命名空间下的具体的数据处理方法。

实践经验

在SPA应用中,在不刷新页面的前提下,各个路径所对应的页面会共享store。这样在实际开发过程中,可能会导致无法准确判断,当前页面对数据请求是否正常。
例如,a路径请求了template相关数据,直接跳转到b页面时,这部分数据已经存在,可以直接使用。但是如果用户直接访问b页面,那么b页面自己本身需要去请求template相关数据。所以为了避免这个盲区,在自测阶段,可以刷新这个页面,来确保每个页面保证了数据获取的正确性。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容