这一部分参考自Vue官方文档以及生命周期详解
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue生命周期学习title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
head>
<body>
<div id="app">
<h1>{{message}}h1>
<br/><br/><br/>
div>
body>
<script>
var vm = new Vue({
// 注释el,可理解beforeMounted之前的生命周期
el: '#app',
// 同步不注释h2定义的template,可理解template是如何加载
// template: "{{message +'这是在template中的'}}
",
data: {
message: 'Vue的生命周期'
},
beforeCreate: function() {
console.group('------beforeCreate创建前状态------');
console.log("%c%s", "color:red" , "el : " + this.$el); //undefined
console.log("%c%s", "color:red","data : " + this.$data); //undefined
console.log("%c%s", "color:red","message: " + this.message)
},
created: function() {
console.group('------created创建完毕状态------');
console.log("%c%s", "color:red","el : " + this.$el); //undefined
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeMount: function() {
console.group('------beforeMount挂载前状态------');
console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
mounted: function() {
console.group('------mounted 挂载结束状态------');
console.log("%c%s", "color:red","el : " + this.$el); //已被初始化
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data); //已被初始化
console.log("%c%s", "color:red","message: " + this.message); //已被初始化
},
beforeUpdate: function () {
console.group('beforeUpdate 更新前状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
updated: function () {
console.group('updated 更新完成状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
beforeDestroy: function () {
console.group('beforeDestroy 销毁前状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message);
},
destroyed: function () {
console.group('destroyed 销毁完成状态===============》');
console.log("%c%s", "color:red","el : " + this.$el);
console.log(this.$el);
console.log("%c%s", "color:red","data : " + this.$data);
console.log("%c%s", "color:red","message: " + this.message)
}
})
script>
html>
多次测试后,总结如下:
beforeCreate
,created
,beforeMount
,mounted
,beforeUpdate
,updated
,beforeDestroy
,destroyed
.beforeCreate
,created
,beforeMount
,mounted
;beforeUpdate
,updated
;beforeDestroy
,销毁后能够调用钩子destroyed
.beforeCreate
之前,仅仅初始化了Vue实例(都为undefined
)created
之前,已经进行了数据的初始化、属性和方法的运算以及watch/event
事件回调,但还没有将其挂载(此时页面的数据已经获取到,如message='Vue'
的生命周期,el='undefined'
)beforeMount
之前,先判断Vue实例是否有el
属性
el
属性时,会停止编译,即beforeMount
,mounted
钩子函数不会执行。此时只有手动调用vm.$(el)
将el
挂载在页面后,才能调用这两个钩子函数。el
属性时,如果Vue实例中有template
属性(用来包含组件的标签),会把它当做render
函数处理,该子组件的内容会替换掉页面中定义的内容。mounted
之前,Vue实例可以继续添加el
属性,内容也会随之覆盖(此时,才将用户定义的内容显示在页面,之前只是用{{message}}
进行占位)beforeUpdate
和updated
钩子函数,详情可见beforeDestroy
钩子函数;在Vue 实例销毁后可以调用destroyed
钩子函数,此时该Vue实例指向的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue计算属性和方法title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
head>
<body>
<div id="example">
<p>Original message: "{{ message }}"p>
<p>Computed reversed message: "{{ compute_reversedMessage }}"p>
<p>method Reversed message: "{{ method_reversedMessage() }}"p>
div>
body>
<script>
// 测试计算属性和方法的区别
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
compute_reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
},
// 在组件中
methods: {
method_reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
script>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue属性侦听title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
head>
<body>
<div id="watch-example">
<p>Ask a yes/no question:<input v-model="question">p>
<p>{{ answer }}p>
div>
body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js">script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js">script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
methods: {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。在这个例子中,我们希望限制访问 yesno.wtf/api 的// 频率AJAX 请求直到用户输入完毕才会发出。想要了解更多关于`_.debounce` 函数 (及其近亲 `_.throttle`) 的// 知识,请参考:https://lodash.com/docs#debounce
getAnswer: _.debounce(
function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
},
// 这是我们为判定用户停止输入等待的毫秒数
500
)
}
})
script>
html>
总结:
message
)发生变化时,才会重新求值。方法则是每次调用时均会进行计算求值。这里需要根据实际需求进行选择用计算属性或者方法。watch
侦听question
属性。只要question
发生变化,就会调用方法function(newQuestion,oldQuestion)
。一般情况下,在数据变化时要执行异步或开销较大的操作,采用这种方式更为合理。v-if
与v-show
两者用法相似,但只有v-if
能跟v-else
等配合使用。v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-show
的元素总是会被渲染,只是简单地基于 CSS (display
)进行切换。v-if
与v-else
对应两个不同的标签,两者有相同的元素时,vue默认会复用它们。可以通过指定不同的key
属性,保证每次切换时不复用而是重新渲染。v-for
拥有对父作用域属性的完全访问权限。(item, index) in items
,可选的index
为当前项的索引。(value, key, index) in object
,可选的key
为对象属性,可选的index
为对象属性的索引。v-for
比v-if
优先级高。这一部分方法会对原数组进行改变:
push(item)
,从数组末尾插入一个元素item
,返回值为新数组的长度pop()
,从数组末尾取出一个元素,返回值为取出的元素shift()
,从数组头部取出一个元素,返回值为取出的元素unshift(item)
,从数组头部插入一个元素item
,返回值为新数组的长度splice(index)
,取出数组中下标index
到末尾的所有元素,返回值为取出元素组成的新数组reverse()
,让数组倒序重新排布,返回值为重新排序后的新数组这一部分方法不会对原数组进行改变
slice(index)
,取出数组中下标index
到末尾的所有元素,返回值为取出元素组成的新数组filter(item)
,过滤出符合条件item
的所有元素,返回值为这些元素组成的数组concat(item)
,添加一个元素到数组中,返回值为这些元素组成的数组由于JavaScript限制,vm.items[indexOfItem] = newValue
改变数组中元素的值,vm.items.length = newLength
改变数组的长度这种方式,在页面是不会生效的
# 改变数组中指定元素的值,会引起状态变化
## 方式一(将数组TargetArray的下标indexOfTarget的元素,值替换为newValue)
Vue.set(TargetArray, indexOfTarget, newValue)
## 方式二(将数组TargetArray的下标indexOfTarget后count个元素,值替换为newValue)
TargetArray.splice(indexOfTarget, count, newValue)
# 改变数组的长度(将数组TargetArray的长度设置为newLength)
TargetArray.splice(newLength)
常用的事件修饰符如下:
# 事件修饰符
...
...
# 按键修饰符
{tab},{delete},{esc},{space},{up},{down},{left},{right}
# 其他
注册并使用一个全局组件
<html>
<body>
<div id="example">
<my-component>my-component>
div>
body>
<script>
// 注册全局组件
Vue.component('my-component', {
template: 'A custom component!'
})
// 创建根实例
new Vue({
el: '#example'
})
script>
html>
组件注入后,在DOM解析时可能会有一些异常,以及解决办法如下:
<table>
<my-row>...my-row>
table>
<table>
<tr is="my-row">tr>
table>
组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。***prop
向下传递,事件向上传递。*** 父组件通过 prop
给子组件下发数据,子组件通过事件给父组件发送消息。
父组件向子组件下发数据的实现:
props
的key
值(myMessage)prop
的key
值(:my-message="parentMsg"
)
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue父子组件通讯-prop向下title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
head>
<body>
<div id="example">
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="myMessage">child>
div>
div>
body>
<script>
Vue.component('child', {
// 在 JavaScript 中使用 camelCase
props: ['myMessage'],
template: '{{ myMessage }}'
})
// 创建根实例
new Vue({
el: '#example',
data: {
parentMsg: ''
},
computed: {
// 如果子组件获取到父组件的数据后想更改,需要用计算属性的方法进行更改
// 下面这个计算属性:将父组件的数据全小写展示在子组件中
myMessage: function() {
return this.parentMsg.toLowerCase()
}
}
})
script>
html>
父组件获知子组件触发了某个事件的实现:
$emit
,$on
)方法实现子组件发送信息给父组件incrementCounter
),该事件会调用Vue实例的方法(this.$emit('increment')
);父组件中时刻监听increment(v-on:increment="incrementTotal"
),接收到了信号increment
后会调用父组件的方法(incrementTotal
),父组件更新total
值。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue父子组件通讯-事件向上title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
head>
<body>
<div id="counter-event-example">
<p>{{ total }}p>
<button-counter v-on:increment="incrementTotal">button-counter>
<button-counter v-on:increment="incrementTotal">button-counter>
div>
body>
<script>
Vue.component('button-counter', {
template: '',
data: function () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
script>
html>
非父子组件的通讯,可参考子组件传递信息给父组件。均是基于Vue实例的方法($emit
,$on
)实现
插槽,一种混合父组件内容与子组件模板内容的实现方式。
slot
插口,否则父组件的内容将会被丢弃。最初在slot
标签中的任何内容都被视为备用内容。只有在宿主元素为空,且没有要插入的内容时才显示备用内容。slot
标签的name
属性进行配对分发内容,父组件匹配不到子组件的name
时,内容会分发到默认的slot
(未指定name
属性)中。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue中的CSS过渡动画效果title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js">script>
<div id="list-complete-demo" class="demo">
<button v-on:click="shuffle">Shufflebutton>
<button v-on:click="add">Addbutton>
<button v-on:click="remove">Removebutton>
<transition-group name="list-complete" tag="p">
<span
v-for="item in items"
v-bind:key="item"
class="list-complete-item"
>
{{ item }}
span>
transition-group>
div>
body>
<script>
new Vue({
el: '#list-complete-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
})
script>
<style>
.list-complete-item {
transition: all 1s;
display: inline-block;
margin-right: 10px;
}
.list-complete-enter, .list-complete-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
style>
html>
总结:
nextTick()
用法常见的nextTick()用法如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue中nextTick用法案例title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js">script>
head>
<body>
<div id="app">
<example>example>
div>
body>
<script>
Vue.component('example', {
template: '{{ message }}',
data: function () {
return {
message: '没有更新'
}
},
mounted () {
this.updateMessage();
},
methods: {
updateMessage: function () {
this.message = '更新完成'
console.log('数据更新后直接打印:' + this.$el.textContent) // => '没有更新'
this.$nextTick(function () {
console.log('数据更新后在nextTick中打印:' + this.$el.textContent) // => '更新完成'
})
}
}
})
new Vue({
el:'#app'
})
script>
html>
控制台输出结果:
说明:我们经常会遇到类似上例的场景—在页面的数据(message
)更新之后,需要根据更新后的数据去实现别的功能A(这里是在控制台打印:“更新完成”)。由于JavaScript中方法执行的异步性,执行功能A时所用数据可能不是更新后的数据,这会导致实现的功能并不符合我们的预期效果(就像这里,控制台输出的是:“数据更新后直接打印:没有更新”)。这时,我们可以用Vue的nextTick
方法,保证功能达到预期效果。它能够保证是在数据更新后才调用该方法,使用的是更新后的DOM.
Vue.set(target,key,value)
说明:前面改变数组中指定元素的值并保证页面正确显示时已经用过这个方法。Vue实例时响应式的,在初始化时声明的属性也是响应式的,但后期挂载的属性并不是响应式的。用Vue.set(target,key,value)
方法能确保新建后的属性也是响应式的。
Vue.component(id, [definition])
// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))
// 注册组件,传入一个选项对象 (自动调用 Vue.extend)
Vue.component('my-component', { /* ... */ })
// 获取注册的组件 (始终返回构造器)
var MyComponent = Vue.component('my-component')
一个最基本的应用了路由的例子如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>router-link实现路由导航title>
head>
<body>
<script src="https://unpkg.com/vue/dist/vue.js">script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js">script>
<div id="app">
<h1>Hello App!h1>
<p>
<router-link to="/foo">Go to Foorouter-link>
<router-link to="/bar">Go to Barrouter-link>
p>
<router-view>router-view>
div>
<script>
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义(路由)组件。
const Foo = { template: 'foo' }
const Bar = { template: 'bar' }
// 2. 定义路由
// 每个路由应该映射一个组件
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
const router = new VueRouter({routes: routes})
// 4. 创建和挂载根实例。
const app = new Vue({router}).$mount('#app')
script>
body>
html>
说明:/user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。要想监测变化,需要用watch
或者beforeRouteUpdate
.
push
,replace
,go
)实现路由跳转用法如下:
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
说明:
push
方法的参数有name
(目标路由名称)和path
(目标路由路径)两种方式,如果提供了path
,此时params
(参数)的使用会失效,这里可以用query
来替代;push
和replace
的用法一样,只是如同字面意思,只会要指向效果,没有history
中添加相应的记录。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>命名视图简单用法title>
head>
<body>
<script src="https://unpkg.com/vue/dist/vue.js">script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js">script>
<div id="app">
<h1>Named Viewsh1>
<ul>
<li>
<router-link to="/">/router-link>
li>
<li>
<router-link to="/other">/otherrouter-link>
li>
ul>
<router-view class="view one">router-view>
<router-view class="view two" name="a">router-view>
<router-view class="view three" name="b">router-view>
div>
<script>
const Foo = { template: 'foo' }
const Bar = { template: 'bar' }
const Baz = { template: 'baz' }
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
},
{
path: '/other',
components: {
default: Baz,
a: Bar,
b: Foo
}
}
]
})
new Vue({
router,
el: '#app'
})
script>
body>
html>
说明:当点击router-link(to='/')
,解析如下:跳转至路径(path
)为/
的路由中,该路由的内容为有命名视图(name:component)构成的三个组件(default:Foo,a:Bar,b:Baz),按名称在router-view中展示相应组件的内容(如router-view中name为a的,展示组件Bar的内容)。这里要注意的是,只有一个路由时用component,有多个路由时用components。
// 解耦前
const User = {
template: 'User {{ $route.params.id }}'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
// 用props解耦后
const User = {
props: ['id'],
template: 'User {{ id }}'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
说明:直接在组件中通过$route
获取参数id,会导致该组件与路由紧紧耦合。用props
解耦后,能让该组件更易于重用和测试。