layout: post
title: Vue(二)生命周期、侦听器、动态组件、Prop参数承接
description: Vue(二)生命周期、侦听器、动态组件、Prop参数承接
tag: 前端
文章目录
v-on绑定事件调用方法
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">反转消息</button>//绑定方法reverseMessage
</div>
reverseMessage方法在Vue实例中定义:
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')//方法实体,先单个分开成单字符(split)再翻转(reverse),再合并(join)
}
}
})
组件化应用构建
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:
// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
template: '<li>这是个待办项</li>'//模板是列表形式
})
var app = new Vue({
el: '#app'
})
用它构建另一个组件模板,让他成为无序列表中的一项
<ol>
<!-- 创建一个 todo-item 组件的实例 -->
<todo-item id= "app"></todo-item>
</ol>
但是这样会为每个待办项渲染同样的文本,这看起来并不炫酷。我们应该能从父作用域将数据传到子组件才对。让我们来修改一下组件的定义,使之能够接受一个 prop:
Vue.component('todo-item', {
// todo-item 组件现在接受一个
// "prop",类似于一个自定义 attribute。
// 这个 prop 名为 todo。
props: ['todo'],
template: '<li>{{ todo.text }}</li>'//这里的todo.text表明了todo是一个对象,具体见下边的Vue实例
})
使用 v-bind 指令将待办项传到循环输出的每个组件中:
<div id="app-7">
<ol>
<!--
现在我们为每个 todo-item 提供 todo 对象
todo 对象是变量,即其内容可以是动态的。
我们也需要为每个组件提供一个“key”,稍后再
作详细解释。
-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item> <!--遍历groceryList,绑定列表的每一项(item)和列表的索引(id)-->
</ol>
</div>
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
})
前端显示数据非响应式
vue的数据property都是响应式的,即修改后前端显示会同步相应修改,
使用 Object.freeze(),这会阻止修改现有的 property,也意味着响应系统无法再追踪变化。
实例生命周期
下图展示了实例的生命周期:
生命周期钩子的 this 上下文指向调用它的 Vue 实例,这里先介绍几个常用的钩子:
- created :组件被创建后
- mounted:组件被渲染后
- updated :组件更新后
- destroye:组件销毁后
计算属性和侦听器
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。所以,对于任何复杂逻辑,你都应当使用计算属性。
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
计算属性缓存 vs 方法:
我们可以通过在表达式中调用方法来达到同样的效果
不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
这一点可以用时间戳来说明,加入计算属性中使用事件戳,第一次用和第二次用,时间戳是相同的,因为计算结果已经在缓存中,仅当缓存更新时,计算属性值才会更新。而假如表达式中两次使用时间戳,则结果是不同的。
计算属性 vs 侦听属性:
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
侦听器(*)
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/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.debouncedGetAnswer()
}
},
created: function () {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: 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
})
}
}
})
</script>
事件处理方法
除了直接绑定到一个方法,还可以在内联 JavaScript 语句中调用方法:
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
<!--注意这里click直接是调用的方法-->
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) {
event.preventDefault()
}
alert(message)
}
}
事件修饰符
在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
<!-- 阻止单击事件继续传播 -->
<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>
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
通过 Prop 向子组件传递数据
当组件变得越来越复杂的时候,.比如一篇博客,我们的博文不只需要标题和内容,还需要发布日期、评论等等。为每个相关的信息定义一个 prop 会变得很麻烦:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>
所以是时候重构一下这个 组件了,让它变成接受一个单独的 post prop:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})
现在,不论何时为 post 对象添加一个新的 property,它都会自动地在 内可用。
值得注意的一点是v-html指令,它表示以html语句解析post.content的内容,相应的有v-text指令,同一个字符串“<h1>这是一个h1元素内容</h1>”
- 使用v-html会解析出一号标题,显示内容
“这是一个h1元素内容”
- 使用v-text则直接会显示字符串本身
“<h1>这是一个h1元素内容</h1>”
监听子组件事件
在我们开发 组件时,它的一些功能可能要求我们和父级组件进行沟通。例如我们可能会引入一个辅助功能来放大博文的字号,同时让页面的其它部分保持默认的字号。
在其父组件中,我们可以通过添加一个 postFontSize 数据 property 来支持这个功能:
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>
</div>
</div>
<!--父级元素blog-posts-events-demo,使用了自定义组件blog-post,组件blog-post内定义有子组件button,有了这个 v-on:enlarge-text="postFontSize += 0.1" 监听器,父级组件就会接收该事件并更新 postFontSize 的值。子组件中通过调用内建的 $emit 方法并传入事件名称来触发-->
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">//子组件可以通过调用内建的 $emit 方法并传入事件名称来触发一个事件
Enlarge text
</button>
<div v-html="post.content"></div>
</div>
`
})
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
],
postFontSize: 1//字体大小赋初值
}
})
总结:自定义父组件中使用v-on绑定事件v-on:enlarge-text=“postFontSize += 0.1”,事件定义中监听数据postFontSize,子组件button的click触发$emit("enlarge-text)
使用事件抛出一个值
有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 组件决定它的文本要放大多少。这时可以使用 $emit 的第二个参数来提供这个值:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
然后当在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值:
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
总结:自定义父组件中使用v-on绑定事件v-on:enlarge-text=“postFontSize += $ event”,事件定义中监听数据postFontSize,子组件button的click触发$ emit("enlarge-text,0.1),指定事件,以及参数,父组件中$ event接收参数。
或者,如果这个事件处理函数是一个方法:
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
那么这个值将会作为第一个参数传入这个方法:
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
在组件上使用v-model(*)
自定义事件也可以用于创建支持 v-model 的自定义输入组件。
<input v-model="searchText">
等价于:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 元素加一个特殊的 is attribute 来实现:
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent 可以包括:
已注册组件的名字,或 一个组件的选项对象
动态组件深层应用以后再说,下边是实现tab切换页面的源码,有注释!
<!DOCTYPE html>
<html>
<head>
<title>Dynamic Components Example</title>
<script src="https://unpkg.com/vue"></script>
<style>
.tab-button {
padding: 6px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border: 1px solid #ccc;
cursor: pointer;
/*cursor:pointer 当鼠标悬空在该元素时会显示一个点击的小手*/
background: #f0f0f0;
margin-bottom: -1px;
margin-right: -1px;
}
.tab-button:hover {
background: #e0e0e0;
}
.tab-button.active {
background: red;
/* 当选中时,按钮的背景色*/
}
.tab {
border: 1px solid #268fd5;
/*边框的颜色*/
padding: 10px;
}
</style>
</head>
<body>
<div id="dynamic-component-demo" class="demo">
<button
v-for="tab in tabs"
v-bind:key="tab"
v-bind:class="['tab-button', { active: currentTab === tab }]"
v-on:click="currentTab = tab"
>
<!--对tabs遍历,仅当遍历到currentTab时,active为1,active是激活属性,
激活后的渲染为.tab-button.active,在原来基础上.activev-on绑定currentTab为当前点击的元素的tab-->
{{ tab }}
<!--这里取出元素的tab,显示在按钮上-->
</button>
<!--至此完成上方的按钮切换-->
<component v-bind:is="currentTabComponent" class="tab"></component>
<!--用is动态绑定currentTabComponent方法-->
</div>
<script>
Vue.component("tab-home", {
template: "<div>Home component</div>"
});
Vue.component("tab-posts", {
template: "<div>Posts component</div>"
});
Vue.component("tab-archive", {
template: "<div>Archive component</div>"
});
Vue.component("tab-add", {
template: "<div>add</div>"
});
//定义选项子组件
new Vue({
el: "#dynamic-component-demo",
data: {
currentTab: "Home",
tabs: ["Home", "Posts", "Archive","add"]
},
computed: {
currentTabComponent: function() {
return "tab-" + this.currentTab.toLowerCase();
}
}
});
// 定义父组件,值得注意的是计算属性中,定义了currentTabComponent方法,用于切换动态组件,实际是
// 返回了要切换的组件名,tab-+currentTab,.toLowerCase()是转化为小写的意思
</script>
</body>
</html>
组件注册
W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。
例如:
Vue.component('my-component-name', { /* ... */ })
全局注册与局部注册
用 Vue.component 来创建组件都是全局注册的,也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
局部注册的方法:
通过一个普通的 JavaScript 对象来定义组件:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
然后在 components 选项中定义你想要使用的组件:
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
对于 components 对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。
局部注册的组件在其子组件中不可用。
在模块系统中局部注册:
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
这样 ComponentA 和 ComponentC 都可以在 ComponentB 的模板中使用。
总结:1、import组件 2、export的components中声明组件
prop
prop的大小写(*)
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
prop的类型
通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
这不仅为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。
传递静态或动态 Prop
<blog-post title="My journey with Vue"></blog-post>
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
element-ui+vue快速创建组件
以创建跑马灯图片横幅为例:
新建pmd.Vue文件,在网上找到组件设置的代码粘贴进去:
<template>
<el-carousel :interval="4000" type="card" height="100px">
<el-carousel-item v-for="item in imagebox" :key="item.id">
<img :src="item.idView" class="image">
</el-carousel-item>
</el-carousel>
</template>
<script>
export default{
name: 'pmd',
data () {
return {
imagebox: [{id: 0, idView: require('../../assets/img/band1.png')},
{id: 1, idView: require('../../assets/img/band2.png')},
{id: 2, idView: require('../../assets/img/band3.png')},
{id: 3, idView: require('../../assets/img/band4.png')},
{id: 4, idView: require('../../assets/img/band5.png')},
{id: 5, idView: require('../../assets/img/band6.png')}
// imagebox是assets下一个放图片的文件夹
]
}
}
}
</script>
<style>
.el-carousel__item h3 {
color: #475669;
font-size: 14px;
opacity: 0.75;
line-height: 200px;
margin: 20px 1px 1px 1px;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
margin: 5px 1px 1px 1px;
}
.el-carousel__item:nth-child(2n+1) {
background-color: #d3dce6;
margin: 5px 1px 1px 1px;
}
.image{
/*设置图片宽度和浏览器宽度一致*/
width:100%;
height:inherit;
}
</style>
我想要把这个跑马灯组件用到我的登录界面login.Vue,因此在login. vue的JS中导入这个pmd.vue:
然后就可以在login的视图层html中直接用这个组件:
效果;