Vue中的computed与watch的区别

前端开发
2020年06月04日
799

对于模板语法中的复杂逻辑运算,通常我们都会考虑使用计算属性

计算属性:computed

vue
<template> <div class="wrapper"> <div> {{ firstName + ' ' + lastName }} </div> <div>{{ fullName }}</div> </div> </template> <script> export default { name: 'ComputedVSWatch', data () { return { firstName: 'Foo', lastName: 'Bar', } }, computed: { fullName () { const name = this.firstName + ' ' + this.lastName; return name; } }, mounted () { setTimeout(() => { this.lastName = 'Baz'; }, 2000) } } </script>

computed.gif

然而,watch也可以完成相应的功能。

侦听器:watch

js
export default { name: 'ComputedVSWatch', data () { return { firstName: 'Foo', lastName: 'Bar', fullName: 'Foo Bar' } }, watch: { firstName () { this.fullName = this.firstName + ' ' + this.lastName; }, lastName () { this.fullName = this.firstName + ' ' + this.lastName; } }, mounted () { setTimeout(() => { this.lastName = 'Baz'; }, 2000) } }

同样的功能,使用methods也可以实现。

方法:methods

vue
<template> <div class="wrapper"> <div> Default: {{ firstName + ' ' + lastName }} </div> <div> Methods: {{ getFullName() }} </div> </div> </template> <script> export default { name: 'ComputedVSWatch', data () { return { firstName: 'Foo', lastName: 'Bar', } }, methods: { getFullName () { return this.firstName + ' ' + this.lastName; } }, mounted () { setTimeout(() => { this.lastName = 'Baz'; }, 2000) } } </script>

computed vs methods

vue
<template> <div class="wrapper"> <div> <p>Computed:</p> <p>{{ fullName }}</p> <p>{{ fullName }}</p> <p>{{ fullName }}</p> </div> <div> <p>Methods:</p> <p>{{ getFullName() }}</p> <p>{{ getFullName() }}</p> <p>{{ getFullName() }}</p> </div> </div> </template> <script> export default { name: 'ComputedVSWatch', data () { return { firstName: 'Foo', lastName: 'Bar', } }, computed: { fullName () { return this.firstName + ' ' + this.lastName; } }, methods: { getFullName () { return this.firstName + ' ' + this.lastName; } }, mounted () { setTimeout(() => { this.lastName = 'Baz'; }, 2000) } } </script>

QQ截图20200604011527.jpg

只是看效果的话,两者并没什么区别,那么两者的区别的哪里呢?

尝试在computedmethods里面分别添加了一行console.log看看控制台上打印的是什么?

js
computed: { fullName () { console.log('computed执行了一次', this.lastName); return this.firstName + ' ' + this.lastName; } }, methods: { getFullName () { console.log('methods执行了一次', this.lastName); return this.firstName + ' ' + this.lastName; } },
plain-text
logs: -------- computed执行了一次 Bar methods执行了一次 Bar methods执行了一次 Bar methods执行了一次 Bar computed执行了一次 Baz methods执行了一次 Baz methods执行了一次 Baz methods执行了一次 Baz

我们可以看到,computed在数据未发生改变之前,无论调用多少次,都只会执行一次其中的代码,也就是说computed它拥有缓存功能,只有它依赖的数据发生变化时,才会再次运算;而methods每调用一次就会执行里面的代码。

computed vs watch

vue
<template> <div class="wrapper"> <div> <p>Computed:</p> <p>{{ computedFullName }}</p> <p>{{ computedFullName }}</p> <p>{{ computedFullName }}</p> </div> <div> <p>Watch:</p> <p>{{ watchFullName }}</p> <p>{{ watchFullName }}</p> <p>{{ watchFullName }}</p> </div> </div> </template> <script> export default { name: 'ComputedVSWatch', data () { return { firstName: 'Foo', lastName: 'Bar', watchFullName: 'Foo Bar' } }, computed: { computedFullName () { return this.firstName + ' ' + this.lastName; } }, watch: { firstName () { this.watchFullName = this.firstName + ' ' + this.lastName; }, lastName () { this.watchFullName = this.firstName + ' ' + this.lastName; } }, mounted () { setTimeout(() => { this.lastName = 'Baz'; }, 2000) } } </script>

QQ截图20200604141030.jpg

同样的功能,computedwatch都能实现,但是,wacth需要我们手动地给予初始值。

那么在什么时候使用computed,什么时候使用watch呢?

以下这种场景,我们需要异步获取某些数据,并且限制我们执行该操作的频率,并且在我们得到最终结果之前,会出示提示信息。这时候,我们应该考虑使用watch

vue
<template> <div class="wrapper"> <p> Question: <input type="text" v-model="question"> </p> <p> Answer: {{ answer }} </p> </div> </template> <script> import _ from 'lodash'; export default { name: 'ComputedVSWatch', data () { return { question: '', answer: '请输入问题', answers: [ { q: '123', a: '答案1', }, { q: '234', a: '答案2', }, { q: '3456', a: '答案3' }, ] } }, created () { this.getAnswerDebounce = _.debounce(this.getAnswer, 500); }, watch: { question (newQuestion) { this.answer = '输入完毕之后才会显示结果...'; this.getAnswerDebounce(newQuestion); } }, methods: { getAnswer (question) { const answers = this.answers, answer = this.answers.find((item) => item.q == question); if (answer) { this.answer = answer.a; return; } this.answer = '问题匹配失败!'; } } } </script>

watch.gif

我们再看一下下面这种场景:

我们需要在用户输入密码1秒后检测密码的正确性,尝试使用computed

vue
<template> <div class="wrapper"> <p> password: <input type="text" v-model.trim="password"> </p> <p :style="msgStyle"> {{ message.text }} </p> </div> </template> <script> export default { name: 'ComputedVSWatch', data () { return { password: '', msg: {} } }, computed: { message () { let password = this.password, msg = { type: 'default', text: '请输入密码' }; setTimeout(() => { if (password.length < 8) { msg = { type: 'error', text: '密码太短了' }; } else { msg = { type: 'success', text: '密码可以使用' }; } }, 1000); return msg; }, msgStyle () { let msg = this.message; return { color: msg.type === 'default' ? 'black' : (msg.type === 'error' ? 'red' : 'green') } } } } </script>

c_1.gif

很显然,使用computed无法达成我们想要的效果。

尝试使用watch

vue
<template> <div class="wrapper"> <p> password: <input type="text" v-model.trim="password"> </p> <p :style="msgStyle"> {{ message.text }} </p> </div> </template> <script> export default { name: 'ComputedVSWatch', data () { return { password: '', message: { type: 'default', text: '请输入密码' } } }, computed: { msgStyle () { let msg = this.message; return { color: msg.type === 'default' ? 'black' : (msg.type === 'error' ? 'red' : 'green') } } }, watch: { password () { setTimeout(() => { if (this.password.length < 8) { this.message = { type: 'error', text: '密码太短了' }; } else { this.message = { type: 'success', text: '密码可以使用' }; } }, 1000); } } } </script>

c_2.gif

使用watch,很轻松就可以完成这种效果。

总结:

  1. 如果一个数据依赖其他数据,那么把这个数据设计为computed则更适合;
  2. 如果需要在某个数据发生变化时,做一些事情,使用watch则更加适合。

computedwatch没有优劣之分,只是使用的场景有所区别,选择合适的无疑会让开发变得更加简单。