文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Vue.js设计与实现之六-computed计算属性的实现

2024-12-02 03:30

关注

1、写在前面

在前面文章介绍了effect的实现,可以用于注册副作用函数,同时允许一些选项参数options,可以指定调度器去控制副作用函数的执行时机和次数等。还有用于追踪和收集依赖的track函数,以及用于触发副作用函数重新执行的trigger函数,结合这些我们可以实现一个计算属性--computed。

2、懒执行的effect

在研究计算属性的实现之前,需要先去了解下懒执行的effect(lazy的effect)。在当前设计的effect函数中,它会在调用时立即执行传递过来的副作用函数。但是事实上,希望在某些场景并不希望它立即执行,而是在需要的时候才执行,前面了解到想要改变effect的执行可以在options参数中设置。

const data = {
name:"pingping",
age:18,
flag:true
}
const state = new Proxy(data,{

})
effect(()=>{
console.log(state.name);
},{
//指定lazy选项,这样函数不会立即执行
lazy: true
})

就这样,通过设置options选项,去修改effect函数的实现逻辑,当options.lazy为true时不会立即执行副作用函数:

// effect用于注册副作用函数
function effect(fn,options={}){
const effectFn = ()=>{
// 调用函数完成清理遗留副作用函数
cleanupEffect(effectFn)
// 当调用effect注册副作用函数时,将副作用函数fn赋值给activeEffect
activeEffect = effectFn;
// 在副作用函数执行前压栈
effectStack.push(effectFn)
// 执行副作用函数
fn();
// 执行完毕后出栈
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// 将options挂载到effectFn函数上
effectFn.options = options
//deps是用于存储所有与该副作用函数相关联的依赖集合
effectFn.deps = [];
// 只有非lazy的时候才执行
if(!options.lazy){
// 执行副作用函数effectFn
effectFn()
}
//否则返回副作用函数
return effectFn
}

在上面代码片段中,在effect函数中先判断了是否需要懒执行,对此会判断options.lazy的值为true时,则将effectFn副作用函数作为参数返回到effect。这样,用户在调用执行effect函数时,可以通过返回值去拿到对应的effectFn函数,这样可以手动执行该函数。

const effectFn = effect(()=>{
console.log(state.name);
},{
//指定lazy选项,这样函数不会立即执行
lazy: true
});
//手动执行副作用函数
effectFn();

但是仅仅实现手动执行副作用函数,对于我们的使用意义并不大,如果将返回到effect的副作用函数作为getter,那么通过这个取值函数就能获取返回任何值。

const effectFn = effect(
()=>state.name + state.age,
{
//指定lazy选项,这样函数不会立即执行
lazy: true
});

//手动执行副作用函数,可以获取到返回的值
const value = effectFn();

这样就可以实现在调用的时候,手动执行获取到各种想要得到的值。在effect函数内部只需要做出些改变,只需要在执行副作用函数时将副作用的值返回即可:

// effect用于注册副作用函数
function effect(fn,options={}){
const effectFn = ()=>{
// 调用函数完成清理遗留副作用函数
cleanupEffect(effectFn)
// 当调用effect注册副作用函数时,将副作用函数fn赋值给activeEffect
activeEffect = effectFn;
// 在副作用函数执行前压栈
effectStack.push(effectFn)
// 执行副作用函数,将执行结果存储到res
const res = fn();
// 执行完毕后出栈
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
// 将res作为effectFn的返回值
return res
}
// 将options挂载到effectFn函数上
effectFn.options = options
//deps是用于存储所有与该副作用函数相关联的依赖集合
effectFn.deps = [];
// 只有非lazy的时候才执行
if(!options.lazy){
// 执行副作用函数effectFn
effectFn()
}
//否则返回副作用函数
return effectFn

}

现在,我们已经实现了能够进行懒执行的副作用函数,能够拿到执行返回的结果,做后续的处理。

3、computed属性

懒计算的computed属性

其实,基于前面的设计和代码实现,大概有了computed属性函数的实现雏形,就是接收一个getter函数作为副作用函数,用于创建一个懒执行的effect。computed函数的执行会返回包含一个访问器属性的对象,只有在读取value值的时候才会去执行effectFn并返回结果。

function computed(getter){
const effectFn = effect(
getter,
{
//指定lazy选项,这样函数不会立即执行
lazy: true
});
const state = {
//当对value进行读取操作时,执行effectFn并将结果进行返回
get value(){
return effectFn();
}
}
return state;
}

在上面代码中,只是粗略做了懒计算处理,只有在真正对sumRes.value的值进行读取操作时,才会去进行计算并得到值。但是在进行多次读取sumRes.value的值,每次访问计算得到的值都是相同的,并不符合我们需要使用上次计算值的要求。『计算属性需要有缓存机制,这样就可以使用到上次计算的结果。』

const sumRes = computed(()=>state.name + state.age);
console.log("hello", sumRes.value);
console.log("hello", sumRes.value);
console.log("hello", sumRes.value);

运行结果:

之所以发生这种情况,多次读取sumRes.value的值时,每次访问都会重新调用effectFn重新计算。

带有缓存的computed

为了解决前面获取不到上次计算值的问题,需要在实现computed函数时,添加对计算值的缓存操作。其实实现很简单,就是添加两个变量value和dirty,value用于缓存上次计算的值,dirty则标识是否需要重新计算。

function computed(getter){
let value;
let dirty = true;
const effectFn = effect(
getter,
{
//指定lazy选项,这样函数不会立即执行
lazy: true
//在调度器重置dirtytrue
scheduler(){
dirty = true
}
});
const state = {
//当对value进行读取操作时,执行effectFn并将结果进行返回
get value(){
//只有当dirty标识为true值时,才会将计算值进行缓存,下一次访问直接使用缓存的值
if(dirty){
value = effectFn();
dirty = false
}
return value
}
}
return state;
}

在上面代码中,初始化设置dirty为true,这样就会把计算值进行缓存,下次进行同样computed计算操作时,就会直接使用缓存的值,而非每次重新计算。同时,在computed函数的effect中添加scheduler属性,在函数内部将dirty的值重置为true,在下次访问sumRes.value时重新调用effectFn的计算值。

const sumRes = computed(()=>state.name + state.age);
console.log("hello", sumRes.value);
console.log("hello", sumRes.value);
console.log("hello", sumRes.value);
state.age++;
console.log("hello", sumRes.value);

执行结果为:

但是,在当前设计的计算属性在另一个effect函数中读取时,修改响应数据state上的属性值并不会触发副作用函数的重新渲染。其实根本原因就是这里存在一个effect嵌套问题,computed内部是effect函数实现的,而在effect中读取computed的值相当于对effect进行了嵌套,外层的effect不会被内层effect的响应式数据收集。

当然,问题很简单,解决方法同样很简单。只需要在读取计算属性值的时候,手动调用track函数进行追踪,当计算属性依赖的响应式数据发生变化时,手动调用trigger函数触发响应:

function computed(getter){
let value;
let dirty = true;

const effectFn = effect(
getter,
{
//指定lazy选项,这样函数不会立即执行
lazy: true,
//在调度器重置dirtytrue
scheduler(){
dirty = true
trigger(state, "value")
}
}
);
const state = {
//当对value进行读取操作时,执行effectFn并将结果进行返回
get value(){
//只有当dirty标识为true值时,才会将计算值进行缓存,下一次访问直接使用缓存的值
if(dirty){
value = effectFn();
dirty = false
}
// 对value进行取值操作时,手动调用track函数进行追踪
track(state, "value")
return value
}
}
return state;
}

写一段简单的demo进行实验:

const sumRes = computed(()=>state.name + state.age);
console.log("hello", sumRes.value);
console.log("hello", sumRes.value);
console.log("hello", sumRes.value);
effect(()=>{
console.log(sumRes.value);
})
state.age++

console.log("hello", sumRes.value);

执行结果:

根据上面的实现demo可以分析出对应的计算属性的响应联系图:

计算属性的响应联系

4、写在最后

计算属性computed其实是一个懒执行的副作用函数,可以通过lazy选项使得副作用函数可以懒执行,被标记为懒执行的副作用函数可以通过手动执行。在读取计算属性的值时,可以手动执行副作用函数,在依赖的响应式数据发生变化时,通过scheduler将dirty标记设置为true,即为脏数据,在下次读取计算属性的值,就会重新计算得到真正的值。

来源:前端一码平川内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