Vue 之computed计算属性的理解与实践

1,187 阅读3分钟

介绍computed的基本用法

基本用法 一
    new Vue({
        data () {
            return {
                num: 0
            }
        }
        computed: {
            result () {
                return this.num++
            }
        }
    })
基本用法 二 配合vuex使用
    // vuex  store.js
    
    const store = new Vuex.Store({
        state: {
            storeNum: 2
        }
    })
    // vue组件 xx.vue 
    <template>
        <div>{{result}}</div>
    </template>
    <script>
    import { mapState } from 'vuex'
    export default {
        data () {
            return {
                dataNum: 0
            }
        },
        computed: mapState({
            result: state => state.storeNum + this.dataNum
        })
    }
    </script>
基本用法 三 配合 vuex使用, 及使用mapState, 还想写一些其他的属性
    import { mapState } from 'vuex'
    export default {
        data () {
            return {
                num: 0
            }
        },
        computed: {
            ...mapState({
                storeNum: state => state.storeNum
            }),
            result() {
                return this.num + 3 + this.storeNum
            }
        }
    }
基本用法 四 修改 computed 属性值, 需要给computed计算属性添加setter

vue的计算属性基于data中的属性,要正确理解这句话,也就是说,可以在计算属性中使用data中的属性值,如果计算属性使用了data中的属性dataNum,修改dataNum的值,计算属性的值也会改变。不要直接修改计算属性, 直接修改计算属性视图不会改变。

    export default {
        data () {
            return {
                dataNum: 0
            }
        },
        computed: {
            result: {
                get () {
                    return this.dataNum + 3
                },
                set (newValue) {
                    this.dataNum = newValue
                }
            }
        }
    }

Vue中如何初始化的computed, 使用computed属性时, vue都干了啥?

在初始化computed之前, Vue已经初始化完了props、methods、data;所以在computed中我们可以使用props、methods、data中的属性,接下来我们看一下computed的初始化过程。

    // 源码位置 instance/ state.js
    const computedWacthers = {lazy: true}
    export function initState (vm) {
         const opts = vm.$options
         ...
         if (opts.computed) initComputed(vm, opts.computed) // 我们传入computed时 computed: {xxx: fn}
         ...
    }
    function initComputed (vm, computed) {
        const watchers = vm._computedWatchers = object.create(null) // 后面用来存储计算属性中每个属性对应的watcher
        for (const key in computed) {
           // 遍历computed属性
           const userDef = computed[key] // 获取computed中的每一项
           const getter = typeof userDef === 'function' ? userDef : userDef.get
           // 此处判断computed中的每一项是不是函数, 或者有没有get方法, 如果没有就抛错
           watchers[key] = new Watcher(
               vm, 
               getter || noop, 
               noop, 
               computedWatchers
               ) // 为计算属性的每一项添加响应式
           // 组件定义的计算属性已在组件原型上定义, 我们只需要在这里定义实例化时定义的计算属性。
           definedComputed(vm, key, userDef)
       }
    }
    
    function definedComputed (vm, key, userDef) {
        // 不考虑服务端渲染
        if (typeof userDef === 'function') {
            sharedPropertyDefinition.get = createComputedGetter
            sharedPropertyDefinition.set = noop
        } else {
            sharedPropertyDefinition.get = userDef.get ? createComputedGetter(key) : noop
            sharedPropertyDefinition.set = userDef.set || noop
        }
        // 如果你设置了get, 没有设置set,然后你去修改计算属性,会报错。
        Object.defineProperty(vm, key, sharedPropertyDefinition)
    }

到这里我们已经了解computed的初始化过程,遍历computed中的属性, 为每个属性设置对应的watcher实例,然后为Vue实例化时的计算属性定义,木得了。完事儿。

使用computed计算属性时, vue都为我们做了什么?

在初始化computed时,为computed中的每个属性都设置了对应的watcher实例。当我们使用计算属性中的属性值时,我们就会调用 computedGetter方法, 这个方法上面没讲到,其实就是definedComputed方法中的createComputedGetter方法的返回值。接下来我们看一下当我们在使用计算属性中的值时,vue都为我们做了什么?

    function createComputedGetter (key) {
        return function computedGetter () {
            const watcher = this._computedWatchers && this._computedWatchers[key]
            // 在上面初始化computed时定义的_computedWatchers现在用上了
            if (watcher) {
                if (watcher.dirty) {
                    watcher.evaluate() // 这个方法其实就是调用watcher.get()获取值, 然后将 watcher.dirty = false, 将dirty设置为false
                }
                if (Dep.target) {
                    watcher.depend() // 搜嘎, 在这里, 他为每个key收集了依赖。
                }
                return watcher.value // 返回调用watcher.get()方法得到的value值, 其实就是调用的你计算属性中设置的函数。
            }
        }
    }

看完上面的代码,我们清晰了,初始化时的new watcher 就埋下了伏笔, 后面当我们调用对应的计算属性时, 就会调用watcher的get方法, 然后将Dep.target赋值为当前watcher实例, 然后调用watcher.depend(), 为这个计算属性收集依赖。