从0开始的Redux旅程(一)

前置知识点

  • 深拷贝与浅拷贝
  • FLUX架构

数据库模型

无论是MVC,MVVM,还是现如今的Flux,最强调的一点就是模块化的分离解耦.
因为整套系统是围绕数据运作的,所以

  • 表现是基于数据而来: 数据库的内容决定了展示的页面
  • 功能是基于数据而实现: 功能是在数据之上做修改

最终的结果就是,:

  • 数据(store)决定了展示(view)
  • 指令(action)改变了数据(store)
  • 展示(view)绑定了指令(action)

而具体是选择mvc, mvvm, 还是flux完全是基于那一部分东西多而决定的, 核心的理念都是相同的.

优先数据

界面归根结底是为了展现数据, 所以优先设计好数据模型. 再设计对这些数据模型所能实现的功能. 最后将这些功能附加到界面上.

Redux初体验

第一步总是做需求分析, 还是先做个todo

需求分析

  • 界面可以展现一个todolist
  • 有个按钮可以增加todolist的内容, 增加的todo默认是未完成
  • 每条todo后面会有一个按钮可以删除该条todo
  • 每条todo后面会有一个按钮可以完成该条todo
  • todolist下面有三个按钮, 分别可以展示所有todolist, 展示完成的todolist, 展示未完成的todolist

数据模型(store)设计

  • 数据是一个简单对象
  • 数据模型就是一个精简版的数据库
  • 数组总是很好的选择
{
  displayMode: // mode类型在我们公司的产品里一般是存放在域表里, 由ID指定.
  todoList: [
    {
       text:  //  todo的内容
       status:  // 状态, 如果需要的话可以把status放域表, 或者直接赋值true / false
  ]
}

动作指令(action)设计

  • 动作指令就是对数据模型操作的一串指令
  • 动作指令是一个对象
  • 这个对象必须包含type属性
{type:'ADD_TODO',payload:'Eating'}
{type:'DEL_TODO',payload:1}
{type:'TOGGLE',payload:2}
{type:'CHANGE_DISPLAY_MODE',payload:1}

其实就像是一堆机器伪代码

方法服务(reducer)设计

  • 后端的常见思路就是将对数据库操作的SQL写成一个个数据库服务.
  • Redux里就是写reducer
  • reducer就是一个函数,接受两个参数,一个是state,一个是action
  • status就是当前的数据模型的一部分,比如如果我们动作是操作数据模型中的displayMode部分,那么我们就设计一个displayModeReducer,接受当前的displayMode的值,再接受一个action作为执行参数.
  • action就是指令参数,传入的就是action
  • 一个reducer只返回state的一部分,比如你对displayMode做调整,就只返回displayMode的值就行了,如果你对todoList做调整,那么就返回一个todoList的数组就好了.
const displayModeReducer = function(state=0,action){
//执行动作
  return state
}
const todoListReducer = function(state=0,action){
//执行动作
  return state
}

合并服务(combineReducers)

  • 其实上边的动作都是纯原生js写的. 到这步才真正是Redux的API部分
  • 合并服务的目的是将所有Reducer合并进一个Reducers池中, 因为每个reducer只返回state的一部分, 所以将所有reducer拼起来必然是一个完整的state
  • 整合reducer的顺序按照store的结构顺序来就好了
import { combineReducers } from 'Redux';
const reducers = combineReducers({
    displayMode: displayModeReducer
    todoList: todoListReducer
}
)

生成数据模型(store)

  • 这步也是Redux的API
  • createStore需要两个蚕食,一个是合并后的服务,一个是初始的state
  • 生成的store带有getState()方法可以获取当前state
  • subscribe()方法可以监听传来的指令,然后执行回调
  • dispatch()方法可以对store执行指令
import { createStore } from 'Redux'
const store = createStore(reducers,{})

此时其实可以进行简单测试了

store.subscriber(()=>{console.log('Listen to State Change) , store.getState()})
//这样如果store监听到了指令的话,就会执行回调,返回store.getState().
//可以尝试发一串指令过去看看会得到什么,比如
store.dispatch({type: "CHANGE_DISPLAY_MODE", payload: 1})
store.dispatch({type: "CHANGE_DISPLAY_MODE", payload: 2})
store.dispatch({type: "CHANGE_DISPLAY_MODE", payload: 3})
store.dispatch({type: "ADD_TODO", payload: "HAPPY"})
store.dispatch({type: "ADD_TODO", payload: "EATING"})
store.dispatch({type: "ADD_TODO", payload: "WASHING"})
store.dispatch({type: "DEL_TODO", payload: 1})
store.dispatch({type: "TOGGLE", payload: 1})
//反正什么也得不到啦只会得到undefine,因为reducer没有正常返回,只返回了一堆空state
//这样的话就可以继续去完善reducer了

完善reducer

  • reducer的作用就是在收到指令action后,会重新返回一个新的state, 因为state的变化,那么界面也会相应变化.
  • 在reducer内主要不要对原始state进行突变(mutant), state部分是引用类型的值, 突变就会导致执行指令前后的state指向的实际对象被改变为相同的值, 这样会产生很多不好的影响比如无法做日志.
  • 现阶段好用的创建一个新的对象的方法, 可以用ES6的Object.assign方法, 结合ES6的展开运算符. 或者用一些其他库比如lodash或者jquery甚至immutable.js
  • 这部分建议多研究些方法, 看看lodash源码或者jq源码, ES6现在已经有许多很好用的原生对象API了, 数组更是能玩出花. 就不赘述了.
import { combineReducers } from 'redux'
const displayModeReducer = function (state = 0, action) {
    switch (action.type) {
        case "CHANGE_DISPLAY_MODE": {
            return action.payload;
        }
        default: {
            return state;
        }
    }
}
> 
const todoListReducer = function (state = [], action) {
    switch (action.type) {
        case "ADD_TODO": {
            return [...state, {text: action.payload, status: false}];
        }
        case "DEL_TODO": {
            return [...state.slice(0, action.payload), ...state.slice(action.payload + 1)]
        }
        case "TOGGLE": {
            return [...state.slice(0, action.payload),
                Object.assign({}, state[action.payload], {status: !state[action.payload][state]}),
                ...state.slice(action.payload + 1)]
        }
        default: {
            return state;
        }
    }
}

用函数创建action

刚才可以看到所有action都是手动打的, 比较麻烦, 可以考虑把生成指令组合成一些方法, 自动生成action对象.

const addTodo = (text) => ({
        type:"ADD_TODO",
        payload:text
    })
> 
const delTodo = (index) => ({
    type:"DEL_TODO",
    payload:index
})
> 
const changeDisplayMode = (modeId) => ({
    type:"CHANGE_DISPLAY_MODE",
    payload:modeId
})
> 
const toggle = (index) => ({
    type:"TOGGLE",
    payload:index
})

小结

到这一步, Redux的数据处理逻辑部分都已经完全实现了, 总共就用了量个API, createStore用来创建store, combineReducers用来合并所有的reducer. 可以说是相当简练了.

  • 设计数据模型, 确定好state的结构
  • 解剖state, 将每一段state的属性做成reducer, 每个reducer里加入处理逻辑
  • 用combineReducers将所有reducer合并成一个reducer
  • 用createStore将reducer制作成store
  • 设计指令模型, 将指令对象用函数生成
  • 此时就可以用store.subscribe()来监听指令了, 用store.dispatch()来发送指令.

推荐阅读更多精彩内容