上一节 的我们有了 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 和 dispatchgetState 用于获取 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(一):优雅地修改共享状态

动手实现 Redux(二):抽离 store 和监控数据变化的更多相关文章

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

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

  2. 动手实现 Redux(一):优雅地修改共享状态

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

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

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

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

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

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

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

  6. geotrellis使用(二)geotrellis-chatta-demo以及geotrellis框架数据读取方式初探

    在上篇博客(geotrellis使用初探)中简单介绍了geotrellis-chatta-demo的大致工作流程,但是有一个重要的问题就是此demo如何调取数据进行瓦片切割分析处理等并未说明,经过几天 ...

  7. Extjs之遍历Store内的数据

    Store作为数据的载体,通过下面的方法可以获得Store内的数据; Ext.define('haomlGeimjTongjGrid_store_data', { extend: 'Ext.data. ...

  8. Vuex 页面刷新后store保存的数据会丢失 取cookie值

    在store.js中 export default new vuex.Store({ // 首先声明一个状态 state state:{ pcid: '', postList: [], } //更新状 ...

  9. 前端笔记之微信小程序(二){{}}插值和MVVM模式&数据双向绑定&指令&API

    一.双花括号{{}}插值和MVVM模式 1.1 体会{{}}插值 index.wxml的标签不是html的那些标签,这里的view就是div. {{}}这样的插值写法,叫做mustache语法.mus ...

随机推荐

  1. jQuery 工具函数

    jQuery工具函数 一.$.browser对象属性 属性列表 说明 webkit webkit相关浏览器则返回true,否则返回false,如google,傲游. mozilla mozilla相关 ...

  2. spring、spring MVC、spring Boot

    Spring 是一个“引擎” Spring MVC 是基于 Spring 的一个 MVC 框架 Spring Boot 是基于 Spring4 的条件注册的一套快速开发整合包 Spring 最初利用“ ...

  3. DataSnap的如果网络断线,如何恢复?

    timer代码很简单:var adbsevertime :TDateTime;begin try adbsevertime := ClientModule1.ServerMethods1Client. ...

  4. POJ1797 Heavy Transportation —— 最短路变形

    题目链接:http://poj.org/problem?id=1797 Heavy Transportation Time Limit: 3000MS   Memory Limit: 30000K T ...

  5. 分段控制器--UISegmentedControl 基本用法

    http://blog.csdn.net/heng615975867/article/details/43527295 http://blog.csdn.net/gf771115/article/de ...

  6. [Selenium] Selenium 疑难杂症

    1. jsclick 也不管用 Actions action = new Actions(driver); WebElement theRow = page.getInvisibleElement() ...

  7. 洛谷 P3957 跳房子 —— 二分答案+单调队列优化DP

    题目:https://www.luogu.org/problemnew/show/P3957 先二分一个 g,然后判断: 由于转移的范围是一个区间,也就是滑动窗口,所以单调队列优化: 可以先令队尾为 ...

  8. 解决IIS中部署WCF时,访问.svc文件的404错误问题

    如果你直接在IIS 7中配置WCF,访问.svc文件时会出现404错误.解决方法,以管理员身份进入命令行模式,运行:"%windir%\Microsoft.NET\Framework\v3. ...

  9. FrameLayout 的使用方法

    一.FrameLayout介绍 FrameLayout帧布局是最简单的布局之一,采用帧布局的容器中无论放入多少个控件,控件默认情况下左上角都对齐到容器的左上角,如果控件一样大,同一时间只能见到最上面的 ...

  10. 51nod1174【基于线段树的RMQ】

    很基础啊~ #include <bits/stdc++.h> using namespace std; typedef long long LL; const int INF=-0x3f3 ...