Vue.js 再学习 01 - Vue实例

Vue.js 再学习 01 - Vue实例

6月剩下的时间,决定在趁热打铁把Vue再看看,用了两本2018年的新书: Vue.js: Up and Running,中文版是Vue.js快跑:构建触手可及的高性能Web应用 Vue.js 2 Web Development Projects,中文版是Vue.js项目实战 昨天开始看上边这本Vue

6月剩下的时间,决定在趁热打铁把Vue再看看,用了两本2018年的新书: 昨天开始看上边这本Vue.js快跑,发现上次跟着老外的视频确实讲的的比较浅显,而国人的Vue.js实战这书对理念其实阐述的不是很好,而这本书就解决了不少疑惑,也理清了我的一些思路,把其中的一些补遗记录下来。 我感觉自己现在集中新内容=>回头补点基础=>写实际代码=>再突击新内容的循环效果也不错,第一次用Vue写了东西,正好回头再看看Vue。 虽然技术在不断进步,但是能仔细的掌握一两个好用的东西,也会加大对于其他内容的理解。

v-if与v-show

v-show需要先把模板处理完毕,而v-if不需要。看下边这个例子:
<div id="app">
    <div v-show="user">
        <p>User name: {{ user.name }}</p>
    </div>
</div>
<script>
new Vue({
    el: '#app',
    data: {
        user: undefined
    }
});
</script>
这里会报错,因为Vue会尝试去获取user.name。如果将v-show改成v-if,内部的元素根本不会去被解释和渲染,所以不会报错。 采用这个方法,可以在父子组件通信的时候先进行父组件传来的数据的判断。 v-show的好处是开销小。v-if变动的时候,其内部组件会被重新计算和渲染,而v-show不会,如果需要展示经常变化的数据,v-show更合适一些。。

向Vue实例添加反应式属性

在之前QQ群讨论里,有朋友就说面试时候被问到如何给Vue实例添加属性,不用说,肯定是问如何添加反应式的属性。 Vue是在初始化实例的时候对data内的对象添加监听方法,为了要给Vue添加反应式的新属性有如下几种方法: 主要有三种方法:
  1. 给要添加的属性提前赋值为undefined,然后再赋值,当然这种其实不叫做添加了,因为已经让Vue初始化了
  2. 使用Object.assign(),创建一个新对象覆盖原来data的对象。一会看下边例子。
  3. 使用Vue.set(object,name, value),其中objectdata中的对象,name是属性名,value设置的属性的值。在组件里想要使用这个方法,则可以通过this.$set来使用这个方法。
第一种的例子就不看了,先来看第二种方法的例子:
<div id="app" class="container">
    <template v-if="formData.user">
        <div>这是新添加的user属性: {{formData.user}}</div>
    </template>
        <div>这是原来的属性: {{formData.username}}</div>
</div>

<script>
    var app= new Vue({
        el: '#app',
        data: {
            formData: {
                username: 'jenny',
            }
        },

    });
</script>
启动这个实例后,在控制台里输入app.formData = Object.assign({},app.formData,{user:'cony'}),页面就能渲染出来新添加的属性。 再来看第三种方法的实例:
<div id="app" class="container">
    <template v-if="formData.user">
        <div>这是新添加的user属性: {{formData.user}}</div>
    </template>
        <div>这是原来的属性: {{formData.username}}</div>
    <button @click="addProperty">添加user=cony属性</button>
</div>

<script>
    var app= new Vue({
        el: '#app',
        data: {
            formData: {
                username: 'jenny',
            }
        },
        methods:{
            addProperty:function () {
                this.$set(this.formData,'user','cony')
            }
        }

    });
</script>
点击这个按键,就会看到新添加的属性被显示了出来。

重新审视Vue实例的内容

在之前学的时候,总以为数据放data,需要计算的放在computed里边,事件处理放在methods中。这也是被Vue.js实战这本书按照实例直接形成的想法。 实际上Vue实例就将其看成一个对象,其中的内容可以将其分为propertiesmethods两大类。 data就是纯粹的保存数据,而methods实际上就是这个Vue实例的方法。而computed介于属性和方法之间,采用函数进行计算,采用属性进行访问。 另外就是this的使用:
  1. 在模板渲染的双花括号里,不要使用this,Vue直接会去找datacomputedmethods对应的名称
  2. methods里访问属性和方法,需要加上this,这个this表示方法所属的Vue实例对象,可以用其调用其他属性和方法(以及computed属性)。
  3. computed里访问属性和方法,也需要加上this
  4. watch里访问属性和方法,也需要加上this
  5. filter中无法通过this访问Vue实例的内容

