博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JS手写状态管理的实现
阅读量:5817 次
发布时间:2019-06-18

本文共 5442 字,大约阅读时间需要 18 分钟。

一次偶然在掘金看到一位大大分享了老外写的js状态管理文章,通读后就决定自己也实现一遍,目的是了解状态管理的内部机制.

当前的项目多数以组件化开发,状态管理库使得组件间状态管理变得非常方便。

1. 订阅发布模块

这个模块实际上是观察者模式,是一种一对多的依赖关系,当对象的某种状态发生改变,所有依赖它的对象都将得到通知,触发已经注册的事件.

在主题Subject类中首先定义this.eventList保存需要注册的事件,依次添加subscribe(订阅)、unsubscribe(取消订阅)、publish(发布订阅)等方法

subscribeunsubscribe的两个参数:name代表注册事件的唯一名字,fn为事件name的回调函数,表示所有fn方法都注册到名为name的集合下

class Subject {  constructor() {    this.eventList = []  }  /**   * 订阅主题   * @param {string} name 事件名称   * @param {function} fn 事件方法   */  subscribe(name, fn) {    if (!this.eventList.hasOwnProperty(name)) {      this.eventList[name] = []    }    this.eventList[name].push(fn)    console.log('this.eventList: ', this.eventList);  }  /**   * 取消订阅主题   * @param {string} name 事件名称   * @param {function} fn 事件方法   */  unsubscribe(name, fn) {    var fns = this.eventList[name];    if (!fns || fns.length == 0) { // 如果没有订阅该事件,直接返回      return false    }    if (!fn) { // 如果传入具体函数,表示取消所有对应name的订阅      fns.length = 0    } else {      for (var i = 0; i < fns.length; i++) {        if (fn == fns[i]) {          fns.splice(i, 1);        }      }    }  }  /**   * 发布主题,触发订阅事件   */  publish() {    var name = Array.prototype.shift.call(arguments)	// 获取事件名称    var fns = this.eventList[name]    if (!fns || fns.length == 0) { // 没有订阅该事件      return false    }    for (var i = 0, fn; i < fns.length; i++) {      fn = fns[i]      fn.apply(this, arguments)    }  }}复制代码

对于观察者类,传入主题、事件名称、事件方法,目的是将事件注册到相应主题上:

class Observer {  constructor(subject, name, fn) {    this.subject = subject    this.name = name    this.subject.subscribe(name, fn)  }}复制代码

2. 核心LibStore

核心LibStore类需要引入上面的订阅发布模块的主题类,状态管理个人理解为一个单例化的主题,所有的状态事件都在同一个主题下进行订阅发布,因此实例化一次Subject即可。同时需要对state数据进行监听和赋值,创建LibStore类需要传入参数params,从参数中获取actionsmutations,或者默认为{}

constructor(params){  var _self = this  this._subject = new Subject()  this.mutations = params.mutations ? params.mutations : {}  this.actions = params.actions ? params.actions : {}}复制代码

为了判LibStore对象在任意时刻的状态,需要定义status用来记录,状态有三种:

this.status = 'resting';this.status = 'mutation'; this.status = 'action';复制代码

存放数据state也会从params传入,但为了监听LibStore中存储的数据变化,我们引入了代理Proxy,使每次访问和改变state数据变化都得到监听,改变state数据时触发主题发布,执行所有依赖stateChange事件的方法。

// 代理状态值,监听状态变化this.state = new Proxy(params.state || {}, {  get(state, key) {    return state[key]  },  set(state, key, val) {    if (_self.status !== 'mutation') {      console.warn(`需要采用mutation来改变状态值`);    }    state[key] = val    console.log(`状态变化:${key}:${val}`)    _self._subject.publish('stateChange', _self.state)    _self.status = 'resting';    return true  }})复制代码

改变state中数据通过commitdispatch方法来执行

/*** 修改状态值* @param {string} name * @param {string} newVal */commit(name, newVal) {  if (typeof (this.mutations[name]) != 'function') {    return fasle  }  console.group(`mutation: ${name}`);  this.status = 'mutation'; // 改变状态  this.mutations[name](this.state, newVal);  console.groupEnd();  return true;}/*** 分发执行action的方法* @param  key 的方法属性名 * @param  newVal 状态的新值 */dispatch(key, newVal) {  if (typeof (this.actions[key]) != 'function') {    return fasle  }  console.group(`action: ${key}`);  this.actions[key](this, newVal);  self.status = 'action';  console.groupEnd();  return true}复制代码

最后,将实例化的主题_subject暴露出来,以便后续注册stateChange事件时使用

getSubject() {   return this._subject }复制代码

3. 实例化核心LibStore组件

使用vuex的同学对这个组件一定不陌生,主要是配置statemutationsactions,并把参数传入核心LibStore组件类的实例当中

import libStore from "./libStore";let state = {  count: 0}let mutations = {  addCount(state, val) {    state.count = val  },}let actions = {  updateCount(context, val) {    context.commit('addCount', val);  }}export default new libStore({  state,  mutations,  actions})复制代码

4.注册stateChange事件

StoreChange类将作为应用组件的继承类使用,目的是使使用组件注册stateChange事件,同时获得继承类的update方法,该方法将在state数据变化时的到触发。

引入刚刚实例化LibStore的对象store和订阅发布模块中的观察者类,并注册stateChange事件和回调update方法

import store from '@/assets/lib/store'import { Observer } from './subject'class StoreChange {  constructor() {    this.update = this.update || function () {};    new Observer(store.getSubject(), 'stateChange', this.update.bind(this))  }}复制代码

5. 应用实例

实例将采用两个组件IndexDetail,分别代表两个页面,通过hash路由切换挂载实现跳转,需要说明的是,每次挂载组件前需要清除已经在状态对象的单例化主题中注册的stateChange方法,避免重复注册。

  • Index

首页


<%= count%>

复制代码
// 组件Jsimport StateChange from '@/assets/lib/stateChange'import store from '@/assets/lib/store'export default class Index extends StateChange{  constructor($root){    super()    this.$root = $root    this.render()    document.querySelector('#btn1').addEventListener('click',this.add.bind(this))    document.querySelector('#btn2').addEventListener('click',this.minus.bind(this))  }  render(){    var indexTmpl = require('./index.art')    this.$root.innerHTML =indexTmpl({
count:store.state.count}) } update(){ document.querySelector('#time').textContent = store.state.count } add(){ var count = store.state.count store.commit('addCount',++count) } minus(){ var count = store.state.count store.dispatch('updateCount',--count) }}复制代码
  • Detail

