现代web开发,大多数都遵循着视图与逻辑分离的开发原则,一反面使得代码更加易懂且易扩展,另一方面带来的问题就是如何优雅的管理数据。因而,社区诞生了很多优秀的状态管理库,比如为React而生的Redux,专为Vue服务 ...
现代web开发,大多数都遵循着视图与逻辑分离的开发原则,一反面使得代码更加易懂且易扩展,另一方面带来的问题就是如何优雅的管理数据。因而,社区诞生了很多优秀的状态管理库,比如为React而生的 二 实现过程1. 基本原理依赖收集的基本原理可以概括为以下3步:
我们要实现的例子: import { observable, observe } from "micro-reaction";
const ob = observable({
a: 1
});
observe(() => console.log(ob.a));
// logs: 1
// logs: 2
ob.a = 2;
复制代码 下面开始我将一步一步的进行实现过程讲解 2. 创建一个可观察对象首先,我们需要创建一个可观察对象,其本质就是将传入的对象进行代理,并且返回这个代理对象,这里我们使用 我们定义了一个名叫 export function observable(obj = {}) {
return createObservable(obj)
}
function createObservable(obj) {
const proxyObj = new Proxy(obj, handlers());
return proxyObj
}
复制代码 可以看到 function handlers() {
return {
get: (target, key, receiver) => {
const result = Reflect.get(target, key, receiver);
return result
},
set: (target, key, value, receiver) => {
const result = Reflect.set(target, key, value, receiver);
return result
}
}
}
复制代码 如上,我们在 3. 关联副作用函数effect完成了对数据的初始定义,我们明确下我们的目的,我们的最终目的是数据改变,副作用函数 因而,这里我们定义了一个 export function observe(fn) {
<!--这一行可以先忽略,后面会有介绍-->
storeFns.push(fn);
<!--Reflect.apply()就相当于fn.call(this.arguments)-->
Reflect.apply(fn, this, arguments)
}
复制代码 可以看到,内部执行了传入的函数,而我们传入的函数是 function handlers() {
return {
get: (target, key, receiver) => {
const result = Reflect.get(target, key, receiver);
<!--触发依赖收集-->
depsCollect({ target, key })
return result
},
set: (target, key, value, receiver) => {
const result = Reflect.set(target, key, value, receiver);
return result
}
}
}
复制代码
{
obj:{
"key-1":fn1,
"key-2":fn2,
....
}
}
复制代码 我们定义了一个全局变量 // 存储依赖对象
const storeReactions = new WeakMap();
// 中转数组,用来临时存储当前可观察对象的反应函数,完成收集之后立即释放
const storeFns = [];
function depsCollect({ target, key }) {
const fn = storeFns[storeFns.length - 1];
if (fn) {
const mapReactions = storeReactions.get(target);
if (!mapReactions.get(key)) {
mapReactions.set(key, fn)
}
}
}
复制代码 至此,我们的依赖收集算是完成了,接下来就是要实现如何监听数据改变,对应 4. 数据变更,effect自动运行数据变更,就是重新设置数据,类似 set: (target, key, value, receiver) => {
const result = Reflect.set(target, key, value, receiver);
executeReactions({ target, key })
return result
}
function executeReactions({ target, key }) {
<!-- 一时看不懂的,回顾下我们的映射关系 -->
const mapReactions = storeReactions.get(target);
if (mapReactions.has(key)) {
const reaction = mapReactions.get(key);
reaction();
}
}
复制代码 ok,我们的例子的实现过程讲解完了,整个实现过程还是很清晰的,最后看看我们的整个代码,去掉空行不到50行代码。 const storeReactions = new WeakMap(),storeFns = [];
export function observable(obj = {}) {
const proxyObj = new Proxy(obj, handlers());
storeReactions.set(obj, new Map());
return proxyObj
}
export function observe(fn) {
if (storeFns.indexOf(fn) === -1) {
try {
storeFns.push(fn);
Reflect.apply(fn, this, arguments)
} finally {
storeFns.pop()
}
}
}
function handlers() {
return {
get: (target, key, receiver) => {
depsCollect({ target, key })
return Reflect.get(target, key, receiver)
},
set: (target, key, value, receiver) => {
Reflect.set(target, key, value, receiver)
executeReactions({ target, key })
}
}
}
function depsCollect({ target, key }) {
const fn = storeFns[storeFns.length - 1];
if (fn) {
const mapReactions = storeReactions.get(target);
if (!mapReactions.get(key)) {
mapReactions.set(key, fn)
}
}
}
function executeReactions({ target, key }) {
const mapReactions = storeReactions.get(target);
if (mapReactions.has(key)) {
const reaction = mapReactions.get(key);
reaction();
}
}
复制代码 5. 多层级数据结构到目前为止,我们实现的还只能观察单级的对象,如果一个对象的层级深了,类似 function handlers() {
return {
get: (target, key, receiver) => {
const result = Reflect.get(target, key, receiver);
depsCollect({ target, key })
if (typeof result === 'object' && result != null && storeFns.length > 0) {
return observable(result)
}
return result
}
}
}
复制代码 回到
// 存储代理对象
const storeProxys = new WeakMap();
export function observable(obj = {}) {
return storeProxys.get(obj) || createObservable(obj)
}
function createObservable(obj) {
const proxyObj = new Proxy(obj, handlers());
storeReactions.set(obj, new Map())
storeProxys.set(obj, proxyObj)
return proxyObj
}
function handlers() {
return {
get: (target, key, receiver) => {
const result = Reflect.get(target, key, receiver);
depsCollect({ target, key })
<!--如果代理存储中有某个key对应的代理直接返回即可-->
const observableResult = storeProxys.get(result);
if (typeof result === 'object' && result != null && storeFns.length > 0) {
return observable(result)
}
return observableResult || result
}
}
}
复制代码 如此, const ob = observable({
a: {
b: 1,
c: []
}
});
observe(() => console.log(ob.a.c.join(", ")));
//logs: 2
ob.a.c.push(2);
复制代码 三 如何结合小程序使用全部完整代码我已发布到我的github中,名字叫做 micro-reaction ,这个库完全无依赖的,纯粹的,故可以为其它界面框架状态管理提供能量,由于小程序跨页面状态共享相关的库不多,故这里以小程序举例,如何结合 micro-reaction 实现跨页面状态共享。 1. 核心原理描述下场景,有两个页面 2. 关键步骤首先,我们需要拿到每个小程序页面的 <!--全局数据-->
import homeStore from "../../store"
<!--将数据映射到页面,同时出发依赖收集,保存页面栈对象-->
import { mapToData } from "micro-reaction-miniprogram"
const connect = mapToData((store) => ({ count: store.credits.count }), 'home')
Page(connect({
onTap(e) {
homeStore.credits.count++
},
onJump(e) {
wx.navigateTo({
url: "/pages/logs/logs"
})
}
}))
复制代码
import { STORE_TREE } from "./createStore"
import { observe, observable } from 'micro-reaction';
function mapToData(fn, name) {
return function (pageOpt) {
const { onLoad } = pageOpt;
pageOpt.onLoad = function (opt) {
const self = this
const dataFromStore = fn.call(self, STORE_TREE[name], opt)
self.setData(Object.assign({}, self.data, dataFromStore))
observe(() => {
<!--映射方法执行,触发依赖收集-->
const dataFromStore = fn.call(self, STORE_TREE[name], opt)
self.setData(Object.assign({}, self.data, dataFromStore))
})
onLoad && onLoad.call(self, opt)
}
return pageOpt
}
}
export { mapToData, observable }
复制代码 然后,页面 四 总结希望我的文章能够让您对依赖收集的认识更深,以及如何举一反三的学会使用,此外,最近在学习周爱民老师的《JavaScript核心原理解析》这门课程,其中有句话对我触动很深,引用的是金庸射雕英雄传里面的文本: 教而不得其法,学而不得其道 ,意思就是说,传授的人没有用对方法,学习的人就不会学懂,其实我自己对学习的方法也一直都很困惑,前端发展越来越快,什么 最后,在贴下文章提及的两个库,欢迎star试用,提pr,感谢~ 依赖收集库 micro-reaction 小程序状态管理库 micro-reaction-miniprogram |