动手实现 Redux(二):抽离 store 和监控数据变化
上一节 的我们有了 appState 和 dispatch:
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
}
}
现在我们把它们集中到一个地方,给这个地方起个名字叫做 store,然后构建一个函数 createStore,用来专门生产这种 state 和 dispatch 的集合,这样别的 App 也可以用这种模式了:
function createStore (state, stateChanger) {
const getState = () => state
const dispatch = (action) => stateChanger(state, action)
return { getState, dispatch }
}
createStore 接受两个参数,一个是表示应用程序状态的 state;另外一个是 stateChanger,它来描述应用程序状态会根据 action 发生什么变化,其实就是相当于本节开头的 dispatch 代码里面的内容。
createStore 会返回一个对象,这个对象包含两个方法 getState 和 dispatch。getState 用于获取 state 数据,其实就是简单地把 state 参数返回。
dispatch 用于修改数据,和以前一样会接受 action,然后它会把 state 和 action 一并传给 stateChanger,那么 stateChanger 就可以根据 action 来修改 state 了。
现在有了 createStore,我们可以这么修改原来的代码,保留原来所有的渲染函数不变,修改数据生成的方式:
let appState = {
title: {
text: 'React.js 小书',
color: 'red',
},
content: {
text: 'React.js 小书内容',
color: 'blue'
}
}
function stateChanger (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
state.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
state.title.color = action.color
break
default:
break
}
}
const store = createStore(appState, stateChanger)
renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
renderApp(store.getState()) // 把新的数据渲染到页面上
针对每个不同的 App,我们可以给 createStore 传入初始的数据 appState,和一个描述数据变化的函数 stateChanger,然后生成一个 store。需要修改数据的时候通过 store.dispatch,需要获取数据的时候通过 store.getState。
监控数据变化
上面的代码有一个问题,我们每次通过 dispatch 修改数据的时候,其实只是数据发生了变化,如果我们不手动调用 renderApp,页面上的内容是不会发生变化的。但是我们总不能每次 dispatch 的时候都手动调用一下 renderApp,我们肯定希望数据变化的时候程序能够智能一点地自动重新渲染数据,而不是手动调用。
你说这好办,往 dispatch里面加 renderApp 就好了,但是这样 createStore 就不够通用了。我们希望用一种通用的方式“监听”数据变化,然后重新渲染页面,这里要用到观察者模式。修改 createStore:
function createStore (state, stateChanger) {
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
stateChanger(state, action)
listeners.forEach((listener) => listener())
}
return { getState, dispatch, subscribe }
}
我们在 createStore 里面定义了一个数组 listeners,还有一个新的方法 subscribe,可以通过 store.subscribe(listener) 的方式给 subscribe 传入一个监听函数,这个函数会被 push 到数组当中。
我们修改了 dispatch,每次当它被调用的时候,除了会调用 stateChanger 进行数据的修改,还会遍历 listeners 数组里面的函数,然后一个个地去调用。相当于我们可以通过 subscribe 传入数据变化的监听函数,每当 dispatch 的时候,监听函数就会被调用,这样我们就可以在每当数据变化时候进行重新渲染:
const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState())) renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
// ...后面不管如何 store.dispatch,都不需要重新调用 renderApp
对观察者模式不熟悉的朋友可能会在这里晕头转向,建议了解一下这个设计模式的相关资料,然后进行练习: 实现一个 EventEmitter 再进行阅读。
我们只需要 subscribe 一次,后面不管如何 dispatch 进行修改数据,renderApp函数都会被重新调用,页面就会被重新渲染。这样的订阅模式还有好处就是,以后我们还可以拿同一块数据来渲染别的页面,这时 dispatch 导致的变化也会让每个页面都重新渲染:
const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState()))
store.subscribe(() => renderApp2(store.getState()))
store.subscribe(() => renderApp3(store.getState()))
...
本节的完整代码:
function createStore (state, stateChanger) {
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
stateChanger(state, action)
listeners.forEach((listener) => listener())
}
return { getState, dispatch, subscribe }
}
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
}
let appState = {
title: {
text: 'React.js 小书',
color: 'red',
},
content: {
text: 'React.js 小书内容',
color: 'blue'
}
}
function stateChanger (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
state.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
state.title.color = action.color
break
default:
break
}
}
const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState())) // 监听数据变化
renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色
总结
现在我们有了一个比较通用的 createStore,它可以产生一种我们新定义的数据类型 store,通过 store.getState 我们获取共享状态,而且我们约定只能通过 store.dispatch 修改共享状态。store 也允许我们通过 store.subscribe 监听数据数据状态被修改了,并且进行后续的例如重新渲染页面的操作。
下一节:动手实现 Redux(三):纯函数(Pure Function)简介
动手实现 Redux(二):抽离 store 和监控数据变化的更多相关文章
- 动手实现 Redux(三):纯函数(Pure Function)简介
我们接下来会继续优化我们的 createStore 的模式,让它使我们的应用程序获得更好的性能. 但在开始之前,我们先用一节的课程来介绍一下一个函数式编程里面非常重要的概念 —— 纯函数(Pure F ...
- 动手实现 Redux(一):优雅地修改共享状态
从这节起我们开始学习 Redux,一种新型的前端“架构模式”.经常和 React.js 一并提出,你要用 React.js 基本都要伴随着 Redux 和 React.js 结合的库 React-re ...
- 动手实现 Redux(六):Redux 总结
不知不觉地,到这里大家不仅仅已经掌握了 Redux,而且还自己动手写了一个 Redux.我们从一个非常原始的代码开始,不停地在发现问题.解决问题.优化代码的过程中进行推演,最后把 Redux 模式自己 ...
- 动手实现 Redux(五):不要问为什么的 reducer
经过了这么多节的优化,我们有了一个很通用的 createStore: function createStore (state, stateChanger) { const listeners = [] ...
- 动手实现 Redux(四):共享结构的对象提高性能
接下来两节某些地方可能会稍微有一点点抽象,但是我会尽可能用简单的方式进行讲解.如果你觉得理解起来有点困难,可以把这几节多读多理解几遍,其实我们一路走来都是符合“逻辑”的,都是发现问题.思考问题.优化代 ...
- geotrellis使用(二)geotrellis-chatta-demo以及geotrellis框架数据读取方式初探
在上篇博客(geotrellis使用初探)中简单介绍了geotrellis-chatta-demo的大致工作流程,但是有一个重要的问题就是此demo如何调取数据进行瓦片切割分析处理等并未说明,经过几天 ...
- Extjs之遍历Store内的数据
Store作为数据的载体,通过下面的方法可以获得Store内的数据; Ext.define('haomlGeimjTongjGrid_store_data', { extend: 'Ext.data. ...
- Vuex 页面刷新后store保存的数据会丢失 取cookie值
在store.js中 export default new vuex.Store({ // 首先声明一个状态 state state:{ pcid: '', postList: [], } //更新状 ...
- 前端笔记之微信小程序(二){{}}插值和MVVM模式&数据双向绑定&指令&API
一.双花括号{{}}插值和MVVM模式 1.1 体会{{}}插值 index.wxml的标签不是html的那些标签,这里的view就是div. {{}}这样的插值写法,叫做mustache语法.mus ...
随机推荐
- Session Timeout 与 $.ajaxSetup
对于session过期跳转的问题,很简单,就是一个过滤器,然后判断session为空?跳转:继续.但是对于ajax的请求,需要做特殊处理,见下面代码中的 // 此处考虑ajax操作session过期的 ...
- POJ 1737 Connected Graph(高精度+DP递推)
题面 \(solution:\) 首先做个推销:带负数的压位高精度(加减乘+读写) 然后:由 \(N\) 个节点组成的无向图的总数为: \(2^{N*(N-1)/2}\) (也就是说这个图总共有 \( ...
- js遍历map
//火狐控制台打印输出: Object { fileNumber="文件编号", fileName="文件名称"} console.log(map); for( ...
- I2S
音频数据传输而制定: Inter—IC Sound : 单线 时钟和数据一条线,时分复用: 标准的I2S总线电缆是由3根串行导线组成的:1根是时分多路复用(简称TDM)数据线:1根是字选择线:1根是时 ...
- Java 字符串比较小知识
使用 String.compareTo 方法: compareTo() 的返回值是int, 它是先比较对应字符的大小(ASCII码顺序). 1.如果字符串相等,返回值0. 2.如果第一个字符和参数的第 ...
- poj 1789 Truck History 解题报告
题目链接:http://poj.org/problem?id=1789 题目意思:给出 N 行,每行7个字符你,统计所有的 行 与 行 之间的差值(就是相同位置下字母不相同),一个位置不相同就为1,依 ...
- oracle:通过shell来运行rman命令
每次都手工输入一批rman命令来进行备份等操作是很繁琐的事,有什么简便的方法吗?可以的,你可以把这批rman命令写在 shell命令里面,需要的时候,运行一下sh即可.下面是一个简单的实例: RMAN ...
- 「LuoguT36048」 Storm in Lover
Description 平成二十四年(2012年),5月11日,东京,某弓道场. "呐,呐,海未酱,你听说了吗?几天后的那场弓道大会?啊-!"橙发少女兴奋地拿着一张传单一样的纸跑向 ...
- 状态空间搜索好题UVA10603
题目 分析:注意这里求的是最少流量, 二不是最少步数!!!所以我们用优先队列去维护一个最小流量,然后进行bfs即可,解释一下一个重要的数组ans[i],表示的是杯子中的水为i时的最小流量 #inclu ...
- bzoj4516
后缀自动机 留个板子 upd:大概懂了 每次新加入的npRight集合肯定只有最后一个位置,那么求所有长得不一样的子串贡献就是Max-Min+1,因为Right集合只有这一个位置,所以这Max-Min ...