Vue3.0 composition API
- reactive 和ref类似,不同的是接受一个对象作为参数,并且会深度遍历这个对象,对各个属性进行拦截
- ref 将一个基本类型的值作为入参,然后返回一个响应式并包含一个value属性的对象
- readonly 只读,set操作时返回警告,不能进行写操作
-
computed 计算属性,基于内部的响应式依赖进行缓存,只有在相关响应式依赖发生改变时才会重新求值
let x= computed (()=> count.value + 3)
- watchEffect 数据变化时立即变化,执行后返回一个函数(stop:停止监听数据变化)
- watch 监听数据变化,并在回调函数中返回数据变更前后的两个值;用于在数据变化后执行异步操作或者开销较大的操作
computed
computed的使用,传入一个函数:let x = computed(() => count.value + 3)
新增computed函数
- 增加一个computed函数,传入一个函数fn,返回一个包含可监听value值的对象
- value值置为传入函数执行的结果,再将value返回
- 由于需要实现缓存,增加dirty标记记录依赖的值是否变化,只有变化了才重新计算
- 计算赋值之后将标记置为false。只要数据没有变化,就不会再重新计算。
- 何时将dirty重置为true?在执行传入函数fn时,监听的响应式数据变化之后将dirty重置为true。
- 就是在执行notify()时,将所有的依赖进行一次广播,将任务加入队列,之后执行。notify中的dep对应的为watchEffect传入的cb,因此需要改造watchEffect。
// 传入一个函数,返回一个包含可监听value值的对象
let computed = (fn) => {
let value
// 需要设置一个标记记录依赖的值是否变化,只有变化了才重新计算
let dirty = true
return {
get value() {
if (dirty) {
// 何时将dirty重置为true
// 在执行fn时,监听的响应式数据变化之后将dirty重置为true
// 就是在执行notify()时
// notify中的dep对应的为watchEffect传入的cb,因此需要改造watchEffect
value = fn() // value值置为传入函数执行的结果
// 计算之后将标记置为false。只要数据没有变化,就不会再重新计算
dirty = false
}
return value
}
}
}
改造watchEffect
- 新增effect函数,将原来watchEffect中的内容放进去
- effect中新建一个
_effect
函数,将fn额外包装了一层,用于给它添加属性,为了保证fn函数的纯粹性
let active
let effect = (fn, options = {}) => {
// _effect 额外包装了一层,用于给它添加属性
// 为了保证fn函数的纯粹性
let _effect = (...args) => {
try {
active = _effect
return fn(...args) // 需要添加return语句用于computed函数中拿到变化之后的值
} finally {
// 无论是否抛出异常最后finally都会执行
// 这句代码是在`return fn(...args)`后需要执行,因此需要放进try{}finally{}中
active = null
}
}
_effect.options = options
return _effect
}
// 之前的watch实现的即是watchEffect函数的功能
let watchEffect = (cb) => {
/* active = cb
active()
active = null */
// 将原来部分的逻辑提取到effect函数中
let runner = effect(cb)
runner()
}
notify
函数中触发
- computed函数中需要用effect去替代fn,这样可以添加钩子函数,即传入effect中的options参数
- notify中执行钩子函数
// 传入一个函数,返回一个包含可监听value值的对象
let computed = (fn) => {
let value
// 需要设置一个标记记录依赖的值是否变化,只有变化了才重新计算
let dirty = true
let runner = effect(fn, {
schedular() {
if (!dirty) {
dirty = true
}
}
})
return {
get value() {
if (dirty) {
// 何时将dirty重置为true
// 在执行fn时,监听的响应式数据变化之后将dirty重置为true
// 就是在执行notify()时
// notify中的dep对应的为watchEffect传入的cb,因此需要改造watchEffect
// value = fn() // value值置为传入函数执行的结果
value = runner()
// 计算之后将标记置为false。只要数据没有变化,就不会再重新计算
dirty = false
}
return value
}
}
}
let ref = initValue => {
let value = initValue
let dep = new Dep()
return Object.defineProperty({}, 'value', {
get() {
dep.depend()
return value
},
set(newValue) {
value = newValue
// active()
dep.notify()
}
})
}
// 触发
notify() {
this.deps.forEach(dep => {
queueJob(dep)
// 执行钩子函数
dep.options && dep.options.schedular && dep.options.schedular()
})
}
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="add">add</button>
<div id="app"></div>
</body>
<script>
let active
let effect = (fn, options = {}) => {
// _effect 额外包装了一层,用于给它添加属性
// 为了保证fn函数的纯粹性
let _effect = (...args) => {
try {
active = _effect
return fn(...args) // 需要添加return语句用于computed函数中拿到变化之后的值
} finally {
// 无论是否抛出异常最后finally都会执行
// 这句代码是在`return fn(...args)`后需要执行,因此需要放进try{}finally{}中
active = null
}
}
_effect.options = options
return _effect
}
// 之前的watch实现的即是watchEffect函数的功能
let watchEffect = (cb) => {
/* active = cb
active()
active = null */
// 将原来部分的逻辑提取到effect函数中
let runner = effect(cb)
runner()
}
let nextTick = (cb) => Promise.resolve().then(cb)
// 队列
let queue = []
// 添加队列
let queueJob = (job) => {
if (!queue.includes(job)) {
queue.push(job)
// 添加之后,将执行放到异步任务中
nextTick(flushJob)
}
}
// 执行队列
let flushJob = () => {
while (queue.length > 0) {
let job = queue.shift()
job && job()
}
}
let Dep = class {
constructor() {
// 存放收集的active
this.deps = new Set()
}
// 依赖收集
depend() {
if (active) {
this.deps.add(active)
}
}
// 触发
notify() {
this.deps.forEach(dep => {
queueJob(dep)
// 执行钩子函数
dep.options && dep.options.schedular && dep.options.schedular()
})
}
}
// 传入一个函数,返回一个包含可监听value值的对象
let computed = (fn) => {
let value
// 需要设置一个标记记录依赖的值是否变化,只有变化了才重新计算
let dirty = true
let runner = effect(fn, {
schedular() {
if (!dirty) {
dirty = true
}
}
})
return {
get value() {
if (dirty) {
// 何时将dirty重置为true
// 在执行fn时,监听的响应式数据变化之后将dirty重置为true
// 就是在执行notify()时
// notify中的dep对应的为watchEffect传入的cb,因此需要改造watchEffect
// value = fn() // value值置为传入函数执行的结果
value = runner()
// 计算之后将标记置为false。只要数据没有变化,就不会再重新计算
dirty = false
}
return value
}
}
}
let ref = initValue => {
let value = initValue
let dep = new Dep()
return Object.defineProperty({}, 'value', {
get() {
dep.depend()
return value
},
set(newValue) {
value = newValue
// active()
dep.notify()
}
})
}
// 使用:
let count = ref(0)
// computedValue 当count.value的值改变时才变化
let computedValue = computed(() => count.value + 3)
document.getElementById('add').addEventListener('click', function () {
count.value++
})
let str
watchEffect(() => {
str = `hello ${count.value} ${computedValue.value}`
document.getElementById('app').innerText = str
})
</script>
</html>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。