[Vue]computed Vs watch

95 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 4 天,点击查看活动详情

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。

可以优先考虑使用计算属性,再或者侦听器。

计算属性 -- computed

  • 计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
  • 计算属性的依赖如果是非响应式的话,计算属性不会更新。
  • 默认只有getter,不过在需要时也可以提供一个 setter

1. 在模板内使用表达式

<div id='app'>
    <!--添加课程-->
    <input type="text" v-model="course" v-on:keydown.enter="addCourse"> 
    <button @click="addCourse">添加课程</button>
    <!--课程展示-->
    <ul>
        <li v-for="item in courses" :key="item">
            {{item}}
        </li>
    </ul>
    <p>
        <!-- 表达式 -->
        课程数量: {{courses.length + '门'}}  
    </p>
    
</div>

const app = new Vue({
    el: '#app',
    data() {
        return {
            courses: ['JavaScript', 'vue', 'react', 'webpack', 'node', '小程序'],
            course: ''
        }
    },
    methods: {
        addCourse() {
            if(this.course) {
                this.courses.push(this.course)
            }
            this.course = ''
        }
    }
})

2. 改用计算属性

计算属性的使用与普通响应式属性一样

<!--略-->

<p>
    <!-- 计算属性 -->
    课程数量:{{courseCount}}
</p>

<!--略-->
const app = new Vue({
    // ......略
    
    computed: {
        courseCount() {
            return this.courses.length + '门';
        }
    }
    // ......略
})

3. 计算属性依赖如果是非响应式的

const app = new Vue({
    el: '#app',
    data() {
        return {
            courses: ['JavaScript', 'vue', 'react', 'webpack', 'node', '小程序'],
            course: '',
            dataReact: {} // 设置依赖属性
        }
    },
    created() {
        console.log('created')
        // 计算属性的依赖如果是非响应式属性的话,计算属性不会更新
        
        // this.dataReact.count = this.courses.length // 不更新
        this.$set(this.dataReact, 'count', this.courses.length ) // 更新了
    },
    computed: {
        courseCount() {
            return this.dataReact.count + '门'
        }
    }
    methods: {
        addCourse() {
            if(this.course) {
                this.courses.push(this.course)
            }
            this.course = ''
            this.dataReact.count = this.courses.length // 每次增加课程,这里得手动更新下
        }
    }
})

4. 什么时候需要使用setter

场景:例如页面显示一个时长。
一、取数据显示:这个时长从后端获取,后端返回的数据是秒(s), 页面需要显示的是分钟(min);
二、时长更新后,回传给后端——秒(s)

<!-- computed setter使用场景 -->
<div>
    <div>时长:{{showTime}} min</div> 
    <div>回传后端的数据:{{duration}} s</div>
</div>

const app = new Vue({
    el: '#app',
    data() {
        return {
            duration: 0
        }
    },
    computed: {
        showTime: {
            get() {
                return (this.duration / 60).toFixed(2)
            },
            set(newValue) {
                this.duration = newValue * 60
            }
        }
    },
    created() {
        this.duration = 120;
        setInterval(() =>{
            this.duration++
        }, 1000)
    }
})

5. 计算属性如果使用箭头函数

注意如果你为一个计算属性使用了箭头函数,则 this 不会指向这个组件的实例,不过你仍然可以将其实例作为函数的第一个参数来访问。

// 原
courseCount() {
    console.log('computed: courseCount')
    return this.dataReact.count + '门'
}

// 箭头函数:
courseCount: (vm) => {
    console.log('computed: courseCount')
    return vm.dataReact.count + '门'
}

侦听器 —— watch

当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

拿上面的例子,改用watch实现

1. 一般写法

// 实例化完后不会立即执行,当监听的数据courses改变时执行
watch: {
    courses(newData, oldData) {
        console.log('newData',newData)
        console.log('oldData',oldData)
        this.watchCourseCount = newData.length + '门'
    }   
},

2. 配置项

// 监听dataReact(这是个对象), 当其属性count改变时,触发更新
dataReact: {
        immediate: true, // 立即调用
        deep: true, // 会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
        handler(newData, oldData) { // 执行函数
            console.log('watch')
            this.watchCourseCount = newData.count + '门'
        } 
    }    

注:

  • 监听数组的变更不需要配置deep
  • 不应该使用箭头函数来定义 watcher 函数

computed VS watch

  • computed适用于多个值影响一个值的情形,也就是我当前计算属性值依赖于其他一个或多个值,有缓存性,只有当其依赖的响应式数据变化是,它才会跟着变。
  • watch 适用于一个值变化影响多个值得情形,适合执行异步操作或比较打的开销。