开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情
Vue.js与Vue2-Vue3知识点整理
<script type="text/javascript">
Vue.config.productionTip=false//可以阻止vue在启动时生成生产提示
</script>
template
它有三个特性:
1.隐藏性:该标签不会显示在页面的任何地方,即便里面有多少内容,它永远都是隐藏的状态;
2.任意性:该标签可以写在页面的任何地方,甚至是head、body、sciprt标签内;
3.无效性:该标签里的任何HTML内容都是无效的,不会起任何作用;
//template里面的内容就是会被vue处理为虚拟dom并渲染的内容,因此Vue单页面上必须要加上template
//并且template下面只能有一个根节点,因为这个根节点是数据挂载的容器,一旦出现多个根节点,数据就不知道从哪个入口加载了
//一个vue单文件就是一个vue实例组件,vue中的根节点就相当于vue.js中的el所挂载的节点
做一个分隔,如果不加的话,vue识别不出你的内容到底是css样式还是模板
一、MVVM模型
M:Model(模型)层,对应data中的数据
V:View(视图)层,对应页面模板
VM:ViewModel(视图模型)层,Vue实例对象,负责将Model和View联系起来。
它里面中的两个方法负责Model和View的数据更新
二、Vue实例化对象的两种写法(已复习)
1.匿名对象写法
new Vue({})
2.变量接收对象
const vm=new Vue({})
vm.$mount("选择器")//模板挂载的标签
=>相关知识:
1.如果使用箭头函数()=>,他的this对象可能是不是vue,因为他会默认向外查找对象,也就是他要往外寻找最近的对象.
例如:
data:()=>{//这是匿名函数,由于是匿名,所以它的类有可能不是vue,而是window对象,也就是他要往外找最近的对象,因此在data中最好不要写匿名函数,有可能Vue会访问不到data中的数据
}
模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
文本
{{}}文本插值
<span v-once(只会渲染一次)>Message: {{ msg }}</span>
原始HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
属性节点
Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind 指令:
<div v-bind:id="dynamicId"></div>
动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<!--
注意,参数表达式的写法存在一些约束,如之后的“对动态参数表达式的约束”章节所述。
-->
<a v-bind:[attributeName]="url"> ... </a>
这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data property attributeName,其值为 "href",那么这个绑定将等价于 v-bind:href。
<a v-on:[eventName]="doSomething"> ... </a>
//绑定一个事件
在这个示例中,当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus。
对动态参数表达式的约束
动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>
修饰符
修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():
<form v-on:submit.prevent="onSubmit">...</form>
三、数据代理(ES6语法)
当数据复杂时,频繁的手动修改数据更新操作,显得非常的复杂困难,不易维护,就出现了数据代理,由指定的对象去帮你实现数据改变更新的操作(好比中介)
ES6语法中的数据代理
let person={
name:"张三",
gender:"男"
}
Object.defineProperty(对象,'属性',{
value:18,
enumerable:true,//控制属性的枚举,默认是false
writable:true,//控制属性是否可以被修改
configurable:true//控制属性是否可以被删除,默认值是false
get:function(){return ;}//控制属性是否可以被访问
set:function(value){ person.name=value;}//控制属性是否可以被修改
})
相关知识:
我们之所以能够在页面上直接使用Vue对象的data属性中的属性名作为插值语法,是因为Vue帮助我们做了数据代理,他将data中的数据进行了getter,setter等一系列操作,让用户操作变得简单,好实现视图到模型的双向绑定,实时更新数据
四、事件
注:事件都有一个event事件参数
-
键盘事件
1.1 keydown:键盘按下
1.2 keyup:键盘按下并松开
1.3 keyup.enter:键盘按下,并且按回车触发事件
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` --> <input v-on:keyup.enter="submit">你可以直接将
KeyboardEvent.key暴露的任意有效按键名转换为 kebab-case 来作为修饰符。<input v-on:keyup.page-down="onPageDown">可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
-
.ctrl -
.alt -
.shift -
.meta(windows是window键)<!-- Alt + C --> <input v-on:keyup.alt.67="clear"> <!-- Ctrl + Click --> <div v-on:click.ctrl="doSomething">Do something</div>.exact修饰符允许你控制由精确的系统修饰符组合触发的事件。<!-- 即使 Alt 或 Shift 被一同按下时也会触发 --> <button v-on:click.ctrl="onClick">A</button> <!-- 有且只有 Ctrl 被按下的时候才触发 --> <button v-on:click.ctrl.exact="onCtrlClick">A</button> <!-- 没有任何系统修饰符被按下的时候才触发 --> <button v-on:click.exact="onClick">A</button>
-
-
v-text:当前复制的内容会覆盖之前的文本内容
-
v-html:可以转换data中的html标签
-
v-on:click():点击事件
<div id="example-1"> <button v-on:click="counter += 1">Add 1</button> <p>The button above has been clicked {{ counter }} times.</p> </div>var example1 = new Vue({ el: '#example-1', data: { counter: 0 } })访问原始的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) } } -
v-show:显示或隐藏dom元素
-
v-model:双向绑定
-
v-once:页面首次渲染之后就不在更新
-
v-pre:原型输出,Vue不解析这段v-pre指定的模板
-
v-cloak:可以不让未经解析的模板加载到页面上,而是等Vue解析完之后再显示
-
v-if_else:判断语句
-
scroll:滚动条事件
-
wheel:滚轮事件
-
v-for:循环
//遍历数组: <ul id="example-2"> <li v-for="(item, index) in items">//item:值,index:下标 {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul> //遍历对象 <ul id="v-for-object" class="demo"> <li v-for="(value,name,index) in object">//value:值,name:键名,index:下标 {{ value }} </li> </ul> -
2.1.0版本新增:v-else-if
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div>v-for不但可以循环数组,还可以循环对象 <p v-for="(a,b) in Person">{{b}}-{{a}}</p> 结果为: age-19 name-小白 new Vue({ data(){ return{ Person:{ age:19, name:'白百', } } } })
五、事件修饰符
1.prevent:阻止默认事件(比如说a标签的自动跳转事件)
2.stop:阻止事件冒泡(当第一个事件执行之后,后面的事件不执行)
3.once:事件只触发一次
4.captrue:事件捕获(捕获当前事件信息)
5.self:只有当前元素才能触发对应的事件
6.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>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
capture
对Dom的操作作为事件会从最外面的祖先Dom逐步传递到目标Dom(捕获过程),然后再从目标的Dom原路传出去(冒泡过程)。通常我们只监听冒泡过程。在vue中,当我们添加了事件修饰符capture后,才会变成捕获监听器。
这时我们有8个监听器了,在捕获过程和冒泡过程都有监听。输出如下图。
六、计算属性
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。
所以,对于任何复杂逻辑,你都应当使用计算属性。
将复杂繁琐的计算方法放在computed中,由computed来更新模板
例子:
<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 计算属性会立即返回之前的计算结果,而不必再次执行函数。
<p>Reversed message: "{{ reversedMessage() }}"</p>
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
- 计算属性默认只加载一次,之后再使用都是使用的缓存中的数据,只有当以来的属性更新时才会触发计算属性的加载
<p>{{方法名}}</p>
//复杂方式
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
//简写方式(当不需要其他的相关属性时,就可以使用简写形式)
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
计算属性vs监听属性
<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
}
}
})
七、监听属性
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
例如:
<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>
//方式一
watch:{
data属性:{
immdiate:true//初始化时加载handler
deep:true//深度检测,检测多个属性的变化并且输出变化的信息
handler(newValue,oldValue){//监听属性的变化,并且记录改变前后的值
}
}
}
//方式二
vm.$watch('属性',{
immdiate:true//初始化时加载handler
deep:true//深度检测,检测多个属性的变化并且输出变化的信息
handler(newValue,oldValue){//监听属性的变化,并且记录改变前后的值
}
})
//简写
watch:{
方法(newValue,oldValue){}
}
computed和watch不同点
例如
computed:{
fullname(){
setTimeOut(()=>{
return ..;//computed不能返回异步数据,而watch可以
},1000)
}
}
计算属性computed:
支持缓存,只有依赖数据发生改变,才会重新进行计算
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
如果computed属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
侦听属性watch:
不支持缓存,数据变,直接会触发相应的操作;
watch支持异步;
监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
当一个属性发生变化时,需要执行对应的操作;一对多;
监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作
八、动态绑定class属性
对象语法:
我们可以传给 v-bind:class 一个对象,以动态地切换 class
<div v-bind:class="{ active: isActive }"></div>
data: {
isActive: true,
hasError: false
}
你可以在对象中传入更多字段来动态切换多个 class。此外,v-bind:class 指令也可以与普通的 class attribute 共存。当有如下模板:
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
//结果为
<div class="static active"></div>
绑定的数据对象不必内联定义在模板中
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
我们也可以在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式:
<div v-bind:class="classObject"></div>
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
数组语法:
<div v-bind:class="[activeClass, errorClass]"></div>
//结果为
<div class="active text-danger"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
三元表达式
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
用在组件上:
当在一个自定义组件上使用 class property 时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。
例如,如果你声明了这个组件:
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
然后在使用它的时候添加一些 class:
<my-component class="baz boo"></my-component>
HTML 将被渲染为:
<p class="foo bar baz boo">Hi</p>
对于带数据绑定 class 也同样适用:
<my-component v-bind:class="{ active: isActive }"></my-component>
当 isActive 为 truthy[1] 时,HTML 将被渲染成为:
<p class="foo bar active">Hi</p>
九、动态绑定内联样式(已复习)
对象语法:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
数组语法:
<div v-bind:style="[baseStyles, overridingStyles]"></div>
多重值:
从 2.3.0 起你可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
<!---这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex。-->
<p :style="styleObj"></p>
new Vue({
data(){
return{
styleObj:{
fontSize:'40px'
}
}
}
})
用key管理可复用的元素和作为key属性(已复习)
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input> 不会被替换掉——仅仅是替换了它的 placeholder。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key attribute 即可:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:
<ul>
<li v-for="a,index in arr" :key="index"></li>//使用下标也可以作为标识,但是如果dom元素顺序错乱,dom元素的标识可能就会被错乱,数据不能对应上,一般用id来代替最好
</ul>
//key的原理
在vue中,他会根据你data中的数据,自动生成虚拟dom,然后最后加工在页面上,形成一个真实的dom,当我们有新的数据加载进模板,他会通过比较key来判断,当前添加的这条数据,是否在之前的dom元素中出现过,如果没有出现,也就是key中的下标在之前的dom元素中没有匹配,他就把当前这条新数据加载进模板,而如果key中的下标和之前的dom元素匹配,他就去拿之前的dom元素,而不拿新添加的元素再去生成模板,这就是key的一个作用,防止重复加载相同的dom元素。
如果不写key,vue会默认将index下标作为dom元素的key关键字
十、filter过滤
过滤掉不满足条件的,并且生成新的数组
computed:{
filPerons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
}
}
意外收获:
#region
#endregion//让注释部分可以折叠
watch-filter案例
computed-filter案例
练习//列表过滤
- {{p.name}}-{{p.age}}-{{p.gender}}
//列表排序
升序 降序 还原
- {{p.name}}-{{p.age}}-{{p.gender}}
}
}
/* watch:{
keyWord:{
immediate:true,
handler(val){
this.txtColor="txtCol"
this.person1=this.person.filter((p)=>{
return p.name.indexOf(val)!==-1
})
}
}
} */
})
</script>
十一、列表渲染(已复习)
Vue是如何更新对象中的属性的
数据代理/数据劫持
首先在创建实例之前,将data中的数据进行加工,添加getter,setter等属性,然后再创建实例,将data赋给vm中的_data属性,之后如果数据发生变化,就会被setter拦截,由setter去更新模板中的内容,实现双向绑定。
Vue是如何更新数组中的数据的(由于 JavaScript 的限制,Vue 不能检测数组和对象的变化)
数组更新检测
由vm对象调用原生对应的方法对数组进行更新,然后解析模板,加载页面
数组更新元素方法
push()//加载进一个新的元素
pop()//移除最后一个元素
shift()//删除第一个元素
unshift()//将新数据添加到第一个
splice()//对应下标修改数据
sort()//排序
reverse()//反转
替换数组
变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
显示过滤/排序后的结果
有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。
<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个方法:
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
Vue.set添加或更新数据
由于对于对象新添加的属性,Vue不做响应式处理,也就是无法解析,可以利用Vue.set方法去添加
Vue.set(对象,'属性名',value)
vm.$set(对象,'属性名',value)
//只能用于data中的数据,不能用于vm或者vm的根属性
v-if vs v-show
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
避免 v-if 和 v-for 用在一起必要
永远不要把 v-if 和 v-for 同时用在同一个元素上。
一般我们在两种常见的情况下会倾向于这样做:
- 为了过滤一个列表中的项目 (比如
v-for="user in users" v-if="user.isActive")。在这种情形下,请将users替换为一个计算属性 (比如activeUsers),让其返回过滤后的列表。 - 为了避免渲染本应该被隐藏的列表 (比如
v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将v-if移动至容器元素上 (比如ul、ol)。
详解
当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,所以这个模板:
| ```
- {{ user.name }}
将会经过如下运算:
| ``` this.users.map(function (user) { if (user.isActive) { return user.name } })
| -------------------------------------------------------------------------------------------- |
因此哪怕我们只渲染出一小部分用户的元素,也得在每次重渲染的时候遍历整个列表,不论活跃用户是否发生了变化。
通过将其更换为在如下的一个计算属性上遍历:
| ```
computed: { activeUsers: function () { return this.users.filter(function (user) { return user.isActive }) } }
``` |
| ---------------------------------------------------------------------------------------------------------------------------------------- |
| ```
<ul> <li v-for="user in activeUsers" :key="user.id" > {{ user.name }} </li> </ul>
``` |
| ------------------------------------------------------------------------------------------------------------ |
我们将会获得如下好处:
- 过滤后的列表*只*会在 `users` 数组发生相关变化时才被重新运算,过滤更高效。
- 使用 `v-for="user in activeUsers"` 之后,我们在渲染的时候*只*遍历活跃用户,渲染更高效。
- 解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。
为了获得同样的好处,我们也可以把:
| ```
<ul> <li v-for="user in users" v-if="shouldShowUsers" :key="user.id" > {{ user.name }} </li> </ul>
``` |
| --------------------------------------------------------------------------------------------------------------------------------- |
更新为:
| ```
<ul v-if="shouldShowUsers"> <li v-for="user in users" :key="user.id" > {{ user.name }} </li> </ul>
``` |
| ----------------------------------------------------------------------------------------------------------------------------- |
通过将 `v-if` 移动到容器元素,我们不会再对列表中的*每个*用户检查 `shouldShowUsers`。取而代之的是,我们只检查它一次,且不会在 `shouldShowUsers` 为否的时候运算 `v-for`。
#### 反例
- {{ user.name }}
- {{ user.name }}
好例子
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
十二、表单输入绑定(已复习)
v-model 会忽略所有表单元素的 value、checked、selected attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
valueproperty 和input事件; - checkbox 和 radio 使用
checkedproperty 和change事件; - select 字段将
value作为 prop 并将change作为事件。
文本
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
多行文本
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
单选框
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
// 当选中时
vm.toggle === 'yes'
// 当没有选中时
vm.toggle === 'no'
多复选框
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
new Vue({
el: '...',
data: {
checkedNames: []
}
})
单选按钮
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
new Vue({
el: '#example-4',
data: {
picked: ''
}
})
选择框
<div id="example-5">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '...',
data: {
selected: ''
}
})
多选框
<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '#example-6',
data: {
selected: []
}
})
v-for渲染选择框
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})
选择框的选项
<select v-model="selected">
<!-- 内联对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
// 当选中时
typeof vm.selected // => 'object'
vm.selected.number // => 123
修饰符
lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">
number
入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。
trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<input v-model.trim="msg">
十三、自定义指令
全局注册
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
局部注册
在directives中定义指令,使用时加v-即可,它可以接收两个参数:dom元素,指令所绑定的data属性
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
directives:{
big(element,binding){//默认没有inserted方法
element.innerText=binging.value
}
}
<input v-focus>
钩子函数
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind:只调用一次,指令与元素解绑时调用。
bind(指令绑定data属性时),inserted(元素加载进页面时),update(重新解析模板时)
注:所有指令中的回调函数中的this都是指的window对象
钩子函数参数
指令钩子函数会被传入以下参数:
-
el:指令所绑定的元素,可以用来直接操作 DOM。 -
binding:一个对象,包含以下 property:
name:指令名,不包括v-前缀。value:指令的绑定值,例如:v-my-directive="1 + 1"中,绑定值为2。oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用。无论值是否改变都可用。expression:字符串形式的指令表达式。例如v-my-directive="1 + 1"中,表达式为"1 + 1"。arg:传给指令的参数,可选。例如v-my-directive:foo中,参数为"foo"。modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为{ foo: true, bar: true }。
-
vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。 -
oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中可用。
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
十四、生命周期
Vue在特定的时间执行调用一些特定的函数
mounted:模板已经加载进页面之后的函数
created:创建完对象之后,但还未生成虚拟dom
当虚拟dom转换为真实dom元素时,会将el中的数据备份一份,到时候新旧dom元素比较,就需要旧数据来实现某些代码复用
destroy:摧毁对象,对象摧毁后,自定义事件就会失效,但是原生dom元素事件的数据还会保留
十五、组件
组件注册
全局注册
是一个可复用的vue实例,有自己的方法和data属性等
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {//全局注册,可被其他注册的vue实例使用,第一个参数就是组件名
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
局部注册
局部注册的组件在其子组件中不可用
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}
基础组件的自动化全局注册
可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把它们称为 [基础组件]
如果你恰好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js) 中全局导入基础组件的示例代码:
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/.\w+$/, '')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
data必须是一个函数
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
data: function () {
return {
count: 0
}
}
如果不是一个函数,那么可能data中的数据等于一个共享的数据就会受其他实例的影响
data: {
count: 0
}
通过 Prop 向子组件传递数据
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
Vue.component('blog-post', {
props: ['title'],//接收来自组件注册的title属性
template: '<h3>{{ title }}</h3>'
})
单个根元素
每个组件必须只有一个根元素
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
监听子组件事件
<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"//给子组件添加一个事件使子组件所有字体变大
></blog-post>
同时子组件可以通过调用内建的 $emit 方法并传入事件名称来触发一个事件:
<button v-on:click="$emit('enlarge-text')">//利用$emit来触发父组件传递的事件
Enlarge text
</button>
使用事件抛出一个值
<button v-on:click="$emit('enlarge-text', 0.1)">//子组件抛出一个值给父组件监听
Enlarge text
</button>
<blog-post
...
v-on:enlarge-text="postFontSize += $event"//组件接收到内部传递的值并触发事件
></blog-post>
或者是组件调用内部定义的方法
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
在组件上使用v-model
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"//将input事件的回调参数的值传递给value属性
></custom-input>
通过插槽分发内容
<alert-box>
Something bad happened.//这段内容显示在内部slot插槽位置
</alert-box>
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot> //组件中间的内容被slot插槽显示在这
</div>
`
})
动态组件
可以通过绑定is属性来切换各个组件的状态
上述内容可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现:
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
将局部功能代码和资源做成一个单独的文件来使用,比如说头部,尾部,实现复用性和可维护性
分为非单文件组件和单文件组件
非单文件组件
使用extend来创建组件
<name></name>//使用组件
const name=Vue.extend({//如果想让创建的组件dom元素节点名称时固定的,可以使用name属性来改变
data:{},
methods:{}
})
new Vue({
template:’
<div>
<p></p>//如果想要自定义显示模板,可以在template中书写,同时,子组件就放在这里面
</div>
‘
components:{
oop //注册组件
}
})
const oop=Vue.extend({//这里面不需要指定el挂载,因为这些组件最终都是归vm管理,由vm中的el来决定
data:{},
methods:{}
template:’
<div>
<p></p>//如果想要自定义显示模板,可以在template中书写,同时,子组件就放在这里面
<name></name>//子组件
</div>
‘,
components:{
name//嵌套组件,让name作为自己的子组件
}
})
相关知识:
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
即Vue帮我们执行的:new VueComponent(options)。
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
4.关于this指向:
(1).组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对 象】。
(2).new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
重要的内置关系
当Vue实例对象新添加一个属性,他会通过它的显示原型属性添加到原型对象中去,然后通过对象的隐式原型属性去访问,之后,其他所有的实例对象都可以访问到新添加的这个属性
比如新创建的继承于vuecomponent的对象,他就可以访问vue中新添加的属性
因此有这层关系,是因为这样可以让vc能访问到vue原型对象中的数据
单文件组件
创建好vue文件之后,为了让其能被外界使用,应当暴露其组件
export default//默认暴露,引入时只需要使用 import
export //单一暴露,引入时需要加import {}
App.vue(汇总所有单文件组件)
import ... from ...
export default{
components:{
...//组件名
}
}
main.js(创建vue对象用来管理App.vue文件,只有管理所有组建的App.vue才有资格被vm管理)
import App from "App.vue"
export default{
el:"#root",
template:‘<App></App>’
components:{
App
}
}
index.html(Vue对象挂载的页面)
<div id="root">
<App></App>//使用组件,如果想让主页干净,不加App标签,可以在main.js中的template下加
</div>
<script src="vue.js"></script>//引入js文件
<script src="main.js"></script>//引入vue对象
Vue脚手架文件结构
文件
1.package.json(包的注释)
2.package-lock.json(管控包的版本,当后期更新包的时候,可以以很快的速度去更新其中的包版本)
3.babel文件 (es6=>es5的作用)
4.src/main.js(当运行完serve命令之后,第一步就会进入到main.js文件中执行vue对象)
5.src/App.vue(所有组件的集合)
6.src/asset(静态资源文件)
7.src/components(存放组件文件)
8.public/index.html()
<noscript>
<strong>We're sorry but default doesn't work properly without JavaScript enabled. Please enable it to continue.
</strong>
</noscript>//当浏览器不支持js时,输出以下内容
命令
1.serve命令:vue帮助我们配置好服务器运行vue
2.build命令:当我们完成vue脚手架文件的构造之后,为了能让浏览器可以识别的语言,就利用此命令构建
3.使用import Vue from "vue"引入的vue.j是残缺的vue文件,它缺少模板解析器,也就是不能加载你的vue模板组件,为了能被使用,就使用render来帮助初始化模板
render: h => h(App),//帮助我们注册App组件
相当于在index.html的
<div>
</div>
中使用组件
十六、脚手架文件结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
十七、关于不同版本的Vue
-
vue.js与vue.runtime.xxx.js的区别:
- vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
-
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。
十八、vue.config.js配置文件
1.使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
2.使用vue.config.js可以对脚手架进行个性化定制,详情见:cli.vuejs.org/zh
十九、ref
vue中我们不使用原生js去获取dom元素,而是利用脚手架中的ref引用
到时候生成vue对象时,它的属性中会有一个$refs,其中就存放着你用ref作为dom标识的标签元素
在js中,我们都是利用js中的函数去获取dom元素,但是在vue脚手架中,这样写就显的负责且不符合规范,因此可以利用ref去获取指定的dom元素,所有使用了ref属性的元素,都会被存入到vue原型对象中,之后其他对象就可以访问到这个ref中的元素了
访问的方式为vm.$refs.dom元素
二十、props
可以使组件能接收外部来的参数,也就是当有其他人输出的内容中,很多都是和组件中的vm对象中的data属性相似的,只是值不同,就可以利用props来来声明一下所需要的属性,这样外部就无需再重写类似的属性
//app.vue
<template>
<div>
<Student name="李四" sex="女" :age="18"/>//props所写的属性
</div>
</template>
<script>
import Student from './components/Student'//引入组件
export default {
name:'App',
components:{Student}//注册组件
}
</script>
//Student.vue
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{myAge+1}}</h2>
<button @click="updateAge">尝试修改收到的年龄</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
console.log(this)
return {
msg:'我是一个尚硅谷的学生',
myAge:this.age
}
},
methods: {
updateAge(){
this.myAge++
}
},
props:['name','age','sex'] //在这里声明之后,外部引入这个组件之后,就可以使用这三个属性来指定他的值
当因为类型不同无法做相应的运算时,可以利用完整一点的props写法来指定类型
//第一种
props:{
name:String,
age:Number,
sex:String
}
//第二种
props:{
name:{
type:String,
required:true//字段是否必须使用·
}
age:{
type:Number,
default:99//设置默认值
}
}
- 功能:让组件接收外部传过来的数据
- 传递数据:
<Demo name="xxx"/>
传递静态或动态prop
<blog-post title="My journey with Vue"></blog-post>
v-bind动态赋值
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
传入一个数字
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:likes="post.likes"></blog-post>
传入一个布尔值
<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>
传入一个数组
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>
传入一个对象
<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
v-bind:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>
传入对象的所有property
post: {
id: 1,
title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
等价于:
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
-
接收数据:
-
第一种方式(只接收):
props:['name'] -
第二种方式(限制类型):
props:{name:String}props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor } -
第三种方式(限制类型、限制必要性、指定默认值):
props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } } 例如: props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 }, // 带有默认值的对象 propE: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].includes(value) } } }
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
也就是如果你想要修改组件中props属性的值,就最好利用data中的属性字段来代替props属性的直接修改,因为你直接对props属性操作导致的结果就是会更改外部所应用的props属性,造成不好的影响
-
这里有两种常见的试图变更一个 prop 的情形:
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
非prop的Attribute
一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 prop 定义的 attribute。
因为显式定义的 prop 适用于向一个子组件传入信息,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。
例如,想象一下你通过一个 Bootstrap 插件使用了一个第三方的 <bootstrap-date-input> 组件,这个插件需要在其 <input> 上用到一个 data-date-picker attribute。我们可以将这个 attribute 添加到你的组件实例上:
<bootstrap-date-input data-date-picker="activated"></bootstrap-date-input>
然后这个 data-date-picker="activated" attribute 就会自动添加到 <bootstrap-date-input> 的根元素上。
替换/合并已有的Attribute
想象一下 <bootstrap-date-input> 的模板是这样的:
<input type="date" class="form-control">
为了给我们的日期选择器插件定制一个主题,我们可能需要像这样添加一个特别的类名:
<bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
></bootstrap-date-input>
在这种情况下,我们定义了两个不同的 class 的值:
form-control,这是在组件的模板内设置好的date-picker-theme-dark,这是从组件的父级传入的
对于绝大多数 attribute 来说,从外部提供给组件的值会替换掉组件内部设置好的值。所以如果传入 type="text" 就会替换掉 type="date" 并把它破坏!庆幸的是,class 和 style attribute 会稍微智能一些,即两边的值会被合并起来,从而得到最终的值:form-control date-picker-theme-dark。
禁用Attribute继承
如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。例如:
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
这尤其适合配合实例的 $attrs property 使用,该 property 包含了传递给一个组件的 attribute 名和 attribute 值,例如:
{
required: true,
placeholder: 'Enter your username'
}
有了 inheritAttrs: false 和 $attrs,你就可以手动决定这些 attribute 会被赋予哪个元素。在撰写基础组件的时候是常会用到的:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
二十一、mixin混合
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
created: function () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
全局混入
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption//可以定义自己的配置
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
可以把多个组件共用的配置提取成一个混入对象使用
//mixin.js
export const hunhe = {//可以同vm一样,使用vm的属性配置等
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
},
}
export const hunhe2 = {
data() {
return {
x:100,
y:200
}
},
}
//main.js
//使用export就需要使用import {组件名} from .. 引入
import {hunhe,hunhe2} from 'mixin.js'
Vue.mixin(hunhe)
Vue.mixin(hunhe2)//全局注册使用组件
//局部混入
import {hunhe,hunhe2} from 'mixin.js'
mixin:[hunhe,hunhe2]
二十二、插件
增强vue的使用
//plugin.js
export default {
install(Vue,x,y,z){
console.log(x,y,z)
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})
//定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
//main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from './plugins'
//关闭Vue的生产提示
Vue.config.productionTip = false
//应用(使用)插件
Vue.use(plugins,1,2,3)
//创建vm
new Vue({
el:'#app',
render: h => h(App)
})
//使用时直接调用插件中注册的字段属性即可
<template>
<div>
<h2>学校名称:{{name | mySlice}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="test">点我测试一个hello方法</button>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷atguigu',
address:'北京',
}
},
methods: {
test(){
this.hello()//调用插件中给vue原型对象添加的属性名来输出
}
},
}
</script>
二十三、todoList案例
用户可以根据搜索实时更新列表数据
//app.vue
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<UserHeader :recieve="recieve"/>
<UserList :todo="todoList" :isCheck="isCheck" :DelTodo="DelTodo" />
<UserFooter :todoLength="todoList" :CheckTodo="CheckTodo" :clearTodo="clearTodo"/>
</div>
</div>
</div>
</template>
<script>
import UserHeader from './components/UserHeader.vue'
import UserList from './components/UserList.vue'
import UserFooter from './components/UserFooter.vue'
export default {
name: 'app',
components:{
UserHeader,
UserList,
UserFooter
},
data() {
return{
todoList:[
{id:'101',title:'吃饭',done:true},
{id:'102',title:'开车',done:false},
{id:'103',title:'喝酒',done:true}
]
}
},
methods:{
recieve(x){
console.log("你好",x)
this.todoList.unshift(x)
},
isCheck(id){
console.log("收到id啦",id)//子组件不可以修改props中的数据,因为你修改了propsvue也检测不到,子组件内容会更新,但是父组件的内容不会更新
this.todoList.forEach((todo)=>{
if(todo.id===id){
todo.done=!todo.done
}
})
},
DelTodo(id){
this.todoList=this.todoList.filter((todo)=>{
return todo.id!==id
})
},
CheckTodo(value){
this.todoList.forEach((todo)=>{
todo.done=value
})
},
clearTodo(){
this.todoList=this.todoList.filter((todo)=>{
console.log(!todo.done)
return !todo.done
})
}
}
}
</script>
<style>
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
</style>
//Header
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="Add"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default{
data() {
return{
}
},
methods:{
Add(e){
var Arr={
id:nanoid(),
title:e.target.value,
done:false
}
this.recieve(Arr)
}
},
props:['recieve']
}
</script>
<style>
</style>
//List
<template>
<div id="todoItem">
<ul class="todo-main">
<UserItem :todo="todo" :isCheck="isCheck" :DelTodo="DelTodo"></UserItem>
</ul>
</div>
</template>
<script>
import UserItem from './UserItem.vue'
export default{
components:{
UserItem
},
data() {
return{
}
},
props:['todo',"isCheck",'DelTodo']
}
</script>
<style>
</style>
//Item
<template>
<div>
<li v-for="item in todo">
<label>
<input type="checkbox" :checked="item.done" @change="isCheck(item.id)"/>
<span>{{item.title}}</span>
</label>
<button class="btn btn-danger" @click="Delto(item.id)">删除</button>
</li>
</div>
</template>
<script>
export default{
methods:{
Delto(id){
this.DelTodo(id)
}
},
mounted() {
console.log(todo)
},
props:['todo','isCheck','DelTodo']
}
</script>
<style>
li:hover{
background-color: #002451;
}
li:hover button{
display: block;
}
</style>
//Footer
<template>
<div class="todo-footer">
<label>
<input type="checkbox" v-model="checkAll"/>
</label>
<span>
<span>已完成{{doneTo}}</span> / 全部{{isAll}}
</span>
<button class="btn btn-danger" @click="clearTodoList">清除已完成任务</button>
</div>
</template>
<script>
export default{
props:['todoLength','CheckTodo','clearTodo'],
computed: {
isAll(){
return this.todoLength.length
},
doneTo(){
return this.todoLength.reduce((pre,current)=>{
return pre+(current.done?1:0)
},0)
},
checkAll:{
get(){
return this.isAll==this.doneTo&&(this.doneTo!==0)
},
set(value){
this.CheckTodo(value)
}
}
},
methods:{
clearTodoList(){
this.clearTodo()
}
}
}
</script>
<style>
</style>
|知识点总结如下
1.nanoid:用于生成唯一的id标识,与uuid类似
安装方式以及引用方式:
--npm i nanoid
--import {nanoid} from 'nanoid'
2.组件之间可以利用props来传递方法和属性
例如:
//app.vue
<template>
<Student :todo="todoList" :receive="receive"/>//组件通过给子组件绑定父组件中的属性和方法,然后子组件利用props来接受这些属性和方法
</template>
//Student.vue
<template>
<div>
<li v-for="item in todo"></li>
</div>
</template>
<script>
export default{
props:['todo','receive']//子组件利用props接收绑定的属性,然后就可以直接使用
}
</script>
data(){
return{
todoList:[
{id:'101',title:'吃饭',done:true},
{id:'102',title:'开车',done:false},
{id:'103',title:'喝酒',done:true}
]
}
}
methods:{
receive(value){
console.log(value)
}
}
3.子组件不可以直接修改props中的属性,因为props是单向数据流,只能获取不能修改,如果直接修改就有可能造成多个子组件中的数据改变,最好是通过父组件修改,然后将更新的内容传递到子组件
4.foreach用法,数组/对象.foreach((参数名)=>{
参数名.属性值
})
5.过滤器用法,数组/对象.filter((属性名)=>{
return 过滤条件
})
6.获取当前事件对象
add(参数名){
参数名.target....//可以直接通过参数名来找到需要的属性值
}
7.兄弟组件之间不能直接进行传值
8.reduce方法
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。
//此处使用reduce方法做条件统计
/*return this.todos.reduce((pre,current)=>{
console.log('@',pre,current)
return pre + (current.done ? 1 : 0)//根据合适条件生成一个新的数组或对象
},0) */
二十三、本地存储(WebStorage)
localStorage:本地存储
sessionStorage:会话存储
区别:除了生命周期不同之外,方法都一样
//localStorage和sessionStorage方法(永久存储)
1.window.localStorage/sessionStorage.setItem("自定义字段名",数据)//存储数据
2.window.localStorage/sessionStorage.getItem("已定义的字段名")//获取数据
3.window.localStorage/sessionStorage.removeItem("已定义的字段名")//移除单个数据
4.window.localStorage/sessionStorage.clear()//移除全部数据
注:本地存储的空间差不多是5MB
二十四、自定义事件(已复习)
自定义组件的v-model
一个组件上的 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)"
>
`
})
<base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。
将原生事件绑定到组件
你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符:
<base-input v-on:focus.native="onFocus"></base-input>
在有的时候这是很有用的,不过在你尝试监听一个类似 <input> 的非常特定的元素时,这并不是个好主意。比如上述 <base-input> 组件可能做了如下重构,所以根元素实际上是一个 <label> 元素:
<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" 将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input> 的你希望它也可以配合 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>
`
})
现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js"></script>
</head>
<body>
<div class="test">
<base-input label="测试" value="测试" v-on:input.native='onInput'></base-input>
</div>
<script>
Vue.component('base-input',{
inheritAttrs:false,
props:['label','value'],
computed:{
// inputListeners(){
// var vm=this
// return Object.assign({},this.$listeners,{
// input(event){
//将事件的返回结果返回给父级组件监听
// vm.$emit('input',event.target.value)
// }
// })
// }
},
template:`<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input',$event.target.value)"
v-on="inputListeners"
>
</label>`
})
new Vue({
el:'.test',
methods:{
onInput(event){
console.log(event);
}
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js"></script>
</head>
<body>
<div class="test">
<!-- <text-document :title.sync="doc.title" @update:title="updateTitle"></text-document> -->
<text-document :title="doc.title" @update:title="doc.title=$event">
//获取回调触发事件</text-document>
</div>
<script>
Vue.component('text-document',{
props:['title'],
methods:{
jt(){
this.$emit('update:title',"测试")
}
},
template:`<div>
<input :value='title'/>
<button @click='jt()'>点击</button>
</div>`
})
new Vue({
el:'.test',
data() {
return{
doc:{
title:'1111'
}
}
},
methods:{
updateTitle(value){
this.doc.title=value
console.log(value,"asdsad");
}
}
})
</script>
</body>
</html>
在父组件中给子组件绑定自定义事件
1.通过v-on来绑定事件
//父组件
<子组件 v-on:自定义事件名="事件名"/>
<script>
methods:{
事件名(x,y):{}
}
</script>
//子组件
<script>
export default{
methods:{
方法(){
this.$emit('父组件传递的自定义事件名',x,y)//通过emit来获取父组件所绑定的事件名,同时还可以传递多个参数给父组件
}
}
}
</script>
2.通过ref自动绑定事件
//父组件
<子组件 ref="student"/>
<script>
export defualt{
methods:{
方法名(){}
}
mounted:{
this.$ref.student.$on('atguigu',this.方法())//ref中的属性值是存放在vc对象中的,于是可以直接利用子组件中的ref属性来绑定指定的方法。之后子组件就可以通过emit来获取方法,回调要么配置在methods方法中,要么用箭头函数,因为匿名对象默认是往外找对象的
}
}
</script>
如果想要给子组件直接绑定原生点击事件,就需要添加native
事件.native="方法名"
解绑事件
vm/vc.$off('')
如果想让自定义事件只触发一次,可以使用once修饰符或者$once方法
二十五、全局事件总线
组件之间可以任意通信
安装事件总线
//main.js
new Vue({
beforecreate(){
Vue.prototype.$bus=this//给vue原型对象上添加一个$bus属性,然后给这个属性添加vm属性,之后就可以利用$bus属性给想要实现自定义事件的组件添加指定事件了
}
})
使用全局总线
-
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) } -
提供数据:
this.$bus.$emit('xxxx',数据)
-
- 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
二十六、消息订阅和发布
和全局事件总线一样,可以在任意组件之间通信
安装方式:npm i pubsub-js
引入方式:import pubsub from 'pubsub-js'
//订阅方
如果A要接收消息,就在A中订阅消息,回调函数就在A中
methods:{
回调函数(data){
}
}
mounted() {
this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息,发布订阅消息的一方会发布消息到订阅方
}
//发布方
pubsub.subcribe('xxx',消息)
//取消订阅
beforeDestroy(){
pubsub.unsubcribe(this.pid)
}
二十七、动画
<transition name="hello" appear>//transition代表的是vue中的动画,被transition包裹的元素会变成动画
<h1 v-show="isShow">你好啊!</h1>
</transition>
<transition-group name="hello" appear>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>//当涉及多个元素实现同一个动画时,要利用transition-group和key来包裹和区分
</transition-group>
export default{
data(){
return{
isShow:true
}
}
}
<style>
h1{
background-color: orange;
}
.hello-enter-active{//vue中进入的动画效果,hello代表的是Transition的name名称,如果不写就默认是v
animation:atguigu 0.5s linear;
}
.hello-leave-active{//vue中退出的动画效果
animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {//关键帧
from{
transform: translateX(-100%);//向左进入的动画
}
to{
transform: translateX(0px);//向左退出的动画
}
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
</style>
二十八、代理服务器
前端出现跨域问题时,几种解决办法
跨域问题的实质是服务器返回的数据被浏览器拦截
1、后端利用cors跨域
2、前端利用vue脚手架
利用代理服务器解决跨域问题
//vue.config.js
方式一
devserver:{
proxy:"http://localhost:5000"//代理服务器本身就是和本地服务器一样的端口,所以,这里的代理地址应该是你访问的地址
}
//访问,代理服务器代理之后,当前的访问地址就只要写本地服务器地址就好
getStudents(){
axios.get('http://localhost:8080/students').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了',error.message)
}
)
},
方式二
在某些时候,本地文件和服务器中的文件重名,或者是不想请求远程服务器,只请求本地服务器时,需要配置路径重写
devserver:{
proxy:{
'/api':{
target:''//请求的服务器地址
pathRewrite:{'^/api':''}//路径重写,因为一般自己命名的名称是不会由子路径的,为了防止报错,就加上这一句,例如,我们要请求服务器下的students文件,但是students是不存在于api路径下的,那么直接加上api然后去访问的话,就会找不到路径,当我们想要访问本地文件时,就无需加这个路径名就行
}
}
}
方法一
在vue.config.js中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
vue-resourse
封装了有关axios的请求方法
安装
npm i vue-resourse --save
使用
import vueResourse from 'vue-resourse'
this.$http.get()
二十六、插槽(已复习)
可以在子组件位置插入一个html结构,组件等
如果想要在子组件的位置插入图片,不使用插槽的话,组件之间的内容就会被抛弃
编译作用域
当你想在一个插槽中使用数据时,例如:
<navigation-link url="/profile">
Logged in as {{ user.name }}//这里可以访问当前模板下的属性
</navigation-link>
该插槽跟模板的其它地方一样可以访问相同的实例 property (也就是相同的“作用域”),而不能访问 <navigation-link> 的作用域。例如 url 是访问不到的
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}//这里不能访问子组件内部的属性
<!--
这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。
-->
</navigation-link>
默认插槽
父组件
app.vue
<Category title="美食" >//当传递的值为字符串常量时,不需要加:,而如果传递是变量值,就需要加:,例如:title="data属性"
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
</Category>
<Category title="游戏" >
<ul>
<li v-for="(g,index) in games" :key="index">{{g}}</li>
</ul>
</Category>
<Category title="电影">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
子组件
CateGory.vue
<title></title>
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>//代表图片位置在这里
后备(默认)内容
有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。例如在一个 <submit-button> 组件中:
<button type="submit">
<slot></slot>
</button>
我们可能希望这个 <button> 内绝大多数情况下都渲染文本“Submit”。为了将“Submit”作为后备内容,我们可以将它放在 <slot> 标签内:
<button type="submit">
<slot>Submit</slot>//当父组件没有提供任何内容到子组件内部时,插槽就会默认加载后背内容submit
</button>
具名插槽
当涉及多个插槽,多个子组件元素时,为了能区分插槽,需要使用name区分
有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout> 组件:
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
对于这样的情况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
然而,如果你希望更明确一些,仍然可以在一个 <template> 中包裹默认插槽的内容:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:default>//明确声明这里是默认插槽的内容
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
注意 v-slot 只能添加在 <template> 上
作用域插槽
当数据存在于子组件中,而非父组件,并且父组件想要操作子组件中的数据时,就可以使用作用域插槽
绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue.js"></script>
</head>
<body>
<div class="test">
<current-user >
//这里v-slot插槽需要使用template标签
<template v-slot:default='slotProps'>
{{slotProps.user.lastName}}
</template>
</current-user>
</div>
<script>
Vue.component('current-user',{
name:'currentUser',
data() {
return{
user:{
firstName:'李',
lastName:'超'
}
}
},
template:`
<div>
//将user绑定到属性上,让父组件可以访问
<slot :user='user'>{{user.firstName}}</slot>
</div>`
})
new Vue({
el:'.test',
data() {
return{
}
},
methods:{
}
})
</script>
</body>
</html>
废弃的slot-scope写法
<template>
<div class="container">
<Category title="游戏">
<template scope="atguigu">//当作对象接收
<ul>
<li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li>
</ul>
</template>
</Category>
<Category title="游戏">
<template scope="{games}">直接接收属性
<ol>
<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
</ol>
</template>
</Category>
<Category title="游戏">
<template slot-scope="{games}">
<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
</template>
</Category>
</div>
</template>
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot :games="games" msg="hello">我是默认的一些内容</slot>//给插槽绑定属性之后,他会把属性交给使用这个插槽的组件,也就是位于当前插槽的标签
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
}
},
}
</script>
独占默认插槽的缩写语法
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:
//只有当子组件中只有默认插槽时,才能将v-slot属性用在组件上
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
//简便写法
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确:
<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>
只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
解构插槽prop
作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里:
function (slotProps) {
// 插槽内容
}
这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop,如下:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将 user 重命名为 person:
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形:
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>
动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
具名插槽缩写
<base-layout>
<template #header>//原写法v-slot:插槽名
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
然而,和其它指令一样,该缩写只在其有参数的时候才可用。这意味着以下语法是无效的:
<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
如果你希望使用缩写的话,你必须始终以明确插槽名取而代之:
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
二十七、Vuex
1.概念
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
2.何时使用?
多个组件需要共享数据时
3.搭建vuex环境
-
创建文件:
src/store/index.js//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state }) -
在
main.js中创建vm时传入store配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
4.基本使用
-
初始化数据、配置
actions、配置mutations,操作文件store.js<template> <div> <h1>计算数值:{{$store.state.sum}}</h1> <div> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="Add">+</button> <button>-</button> <button>基数加一</button> <button>延时加载</button> </div> </div> </template> <script> export default{ data() { return{ n:1 } }, methods:{ Add(){ this.$store.dispatch('jia',1) } } } </script> //引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 jia(context,value){ // console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value) }, } const mutations = { //执行加 JIA(state,value){ // console.log('mutations中的JIA被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })5.getters
当我们想要state中的数据进行加工或者想要复用时,可以使用getters
const getters={ bigSum(state){ return state.sum*10 } } export default new Vuex.Store({ getters }) 读取数据 $store.getters.bigSum6.四个map方法的使用
当我们想要程序自动生成一个类似于computed计算属性的代码以及简化代码的名称,就可以借助map方法来实现
-
mapState方法: 用于帮助我们映射
state中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), }, -
mapGetters方法: 用于帮助我们映射
getters中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) }, -
mapActions方法: 用于帮助我们生成与
actions对话的方法,即:包含$store.dispatch(xxx)的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) } -
mapMutations方法: 用于帮助我们生成与
mutations对话的方法,即:包含$store.commit(xxx)的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
<template> <div> <h1>计算数值:{{sum}}</h1> <div> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="Add(n)"></button>//mapActions如果需要传递参数需要在方法中传递参数,否则传递的就是事件参数 <button>-</button> <button>基数加一</button> <button @click="TimeAdd(n)">延时加载</button> </div> </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' export default{ data() { return{ n:1 } }, methods:{ // sum(){//利用getters对其进行复杂的操作 // return this.$store.getters.multiply // }, // Add(){ // this.$store.dispatch('jia',this.n) // }, // TimeAdd(){ // this.$store.dispatch('TimeOutChange',this.n) // } ...mapActions({Add:'jia',TimeAdd:'TimeOutChange'}) }, computed:{ // ...mapState({KeyValue:'sum'})//映射state中的属性值 ...mapGetters({sum:'multiply'})//使用简写 } } </script> <style> </style>import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const actions={//用于处理后台请求或者异步操作 jia(context,value){ console.log(context) context.commit('JIA',value) }, TimeOutChange(context,value){ setTimeout(()=>{ context.commit('JIA',value) console.log("试试",value) },3000) } } const mutations={//执行修改操作 JIA(state,value){ state.sum+=value } } const state={//存储数据的状态 sum:0 } const getters={ multiply(state){ return state.sum*10 } } export default new Vuex.Store({ actions, mutations, state, getters })多组件共享数据
<template> <div> <h1>总人数{{people.length}}</h1> <input type="text" v-model="Content"/><button @click="addPerson">添加</button> <ul> <li v-for="item in people"> {{item.name}} </li> </ul> </div> </template> <script> import {nanoid} from 'nanoid' import {mapState} from 'vuex' export default{ data() { return{ Content:null } }, methods:{ addPerson(){ const person={ id:nanoid(), name:this.Content } this.$store.commit('Add',person)//将一个对象添加到state中 } }, computed:{ ...mapState({people:'personList'}) } } </script> <style> </style>import Vue from 'vue' import Vuex from 'vuex' import {nanoid} from 'nanoid' Vue.use(Vuex) const actions={//用于处理后台请求或者异步操作 jia(context,value){ console.log(context) context.commit('JIA',value) }, TimeOutChange(context,value){ setTimeout(()=>{ context.commit('JIA',value) console.log("试试",value) },3000) } } const mutations={//执行修改操作 JIA(state,value){ state.sum+=value }, Add(state,value){ state.personList.unshift(value) } } const state={//存储数据的状态 sum:0, personList:[{//所有组件可以共享这个数组,并且可以对其添加修改 id:nanoid(), name:"笑话" }] } const getters={ multiply(state){ return state.sum*10 } } export default new Vuex.Store({ actions, mutations, state, getters })namespaced
<template> <div> <h1>总人数{{personList.length}}</h1> <input type="text" v-model="Content"/><button @click="addPerson">添加</button> <ul> <li v-for="item in personList"> {{item.name}} </li> </ul> </div> </template> <script> import {nanoid} from 'nanoid' import {mapState} from 'vuex' export default{ data() { return{ Content:null } }, methods:{ addPerson(){ const person={ id:nanoid(), name:this.Content } this.$store.commit('personAbout/Add',person)//如果是以这样的方式访问数据,那么访问大的方法名前面就要加上模块化的名称然后再加上方法即可 firstPersonName(){ return this.$store.getters['personAbout/firstPersonName'] } } }, computed:{ ...mapState('personAbout',['personList'])//如果是以map方法访问数据,那么左边就是模块化名称,右边就是方法名 ...mapActions('countAbout',{Add:'jia',TimeAdd:'TimeOutChange'}) } } </script> <style> </style>import Vue from 'vue' import Vuex from 'vuex' import {nanoid} from 'nanoid' Vue.use(Vuex) const construction={//当有很多的mutations方法时,显得冗余,所以采用这种模块化对象方式能很好地区分每个结构方法 namespaced:true, actions:{ jia(context,value){ console.log(context) context.commit('JIA',value) }, TimeOutChange(context,value){ setTimeout(()=>{ context.commit('JIA',value) console.log("试试",value) },3000) } }, mutations:{ JIA(state,value){ state.sum+=value }, }, state:{ sum:0, }, getters:{ multiply(state){ return state.sum*10 } } } const constructionAbout={ namespaced:true, actions:{}, mutations:{ Add(state,value){ state.personList.unshift(value) } }, state:{ personList:[{ id:nanoid(), name:"笑话" }] }, getters:{} } /* const actions={//用于处理后台请求或者异步操作 jia(context,value){ console.log(context) context.commit('JIA',value) }, TimeOutChange(context,value){ setTimeout(()=>{ context.commit('JIA',value) console.log("试试",value) },3000) } } */ // const mutations={//执行修改操作 // JIA(state,value){ // state.sum+=value // }, // Add(state,value){ // state.personList.unshift(value) // } // } // const state={//存储数据的状态 // sum:0, // personList:[{ // id:nanoid(), // name:"笑话" // }] // } // const getters={ // multiply(state){ // return state.sum*10 // } // } export default new Vuex.Store({ modules:{ personAbout:constructionAbout, countAbout:construction } }) -