Vue操作数组

Vue里不能够直接通过索引操作数组。看下边这个例子:
<div id="app" class="container" v-cloak="">
    <p>{{games}}</p>
</div>

<script>
    var app= new Vue({
        el: '#app',
        data: {
            games: ['Baldur\'s gate 3','Baldur\'s gate 2','Neverwinter Nights','TotalWar: ThreeKingdoms', 'Planescape: Torment']
        },
        methods:{
        }
    });
</script>
如果在控制台中输入app.games[2]='Neverwinter Nights 2',会发现数组没有任何变化,这是因为Vue在代理数组的时候,没有针对索引进行监听,而是针对数组的原生方法进行监听。 想要更改数组的内容,有如下几种办法:
  1. Vue内置了对于.splice等数组常用内部方法的监听,所以可以用这些方法来更改。
  2. 用上边提到的Vue.set直接更新整个数组
来看一下第一种方法的例子,在控制台中输入app.games.splice(2,1,'Neverwinter Nights 2'),就可以成功的更新了。 在控制台中输入app.games.push('Neverwinter Nights 2')也可以更新数组。 第二种方法的例子和之前设置对象的区别是第二个参数直接用数组的索引就可以,在控制台中输入Vue.set(app.games,2,'Neverwinter Nights 2')就可以改变内容。

双向绑定的补充

双向绑定已经了解了其实是一个语法糖,会绑定value属性并且通过事件进行监听。 双向绑定之后,HTML元素的valuecheckedselected属性会失效,这个要注意。 由于绑定可以用来监听value属性,所以对于radio,checkbox,select都可以用来监听。只需要给要监听的一组内容绑定同一个属性即可。而且还可以反向传递value来控制选中哪个内容。

computed属性的set

computed设置的东西,也像一个属性一样,可以实例内访问。datacomputedmethods的区别如下:
data-computed-methods
名称 可读 可写 是否能接受参数 动态计算 缓存
data Yes Yes No No 不会被计算
methods Yes No Yes Yes No,每次调用都重新计算
computed Yes Yes No Yes Yes
这里computed属性也可以set,要通过设置具体的getset方法才行:
<div id="app">
    <p>{{numbers}}</p>
    <p>Sum of numbers: {{ numberTotal }}</p>
</div>

<script>
    var app = new Vue({
        el: '#app',
        data: {
            numbers: [5, 8, 3]
        },
        computed: {
            numberTotal: {
                get() {
                    return this.numbers.reduce((sum, val) => sum + val);
                },
                set(newValue) {
                    const oldValue = this.numberTotal;
                    const difference = newValue - oldValue;
                    this.numbers.push(difference)
                }
            }
        }
    });
</script>
这里的get就是访问属性的时候得到的结果。set方法则是传入一个新要设置的值作为参数,然后在set方法中进行更改。 这样就极大的提高了computed属性的灵活性。

watch监听对象属性和获取新旧值

watch的内容比较多,要分成几个部分总结。

监听对象的属性

watch能做的事情,如果只是纯粹更改数值,最好还是使用methodscomputed,否则逻辑会乱。watchers的真正意义在于异步进行一些操作,比如模拟一个异步:
<div id="app">
    <input type="text" v-model="inputValue">
    <p>Five seconds ago, the input said "{{ oldInputValue }}".</p>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            inputValue: '',
            oldInputValue: ''
        },
        watch: {
            inputValue() {
                const newValue = this.inputValue;
                setTimeout(() => {
                    this.oldInputValue = newValue;
                }, 5000);
            }
        }
    });
</script>
注意watch是在数字变动之后才执行的。这里就监听了count属性,然后每次有变动的时候,获取新的属性,过五秒在设置到oldInputValue上。 普通情况下,如果watch一个对象,只有整个对象被替换成其他对象,才会触发watch。修改对象的属性,不会触发watch,这个时候需要特殊的写法:
<div id="app">
    <p>outer对象的value是{{outer.value}}</p>
</div>

<script src="https://unpkg.com/vue"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            outer: {
                value: 1,
            }
        },
        watch: {
            'outer.value'() {
                console.log('outer.value watcher 被触发了')
            },
            outer() {
                console.log('outer watcher整个对象被替换了')
            }
        }
    });
</script>
如果在控制台输入app.outer.value =3,会发现只触发了'outer.value'的监听,如果输入app.outer={},就可以看到两个监听器都被触发了。

监听时获取变化前后的值

