动手实现 Redux(一):优雅地修改共享状态
从这节起我们开始学习 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 和监控数据变化
动手实现 Redux(一):优雅地修改共享状态的更多相关文章
- 动手实现 Redux(四):共享结构的对象提高性能
接下来两节某些地方可能会稍微有一点点抽象,但是我会尽可能用简单的方式进行讲解.如果你觉得理解起来有点困难,可以把这几节多读多理解几遍,其实我们一路走来都是符合“逻辑”的,都是发现问题.思考问题.优化代 ...
- 动手实现 Redux(二):抽离 store 和监控数据变化
上一节 的我们有了 appState 和 dispatch: let appState = { title: { text: 'React.js 小书', color: 'red', }, conte ...
- 动手实现 Redux(五):不要问为什么的 reducer
经过了这么多节的优化,我们有了一个很通用的 createStore: function createStore (state, stateChanger) { const listeners = [] ...
- 动手实现 Redux(三):纯函数(Pure Function)简介
我们接下来会继续优化我们的 createStore 的模式,让它使我们的应用程序获得更好的性能. 但在开始之前,我们先用一节的课程来介绍一下一个函数式编程里面非常重要的概念 —— 纯函数(Pure F ...
- 动手实现 Redux(六):Redux 总结
不知不觉地,到这里大家不仅仅已经掌握了 Redux,而且还自己动手写了一个 Redux.我们从一个非常原始的代码开始,不停地在发现问题.解决问题.优化代码的过程中进行推演,最后把 Redux 模式自己 ...
- ZT 获得/修改共享互斥量属性:pthread_mutexattr_t
bbs.chinaunix.net/thread-965755-1-1.html 5. 获得/修改共享互斥量属性: #include<pthread.h> intpthre ...
- kettle_Spoon 修改共享DB连接带汉字引发的错误
win10下: kettle_Spoon 修改共享DB连接带汉字引发的错误: Unexpected problem reading shared objects from XML file : nul ...
- python--如何在线上环境优雅的修改配置文件?
1.如何在线上环境优雅的修改配置文件? 原配置文件 #原配置文件 global log 127.0.0.1 local2 daemon maxconn 256 log 127.0.0.1 local2 ...
- 你再也不用使用 Redux、Mobx、Flux 等状态管理了
Unstated Next readme 的中文翻译 前言 这个库的作者希望使用 React 内置 API ,直接实现状态管理的功能.看完这个库的说明后,没有想到代码可以这个玩.短短几行代码,仅仅使用 ...
随机推荐
- iOS内存管理机制解析之MRC手动引用计数机制
前言: iOS的内存管理机制ARC和MRC是程序猿參加面试基本必问的问题,也是考察一个iOS基本功是 否扎实的关键,这样深入理解内存管理机制的重要性就不言而喻了. iOS内存管理机制发展史 iOS 5 ...
- 有奖试读&征文——我们在互联网上奋斗的故事 获奖名单发布
互联网是一个年轻的行业,同一时候也是一个推陈出新.不断进化的行业. 中国互联网行业在近期的十五年里.以如何的方式在"进化".我相信非常多奋斗在互联网战线上的你们最深有感触.读一读& ...
- java map 装入list
需要生成多组数据的时候,应将map时候放入循环,否则循环出来会一直覆盖之前的,只能保存一条数据. 具体如下: if (rs.next()) { do { Map<String, String&g ...
- Kafka使用经验小结
本文尽量从一个使用者的角度去记录一些在实战当中使用Kfaka所需要关注的要点,这样可能会贴切更多的读者,本文并不会介绍太多的Kafka的一些架构层次设计的知识,因为网上已经有一大堆的重复搬运的资料任由 ...
- java 后台的学习步骤
一.JavaWeb部分 第一阶段:JavaWeb前端技术 web前端技术 HTML, CSS, JavaScript基础, jQuery基础, BootStrap. 第二阶段:服务器端技术 Mysql ...
- [Selenium] 如何使用Chrome Options 定制测试Chrome 浏览器的特定属性 (类似FirefoxProfiles)
Chrome Options 类似于FirefoxProfiles,用于定制待测试的Chrome 浏览器的特定属性 1)如果希望测试某个浏览器插件,可通过addExtensions方式提前加载以.cr ...
- java8的常用的新特征
一.Java 8 对接口做了进一步的增强.(默认方法和静态方法) a. 在接口中可以添加使用 default 关键字修饰的非抽象方法.即:默认方法(或扩展方法不用被实现)如:comparator接口. ...
- Linux 系统开机启动项清理
一般情况下,常规用途的 Linux 发行版在开机启动时拉起各种相关服务进程,包括许多你可能无需使用的服务,例如蓝牙bluetooth.Avahi. 调制解调管理器ModemManager.ppp-dn ...
- Socket教程
Socket网络编程学习笔记(6):使用线程池提高性能 Socket网络编程学习笔记(5):发送和接收实体类数据 Socket网络编程学习笔记(4):TCP消息边界处理 Socket网络 ...
- oracle 查看表是否存在、包含某字段的表、表是否包含字段
表是否存在: select count(*) from user_tables where table_name = #{tablename} 包含某个字段的表 select * from user_ ...