从这节起我们开始学习 Redux,一种新型的前端“架构模式”。经常和 React.js 一并提出,你要用 React.js 基本都要伴随着 Redux 和 React.js 结合的库 React-redux。

要注意的是,Redux 和 React-redux 并不是同一个东西。Redux 是一种架构模式(Flux 架构的一种变种),它不关注你到底用什么库,你可以把它应用到 React 和 Vue,甚至跟 jQuery 结合都没有问题。而 React-redux 就是把 Redux 这种架构模式和 React.js 结合起来的一个库,就是 Redux 架构在 React.js 中的体现。

如果把 Redux 的用法重新介绍一遍那么这本书的价值就不大了,我大可把官网的 Reducers、Actions、Store 的用法、API、关系重复一遍,画几个图,说两句很玄乎的话。但是这样对大家理解和使用 Redux 都没什么好处,本书初衷还是跟开头所说的一样:希望大家对问题的根源有所了解,了解这些工具到底解决什么问题,怎么解决的。

现在让我们忘掉 React.js、Redux 这些词,从一个例子的代码 + 问题开始推演。

用 create-react-app 新建一个项目 make-redux,修改 public/index.html 里面的 body 结构为:

  <body>
<div id='title'></div>
<div id='content'></div>
</body>

删除 src/index.js 里面所有的代码,添加下面代码,代表我们应用的状态:

const appState = {
title: {
text: 'React.js 小书',
color: 'red',
},
content: {
text: 'React.js 小书内容',
color: 'blue'
}
}

我们新增几个渲染函数,它会把上面状态的数据渲染到页面上:

function renderApp (appState) {
renderTitle(appState.title)
renderContent(appState.content)
} function renderTitle (title) {
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = title.text
titleDOM.style.color = title.color
} function renderContent (content) {
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = content.text
contentDOM.style.color = content.color
}

很简单,renderApp 会调用 rendeTitle 和 renderContent,而这两者会把 appState里面的数据通过原始的 DOM 操作更新到页面上,调用:

renderApp(appState)

你会在页面上看到:

这是一个很简单的 App,但是它存在一个重大的隐患,我们渲染数据的时候,使用的是一个共享状态 appState,每个人都可以修改它。如果我在渲染之前做了一系列其他操作:

loadDataFromServer()
doSomethingUnexpected()
doSomthingMore()
// ...
renderApp(appState)

renderApp(appState) 之前执行了一大堆函数操作,你根本不知道它们会对 appState做什么事情,renderApp(appState) 的结果根本没法得到保障。一个可以被不同模块任意修改共享的数据状态就是魔鬼,一旦数据可以任意修改,所有对共享状态的操作都是不可预料的(某个模块 appState.title = null 你一点意见都没有),出现问题的时候 debug 起来就非常困难,这就是老生常谈的尽量避免全局变量。

你可能会说我去看一下它们函数的实现就知道了它们修改了什么,在我们这个例子里面还算比较简单,但是真实项目当中的函数调用和数据初始化操作非常复杂,深层次的函数调用修改了状态是很难调试的。

但不同的模块(组件)之间确实需要共享数据,这些模块(组件)还可能需要修改这些共享数据,就像上一节的“主题色”状态(themeColor)。这里的矛盾就是:“模块(组件)之间需要共享数据”,和“数据可能被任意修改导致不可预料的结果”之间的矛盾。

让我们来想办法解决这个问题,我们可以学习 React.js 团队的做法,把事情搞复杂一些,提高数据修改的门槛:模块(组件)之间可以共享数据,也可以改数据。但是我们约定,这个数据并不能直接改,你只能执行某些我允许的某些修改,而且你修改的必须大张旗鼓地告诉我。

我们定义一个函数,叫 dispatch,它专门负责数据的修改:

function dispatch (action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
appState.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
appState.title.color = action.color
break
default:
break
}
}

所有对数据的操作必须通过 dispatch 函数。它接受一个参数 action,这个 action是一个普通的 JavaScript 对象,里面必须包含一个 type 字段来声明你到底想干什么。dispatch 在 swtich 里面会识别这个 type 字段,能够识别出来的操作才会执行对 appState 的修改。

上面的 dispatch 它只能识别两种操作,一种是 UPDATE_TITLE_TEXT 它会用 action的 text 字段去更新 appState.title.text;一种是 UPDATE_TITLE_COLOR,它会用 action 的 color 字段去更新 appState.title.color。可以看到,action 里面除了 type 字段是必须的以外,其他字段都是可以自定义的。

任何的模块如果想要修改 appState.title.text,必须大张旗鼓地调用 dispatch

dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色

我们来看看有什么好处:

loadDataFromServer() // => 里面可能通过 dispatch 修改标题文本
doSomethingUnexpected()
doSomthingMore() // => 里面可能通过 dispatch 修改标题颜色
// ...
renderApp(appState)

我们不需要担心 renderApp(appState) 之前的那堆函数操作会干什么奇奇怪怪得事情,因为我们规定不能直接修改 appState,它们对 appState 的修改必须只能通过 dispatch。而我们看看 dispatch 的实现可以知道,你只能修改 title.text 和 title.color

如果某个函数修改了 title.text 但是我并不想要它这么干,我需要 debug 出来是哪个函数修改了,我只需要在 dispatch的 switch 的第一个 case 内部打个断点就可以调试出来了。

原来模块(组件)修改共享数据是直接改的:

我们很难把控每一根指向 appState 的箭头,appState 里面的东西就无法把控。但现在我们必须通过一个“中间人” —— dispatch,所有的数据修改必须通过它,并且你必须用 action 来大声告诉它要修改什么,只有它允许的才能修改:

