🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 计算属性及监听器 ## 计算属性 模板内表达式是非常方便的,但它们是为简单操作设计的。太多的逻辑会造成模板臃肿难以维护。例如,有一个嵌套数组: ~~~ Vue.createApp({ data() { return { author: { name: '小明', books: [ 'Vue 2 - 高级手册', 'Vue 3 - 基础手册', 'Vue 4 - 神秘之旅' ] } } } }) ~~~ 在作者是否有```books```时想展示不同的内容: ~~~ <div id="computed-basics"> <p>他的出版物::</p> <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span> </div> ~~~ 这种情况下模板不再简单可读,你需要仔细阅读几秒钟才能意思到展示内容是依赖于```author.books```。如果在模板中想包含这个计算多次会导致更多的问题。 这就是为什么响应式数据里含有复杂逻辑时就需要使用计算属性。 <br /> ### 基础示例 ~~~ <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>vue demo4</title> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="app"> <p>他的出版物:</p> <span>{{ publishedBooksMessage }} </div> </body> <script type="text/javascript"> const app = { data() { return { author: { name: '小明', books: [ 'Vue 2 - 高级手册', 'Vue 3 - 基础手册', 'Vue 4 - 神秘之旅' ] } } }, computed: { // 计算属性寄存器 publishedBooksMessage() { return this.author.books.length>0? 'Yes':'No' } } } Vue.createApp(app).mount("#app") </script> </html> ~~~ 我们定义了一个计算属性```publishedBooksMessage```。试着更改data中的```books```数组,```publishedBooksMessage```也会相应的发生改变。 你可以在模板中像普通属性一样绑定计算属性,Vue会知道```vm.publishedBooksMessage```依赖于```vm.author.books```,所以会连动变化。最妙的是我们已声明式的创建了它们之间的依赖关系:计算属性寄存器函数没有副作用,反而更加容易测试和理解。 ### 计算属性缓存 VS 方法 你可能注意到了,我们可以通过方法来实现一样的功能: ~~~ <p>{{ calculateBooksMessage() }} ~~~ ~~~ // 组件实例中 methods: { calculateBooksMessage() { return this.author.books.length > 0 ? 'Yes' : 'No' } } ~~~ 计算属性和方法也确实得到了相同的结果。不同点是**计算属性缓存响应式依赖关系**。依赖数据源不变化,多次调用计算属性直接返回前面缓存的计算结果。就是说```author.books```没有发生变化,调用```publishedBooksMessage```会立即返回前面缓存的计算结果而不需要重新运行函数。 下面这个计算属性永远不会更新,因为```Date.now()```不是响应式依赖: ~~~ computed: { now() { return Date.now() } } ~~~ 相对而言,调用方法不管有没有重新渲染都会重复执行。 <br/> 为什么要缓存计算属性?相像一下,有一个非常耗费资源的```list```属性,在计算属性中需要遍历这个大型数组,而且有其他的计算属性依赖于```list```,如果不缓存我们就会没有必要的反复耗费资源来读取```list```。如果不想缓存就用```方法```来代替。 ### 计算属性 Setter 计算属性默认是只读的,但如果需要你也可以进行set: ~~~ <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>vue demo6</title> <script src="https://unpkg.com/vue@next"></script> </head> <body> <div id="app"> </div> </body> <script type="text/javascript"> const app = { data() { return { firstName : 'zhou', lastName : 'yu' } }, computed: { fullName: { // getter get() { return this.firstName + ' ' + this.lastName }, // setter set(newValue) { const names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] } } } } const vm = Vue.createApp(app).mount("#app") // get console.log(vm.fullName) // set vm.fullName = 'John Doe' console.log(vm.fullName) </script> </html> ~~~ 现在你执行```vm.fullName = 'John Doe'```,set方法会更新```vm.firstName```和```vm.lastName```。 ## 监听器 计算属性适用于大部分案例,是时候定制一个监听器了。Vue提供了很多通用的途径通过```监听```观察数据的变化。这在执行异步和耗时操作时监听返回数据变化时非常有用: ``` <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>vue demo7</title> <script src="https://unpkg.com/vue@next"></script> <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script> </head> <body> <div id="app"> 对/错提问: <input v-model="question" /> <p>{{ answer }}</p> <p v-if="question_img"> <img :src="question_img" /> </p> </div> </body> <script type="text/javascript"> const app = { data() { return { question: '', answer: '问题一般以问号结束哦. ;-)', question_img: '', count: 0 } }, watch: { // 无论问题是否变化,这个函数都会执行 question(newQuestion, oldQuestion) { console.log("执行" + (++this.count) + "次") if (newQuestion.indexOf('?') > -1 || newQuestion.indexOf("?")>0) { this.getAnswer() } } }, methods: { getAnswer() { this.answer = '思考中...' axios .get('https://yesno.wtf/api') .then(response => { this.answer = response.data.answer this.question_img = response.data.image }) .catch(error => { this.answer = '错误! 网络访问失败. ' + error }) } } } Vue.createApp(app).mount('#app') </script> </html> ``` 输入一个问题,以问号结束,执行结果: ![](https://img.kancloud.cn/7d/1b/7d1b0540c92c8d152983ad25914089a6_1467x846.png) 上面这个示例,使用```watch```选项演示了一个异步操作(网络访问API)。```computed```实现不了。 ### 计算属性 VS 监听属性, Vue提供了一个更加通用的方法实现当前活动实例的数据响应和监听:**监听属性**,如果有些数据依据其他数据的变化而变化,会导致滥用```watch``` -- 特别是你有AngularJS背景。但是,很多时候使用计算属性比使用```watch```回调更好。看下面的例子: ~~~ <div id="demo">{{ fullName }}</div> ~~~ ~~~ const vm = Vue.createApp({ data() { return { firstName: 'Foo', lastName: 'Bar', fullName: 'Foo Bar' } }, watch: { firstName(val) { this.fullName = val + ' ' + this.lastName }, lastName(val) { this.fullName = this.firstName + ' ' + val } } }).mount('#demo') ~~~ 上面这些代码是命令式且重复的,与计算属性比较下: ~~~ const vm = Vue.createApp({ data() { return { firstName: 'Foo', lastName: 'Bar' } }, computed: { fullName() { return this.firstName + ' ' + this.lastName } } }).mount('#demo') ~~~ 是不是好多了?