Vue的计算属性

发布于 大漠

今天在学习Vue 2.0中的计算属性。发现@混元霹雳手 老师整理的一篇笔记《史上最详细 VUE2.0 全套 Demo 讲解 基础3(计算属性)》。看后感觉受益非浅。特意把自己根据老师的笔记一路学习下来。感兴趣的同学,也可以一起来玩。

Vue中的计算属性是所有属性的计算,而这些计算都是变向的在过滤值,通过数据的不断变化计算出来不同的值和操作不同的方法. 而在Vue中,会使用到计算属性的场景常见的有:

  • 模板内的表达式
  • 属性v-bind里可以进行的表达式
  • 指令中可以进行的表达式

以上三者的主要优势是简洁。如果只是一个小操作,比如说一些简单的数值++,字符拼接,三元表达式,那么使用相当方便。当然,以上方式也有自己的劣势。一旦要处理的逻辑复杂,代码量就会变得大得多,而且难于维护。比如说用到if语句来控制流程。那么这个时候可能会想到用filter,那么filter场景适用于哪里,我们后面再聊,咱们先来看一个简单的示例。

<div id="app">
    <button @click="count++">{{count + '分'}}</button>
    <div>
        <input v-model="message" />
    </div>
    <p>{{ message.split(' ').reverse().join(' ') }}</p>
</div>

let app = new Vue({
    el: '#app',
    data () {
        return {
            count: 0,
            message: ''
        }
    }
})

效果如下:

从上面的示例中我们可以看出:

  • 我们在模板内使用字符串拼接,{{count + '分'}}
  • 通过v-bind指定绑定了一个click事件,它只做了一件事情,就是count++button每点击一次,count值增加1,注意,这里并没有使用methods
  • input通过v-model做了数据双向绑定,绑定了message,并且在p标签中对message字符串进行反转。

p标签中对message进行计算转换的时候,是不是觉得语义不是很强烈,那么用什么办法更好呢?这就需要我们使用到前面提到的**filter**。

使用 filter

我们可以使用filter来修改前面的示例。事实下,在Vue中使用filter具有自己的优势,也具有自己的劣势:

  • 优势filter给我们用于计算和过滤一些模板表达式和v-bind属性表达式一些弊端的地方进行计算,他们会返回当前计算的值,可以进行传参在多地方共用这个过滤方法
  • 劣势:如果我们要计算多个数据不同变化结合而成的地方,那么filter就比较难过滤到了,本质上filter就是一个一对一的行为,对单个数据进行过滤,可以进行传参,同方法,但不同参

把上面的示例,用filter修改之后就像下面这样:

<div id="app">
    <button @click="count++">{{count + '分'}}</button>
    <div>
        <input v-model="message" />
    </div>
    <p>{{ message | reverseString }}</p>
</div>

let app = new Vue({
    el: '#app',
    data () {
        return {
            count: 0,
            message: ''
        }
    },
    filters: {
        reverseString (value) {
            if (!value) return ''
            value = value.split('').reverse().join('')
            return value
        }
    }
})

在这个示例中,我们使用filter来实现,很明显代码量多了那么一点点,但整体的语意化就变得相当明显了,让人一看这里就要进行一些过滤计算,看到reverseString就知道是字符串反转。

computed

前面说了这么多,其实我们都是为了给computed的应用场景做一个铺垫。那问题又来了,computed可以做些什么?又能应用于什么场景呢?

Vue中的computed其实规避了模板语法和filter两个所有的劣势,他的优势在于通过计算所有依赖的数据进行计算,然后返回一个值,记住可以依赖方法里所有的数据,只要一个数据发生变化,则会重新计算,来更新视图的改变。是不是很意思,迫切的想知道它是怎么玩。

咱们还是拿使用场景来说事吧。先给大家演示一个简单实用的应用场景,后面再做一个更好玩一点的应用场景。

大家是否还记得,我们在玩微博的时候,发微博会有一个字数限制,比如说只限输入140个字符。为了让用户体验更好,在文本域中输入内容的时候,同时有一个提示信息,告诉用户你还能输入多少字符。那么使用Vue来做这样的事情就会显得容易的多。比如下面这个示例:

<div id="app">
    <div class="twitter">
        <img :src="imgUrl" />
        <div class="content">
            <textarea v-model="content" :maxlength="totalcount">有什么新鲜事情?</textarea>
            <p>您还可以输入{{ reduceCount }}字</p>
        </div>
    </div>
</div>

let app = new Vue({
    el: '#app',
    data () {
        return {
            imgUrl: '//pbs.twimg.com/profile_images/468783022687256577/eKHcWEIk_normal.jpeg',
            totalcount: 140, // 总共只给输入140字
            content: ''
        }
    },
    computed: {
        reduceCount () {
            return this.totalcount - this.content.length
        }
    }
})

效果如下:

