用JS打造一款记账App——Redux架构设计

上一篇我们学习了构建一个react项目以及配置webpack并运行项目,这篇我们在引入redux之前学习它的运行机制及设计思路。

Redux的架构思想

1. state和store 的概念

state是React中定义的应用状态,本质是一个数据集的普通对象,例如

/** 应用初始state **/
{
    status: 0,
    todos: []
}

store是应用状态state的管理者,包含四个函数:

getState() #获取整个state
dispatch(action) #触发state改变的唯一途径
subscribe(listener) #可以理解为DOM中的addEventListener
replayceReducer(nextReducer) #一般在Webpack按需加载的时候用

二者的关系 state = store.getState()

Redux规定:一个应用只应有一个单一的store,其管理着唯一的应用状态state且不能直接被修改,若要改变state,必须执行dispatch一个action,这是修改应用状态的唯一方法。

现在您只需要记住action只是一个包含type属性的普通对象即可
例如{type:'INCREMENT'}

我们知道了state是通过store.getState()获取的,那么store是怎么来的呢?那就需要用到了Redux定义的createStore方法了。

import { createStore } from 'redux'
...
//store是将reducer传入生成的
const store = createStore(reducer, initialState) 

现在您只需要记住reducer是一个函数,负责更新并返回一个新的state,而initialState主要用于前后端同构的数据同步(详情请关注React服务端渲染)

2. reducer

这里我们又要用到Redux中定义的另外一个方法combineReducers。

import { combineReducers } from 'redux'
...
const rootReducer = combineReducers({
  captchaData,
  loginState,
  status,
  userInfo
})

export default rootReducer

具体工作原理是dispatch执行action之后,reducer负责根据action的type属性重新将对应action的payload值赋给nextState

3.Action

上面我们提到了action实质的包含type属性的普通对象,这个type是实现用户行为的关键。例如我们增加一个支出分类

{
    type: 'ADD_CATEGORY',
    payload: {
        id:1,
        content:'交通'
    }
}

当然,action可以根据具体的业务来自由设定,唯一的必要属性就是type,甚至我们可以写一个只包含type属性的action

下面的action都是合法的

{
    type: 'ADD_CATEGORY',
    id:1,
    content:'交通'
}

{
    tyep: 'ADD_CATEGORY',
    aabbcc: {
        id: 1,
        content: '交通'
    }
}
{
    type: 'ADD_CATEGORY'
}

虽然没有约束,但最好还是遵循规范

这里有一个思考问题,action难道就只是包含type等一些属性的对象吗?这里留一个悬念,后面我在具体用Redux的过程中再讲。

Redux优缺点

Action Creator => action => store.dispatch(action) => reducer(state, action) => 原state state = nextState

优点:清晰的数据流向,让我们处理复杂的业务逻辑,可以更加方便的梳理业务线,action只负责执行逻辑操作和数据获取,reducer只负责返回数据集,然后connect给react中的组件使用,让react只关心交互界面的逻辑,无需关心数据逻辑。
另外我们使用redux还有如下好处

  • 方便地能够将应用状态存储到本地并且重启动时能够读取恢复状态
  • 方便地能够在服务端完成初始状态设置,并且完成状态的服务端渲染
  • 能够序列化记录用户操作,能够设置状态快照,从而方便进行Bug报告与开发者的错误重现
  • 能够将用户的操作或者事件传递给其他环境而不需要修改现有代码
  • 能够添加重放或者撤销功能而不需要重构代码
  • 能够在开发过程中实现状态历史的回溯,或者根据Action的历史重现状态
  • 能够为开发者提供全面透彻的审视和修改现有开发工具的接口,从而保证产品的开发者能够根据他们自己的应用需求打造专门的工具
  • 能够在复用现在大部分业务逻辑的基础上构造不同的界面

使用Redux我们必须遵循Redux的守则

  • 必须使用基本对象与数组来描述应用状态
  • 必须使用基本的对象来描述系统变化
  • 必须使用纯函数来处理系统中的业务逻辑

缺点:由于必须遵守Redux的守则,即使很简单的业务逻辑,我们也必须有store,reducer,action.type。这样给人的感觉就是杀鸡用牛刀,所以对于简单的业务逻辑,我们无需引用Redux库。

最后,Redux真正的灵魂在于其设计思想,很多时候并不一定需要引用Redux库本身,但可以尝试使用redux的思想。

import React, { Component } from 'react'

class Counter extends Component {
  state = { value: 0 }
  
  increment = () => {
    this.setState(prevState => ({
      value: prevState.value + 1
    }))
  }

  decrement = () => {
    this.setState(prevState => ({
      value: prevState.value - 1
    }))
  }
  
  render() {
    return (
      <div>
        {this.state.value}
        <button onClick={this.increment}>+</button>
        <button onClick={this.decrement}>-</button>
      </div>
    )
  }

使用redux,我们可能会有禁用Local State的习惯,其实Local State有时候恰恰是用最简单的方法帮我们解决问题。我们对上面代码用redux思想进行改造

import React, { Component } from 'react'

const counter = (state = { value:0 }, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { value: state.value + 1}
        case 'DECREMENT':
            return { value: state.value - 1}
        default:
            return state
    }
}

class Counter extends Component {
    state = counter(undefined, {})
    
    dispatch(action) {
        this.setState(prevState => counter(prevState, action))
    }
    
    increment = () => {
        this.dispatch({type: 'INCREMENT'})
    }
    
    decrement = () => {
        this.dispatch({type: 'DECREMENT'})
    }
    
    render() {
      return (
         <div>
         {this.state.value}
            <button onClick={this.increment}>+</button>
            <button onClick={this.decrement}>-</button>
         </div>
    )
  }
}

总结

  • store由Redux的 createStore(reducer) 生成
  • state通过store.getState()获取,本质是一个存储整个应用状态数据集的对象
  • reducer用于更新state并返回nextState的函数,且reducer必须有返回值,否则nextState将为undefined
  • action是一个包含type属性的普通对象,用于dipatch来改变state

这一篇我们理解了Redux的几个概念以及设计思想,下一篇我们要在项目中引用Redux库并学习怎么使用它。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容