默认标题
<p><a href="https://juejin.cn/post/7090328834318270494#heading-7">https://juejin.cn/post/7090328834318270494#heading-7</a></p>
<hr />
<p>我想实现一个场景,希望1秒后 app 的内容变成 hello Vue3</p>
<pre><code class="language-javascript">const $app = document.querySelector('#app')
let state = {
text: 'hello fatfish'
}
function effect() {
$app.innerText = state.text
}
effect()
setTimeout(() =&gt; {
state.text = 'hello Vue3'
}, 1000)</code></pre>
<p>这个简单,只要拦截state对象,在对text进行取值时,收集effect函数依赖,然后text设置值时,把收集的effect函数执行一波就可以
核心只有两步:</p>
<ul>
<li>第一步:收集依赖(effect函数),在读取key时,将effect函数存储起来</li>
<li>第二步:设置值时,将依赖(effect函数)执行</li>
</ul>
<pre><code class="language-javascript">const $app = document.querySelector('#app')
const bucket = new Set()
const state = new Proxy({ text: 'hello fatfish' }, {
get (target, key) {
const value = target[ key ]
// 第一步:收集依赖,在读取key时,将effect函数存储起来
bucket.add(effect)
console.log(`get ${key}: ${value}`)
return value
},
set (target, key, newValue) {
console.log(`set ${key}: ${newValue}`)
target[ key ] = newValue
// 第二步:设置值时,将依赖执行
bucket.forEach((fn) =&gt; fn())
}
})
function effect() {
console.log('执行了effect')
$app.innerText = state.text
}
effect()
setTimeout(() =&gt; {
state.text = 'hello Vue3'
}, 1000)
</code></pre>
<p>功能是实现了,但这里收集依赖是写死的函数名字effect,只要稍微变化一下题目,就不行了</p>
<pre><code class="language-javascript">&lt;div id=&quot;container&quot;&gt;
&lt;div id=&quot;app1&quot;&gt;&lt;/div&gt;
&lt;div id=&quot;app2&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
const $app1 = document.querySelector('#app1')
const $app2 = document.querySelector('#app2')
const state = {
text: 'hello fatfish',
text2: 'hello fatfish2'
}
// 改变app1的值
function effect1() {
console.log('执行了effect')
$app1.innerText = state.text
}
// 改变app2的值
function effect2() {
console.log('执行了effect2')
$app2.innerText = state.text2
}
// 1秒钟之后两个div的值要分别改变
setTimeout(() =&gt; {
state.text = 'hello Vue3'
state.text2 = 'hello Vue3-2'
}, 1000)
</code></pre>
<p>大意了,应该把 effect 依赖函数通过某种机制,主动注册到桶中,这样无论你是匿名函数亦或者是具名函数都一视同仁</p>
<pre><code class="language-javascript">const $app1 = document.querySelector('#app1')
const $app2 = document.querySelector('#app2')
const bucket = new Set()
let activeEffect
// 变化点:
// 通过effect函数来主动收集依赖
const effect = function (fn) {
// 每执行一次,将当前fn赋值给activeEffect,这样在fn中触发读取操作时,就可以被收集进bucket中了
activeEffect = fn
// 主动执行一次很重要,必不可少
fn()
}
const state = new Proxy({ text: 'hello fatfish', text2: 'hello fatfish2' }, {
get(target, key) {
const value = target[key]
// 变化点:由版本1的effect变成了activeEffect,从而不再依赖具体的函数名字
bucket.add(activeEffect)
console.log(`get ${key}: ${value}`)
return value
},
set(target, key, newValue) {
console.log(`set ${key}: ${newValue}`)
target[key] = newValue
bucket.forEach((fn) =&gt; fn())
}
})
effect(function effect1() {
console.log('执行了effect1')
$app1.innerText = state.text
})
effect(function effect2() {
console.log('执行了effect2')
$app2.innerText = state.text2
})
setTimeout(() =&gt; {
state.text = 'hello Vue3'
state.text2 = 'hello Vue3-2'
}, 1000)</code></pre>
<p>有个问题,给 state 增加一个之前不存在的属性,bucket 却会把收集的依赖执行一次,是不是有点浪费?
能否做到 effect 中依赖了 state 的什么值,其值改变了回调才会被执行?</p>