你可以尝试在文本域中输入内容,你将体验的效果类似下图一样:

computed创建了一个reduceCount,一直在监听文字的字符长度,来再次进行计算,返回值给视图,让视图进行变化。这也是一个很简单的示例。前面也提到过,我们可以监听多个数据,只要一个数据变了,整个方法就会重新计算,然后反馈到视图,这个方法只是一个简单的应用。咱们再来看一个示例,这样会更好的帮助我们理解。

这个示例是一个足球比赛记分的示例。简单的对示例进行分析:

  • 比赛时间,用time来维护
  • 比赛双方的进球数,用对象team来维护
  • 比赛的播报情况,在90分钟内,要显示中国领先或者韩国领先或者双方僵持,如果到了90分钟我们要显示中国队赢还是韩国队赢,还是平局

第三个数据相对而言比较复杂,也是比较关键的数据。那么我们用什么方式来维护,可以说比赛情况是多样化的,用一个数据去定死,这样不符合我们的场景。那么我们先列出将会改变的地方:

  • 我们需要检测双方的进球数
  • 通过时间来比对,确定比赛是否结果,然后显示对应的方案

这样我们就要不断的监听两个维护的数据,一是比赛时间,二是比赛两队进球数。

<div id="app">
    <h1>比赛时间:{{ time }}s</h1>
    <h2>直播播报:{{ result }}</h2>
    <div class="team">
        <div>
            <span>中国队进球数:{{ team.china }}</span>
            <button @click="team.china++">点击中国队进一球</button>
        </div>
        <div>
            <span>韩国队进球数:{{ team.korea }}</span>
            <button @click="team.korea++">点击韩国队进一球</button>
        </div>
    </div>
</div>

let app = new Vue({
    el: '#app',
    data () {
        return {
            time: 0,
            team: {
                china: 0,
                korea: 0
            }
        }
    },
    created () {
        let time = setInterval(() => {
            this.time++
            if (this.time == 90) {
                clearInterval(time)
            }
        }, 1000)
    },
    computed: {
        result () {
            if (this.time < 90) {
                if (this.team.china > this.team.korea) {
                    return '中国队领先'
                } else if (this.team.china < this.team.korea) {
                    return '韩国领先'
                } else {
                    return '双方僵持'
                }
            } else {
                if (this.team.china > this.team.korea) {
                    return '中国队赢'
                } else if (this.team.china < this.team.korea) {
                    return '韩国队赢'
                } else {
                    return '平局'
                }
            }
        }
    }
})

看到的效果如下:

**注:**这里时间90分钟是一个写死的时间值。如果要让Demo更为完美,这个时间我们可以写一个90分钟的倒计时效果。如果你感兴趣的话,可以自己动手修改上面的Demo,然后在下面的评论中与我们一起分享。

这个示例中,用了点击事件来进行双方进球数,这个Demo帮助我们能能更充分的理解Vue中的computed的含义。说到底是观察一个或多个数据,每当其中一个数据改变的时候,这个函数就会重新计算,还有就是通过观察所有数据来维护一个状态,就是所谓的返回一个状态值。从上面这个Demo,我们就可以很容易的知道computed到底用在什么场景,如何去维护返回一个多状态的场景。

methods vs computed

在Vue中,使用methods可以做computed同样的事情,不同的是,computed可以进行缓存。什么意思呢?就是在上个例子中我们对比赛时间和两个球队的进球数进行了检测数据。如果随着时间的改变,但是球数没动,对于computed来说只会重新计算这个球数,进入缓存,而不会再次计算,而重新计算的是这个时间,而且页面的DOM更新也会触发methods来重新计算属性。所以,如果不想让计算属性进入缓存,请使用methods,但我个人更推荐使用computed,语义化会更好一点。毕竟是什么选项里就应该做什么事,methods里面就是应该来管事件的。个人认为,同样的操作我就不演示了,官方的示例对此阐述的也非常明了。

computed vs watch

computedwatch都可以做同一件事,就像跑步运动员都可以跑步,但是分100米和1000米,术业有专攻嘛,两个选项都是对数据进行时时监听,但是两个的适用场景就不一样了:

  • computed前面说了是适用于对多数据变动进行监听,然后来维护一个状态,就是返回一个状态
  • wacth是对一个数据监听,在数据变化时,会返回两个值,一个是value(当前值),二是oldvalue是变化前的值

我们可以通过这些变化也可以去维护一个状态,但是不符合场景。那么watch主要用于什么地方呢?其主要用于监听一个数据来进行复杂的逻辑操作

<div id="app">
    <h1>比赛时间:{{ time }}s</h1>
    <h2>直播播报:{{ result }}</h2>
    <div class="team">
        <div>
            <span>中国队进球数:{{ team.china }}</span>
            <button @click="team.china++">点击中国队进一球</button>
        </div>
        <div>
            <span>韩国队进球数:{{ team.korea }}</span>
            <button @click="team.korea++">点击韩国队进一球</button>
        </div>
    </div>