watch还一个重要的内容是可以获取变化前后的值,这两个值是作为watch方法的两个参数存在:
<div id="app">
    <p>outer对象的value是{{outer.value}}</p>
</div>

<script src="https://unpkg.com/vue"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            outer: {
                value: 1,
            }
        },
        watch: {
            'outer.value'(newval, oldval) {
                console.log('outer.value watcher 被触发了');
                console.log('新的值是:' + newval);
                console.log('老的值是:' + oldval);
            },
            outer() {
                console.log('outer watcher整个对象被替换了')
            }
        }
    });
</script>
可以方便的通过参数来获取要设置的值和原来的值,这就可以进行一些特殊的操作。

深度监听

如果要监听一个对象所有的属性变化,就需要将watch拆开来写:
<div id="app">
    <p>outer对象的value是{{outer.value}}</p>
</div>

<script>
    var app = new Vue({
        el: '#app',
        data: {
            outer: {
                value: 1,
                inner:{
                    value:2
                }
            }
        },
        watch: {
            outer: {
                handler(newval, oldval) {
                    console.log(newval, oldval);
                },
                deep: true
            }
        }
    });
</script>
这里实际上是递归增加了所有监听器,所以不能直接显示前后的值,监听某个具体属性是可以的。watch的其他一些高级用法可以看这里

过滤器filter补遗

filter本质上就是接受一个参数的函数。一般来说可以有两种方法编写过滤器:
  1. 在Vue实例的filters属性之内编写
  2. Vue.filter(filtername,function)来注册
一般最好把过滤器单独放在一个文件里。注意在过滤器中无法使用this来访问Vue实例,可以将过滤器认为就是一个纯粹的功能函数。 还一个注意事项是,过滤器只能放在v-bind绑定的属性里或者放在双花括号的解释语句中

ref引用

已经知道了<canvas ref="myCanvas"></canvas>这样一个元素可以用this.$refs.myCanvas来获取对应的vnode节点对象。 这里还需要知道的是this.$refs只是对当前组件内的元素进行查找,不会查到其他组件去,所以可以很方便的使用,省去了使用queryselector等方式定位元素。

处理事件

之前的内容主要都是数据展示,还不涉及到交互。如果要使用交互,就要涉及到处理事件了。 处理事件的时候,可以直接写在绑定内部,是因为已经知道绑定内部的东西都按照JS语句解析。 但实际上,还是采用处理函数比较方便,因为处理函数的第一个参数会被传入事件对象。

修饰符

  1. prevent,阻止默认事件,常用于a标签,submit类型的按钮之类
  2. stop,阻止冒泡,很常用
  3. once,只有第一次才生效
  4. capture,这是一个新了解的,即元素自身触发的事件先在此处理,然后才交由内部元素进行处理。像反向冒泡。
  5. self,event.target是当前元素,即事件不是从内部冒泡上来的。
监听键盘按键和按键的一堆修饰符也是属于已经了解但用的不多的内容。

详细的生命周期

一个Vue实例的生命周期对应八个钩子,其实是四个大阶段+四个before,具体如下:
  1. new Vue()执行,新实例初始化
  2. beforeCreate调用,反应式功能初始化
  3. created调用,模板被初始化,会根据组件的template和render函数等初始化模板,模板还未加载到DOM上
  4. beforeMount调用,根据模板生成了HTML元素,依然没有挂载到DOM上
  5. mounted在生成好HTML元素之后调用,Vue 2.0中不能保证这个钩子调用之后Vue实例立刻被挂载到DOM上,必须在mounted中调用Vue.nextTick()(组件中使用this.$nextTick())才能确保,这个还有一个回调函数可以执行自己的代码。详细看下边例子。此钩子执行完毕之后,用户可以看到DOM元素,但数据还没更新上去。
  6. beforeUpdate在数据还没有更新到DOM上之前调用,去计算新数据和准备更新
  7. updated在用数据更新DOM完成之后调用,这个钩子会反复调用,如果有多个地方需要更新数据
  8. beforeDestroy调用,在需要从DOM中去除组件的时候调用
  9. destroyed调用,从DOM中去除组件之后调用

Vue.nextTick()的例子是:

<script>
new Vue({
    el: '#app',
    mounted() {
// Element might not have been added to the DOM yet
        this.$nextTick(() => {
// Element has definitely been added to the DOM now
        });
    }
});
</script>

过渡与动画

Vue提供了很多过渡效果,之前一直没有看过,现在要稍微入入门看看。

CSS过渡效果

