最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 为什么Vuex的State需要通过computed引入到视图组件中以完成视图更新响应化?

    正文概述 掘金(Jayden.李)   2021-04-22   911

    Motivation

    在Vuex的官方文档中,有这么一段话。

    Vuex宣称自己的store数据是响应式的。当Vue组件从store里获取到状态时, Vue组件会有效更新视图去反应状态的变化。

    我们都知道,在Vue中,我们可以使用computed和watch两种技术监听响应式的data。那么,同理,我们应该可以使用computed和watch来对Vuex的state进行监听和回调。

    vue数据和视图绑定

    通读Vuex的文档,我们可以发现,文档中只提到了使用computed技术来观察store里的state(或者直接在template里面使用store数据),以此来绑定数据和视图。那么,为什么没有提到watch的方式呢?那我们可不可以使用watch技术来实现数据和视图的绑定呢?可不可以watch vuex store数据呢?怎么watch呢?(可以直接看后面关于watch store数据部分)

    这里提到的computed和watch技术包括通过option创建的computed和watch, 还有 composition api 创建的computed和watch。 composition api中关于computed 和 watch方法和option api类似,这里只讲option api。

    代码解释

    来看看option api: 创建computed watcher和watch watcher是通过initComputed和initWatch来创建的。

    首先了解下,为什么使用computed技术,能实现store state和视图的绑定。下面是initComputed的简化版

    const computedWatcherOptions = { lazy: true }
    
    function initComputed (vm, computed) {
    
    	// 组件上的computedWatchers对象
      var watchers = vm._computedWatchers = Object.create(null);
     
    
    	// 遍历options中的computed对象
      for (var key in computed) {
    	   // 定义在computed中的方法,通过computed中的key值获取
        var userDef = computed[key];
    	
        // 赋值给新变量getter
        var getter = typeof userDef === 'function' ? userDef : userDef.get;
        
         // 创建wather, 收集依赖,并将Watcher实例放入组建的wather队列里
    	   // Watcher构造器接受的入参:
         // vm,expOrFn,cb,options,isRenderWatcher
         watchers[key] = new Watcher(
           vm,
           getter || noop,
           noop,
           computedWatcherOptions
         );
        
         // 用于定义computed属性为响应式。
         defineComputed(vm, key, userDef);    
      }
    }
    
    

    这段代码通俗点来讲的话,大概分如下几个步骤:

    1. 为当前组件创建computedWatchers对象
    2. 接着,遍历computed的key,为每一个key创建一个watcher,用于观察computed[key]方法中所‘touch’(vue说法)到的可观察数据。
    3. 定义computed属性(例如vm.a)的getter方法,使其在被‘touch’的时候,能够被当前正在创建的Watcher所依赖(所谓的依赖收集)。

    touch数据?

    为什么Vuex的State需要通过computed引入到视图组件中以完成视图更新响应化?

    在vue的官方文档中,关于‘Reactivity in Depth’中有这么一幅图,提到了render去touch数据。什么是touch数据? 其实就是调用数据的getter方法。不过这个getter方法已经在defineReactive方法里面被劫持过,拥有被观察的能力。

    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        const value = getter ? getter.call(obj) : val
        // 当被touch时,亦即该property被调用getter时,检查当前是否有全局Watcher
        // 依赖收集逻辑
        if (Dep.target) {
          // 如果存在全局Watcher,那么让此Watcher收集当前data为其依赖
          dep.depend()
          if (childOb) {
            childOb.dep.depend()
            if (Array.isArray(value)) {
              dependArray(value)
            }
          }
        }
        return value
      }
    )
    

    回到initComputed来。在该函数中,通过为每个computed的key创建Watcher来touch在computed[key]中被使用的data(observed data)。 在我们的分析中,就是touch在store里定义的数据。 这样,当store中的数据被修改后,watcher就会接收到通知。 不过,由于computedWatcherOptions = {lazy: true}, 所创建的computed watcher是惰性watcher,即,当watcher所观察的依赖发生变化时,并不是立即执行watcher的callback,而是将watcher标记为dirty,当然,computed watcher是没有callback的(其实有一个noop空函数)。

    到此为止, 当store的数据发生变化时,是不会更新视图的。我们需要使这个computed属性也是可以被touch的。这就有了defineComputed方法。

    defineComputed方法的会为每一个computed属性定义一个新的getter,该getter使其具备被touch的能力,即在mount的时候,被touch,且被更新视图的Watcher依赖上。

    下面的代码是computed属性被定义上的新的getter。当被touch(get)时,检查其computed watcher是否dirty,就是检查该computed所观察的数据是否发生过改变(我们前面提到过,computed watcher是惰性watcher)。若果发生改变的话,重新求值。 接下来,检查global的watcher,检查当前是否在发生依赖收集,如果是的话,让这个watcher依赖上我们的computed 属性(视图更新Watcher就是这是依赖上computed属性的)。最后返回watcher的值。

    // 获取对应的watcher
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 检查watcher是否有变化,有变化的话,会调用watcher上面的evaluate来获取新的值
      if (watcher.dirty) {
        watcher.evaluate()
      }
      // 检查当前是否有watcher在收集依赖
      if (Dep.target) {
        watcher.depend()
      }
      // 这个返回值就是所谓的cache上的computed值(在watcher不是dirty的情况下)
      return watcher.value
    }
    
    

    使用watch来观察store的数据?

    那我们是否可以使用watch的方式来绑定vuex数据和视图呢?答案是肯定的,不过需要不一样的技术。先看看initWatcher

    for (const key in watch) {
      const handler = watch[key]
      if (Array.isArray(handler)) {
        for (let i = 0; i < handler.length; i++) {
          createWatcher(vm, key, handler[i])
        }
      } else {
        createWatcher(vm, key, handler)
      }
    }
    

    在initWatch里面,首先遍历option里的watch对象,然后为watch对象的每一个key值创建一个或多个watcher(当watch[key]的value为一个数组。数组的元素可以是组件method的名字,也可以是包含handler的object,也可以是函数)。这里使用了createWatcher方法,createWatcher内部逻辑如下:

    function createWatcher (
      vm: Component,
      expOrFn: string | Function,
      handler: any,
      options?: Object
    ) {
      if (isPlainObject(handler)) {
        options = handler
        handler = handler.handler
      }
      if (typeof handler === 'string') {
        handler = vm[handler]
      }
      return vm.$watch(expOrFn, handler, options)
    }
    
    

    createWatcher封装了一些入参的判断逻辑,最后,调用了vue的$watch方法来观察expOrFn的变化。这里的expOrFn的入参类型为string, 即为watch的key。

    到这里,我们可以发现computed的初始化和watch的初始化的最大不同,就是这里的expOrFn和handler.

    computed的expOrFn传入的是computed[key](为function),handler为noop。 watch的expOrFn则传入的是watch的key (string ), handler为watch[key]。

    本质区别

    这就造成了computed和watch的本质区别。

    所以,如果我们有如下代码:

    computed: {
    	a: () => { store.state.count++ }
    },
    watch: {
    	b: () => { this.a++ }
    }
    

    computed watcher 观察的是store.state.count的变化(touch了store.state.count),而watch watcher观察的是b的变化。

    computed观察的是computed[key]函数中调用过getter的可观察数据, 所以在computed[key]中调用了多个可观察数据的getter的话,就可以观察多个数据。而watch观察的是唯一的key, 所以只能进行唯一数据观察。同时,computed没有handler,所以当computed的可观察数据发生变化时,computed的watcher没有调用回调函数的操作,同时由于设置了lazy属性,所以只是标记自己(watcher)为dirty。而wach的可观察数据发生变化时,将会调用handler (watch[key])。

    他们的关系大概如下:

    为什么Vuex的State需要通过computed引入到视图组件中以完成视图更新响应化?

    watch在store中的数据

    那如果我们想直接watch在vuex store中的数据呢,同时又不产生不必要的computed属性的话呢?如下图的红线标示。

    为什么Vuex的State需要通过computed引入到视图组件中以完成视图更新响应化?

    如果使用option api的watch, 可以这样做: 不过这样watch vuex store中的data是不会更新视图的。

    watch: {
      '$store.state.count': function (newval) {
        console.log(newval);
      }
    },
    

    我们同时可以利用$watch来做到。 当然,使用这种方法,也是不会更新视图的。

    created() {
        // top-level property name
        this.$watch('a', (newVal, oldVal) => {
          // do something
        })
    
        // function for watching a single nested property
        this.$watch(
          () => this.c.d,
          (newVal, oldVal) => {
            // do something
          }
        )
    
        // function for watching a complex expression
        this.$watch(
          // every time the expression `this.$store.state.a + this.$store.state.b` yields a different result,
          // the handler will be called. It's as if we were watching a computed
          // property without defining the computed property itself
          () => this.$store.state.a + this.$store.state.b,
          (newVal, oldVal) => {
            // do something
          }
        )
      }
    

    为了更新视图,你可以在handler里面对一个可观察数据进行赋值,然后再在视图层使用这个值,就能在回调handler以后,更新视图啦。

    总结

    综上所述,我们知道,如果想追踪store中的数据,并及时更新视图的最简单方法就是使用computed技术来观察数值变化。同时,我们还了解到在组件中使用watch技术来追踪store中的数据来触发回调函数。 我们还在文中通过查看源代码探讨了computed和watch技术的异同点,总结了在组件中观察vuex数据的不同方法。


    起源地下载网 » 为什么Vuex的State需要通过computed引入到视图组件中以完成视图更新响应化?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元