</div>

let app = new Vue({
    el: '#app',
    data () {
        return {
            time: 0,
            team: {
                china: 0,
                korea: 0
            },
            result: '双方僵持'
        }
    },
    created () {
        let time = setInterval(() => {
            this.time++
            if (this.time == 90) {
                clearInterval(time)
            }
        }, 1000)
    },
    wacth: {
        time (value, oldval) {
            if (value < 90) {
                if (this.team.china > this.team.korea) {
                    this.result = '中国队领先'
                } else if (this.team.china < this.team.korea) {
                    this.result = '韩国队领先'
                } else {
                    this.result = '双方僵持'
                }
            } else {
                if (this.team.china > this.team.korea) {
                    this.result = '中国队赢'
                } else if (this.team.chian < this.team.korea) {
                    this.result = '韩国队赢'
                } else {
                    this.result = '平局'
                }
            }
        },
        team (value, oldval) {
            if (this.time < 90) {
                if (value.china > value.korea) {
                    this.result = '中国队领先'
                } else if (value.china < value.korea) {
                    this.result = '韩国队领先'
                } else {
                    this.result = '双方僵持'
                }
            } else {
                if (value.china > value.korea) {
                    this.result = '中国队赢'
                } else if(value.chian < value.korea) {
                    this.result = '韩国队赢'
                } else {
                    this.result = '平局'
                }
            }
        }
    }
})

以上代码和computed产生的效果是一模一样,但是很明显,就像我对computedwatch阐述过了应用场景,这个场景只是维护了一个比赛状态,而不牵扯到逻辑操作。虽然也能完成,但无论从代码量的比对还是可读性,还是可维护性的都不胜于computed。但说到底谁更强大呢?我还是老实说**watch更强大**,哪里有然他有场景的局限性,但是他可以做牵扯到计算属性的一切操作,缺点是watch只能一个一个监听。

watch应用场景

我相信图片预加载大家肯定都有接触过,当图片量大的时候,为了保证页面图片都加载出来的时候,才渲染页面,再进行一些 Ajax请求,或者逻辑操作。那些时你用computed对这种监听一个数据然后进行一系列逻辑操作和Ajax请求,那watch再适合不过了,如果用computed的话,那是无法实现的。

<template>
    <div v-show=show>
        <img src="https://img.alicdn.com/simba/img/TB14sYVQXXXXXc1XXXXSutbFXXX.jpg" alt="">
        <img src="//img.alicdn.com/tfs/TB1iZ6EQXXXXXcsXFXXXXXXXXXX-520-280.jpg_q90_.webp" alt="">
        <img src="https://img.alicdn.com/simba/img/TB1C0dOPXXXXXarapXXSutbFXXX.jpg" alt="">
        <img src="//img.alicdn.com/tfs/TB1iZ6EQXXXXXcsXFXXXXXXXXXX-520-280.jpg_q90_.webp" alt="">
    </div>
</template>
<script>
    export default {
        mounted () {
            var _this = this
            let imgs = document.querySelectorAll('img')
            console.log(imgs)
            Array.from(imgs).forEach((item)=>{
                let img = new Image()
                img.onload = ()=>{
                    this.count++
                }
                img.src=item.getAttribute('src')
            })
        },
        data () {
            return {
                count : 0,
                show : false
            }
        },
        watch : {
            count (val,oldval) {
                if(val == 4){
                    this.show = true
                    alert("加载完毕")
                    //然后可以对后台发送一些ajax操作
                }
            }
        }
    }
</script>

我们可以发现四张图片都加载完毕的时候,页面才渲染出来。

Vue官方有一句话说得很重要:

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的watcher。这是为什么Vue提供一个更通用的方法,使用wacth来响应数据的变化。当你想要在数据变化响应时,执行异步操作或开销较大的操作,这是很有用的。

基于这个描述,我对computedwatch的总结:

  • computed:监听多个数据或者一个数据来维护返回一个状态值,只要其中一个或多个数据发生了变化,则会重新计算整个函数体,重新返回状态值
  • watch:只能一个一个监听数据,只要这个数据发生变化,就会返回两个参数,第一个是当前的值,第二个是变化前的值。每当变化的时候,则会触发函数体的逻辑行为,根据逻辑行为做后续的操作

其实,computedwatch这几个都不是有多难,如果从表层上来说都很易理解,但从深层面上看,很多时候还是会存在问题。比如说会滥用,混用。这篇文章通过一些示例来讲解Vue的计算属性,并且对其做了一定的分析。我想这些对学习Vue的初级人员或许会有一些帮助。

最后再次感谢@混元霹雳手 老师给我们带来这么好的教程。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/vue/vue-computed.htmlKobe 11 ELite Glowing