详情


<%= count%>

复制代码
import StateChange from '@/assets/lib/stateChange'import store from '@/assets/lib/store'export default class Index extends StateChange {  constructor($root){    super()    this.$root = $root    this.render()  }  render(){    var detailTmpl = require('./detail.art')    this.$root.innerHTML = detailTmpl({
count:store.state.count}) }}复制代码

文章参考

最后感谢原文作者和分享作者! 完整代码见,欢迎交流和star!

转载地址:http://pfhbx.baihongyu.com/

你可能感兴趣的文章
深入理解浏览器的缓存机制
查看>>
又拍云沈志华:如何打造一款安全的App
查看>>
dubbo源码分析-架构
查看>>
Windows phone 8 学习笔记
查看>>
我的友情链接
查看>>
LeetCode--112--路径总和
查看>>
感悟贴2016-05-13
查看>>
参加婚礼
查看>>
Java重写equals方法和hashCode方法
查看>>
Spring ’14 Wave Update: Installing Dynamics CRM on Tablets for Windows 8.1
查看>>
MySQL 备份与恢复
查看>>
TEST
查看>>
PAT A1037
查看>>
(六)Oracle学习笔记—— 约束
查看>>
[Oracle]如何在Oracle中设置Event
查看>>
top.location.href和localtion.href有什么不同
查看>>
Gradle之module间依赖版本同步
查看>>
java springcloud版b2b2c社交电商spring cloud分布式微服务(十五)Springboot整合RabbitMQ...
查看>>
Windwos Server 2008 R2 DHCP服务
查看>>
d3 v4实现饼状图,折线标注
查看>>