Vue 2.0的学习笔记:Vue的观察者

发布于 大漠

有时你可能想要观察数据的变化和反应。通常,我们会使用计算属性(computed,但是有些场景需要实现定制的监视程序。在我们讨论何使使用观察者之前,让我们先看一个如何使用它们的例子。我们将构建一个Live搜索,当我们在文本框输入关键词时,它将返回搜索结果。我们使用JavaScript的setTimeout函数来模拟从API中抓取结果。

我已经准备好了模板以及一些数据属性。到目前为止,它只将文本的value值绑定到data中的searchQuery属性。而搜索结果将存储在results中,该属性将是字符串数组。为了判断我们是否在获取搜索结果,我将使用一个isSearching布尔属性。这是我们以前见过的。

<!-- HTML -->
<div id="#app">
    <input type="text" v-model="searchQuery" />
    <p v-if="isSearching">Searching...</p>
    <div v-else>
        <ol>
            <li v-for="result in results">{{ result }}</li>
        </ol>
    </div>
</div>

let app = new Vue({
    el: '#app',
    data () {
        searchQuery: '',
        results: [],
        isSearching: false
    }
})

searchQuery属性的值发生变化时,我想触发一个搜索,那我该怎么做呢?我们可以用一个叫做观察者的东西。观察者是当他们监视数据属性值发生变化时被调用的函数。因此,我们将一个观察者与一个数据属性关联起来。观察者被添加到一个名为watch属性中,该属性被添加到Vue实例的顶层,类似于datamethodscomputed

这个watch具有和前面介绍的methods具有相同的语法,但是有一个关键的区别。虽然嵌套属性的值确实是函数,但键的名称必须对应数据属性中的名称。因此,如果我们想要查看searchQuery数据属性的更改,我们需要在watch中输入searchQuery作为我们观察者的键(key)。

let app = new Vue({
    el: '#app',
    data () {
        searchQuery: '',
        results: [],
        isSearching: false
    },
    watch: {
        searchQuery: function (query) {

        }
    }
})

这就是我们告诉Vue的方法。观察者监视和响应数据属性。注意,我们向监视的函数添加了一个参数。这是数据属性的新值,当searchQuery的值发生变化时,该值作为Vue的参数传递。为了向您展示,每当数据属性被更改时,这个函数确实被调用,我将新值记录到控制台。我们在searchQuery中添加一个console.log(query),尝试在input输入内容,你在控制台上就可以看到相应的值。

Vue的观察者

在键入时,我们可以看到在控制台中正在输出的搜索查询。

上面是为了向大家演示,Vue的观察者在监视searchQuery。那么我们把实际需要的搜索逻辑放到这个函数当中。我们要做的第一件事是将isSearch属性的值设置为true,表明我们正在等待从服务器返回的搜索结果。与methods一样,将使用这个关键字来访问观察者内的函数。

let app = new Vue({
    el: '#app',
    data () {
        searchQuery: '',
        results: [],
        isSearching: false
    },
    watch: {
        searchQuery: function (query) {
            this.isSearch = true
            console.log(query)
        }
    }
})

Vue的观察者

为了让体验更佳,接下来在searchQuery中添加一个setTimeout函数,并在500ms后运行这个函数,以模拟从服务器检索结果。

let app = new Vue({
    el: '#app',
    data () {
        searchQuery: '',
        results: [],
        isSearching: false
    },
    watch: {
        searchQuery: function (query) {
            this.isSearch = true
            console.log(query)
            
            setTimeout(() => {
                console.log(this)
            },500)
        }
    }
})

Vue的观察者

在这个函数中,我们需要更新我们的搜索结果results属性。但是我们不能在这使用这个函数,那是因为现在的作用范围不同。同样先做个测试,观察者先监视searchQuery,并把搜索的值打印到控制台,然后借助setTimeout函数,延迟500msthis打印到控制台。我们看到的效果是,每次在input中输入值时,控制台会对应的打印出你的输入值,并且在500ms后会将Vue实例打印出来。能在嵌套函数中访问到Vue的实例,那是因为我们使用了ES6的箭头函数,如果不是箭头函数,你访问到的是window对象。

Vue的观察者

另外一种方法是比较老的技巧,将this分配给一个变量,然后在嵌套函数中引用它。我常喜欢将其添加给一个meself的变量,很多同学喜欢用that来区分。但在Vue中,也有很多人用vm来声明这个变量,也就是Vue model的缩写,也是一种命名Vue实例的约定,并将其分配给它。

有了这些,我们现在就可以回到实现搜索功能的案例中来。先将搜索结果results的值设置为JavaScriptCSSHTMLMySQL。注意results是一个数组。为了要让搜索结果显示出来,我们还需要把isSearching值设置为false

let app = new Vue({
    el: '#app',
    data () {
        return {
        searchQuery: 'Search',
        results: [],
        isSearching: false
        }
    },
    watch: {
        searchQuery: function (query) {
        this.isSearching = true;
        console.log(query)
        
        setTimeout(() => {
            this.results = ['JavaScript', 'CSS', 'HTML', 'MySQL']
            this.isSearching = false
        }, 500)
        }
    }
})

Vue的观察者

正如你看到效果一样,只要你对文本框进行操作,改变input的值,就可以看到搜索结果的列表显示出来。

你可能会问,有什么大不了的。为什么我们不能只使用computed或者事件监听器?实际上,你可以使用后者,但是我们没法通过computed来完成它。原因是计算的属性是同步的,必须返回一个值。AJAX请求或者这个示例中的setTimeout函数是异步运行的,这就是为什么我们不能使用computed。因为Vue侦听函数的返回值。正如我提到的,你可以与一个事件侦听器完成相同的事情,但是这也有一定的缺点,你要手动处理这个事件和调用一个方法,而不是仅仅监听数据变化。而且,如果数据在某种程度上被改变,那么我们应用程序是不会做出相应的反应。

在Vue中,当你想要异步地对数据更改做出反应时,通常使用Vue的观察者。当然也可以不必在一个观察者中返回任何与计算属性相关的内容。这就是说,你通常应该尽可能地使用计算属性,除非你需要做一些无法用计算属性完成的事情。

这是为什么 Vue 通过 watch 选项提供一个更通用的方法,来响应数据的变化。当你想要在数据变化响应时,执行异步操作或开销较大的操作,这是很有用的。

前面通过一个简单地搜索实例向大家演示了Vue中的观察者如何使用?在Vue中,除了观察者watch之外,还提供了$watch实例方法。接下来咱们继续通过示例向大家演示如何在Vue中动态地添加观察者,即“外部”Vue实例。

在Vue中,如果你已经将一个Vue实例赋给一个变量,那么这个变量上就会有一个$watch实例方法。这个方法让我们添加一个观察者。下面来看我们怎么用这个。

我们前面使用Vue的methods做了一个计数器,接下来咱们继续用计数器的例子来演示Vue的$watch实例方法的使用。

把上面的示例简化一下,变成这样:

<!-- HTML -->
<div id="app">
    <button @click="count++">+</button>
    <p>Counter: {{ count }}</p>
</div>

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

看到的效果如下:

Vue的观察者

现在,给它添加一个观察者,当值发生变化时,就会得到通知。当然,我们可以通过前面介绍的观察者来实现这一点,但我们想了解的是如何动态的添加这么一个观察者。

在看如何实现动态添加一个观察者之前,先看看Vue官网对$watch的简单介绍。

$watch的语法很简单:

vm.$watch(expOrFn, callback, [options])

其中vm指的是Vue中的一个实例,就是我们示例中的app。那么$watch接受下面几个参数:

  • {string | Function} expOrFn
  • {Function | Object} callback
  • {Object} [options]

它将会得到一个{Function} unwatch的返回值。简单的了解了这些,咱们来看其怎么用。在上例的基础上,咱们添加下面的代码:

app.$watch('count', function(newValue, oldValue){
    alert('Count changed from ' + oldValue + ' to ' + newValue + '!')
}) 

这个时候,我们每点击一次button按钮,data中的count就会有变化,从旧的值oldValue变成一个新的值newValue。如下图所示:

Vue的观察者

注意:这里我们不能使用ES6的箭头函数,因为箭头函数被绑定到父的上下文,而this这个关键词将不会被正确绑定到Vue的实例。

我们还可以指定一个关键路径作为表达式,这也意味着我们可以使用点表示法引用嵌套对象中的属性。为了向大家演示这一点,先在上面的示例的data中添加person对象:

let app = new Vue({
    el: '#app',
    data () {
        return {
            count: 0,
            person: {
                name: {
                    firstName: 'Airen',
                    lastName: 'Liao'
                }
            }
        }
    }
})

如果我们想观察firstName属性的变化,我们可以使用点方法访问到这个属性,像下面这样:

app.$watch('person.name.firstName', function(newValue, oldValue){
    alert('firstName 从 ' + oldValue + ' 变成 ' + newValue + '!')
})

为了看到效果,在HTML中,添加一个button,同样绑定一个click事件,并将firstName指定一个新的值:

<button @click="person.name.firstName = '大漠'">改变firstName</button>

当你在渲染出来的页面中点击“改变firstName”按钮看,弹框弹出的信息如下:

Vue的观察者

但如果我们想要观察对象的变化而不是对象中特定属性呢?比如我们只希望在name对象中发生变化时得到通知,而不是具体地侦听firstName属性。这个时候,我们可以这样做:

app.$watch('person.name', function(newValue, oldValue){
    alert('firstName从 ' + oldValue.firstName + ' 变成 ' + 'newValue.firstName' + '!')
})

注意:传递的值和新值将匹配我们指定的键路径,因此在本例中,它们都将是name对象。

我们运行我们的代码,并且改变firstName。似乎没得到我们预期想要的效果,观察似乎没有工作。这是因为我们必须告诉Vue也要在对象内查找嵌套的值。我们可以通过将选项的对象作为$watch方法的第三个参数来实现。这里选择的是deep选项,并且将其值设置为true

app.$watch('person.name', function(newValue, oldValue){
    alert('firstName从 ' + oldValue.firstName + ' 变成 ' + 'newValue.firstName' + '!')
}, {deep: true})

这个时候我们看到的效果是这样:

Vue的观察者

从弹出的框中,我们可以看出oldValuenewValue的值是一样的。这是因为当改变一个对象或数组(意味着在不创建新副本情况下修改它)时,oldValuenewValue的值将是相同的,因为Vue没有保留以前的值作为副本。因此,两个参数都引用同一个对象或数组。

有的时候你可能需要一个更复杂的表达式。在这种情况下,不添加表达式作为$watch方法的第一个参数,你也可以传入一个函数。这个函数应该简单地返回你希望监视更改的表达式。上面的示例的计数器,咱们可以改成这样:

app.$watch(
    function () {
        return this.count
    },
    function (newValue, oldValue) {
        alert('Count从 ' + oldValue +  ' 变成 ' + newValue + '!')
    }
)

Vue的观察者

当然,你可以添加比这个更复杂的表达式。每当表达式中的一个依赖项发生变化时,观察者就会被触发。这种方法支持更高级的用例,当然这种情况可能很少出现。

前面演示的都是如何动态的添加一个观察者,既然可以动态的添加,同样就可以动态删除。接下来咱们就看如何动态删除一个观察者。每当我们使用$watch方法时,它实际上会返回一个函数,然后我们可以调用它来停止观察脚本中后面的更改。通过将方法的返回值赋给一个变量来尝试使用这个方法。

let unwatch = app.$watch(
    function () {
        return this.count
    },
    function (newValue, oldValue) {
        alert('Count从 ' + oldValue + ' 变成 ' + newValue + '!')
    }
)

这样我们就能看到区别,让我们在五秒种后停止观察变化。

setTimeout(function() {
    unwatch();
}, 5000);

在运行代码时,我们一开始在单击按钮时看到了警报。但五秒钟后,我们不再看到警报,因为观察者已经被删除了。

Vue的观察者

修改后的完成代码可以看CodePen上的这个示例:

这就是动态增加和删除观察者的一些知识点。如果你需要应用一些逻辑,或者有条件地添加观察者,这是很有用的。最后要注意的是,除了可以观察data属性之外,还可以观察computed

总结

今天我们主要学习了Vue中的观察者相关的知识。我们可以在Vue中像使用methodscomputed一样的使用watch。特别是当你想对data中的某个属性进行观察的时候,侦听其变化。除了使用watch之外,还可以使用$watch的API,实现动态添加和删除watch

Vue中的watch可以帮助我们做很多事情,比如下面这个小例子,通过watchdata中的date进行观察,当时间变化时,对应的容器背景色也会改变。感兴趣的可以在CodePen上看示例的整个代码:

在Vue中除了可以使用watch来侦听属性的变化之外,还可以使用$emit$on来做一些侦听的事情,具体怎么使用,我们后续会学习这方面的东西。

由于作者是Vue的初学者,如果文章中有不对之处,还请各路大婶拍正,如果你有更好的经验或建议,欢迎在下面的评论中与我们一起分享。

大漠

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

如需转载,烦请注明出处:https://www.fedev.cn/vue/vue-watch.htmlNike Air Shox Deliver 809 Men Running shoes Deep Blue Black