从这节起我们开始学习 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 结构为:

  1. <body>
  2. <div id='title'></div>
  3. <div id='content'></div>
  4. </body>

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

  1. const appState = {
  2. title: {
  3. text: 'React.js 小书',
  4. color: 'red',
  5. },
  6. content: {
  7. text: 'React.js 小书内容',
  8. color: 'blue'
  9. }
  10. }

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

  1. function renderApp (appState) {
  2. renderTitle(appState.title)
  3. renderContent(appState.content)
  4. }
  5.  
  6. function renderTitle (title) {
  7. const titleDOM = document.getElementById('title')
  8. titleDOM.innerHTML = title.text
  9. titleDOM.style.color = title.color
  10. }
  11.  
  12. function renderContent (content) {
  13. const contentDOM = document.getElementById('content')
  14. contentDOM.innerHTML = content.text
  15. contentDOM.style.color = content.color
  16. }

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

  1. renderApp(appState)

你会在页面上看到:

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

  1. loadDataFromServer()
  2. doSomethingUnexpected()
  3. doSomthingMore()
  4. // ...
  5. renderApp(appState)

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

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

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

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

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

  1. function dispatch (action) {
  2. switch (action.type) {
  3. case 'UPDATE_TITLE_TEXT':
  4. appState.title.text = action.text
  5. break
  6. case 'UPDATE_TITLE_COLOR':
  7. appState.title.color = action.color
  8. break
  9. default:
  10. break
  11. }
  12. }

所有对数据的操作必须通过 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

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

我们来看看有什么好处:

  1. loadDataFromServer() // => 里面可能通过 dispatch 修改标题文本
  2. doSomethingUnexpected()
  3. doSomthingMore() // => 里面可能通过 dispatch 修改标题颜色
  4. // ...
  5. 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 了。

本节完整的代码如下:

  1. let appState = {
  2. title: {
  3. text: 'React.js 小书',
  4. color: 'red',
  5. },
  6. content: {
  7. text: 'React.js 小书内容',
  8. color: 'blue'
  9. }
  10. }
  11.  
  12. function dispatch (action) {
  13. switch (action.type) {
  14. case 'UPDATE_TITLE_TEXT':
  15. appState.title.text = action.text
  16. break
  17. case 'UPDATE_TITLE_COLOR':
  18. appState.title.color = action.color
  19. break
  20. default:
  21. break
  22. }
  23. }
  24.  
  25. function renderApp (appState) {
  26. renderTitle(appState.title)
  27. renderContent(appState.content)
  28. }
  29.  
  30. function renderTitle (title) {
  31. const titleDOM = document.getElementById('title')
  32. titleDOM.innerHTML = title.text
  33. titleDOM.style.color = title.color
  34. }
  35.  
  36. function renderContent (content) {
  37. const contentDOM = document.getElementById('content')
  38. contentDOM.innerHTML = content.text
  39. contentDOM.style.color = content.color
  40. }
  41.  
  42. renderApp(appState) // 首次渲染页面
  43. dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
  44. dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
  45. 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. 使用Java绘制验证码

    效果图: JDemo.java import java.io.File; import java.io.IOException; import static java.lang.System.out; ...

  2. POJ3278 Catch That Cow —— BFS

    题目链接:http://poj.org/problem?id=3278 Catch That Cow Time Limit: 2000MS   Memory Limit: 65536K Total S ...

  3. poj 2771 Guardian of Decency 解题报告

    题目链接:http://poj.org/problem?id=2771 题目意思:有一个保守的老师要带他的学生来一次短途旅行,但是他又害怕有些人会变成情侣关系,于是就想出了一个方法: 1.身高差距   ...

  4. 「LuoguP1122」 最大子树和

    Description 小明对数学饱有兴趣,并且是个勤奋好学的学生,总是在课后留在教室向老师请教一些问题.一天他早晨骑车去上课,路上见到一个老伯正在修剪花花草草,顿时想到了一个有关修剪花卉的问题.于是 ...

  5. HNOI2008 GT考试 (KMP + 矩阵乘法)

    传送门 这道题目的题意描述,通俗一点说就是这样:有一个长度为n的数字串(其中每一位都可以是0到9之间任意一个数字),给定一个长度为m的模式串,求有多少种情况,使得此模式串不为数字串的任意一个子串.结果 ...

  6. 动画浅析-CAAnimation和CATransition

      出处: http://blog.csdn.net/mad2man/article/details/17260887   //动画播放完之后不恢复初始状态 baseAnimation.removed ...

  7. lua 与C通过c api传递table (2)

    本文转自http://blog.csdn.net/a_asinceo/article/details/49907903(感谢...) 一.单个参数的传递 首先我们在Lua中注册一个C类PJYCallb ...

  8. Jquery之each函数详解

    最近项目被each函数坑惨了,想来还是好好整理下关于each函数的方方面面,一来方便自己查阅,二来为读者提供经验和教训,废话不多说,来看看Each函数到底是怎么坑人的. 一. 全局jQuery.eac ...

  9. 百度也推出公共DNS服务:180.76.76.76(转载)

    转自:http://www.cnbeta.com/articles/352221.htm

  10. mq4参考

    更新: 2017/05/24 其实也就是照搬文档,主要是用到一个记一个.方便掌握 ----------------------------------------------------------- ...