Vue提供了一个组件<transition>,这个组件会给其中的所有元素添加一个v-if指令和一个css类,这样就可以通过CSS类给元素添加挂载到DOM和从DOM移除时候的动画效果。 下边是一个初始的例子:
<div id="app">
    <button @click="divVisible = !divVisible">Toggle visibility</button>
    <div v-if="divVisible">This content is sometimes hidden</div>
</div>


<script src="https://unpkg.com/vue"></script>
<script>
    new Vue({
        el: '#app',
        data: {
            divVisible: true
        }
    });
</script>
这个例子很简单,就是点击按钮来切换下边的元素是否显示,由于我们使用了v-if,所以这个元素每次被移入和移出DOM树。 先来通过<transition>添加一个简单的效果:
<transition name="fade">
    <div v-if="divVisible">This content is sometimes hidden</div>
</transition>
单独加上这个也没效果,必须配合样式:
.fade-enter-active, .fade-leave-active {
    transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
    opacity: 0;
}
这个生效的原理是:Vue会使用<transition name="fade">中的name属性的值,加上一些后缀拼合成在不同阶段应用的CSS类名,这些类及对应作用是:
  1. {name}-enter,元素添加进DOM的时候会被加上这个类,在1帧之后就会取消这个类。用来设置元素进入DOM之后初始并且想要逐渐消失的样式,例子里设置了完全透明,在元素进入的时候,一开始就是完全透明的。
  2. {name}-enter-active,这个样式在整个加入过程中都生效,和{name}-enter同时添加到元素上,在动画完成后移除。这个类里最好设置CSS的transition属性,包括动画时间,属性和过渡曲线。
  3. {name}-enter-to{name}-enter被移除的时候添加到元素上,一般用于设置逐渐出现在元素上的样式。
  4. {name}-leave,在触发要移除DOM的第一帧添加上,然后下一帧就会移除
  5. {name}-leave-active,在移除的全过程生效。
  6. {name}-leave-to,很像enter-to,当{name}-leave被移除的时候添加到元素上。
一般来说,主要使用{name}-enter{name}-enter-active{name}-leave-active{name}-leave-to四个类。

JS过渡效果

JS过渡就是<transition>在不同阶段提供的钩子,就可以使用JS库来写动画。 这里的钩子,是指特殊的事件,在<transition>元素上监听这些事件然后写处理函数就可以了。 钩子和CSS动画的阶段本质上是一致的,列表如下:
  1. before-enter,在进入动画开始之前执行,用于设置初始样式
  2. enter,进入动画开始的时候执行,可以在其中使用done回调来指示动画结束
  3. afterEnter,进入动画结束的时候调用
  4. enterCancelled,进入动画被取消的时候调用
  5. beforeLeave,移出动画开始之前调用,用于设置移出的初始样式
  6. leave,移出动画开始的时候执行
  7. afterLeave,和上边的阶段类似
  8. leaveCancelled和上边的阶段类似
使用这些钩子的例子:
<div id="app">
    <button @click="divVisible = !divVisible">Toggle visibility</button>
    <transition
            v-on:before-enter="handleBeforeEnter"
            v-on:enter="handleEnter"
            v-on:leave="handleLeave">
    <div v-if="divVisible">The content</div>
</transition>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<script src="https://unpkg.com/vue"></script>
<script>
    new Vue({
        el: '#app',
        data: {
            divVisible: false
        },
        methods: {
            handleBeforeEnter(el) {
                el.style.opacity = 0;
            },
            handleEnter(el, done) {
                TweenLite.to(el, 1, { opacity: 1, onComplete: done });
            },
            handleLeave(el, done) {
                TweenLite.to(el, 1, { opacity: 0, onComplete: done });
            }
        }
    });
</script>
果然还是老外写的书好,昨天看了一会,就发现还有如此多的细节没有掌握。 现在对于一个Vue实例的作用也比较清楚了。data用来定义原始的数据,computed用于基于原始数据生成可以反复使用的结果,methods就是这个实例的方法,可以包括各种方法,无论是普通功能还是事件调用。当然不同的方法有不同的参数,尤其是处理事件的方法。watch则用于对数据的异步处理。 前边几个属性都和Vue实例有着紧密的联系,而filter是比较独立的功能函数,无法通过this访问实例。 这样基本上一个实例的内部就理清楚了。把实例进化到组件的话,还要再添加一个props作为父组件传入的基础数据,和data一样共同构成组件的基础数据。
LICENSED UNDER CC BY-NC-SA 4.0
Comment