响应系统的设计与实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// 存储副作用函数的桶
const bucket = new WeakMap()
 
// 原始数据
 
export const listenInit = (data) => {
  return new Proxy(data, {
    // 拦截读取操作
    get(target, key) {
      track(target, key)
      // 返回属性值
      return target[key]
    },
    // 拦截设置操作
    set(target, key, newVal) {
      // 设置属性值
      target[key] = newVal
 
      trigger(target, key)
      return Reflect.set(...arguments)
    },
  })
}
 
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
  // 没有 activeEffect,直接 return
  if (!activeEffect) return
  let depsMap = bucket.get(target)
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()))
  }
  let deps = depsMap.get(key)
  if (!deps) {
    depsMap.set(key, (deps = new Set()))
  }
  deps.add(activeEffect)
  activeEffect.deps.push(deps)
}
 
// 在 set 拦截函数内调用 trigger 函数触发变化
function trigger(target, key) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
 
  const effectsToRun = new Set()
  effects &&
    effects.forEach((effectFn) => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
 
  effectsToRun.forEach((effectFn) => {
    // 如果一个副作用函数存在调度器,调用调度器,然后将副作用函数作为参数传递
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      //否则直接执行副作用函数
      effectFn()
    }
  })
}
 
// 用一个全局变量存储被注册的副作用函数
let activeEffect
let effectStack = []
 
// effect 函数用于注册副作用函数
export const jobEffect = (fn, options = {}) => {
  const effectFn = () => {
    cleanup(effectFn)
    // 当 effectFn 执行时,将其设置为当前激活的副作用函数
    activeEffect = effectFn
    effectStack.push(effectFn)
    const res = fn()
 
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    return res
  }
 
  // 将options挂载到effectFn上
  effectFn.options = options
 
  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = []
  // 执行副作用函数
  if (!options.lazy) {
    effectFn()
  }
  return effectFn
}
 
function cleanup(effectFn) {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 是依赖集合
    const deps = effectFn.deps[i]
    // 将 effectFn 从依赖集合中移除
    deps.delete(effectFn)
  }
  // 最后需要重置 effectFn.deps 数组
  effectFn.deps.length = 0
}
 
// 定义一个任务队列
const jobQueue = new Set()
// 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve()
 
// 一个标志代表是否正在刷新队列
let isFlushing = false
function flushJob() {
  console.log('flushing!!')
  // 如果队列正在刷新,则什么都不做
  if (isFlushing) return
  console.log('flusingDone!!!')
  // 设置为 true,代表正在刷新
  isFlushing = true
  // 在微任务队列中刷新 jobQueue 队列
  p.then(() => {
    jobQueue.forEach((job) => job())
  }).finally(() => {
    // 结束后重置 isFlushing
    isFlushing = false
  })
}
 
export const scheduler = (fn) => {
  // 每次调度时,将副作用函数添加到 jobQueue 队列中
  jobQueue.add(fn)
  // 调用 flushJob 刷新队列
  flushJob()
}
 
export const jobWatch = (source, cb, options = {}) => {
  let getter
  if (typeof source === 'function') {
    getter = source
  } else {
    getter = () => traverse(source)
  }
  // 定义旧值与新值
  let oldValue, newValue
 
  // 提取 scheduler 调度函数为一个独立的 job 函数
  const job = () => {
    newValue = effectFn()
    cb(newValue, oldValue)
    oldValue = newValue
  }
 
  // 使用 effect 注册副作用函数时,开启 lazy 选项,并把返回值存储到 effectFn 中以便后续手动调用
  const effectFn = jobEffect(() => getter(), {
    lazy: true,
    scheduler: job,
  })
 
  if (options.immediate) {
    // 当 immediate 为 true 时立即执行 job,从而触发回调执行
    job()
  } else {
    oldValue = effectFn()
  }
}
 
function traverse(value, seen = new Set()) {
  // 如果要读取的数据是原始值,或者已经被读取过了,那么什么都不做
  if (typeof value !== 'object' || value === null || seen.has(value)) return
  // 将数据添加到 seen 中,代表遍历地读取过了,避免循环引用引起的死循环
  seen.add(value)
  // 暂时不考虑数组等其他结构
  // 假设 value 就是一个对象,使用 for...in 读取对象的每一个值,并递归地调用 traverse 进行处理
  for (const k in value) {
    traverse(value[k], seen)
  }
 
  return value
}
 
 
// 调用方法
import { listenInit, jobWatch } from './jobQueue'
let errorMessage = listenInit({ msg: '' })
jobWatch(
  () => errorMessage.msg,
  (newVal, oldVal) => {
    if (newVal != oldVal) {
      message.error(newVal)
    }
  }
)

  《Vue.js设计与实现》,书写的真好,学习到很多,vue3实现数据响应的方式了解了。

  “他的眼睛很小,跟猪的差不多,却闪动着十足的热情。不知是怎的,人们见到这样的人总是心生厌恶” ——《巴黎伦敦落魄记》

  这么回事嘛,要内敛一些呀。

posted @   艾路  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示