Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js">script>
中文官方文档:
https://vuejs.bootcss.com/guide/
介绍
Vue.js是一个MVVM框架,即数据双向绑定,当数据发生变化时,视图也就发生变化,当视图发生变化时,数据也会跟着同步发生变化,这也是Vue.js的精髓之处。
注意:我们说的数据双向绑定,一定是对于UI控件来说的,非UI控件不会涉及到数据双向绑定,单向数据绑定是使用 状态管理工具的前提。如果我们使用vuex,那么数据流也是单项的,这时就会和双向绑定有冲突
为什么要实现数据的双向绑定
在Vue.js中,如果使用vuex,实际上数据还是单向的,之所以说是数据双向绑定,这是用的UI控件来说,对于我们处理表单,Vue.js的双向数据绑定用起来就特别舒服,即两者并不互斥,在全局性数据流使用单项,方便跟踪,局部性数据流使用双向,简单易操作
表单使用双向绑定
你可以用v-model指令在表单**、、** 元素上创建双向数据绑定,它会根据控件类型自动选取正确的方法来更新元素,尽管有些神奇,但v-model本质上不过是语糖法。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理
注意:v-model会忽略所有表单元素的value、checked、selected特性的初始值而总是将Vue实例的数据作为数据来源,你应该通过JavaScript在组件的data选项中声明初始值!
代码体验:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<p>普通文本p>
<div id="input">
<input type="text" name="name" v-model="message">
数据:{{message}}
div>
<p>文本域p>
<div id="textarea">
<textarea name="" id="" cols="30" rows="10" v-model="TextArea_data">textarea>
文本域中的数据: {{TextArea_data}}
div>
<p>单选框p>
<div id="radio">
<input type="radio" name="sex" value="男" v-model="sexData">男
<input type="radio" name="sex" value="女" v-model="sexData">女
性别:{{sexData}}
div>
<p>复选框p>
<div id="select">
<select name="" v-model="selectData">
<option value="" disabled>--请选择---option>
<option value="select1">选择一option>
<option value="select2">选择二option>
<option value="select3">选择三option>
select>
你选择了:{{selectData}}
div>
body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js">script>
<script>
var vm1 = new Vue({
el: "#input",
data: {
message: "data"
}
});
var vm2 = new Vue({
el: "#textarea",
data: {
TextArea_data: "ffffdffff"
}
});
var vm3 = new Vue({
el: "#radio",
data: {
sexData: ""
}
});
var vm4 = new Vue({
el: "#select",
data: {
selectData: ""
}
});
script>
html>
注意:如果v-model表达式的初始值未能匹配任何选项,select元素将被渲染为 “未选择中” 状态,在IOS中,这会使用户无法选择第一个选项,因为这样的情况下,IOS不会触发change事件,因此,更推荐像上面这样提供一个值为空的禁用选项。
学习vue我们必须知道它的7个属性,8个 方法,以及7个指令。787原则
<div id="app">
{{msg}}
<div>这是模板的第一种使用方法1div>
div>
<template id="bot">这是模板的第三种使用方法,不常用3template>
<script>
<div id="bot">模板的第四种创建方法4</div>
script>
<script>
var vm1 = new Vue({
data: {
msg: "data属性"
},
methods: {
Personal:function () {
console.log("methods方法存放方法")
}
},
template: '模板的第二种使用方法2',
//template:"#bot",
render: (createElement) => {
return createElement("h1",{"class":"类名","style":"color: red"},"这一个render属性创建的虚拟dom")
},
})
script>
methods和computed其中都可以写算法,有什么区别呢?
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="javascript/vue.min.js">script>
head> <body>
<div id="app">
<p>{{message}}p> //直接在模板中绑定表达式
<p>{{message.split('').reverse().join('')}}p> //运用计算属性
<p>{{reverseMessage}}p> //运用methods方式
<p>{{methodMessage()}}p>
div>
<script>
var vm=new Vue({
el:"#app",
data:{
message:"hello"
},
computed:{
reverseMessage:function(){
return this.message.split('').reverse().join('');
}
},
methods:{
methodMessage:function () {
return this.message.split('').reverse().join('');
}
}
})
script>
body>
html>
我在此将三种方式进行了比较。返回的结果是一样的,但在写法上computed计算属性的方式在用属性时不用加(),而methods方式在使用时要像方法一样去用,必须加().
两种方式在缓存上也大有不同,利用computed计算属性是将 reverseMessage与message绑定,只有当message发生变化时才会触发reverseMessage,而methods方式是每次进入页面都要执行该方法,但是在利用实时信息时,比如显示当前进入页面的时间,必须用methods方式
条件:
v-if v-else
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<p>条件判断的结果是:p>
<div id="if_label">
<span v-if="type===A">文本一span>
<span v-else-if="type===B">文本二span>
<span v-else="type===C">文本三span>
div>
<p>循环得到信息:p>
<div id="for_message">
<li v-for="(item,index) in items">
{{item.message}}-----{{index}}
li>
div>
body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js">script>
<script>
var vm = new Vue({
el: "#if_label",
data: {
type: "C"
}
})
var vm = new Vue({
el: "#for_message",
data: {
items: [
{ message: "信息一" },
{ message: "信息二" },
{ message: "信息三" },
{ message: "信息四" }
]
}
})
script>
html>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="el_bing">
<p>元素绑定p>
<span v-bind:title="message">鼠标悬停几秒查看此处动态绑定的提示信息span>
div>
body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js">script>
<script>
var vm = new Vue({
el: "#el_bing",
data: {
message: "页面加载于" + new Date().toLocaleString()
}
})
script>
html>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app1">
<button v-on:click="click_bing1">请点击按钮button>
div>
<div id="app2">
<p>{{message}}p>
<button v-on:click="click_bing2">反转消息button>
div>
body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js">script>
<script>
var vm = new Vue({
el: "#app1",
data: {
message: "已点击"
},
methods: {
click_bing1: function () {
alert(this.message);
}
}
});
var vm1 = new Vue({
el: "#app2",
data: {
message: "已点击"
},
methods: {
click_bing2: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
script>
html>
Vue组件即为自定义标签,
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:
步骤:
定义组件 Vue.component(‘组件名’, { template: ‘模板’ } )
构建模板 <组件名>组件名>
接收参数 props: [‘todo’]
注:自定义的标签中不能有大写
// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
template: '<li>这是个待办项li>'
})
var app = new Vue(...)
现在你可以用它构建另一个组件模板:
<ol>
<todo-item>todo-item>
ol>
但是这样会为每个待办项渲染同样的文本,这看起来并不炫酷。我们应该能从父作用域将数据传到子组件才对。让我们来修改一下组件的定义,使之能够接受一个 prop:
Vue.component('todo-item', {
// todo-item 组件现在接受一个
// "prop",类似于一个自定义 attribute。
// 这个 prop 名为 todo。
props: ['todo'],
template: '<li>{{ todo.text }}li>'
})
现在,我们可以使用 v-bind
指令将待办项传到循环输出的每个组件中:
<div id="app-7">
<ol>
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
>todo-item>
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: '随便其它什么人吃的东西' }
]
}
})
例2
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<wangping v-for="hobby in hobbies" v-bind:wang="hobby">wangping>
div>
body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js">script>
<script>
//定义组件
Vue.component("wangping", {
props: ['wang'], //props用于接收参数
template: '{{wang}} '
})
var vm = new Vue({
el: '#app',
data: {
hobbies: ["Spring", "SpringMVC", "SpringBoot", "Mybatis", "Maven", "Vue", "Redis", "Mysql", "Oracle"]
}
})
script>
html>
尽管这只是一个刻意设计的例子,但是我们已经设法将应用分割成了两个更小的单元。子单元通过 prop 接口与父单元进行了良好的解耦。我们现在可以进一步改进
组件,提供更为复杂的模板和逻辑,而不会影响到父单元。
在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。在后续教程中我们将详述组件,不过这里有一个 (假想的) 例子,以展示使用了组件的应用模板是什么样的:
你可能已经注意到 Vue 组件非常类似于自定义元素——它是 Web 组件规范的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API 与 is
attribute。但是,还是有几个关键差别:
虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。
如果只是单纯地知道vue里面的简单语法,你是可以实现对应的一些项目和对应的想法,但是如果一旦发生问题,我们就需要借助生命周期去寻找问题,甚至说有一些需求的话,你也通过生命周期的情况来定我们该把这个东西写在哪里。所以理解vue的生命周期还是很有必要的。
vue的生命周期就是每个vue实例被创建之前一直到结束经历的一系列的初始化过程。
下面是官网关于vue生命周期的图解,
可以看出在vue整个生命周期中会有很多钩子函数(图中标红的部分),虽然我们用到的就那么几个,但我们都来了解一下。首先先明白一个概念:Vue的实例。它是Vue框架的入口,可以理解为前端的ViewModel,它包含它包含了页面中的业务逻辑处理、数据模型等,它也有自己的一系列的生命周期的函数钩子,辅助我们进行对整个Vue实例生成、编译、挂着、销毁等过程进行js控制。
1、beforeCreated()
在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。(这里是在index.html里做的测试)
2、created()
实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
3、beforeMount()
在挂载开始之前被调用:相关的 render 函数首次被调用。
4、mounted()(钩子函数)
el 被新创建的 vm. e l 替 换 , 并 挂 载 到 实 例 上 去 之 后 调 用 该 钩 子 。 如 果 r o o t 实 例 挂 载 了 一 个 文 档 内 元 素 , 当 m o u n t e d 被 调 用 时 v m . el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm. el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.el 也在文档内。
5、beforeUpdate()
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
6、updated()
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。
该钩子在服务器端渲染期间不被调用。
7、beforeDestroy()
实例销毁之前调用。在这一步,实例仍然完全可用。
8、destroyed()
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
由于Vue.js是一个视图层框架,并且作者严格遵守SoC(关注度分离原则),所以Vue.js并不包括AJAX的通信功能,为了解决通信问题而推出Axios框架(可以去该官网学习),少用jQuery,因为它操作Dome太频繁了
引入JS文件:
<script src="https://unpkg.com/axios/dist/axios.min.js">script>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="vue">
<div>{{info.datail.name}}div>
div>
body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js">script>
<script src="https://unpkg.com/axios/dist/axios.min.js">script>
<script>
var vm = new Vue({
el: '#vue',
data() { //区分 data方法和data:属性
return {
info: { //摆格式
name: null
}
}
},
mounted() { //钩子函数
axios.get('data.json').then(response => (this.info = response.data))
//axios.get('data.json').then(response => (console.log(response.data))) //axios完成通信
}
});
script>
html>
是一个将计算结果缓存起来的属性(将行为转化成了静态的属性),可以想象成为缓存
计算属性的特点就是为了将不经常计算的结果进行缓存,以节约我们的系统开销
有时候我们可能需要在{{}}
里添加一些需要计算再展示出来数据
例如:在页面中展示学生的成绩总分和平均分:
<div id="app">
<table border="1">
<thead>
<th>学科th>
<th>分数th>
thead>
<tbody>
<tr>
<td>数学td>
<td><input type="text" v-model="Math">td>
tr>
<tr>
<td>英语td>
<td><input type="text" v-model="English">td>
tr>
<tr>
<td>化学td>
<td><input type="text" v-model="chemistry">td>
tr>
<tr>
<td>总分td>
<td>{{Math + English + chemistry}}td>
tr>
<tr>
<td>平均分td>
<td>{{(Math + English + chemistry)/3}}td>
tr>
tbody>
table>
div>
new Vue({
el:'#app',
data:{
Math:88,
English:77,
chemistry:99,
}
})
以上是通过在{{}}
里运算,得出总分和平均分
虽然也能解决问题,但是特别不清晰,特别是当运算比较复杂的时候
那怎么办呢?
了解一点的,应该会想到methods
,
没错,确实methods
也可以!但事实上,vue
给我们提供了一个更好的解决方案叫计算属性
计算属性是vue
实例中的一个配置选项:computed
通常里面都是一个个计算相关的函数,函数里头可以写大量的逻辑,最后返回计算出来的值
即我们可以把这些计算的过程写到一个计算属性中去,然后让它动态的计算。
<div class="app">
<table border="1">
<thead>
<th>学科th>
<th>成绩th>
thead>
<tbody>
<tr>
<td>数学td>
<td><input type="text" v-model.number="Math">td>
tr>
<tr>
<td>英语td>
<td><input type="text" v-model.number="English">td>
tr>
<tr>
<td>化学td>
<td><input type="text" v-model.number="chemistry">td>
tr>
<tr>
<td>总分td>
<td>{{sum}}td>
tr>
<tr>
<td>平均分td>
<td>{{average}}td>
tr>
tbody>
table>
div>
var vm = new Vue({
el:'.app',
data:{
Math:88,
English: 77,
chemistry:99,
},
computed:{
sum:function(){
return this.Math+ this.English+this.chemistry;
},
average:function(){
return Math.round(this.sum/3);
}
}
});
计算属性一般就是用来通过其他的数据算出一个新数据,而且它有一个好处就是,它把新的数据缓存下来了,当其他的依赖数据没有发生改变,它调用的是缓存的数据,这就极大的提高了我们程序的性能。而如果写在methods
里,数据根本没有缓存的概念,所以每次都会重新计算。这也是为什么这里我们没用methods
的原因
以上就是Vue
的计算属性的基本用法!
v-
前缀作为一种视觉提示,用来识别模板中 Vue 特定的 attribute。当你在使用 Vue.js 为现有标签添加动态行为 (dynamic behavior) 时,v-
前缀很有帮助,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue 管理所有模板的单页面应用程序 (SPA - single page application) 时,v-
前缀也变得没那么重要了。因此,Vue 为 v-bind
和 v-on
这两个最常用的指令,提供了特定简写:
<a v-bind:href="url">...a>
<a :href="url">...a>
<a :[key]="url"> ... a>
<a v-on:click="doSomething">...a>
<a @click="doSomething">...a>
<a @[event]="doSomething"> ... a>
它们看起来可能与普通的 HTML 略有不同,但 :
与 @
对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的,但随着你更深入地了解它们的作用,你会庆幸拥有它们。
在vue中,引入的子组件标签中间是不允许写内容的。为了解决这个问题,官方引入了插槽(slot)的概念。
插槽,其实就相当于占位符。它在组件中给你的HTML模板占了一个位置,让你来传入一些东西。插槽又分为匿名插槽、具名插槽以及作用域插槽。
你可能不太明白,为什么我要给子组件中传入HTML,而不直接写在子组件中呢?答案是这样的。你可以想象一个场景,你有五个页面,这五个页面中只有一个区域的内容不一样,你会怎么去写这五个页面呢?复制粘贴是一种办法,但在vue中,插槽(slot)是更好的做法。
初体验
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<todo>
<todo-title slot="todo-title" :title="title">todo-title>
<todo-items slot="todo-items" v-for="item in todoItems" :item="item">todo-items>
todo>
div>
body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
//定义组件在前,new Vue在后,否则报错
//插槽
Vue.component("todo", {
template: '\
\
\
\
\
'
});
//填插槽
Vue.component("todo-title", {
props: ['title'],
template: '{{title}}'
});
Vue.component("todo-items", {
props: ['item'],
template: '{{item}} '
});
var vm = new Vue({
el: "#app",
data: {
title: '书籍列表',
todoItems: ['java', 'javascript', 'vue', 'Spring', 'SpringMVC', 'SpringBoot']
}
});
script>
html>
匿名插槽,我们又可以叫它单个插槽或者默认插槽。与具名插槽相对,它不需要设置name属性。(它隐藏的name属性为default。)
上面已经说过,插槽有一个name属性。与匿名插槽相对,加了name属性的匿名插槽就是具名插槽。
注意 v-slot 只能添加在template标签上 (只有一种例外情况)。
例外情况(被废弃的slot=‘name’)
带slot属性的具名插槽自 2.6.0 起被废弃,vue3.x被完全废弃。只有vue3之前的cli可以使用
具名插槽的小知识点
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header。
作用域插槽
作用域插槽其实就是可以传递数据的插槽。子组件中的一些数据想在父组件中使用,必须通过规定的方法来传递。在官方文档中提出了一条规则,**父级模板里的所有内容都是在父级作用域中编译的。子模板里的所有内容都是在子作用域中编译的。**如果你在父组件直接使用子组件中的值,是会报错的。
匿名插槽的作用域插槽
为了让 子组件中的数据 在父级的插槽内容中可用,我们可以将 数据 作为 元素的一个特性绑定上去:
不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件:
this.$emit('myEvent')
则监听这个名字的 kebab-case 版本是不会有任何效果的:
<my-component v-on:my-event="doSomething">my-component>
不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent将会变成 v-on:myevent——导致 myEvent 不可能被监听到。
因此,推荐你始终使用 kebab-case 的事件名。
2.2.0+ 新增
一个组件上的 v-model 默认会利用名为 value
的 prop 和名为 input
的事件,但是像单选框、复选框等类型的输入控件可能会将 value
attribute 用于不同的目的。model
选项可以用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
现在在这个组件上使用 v-model
的时候:
这里的 lovingVue
的值将会传入这个名为 checked
的 prop。同时当
触发一个 change
事件并附带一个新的值的时候,这个 lovingVue
的 property 将会被更新。
注意你仍然需要在组件的 props
选项里声明 checked
这个 prop。
你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on
的 .native
修饰符:
在有的时候这是很有用的,不过在你尝试监听一个类似 的非常特定的元素时,这并不是个好主意。比如上述
组件可能做了如下重构,所以根元素实际上是一个 元素:
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
label>
这时,父级的 .native
监听器将静默失败。它不会产生任何报错,但是 onFocus
处理函数不会如你预期地被调用。
为了解决这个问题,Vue 提供了一个 $listeners
property,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
有了这个 $listeners
property,你就可以配合 v-on="$listeners"
将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 的你希望它也可以配合
v-model
工作的组件来说,为这些监听器创建一个类似下述 inputListeners
的计算属性通常是非常有用的:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
label>
`
})
现在
组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用
.native
监听器。
2.3.0+ 新增
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。
这也是为什么我们推荐以 update:myPropName
的模式触发事件取而代之。举个例子,在一个包含 title
prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:
this.$emit('update:title', newTitle)
然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:
为了方便起见,我们为这种模式提供一个缩写,即 .sync
修饰符:
注意带有 .sync
修饰符的 v-bind
不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’”
是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 v-model
。
当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync
修饰符和 v-bind
配合使用:
这样会把 doc
对象中的每一个 property (如 title
) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on
监听器。
将 v-bind.sync
用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”
,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。
splice()方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
var arr = ['Nicrosoft " , ‘Apple ', 'vahoo ' , 'AoL ', 'Excite ' , 'oracle' ];
//[从索引2开始翻除3个元素,然后再添加两个元素:
arr.splice(2,3,'6oogle', 'Facebook');//返回剧除的元素[ 'vahoo', 'AOL ', 'Excite']
arr; // [ "Nicrosoft ', "Apple " , 'Google ',, "Facebook ' , "oracle']
//只删除,不添加:
arr.splice(2,2); //[ "Google ' , 'Facebook " ]
arr; //[" wicrosoft ' ," apple " , 'oracle']
//只添加i,不删除:
arr.splice(2,0, '6oogle ','Facebook ' );//返回[],因为没有删除任何元素
arr; // [ 'wicrosoft ', "Apple' , 'Google', 'Facebook ', 'oracle']