这一部分参考自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>
在浏览器中打开,控制台输出如下图所示:
多次测试后,总结如下:
<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)。一般情况下,在数据变化时要执行异步或开销较大的操作,采用这种方式更为合理。
这一部分方法会对原数组进行改变:
- 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)
常用的事件修饰符如下:
# 事件修饰符
<!– 阻止单击事件继续传播 –>
<a v-on:click.stop=“doThis”>a>
<!– 提交事件不再重载页面 –>
<form v-on:submit.prevent=“onSubmit”>form>
<!– 修饰符可以串联 –>
<a v-on:click.stop.prevent=“doThat”>a>
<!– 只有修饰符 –>
<form v-on:submit.prevent>form>
<!– 添加事件监听器时使用事件捕获模式 –>
<!– 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 –>
<div v-on:click.capture=“doThis”>…div>
<!– 只当在 event.target 是当前元素自身时触发处理函数 –>
<!– 即事件不是从内部元素触发的 –>
<div v-on:click.self=“doThat”>…div>
<!– 点击事件将只会触发一次 –>
<a v-on:click.once=“doThis”>a>
keyCode 是 13 时调用 vm.submit()
–>
<input v-on:keyup.13=“submit”>
<input v-on:keyup.enter=“submit”>
{tab},{delete},{esc},{space},{up},{down},{left},{right}
<input v-model.lazy=“msg”>
<input v-model.number=“age” type=“number”>
<input v-model.trim=“msg”>
注册并使用一个全局组件
<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 给子组件下发数据,子组件通过事件给父组件发送消息。
父组件向子组件下发数据的实现:
<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>
父组件获知子组件触发了某个事件的实现:
1. 通过Vue实例的(emit,” role=”presentation” style=”position: relative;”>emit,emit,on)方法实现子组件发送信息给父组件
2. 点击按钮,触发子组件的点击事件(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,” role=”presentation” style=”position: relative;”>emit,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>
总结:
- Vue项目是通过transition标签包裹需要动画效果的元素,基于该图片的四种状态,设置相应的播放效果。
常见的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.
用法如下:
// 字符串
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解耦后,能让该组件更易于重用和测试。