我们再也不用担心共享数据状态的修改的问题,我们只要把控了 dispatch,所有的对 appState 的修改就无所遁形,毕竟只有一根箭头指向 appState 了。

本节完整的代码如下:

let appState = {
title: {
text: 'React.js 小书',
color: 'red',
},
content: {
text: 'React.js 小书内容',
color: 'blue'
}
} function dispatch (action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
appState.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
appState.title.color = action.color
break
default:
break
}
} function renderApp (appState) {
renderTitle(appState.title)
renderContent(appState.content)
} function renderTitle (title) {
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = title.text
titleDOM.style.color = title.color
} function renderContent (content) {
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = content.text
contentDOM.style.color = content.color
} renderApp(appState) // 首次渲染页面
dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
renderApp(appState) // 把新的数据渲染到页面上

下一节我们会把这种 dispatch 的模式抽离出来,让它变得更加通用。

下一节:动手实现 Redux(二):抽离 store 和监控数据变化

上一节:React.js 的 context

动手实现 Redux(一):优雅地修改共享状态的更多相关文章

  1. 动手实现 Redux(四):共享结构的对象提高性能

    接下来两节某些地方可能会稍微有一点点抽象,但是我会尽可能用简单的方式进行讲解.如果你觉得理解起来有点困难,可以把这几节多读多理解几遍,其实我们一路走来都是符合“逻辑”的,都是发现问题.思考问题.优化代 ...

  2. 动手实现 Redux(二):抽离 store 和监控数据变化

    上一节 的我们有了 appState 和 dispatch: let appState = { title: { text: 'React.js 小书', color: 'red', }, conte ...

  3. 动手实现 Redux(五):不要问为什么的 reducer

    经过了这么多节的优化,我们有了一个很通用的 createStore: function createStore (state, stateChanger) { const listeners = [] ...

  4. 动手实现 Redux(三):纯函数(Pure Function)简介

    我们接下来会继续优化我们的 createStore 的模式,让它使我们的应用程序获得更好的性能. 但在开始之前,我们先用一节的课程来介绍一下一个函数式编程里面非常重要的概念 —— 纯函数(Pure F ...

  5. 动手实现 Redux(六):Redux 总结

    不知不觉地,到这里大家不仅仅已经掌握了 Redux,而且还自己动手写了一个 Redux.我们从一个非常原始的代码开始,不停地在发现问题.解决问题.优化代码的过程中进行推演,最后把 Redux 模式自己 ...

  6. ZT 获得/修改共享互斥量属性:pthread_mutexattr_t

    bbs.chinaunix.net/thread-965755-1-1.html 5.   获得/修改共享互斥量属性:    #include<pthread.h>    intpthre ...

  7. kettle_Spoon 修改共享DB连接带汉字引发的错误

    win10下: kettle_Spoon 修改共享DB连接带汉字引发的错误: Unexpected problem reading shared objects from XML file : nul ...

  8. python--如何在线上环境优雅的修改配置文件?

    1.如何在线上环境优雅的修改配置文件? 原配置文件 #原配置文件 global log 127.0.0.1 local2 daemon maxconn 256 log 127.0.0.1 local2 ...

  9. 你再也不用使用 Redux、Mobx、Flux 等状态管理了

    Unstated Next readme 的中文翻译 前言 这个库的作者希望使用 React 内置 API ,直接实现状态管理的功能.看完这个库的说明后,没有想到代码可以这个玩.短短几行代码,仅仅使用 ...

随机推荐

  1. Maven 用法

    scope标签 provided:如果存在编译需要而发布不需要的jar包,使用provided属性值

  2. filename extension

    题目描述 Please create a function to extract the filename extension from the given path,return the extra ...

  3. Codeforces Round #346 (Div. 2) E. New Reform

    E. New Reform time limit per test 1 second memory limit per test 256 megabytes input standard input ...

  4. Windows 7 繁体中文MSDN原版

    Win7 SP1 64位旗舰版繁体版ISO镜像(香港):文件名:hk_windows_7_enterprise_with_sp1_x64_dvd_620688.isoSHA1:82D59B099333 ...

  5. linux 设备驱动程序中的一些关联性思考

    首先,个人感觉设备驱动程序与应用程序中的文件操作隔得有点远,用户空间不论是直接使用系统调用还是库函数都是通过系统调用的接口进入内核空间代码的.但是看过一个博客的分析整个过程,感觉中间层太过麻烦,必须经 ...

  6. i2c_set_clientdata函数【转】

    本文转载自‘:http://blog.csdn.net/jk198310/article/details/43738367 在i2c驱动中有很多函数和数据结构,很多一时难以理解,所以写下本文共同学习. ...

  7. Oracle:ORA-01790: expression must have same datatype as corresponding expression

    今天有现场报sql错误,错误sql语句 类似  select * from  tableA  where (exists  条件A   or exists 条件B), 单独执行  select * f ...

  8. 架构-MVVM:MVVM核心概念

    ylbtech-架构-MVVM:MVVM核心概念 1.返回顶部 1. MVVM模式是Model.View.ViewModel的简称,最早出现在WPF,现在Silverlight中也使用该模式,MVVM ...

  9. ORA-00020: maximum number of processes (xxxx) exceeded 报错解决方法

    转自:http://blog.51cto.com/lee90/1788124 今天java开发在连接线上的oracle大量导数据,一会提示连接不上数据库了.我本地用sqldeveloper也连接不上. ...

  10. python mixin到底是什么 django

    1.什么是Mixin 在面向对象编程中,Mixin是一种类,这种类包含了其他类要使用的方法,但不必充当其他类的父类.其他类是如何获取Mixin中的方法因语言的不同而不同.所以有时候Mixin被描述为' ...