Vue 3之Ref VS Reactive

458 阅读3分钟

概念

ref和reactive都是vue用来做响应式的对象,就是通过它们定义的值,在vue的dom中用到的,只要值变化,那dom也会跟着变化,这就是响应式。

栗子

const com = ()=>{
    setup(){
        const input = ref(0);
        const select = reactive({option1:"1",options2:"2"});
        
        return ()=>{
                return <div>
                    <span onClick={e=>{input.value = e.target.value}}>{input.value}</span>
                    <div onClick={e=>
                        select.option1 = e.target.value;
                        select.option2 = e.target.value + 1;
                    }>
                        {select.option1}
                    </div>
                </div>
        }
    }
}

大家可以看到,如果点击对应的节点,那么会重新渲染这个虚拟dom。

区别

从直观语法上的区别看

ref响应的是一个值或者一个对象,而reactive响应的是一个对象里的所有成员。大家可以根据自己项目实际情况选择合适自己的响应式的对象来驱动变化。

从原理上来

我们写一个伪代码来宏观了解一下

Ref的伪代码实现

class Ref {
    constructor(value){
        this.value = value
    }
    get value(){
        track();
        return this.value;
    }
    set value(value){
        if(oldValue !== value){
            trigger();
            this.value = value
        }
    }
}

从代码来看,get一个值的核心逻辑在track这个函数里,那track是什么意思呢?track就是跟踪,它跟踪的就是这个ref值的依赖,将这些依赖收集起来,那么有人会说,为什么是get一个值的时候收集依赖,而不是vue来去做这个事情?这个问题挺好的,我们思考一下,如果是vue去做,一个是它应不应该做,其次是它要是做了,优点是什么呢?如果是ref这个值去做依赖收集,那合理性又在哪?我们可以深入思考一下,我个人的理解就是,一个事物只做它应该做的并且把它做好就够了。这个也符合软件架构的最小知识原则。

我们在写个伪代码来看看track的实现。

const activeEffect = null;
const track = ()=> {
    const deps = [];
    deps.add(activeEffect)
}

track的核心就是把用到的activeEffect收集起来,那么activeEffect又是什么呢?它其实就是副作用,所谓副作用就是除了计算逻辑之外的都可以称作是副作用,比如点击事件触发的操作,跳转到一个新的链接等等。这个activeEffect是vue引擎去做这个事情,因为收集的过程也很复杂并且是在渲染的过程中才能拿到谁应该是这个ref所用到的副作用,把他们收集起来。 我们接下来看看trigger的简单实现

const trigger = (deps)=> {
    for(let dep of deps){
        if(dep.scheduler){
            dep.scheduler()
        }else{
            dep()
        }
        
    }
}

trigger的核心就是把收集到的依赖依次执行,如果这个依赖中有调度器,那优先执行调度器,否则执行这个依赖。那么调度器是什么呢?就是如果这个依赖中有setTimeout、Promise等操作,优先执行这些。具体调度器的逻辑,大家可以再翻翻源码了解一下。

Reactive的伪代码实现

class Reactive {
    constructor(target){
        this.target = target;
    }
    return createReactiveObject(
        target,
        isReadOnly,
        hanlders
    )
}
const createReactiveObject = (target,isReadOnly,handlers)=>{
    const proxy = new Proxy(target,{
        targetType(){...}
    })
    function createGetter(target){
        track(target,target.key)
    }
    function createSetter(){
        trigger(target,target.key)
    }
    return proxy;
}

我们可以看到,Reactive这个类处理的一个目标值是一个对象,在拿到这个对象的时候,我们需要判断这个对象的类型来做不同分析,这在targetType中可以看到,具体的代码实现可以翻看源码,这里是带大家宏观了解一下,在createRectiveObject中创建了一个Proxy,proxy的好处就是可以达到拦截的目的,我们在操作一个值之前,我们可以对这个值做一些处理,从而优雅而无感知的实现我们要达到追踪依赖的效果。最终这个值也会走track和trigger,因此,从原理实现上来看,ref和reactive是很相似的,一个是对一个值的依赖收集和执行,一个是对一个值的所有成员进行依赖收集和执行。

总结

好了,今天大概从使用和原理的角度带大家了解ref和reactive的区别,大家多在实践中用用就能感受到它的魅力了。如有不对,欢迎指出~