Vue一文学会?
Vue大家都知道就是一个国内非常流行的框架,最近因为过了许久没用Vue对于Vue的许多早已淡忘,所以目前正在复习Vue顺便记录一下笔记,以后忘了也可以进行查证,因为这是根据自己的性格所写的笔记,可能大家会看不懂,如果看不懂请尽量不要试着看,可以去看看其他的博主写的文章!
起步
如果需要知道怎么使用webpack手动搭建vue环境的麻烦传送
那么我们还是使用Vue的官方cli进行构建Vue程序 npm i -g @vue/cli
然后我们 vue create -n suiyue_m
-n选项你随意加不加,是用来表示不初始化git的
然后就是一个选项栏
我之前已经保存过一次选项了所以大家可以看看,我这里在选择一次
image.png
选择最后一项按回车键进入二次选择
image.png
空格键选择
大家先跟着我的选择选吧,一般就是这样,最多就是多一个typescript什么的之类,然后我们直接敲回车
之后这里又有一个选项
image.png
意思是要不要开启历史模式,什么区别呢?
就是路由的时候
默认是启用的(因为Y是大写,n是小写),启用此历史模式就是
example url: http://www.xxx.com/user/xiaoming/info
不启用就是熟悉的
http://www.xxx.com/#/user/xiaoming/info (应该没记错)
(启用此模式需要配置服务器因为,这样发送http请求的话会请求对应服务器对应的文件夹下的index.html所以需要进行配置,具体配置请移步Vue官方文档,不放链接了,因为我忘了那个链接了)
所以我们选择N,主要是懒得配置服务器了
image.png
下面就是配置css预处理了,我喜欢用less大家随意按照自己喜欢的擅长的那种进行选择,下一个选项就是
语法检测eslint然后我默认就选择第一个了,代表eslint用来检测语法没有错误,其他的不了解也不使用
image.png
下一个默认也选择第一个代表在保存时进行语法检查
image.png
这个的意思大意就是Babel,PostCss,ESLint这些配置的配置文件(个人理解,错误勿怪)存放在一个单独的文件还是跟package.json文件放在一起
那么就选择第一项了,放在一个独立的文件中
image.png
最后一个就是是否保存这次的选择,然后因为我保存了所以有三个选项大家随意保存不保存,如果敲y的话会让你输入这个preset的名字,默认是不保存的,然后就开始下载依赖什么的了
最后想说的就是建议大家使用yarn或者pnpm这等之类的npm包管理工具,因为速度快楼主使用的是yarn
大家可以使用
npm i -g yarn
进行安装image.png
然后我们直接运行Vue服务
image.png
默认是不会打开浏览器自动运行的需要大家手动打开浏览器查看页面,好了关于vue-cli的用法就结束了
接下来就是Hello Vue的实现了,我们先更改一些配置文件
在文件夹的根目录新建一个vue.config.js文件,插入一下这些内容将vue服务的端口改为3000,让它自动打开浏览器,至于热更新本身就热更新了
然后我们重新
yarn serve
然后就会看到我们熟悉的一幕了image.png
因为3000端口跑了react的项目所以缓存了图标
然后接下来就是Hello Vue了,直接打开App.vue
将template下面改成这样
image.png
就是一个Hello Vue了
image.png
那么因为我是复习我就想到什么写什么了
进阶
-
计算属性与监听
计算属性,类似于一个Vue属性但是计算属性是实时进行计算出来的,计算属性可以依赖Vue的属性,当Vue的属性改变时,计算属性会同步的改变,类似于
image.png
然后其实下面就是一个计算属性永远不会更新的死节
image.png
因为下面的这个计算属性并没有依赖任何Vue的属性导致此计算属性永远也不会更新,其实此计算属性的效果也可以同样使用methods进行替代,类似于
image.png
然后就是此方法与使用计算属性的不同之处在于,当页面被触发渲染时计算属性所依赖的属性改变时才会重新计算求值,但是函数每一次页面重新渲染时都会进行重新的一次计算求值
监听
使用属性名作为函数名,每一次当对应的属性值发生改变时会进行调用对应的函数(不进行详细的截图了)
class与style的绑定
绑定class
image.png使用v-bind指令绑定class传入一个对象,属性名决定类名,是否指定此类名决定值的真或者是假,同样的我们也可以将class后面的属性提出为一个对象指定变量的方式
image.png
同样的class后面也可以指定为一个函数或者一个计算属性要求是返回对象形式的
class绑定数组语法
image.png
渲染为
image.png
同时也可以在数组中使用三元运算符来指定对应的类名
image.png
渲染为
image.png
最后就是还可以在数组之中使用对象来进行混入
image.png
image.png
(对组件传递class类名会默认绑定到此组件的根元素身上)
style绑定
image.pngimage.png
(在强调一遍,vue中指令后面的引号中的字符串会被当做js代码进行执行)
同样的因为style是一个对象我们可以将这个对象取出直接指定变量的名字进行绑定也可以类class的绑定方法
style数组绑定法类对象方法
指令
v-if
使用v-if后面的表达式为true时才会渲染对应的html元素,否则不会渲染(使用的是上dom树和下dom树)
我这里用了一下v-if
<template>
<div id="app">
<h1 v-if="isShow">Hello World</h1>
<input type="button" value="点我上树下树" @click="shangxiashu">
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
isShow:true
}
},
methods:{
shangxiashu(){
this.isShow = !this.isShow;
}
}
}
</script>
通过isShow控制h1标签的上树与下树,这里使用的v-if是直接从dom树上面删除的,如果需要频繁的操作dom需要使用另外一个指令(暂时忘了)
vue还提供了一个指令与此指令配套,就是v-else,我们稍微修改代码
<template>
<div id="app">
<h1 v-if="isShow">Hello World</h1>
<h2 v-else>h2 ---> Hello World</h2>
<input type="button" value="点我切换元素" @click="shangxiashu">
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
isShow:true
}
},
methods:{
shangxiashu(){
this.isShow = !this.isShow;
}
}
}
</script>
你会发现使用vue能够让你用极少的代码来操作dom
vue2.1新增了一个v-else-if指令
v-else 必须添加到v-if或者v-else-if后面否则无法被识别
我们继续修改代码
<template>
<div id="app">
<h1 v-if="isShow <=6">Hello World</h1>
<h2 v-else-if="isShow >6 && isShow<=10">h2 ---> Hello World</h2>
<h3 v-else>h3 ---> Hello World</h3>
<input type="button" value="点我切换元素" @click="shangxiashu">
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
isShow:1
}
},
methods:{
shangxiashu(){
this.isShow++;
}
}
}
</script>
我们直接将isShow改变为数字通过每一个区间的不同渲染不同的元素
v-show --> v-show指令只是单纯的切换标签的display属性,所以并不是真正的销毁一个dom元素
v-if初始化加载开销低,每次切换开销高 v-show初始化开销高,但是切换开销低
用法一般是: 只需要渲染一两次的dom元素可以使用v-if,需要频繁显示隐藏的一些效果需要v-show
v-for
<template>
<div id="app">
<ul>
<li v-for="(item,index) in items" :key="index">
{{ item }}
</li>
</ul>
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
isShow:1,
items:[
"Foo","Bar"
]
}
},
methods:{
shangxiashu(){
this.isShow++;
}
}
}
</script>
可以使用of替换in,这样更像迭代器
v-for="item of items"
遍历数组有两个参数
(item,index)遍历对象有三个参数 (value,name,index)
数组更新检测
data中数组的某一些方法也会触发vue的视图更新
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
当data中的数组通过上面的方法数据发生改变时同样会出发虚拟dom的更新进而刷新视图
(上述方法统称为变异方法,就是会改变调用这些方法的数组)
由于JavaScript的某些限制,vue无法监控两类数组的变化
- items[0] = "Xiao Ming" 直接修改某一位置的值
- items.length = 99 修改数组的长度
为了解决第一类问题,可以使用下面的两种方法
- Vue.set(items,0,"XiaoMing")
- items.splice(0,1,"XiaoMing")
解决第二类问题的方法
items.splice(newLength)
(我这里直接写的items,大家一定要使用vue的实例打点调用)
由于JavaScript的限制vue无法检测对象属性的添加和删除,例如当Vue对象实例化之后可以为这个实例化对象vm(viewModel)继续添加属性但是这时候添加的属性vue是无法动态监控到的(此属性没有与vm进行绑定)
解决方案:
Vue.set(obj,newProp,newPropValue))
(这代表是Vue这个对象的静态方法,不是实例化vm的方法,实例化vm也有一个此方法为$set()这个方法是全局set方法的别名)
v-on
v-on:事件名.事件修饰符 --> 一个简单的使用
<template>
<div id="app">
<h1>{{counter}}</h1>
<input type="button" value="click me" v-on:click="counter++">
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
}
},
mounted(){
window.items = this.items;
}
}
</script>
image.png
我们除了直接在v-on后面书写语句外我们还可以书写方法(methods中定义的)
<template>
<div id="app">
<h1>{{counter}}</h1>
<input type="button" value="click me" v-on:click="add">
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(){
// this代表vue的实例
this.counter++;
}
},
mounted(){
window.items = this.items;
}
}
</script>
一样可以实现点击按钮+1的操作,然后我们除了可以使用方法外我们还可以给这个方法进行传参,类似于
<template>
<div id="app">
<h1>{{counter}}</h1>
<input type="button" value="click me" v-on:click="add(99999999999)">
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(num){
num = num || 1;
// this代表vue的实例
this.counter += num;
}
},
mounted(){
window.items = this.items;
}
}
</script>
有时候我们可能需要访问原声事件对象的一些属性,我们也可以将$event这个属性传递给方法
<template>
<div id="app">
<h1>{{counter}}</h1>
<input type="button" value="click me" v-on:click="add($event)">
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(e,num){
num = num || 1;
// this代表vue的实例
this.counter += num;
// 当前事件的事件对象
console.log(e);
}
},
mounted(){
window.items = this.items;
}
}
</script>
image.png
事件修饰符
按照vue官方的说法就是事件中就是纯粹的逻辑而不去处理dom事件的细节所以我们可以使用事件修饰符做一些操作事件细节的事情如:阻止默认事件,阻止事件冒泡等等
比如说我这里有一个a标签我这个a标签点击之后就会跳转到baidu去
<template>
<div id="app">
<h1>{{counter}}</h1>
<a href="https://baidu.com" target="_blank">点我+1</a>
<router-view/>
</div>
</template>
那我想干啥呢?我想要这个a标签点击之后counter+1,所以我就这样写
<template>
<div id="app">
<h1>{{counter}}</h1>
<a href="https://baidu.com" target="_blank" v-on="add">点我+1</a>
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(e,num){
num = num || 1;
// this代表vue的实例
this.counter += num;
// 当前事件的事件对象
console.log(e);
}
},
mounted(){
window.items = this.items;
}
}
</script>
那么你就会发现点击了这个a标签之后counter+1后又跳转到baidu去了,这就是所谓的默认事件,顾名思义就是a标签本来就是表示一个链接的点击之后自然是要跳转到对应的地址去,所以我们可以通过
prevent
事件修饰符阻止默认事件,我们可以试试
<template>
<div id="app">
<h1>{{counter}}</h1>
<a href="https://baidu.com" target="_blank" v-on:click.prevent="add">点我+1</a>
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(e,num){
num = num || 1;
// this代表vue的实例
this.counter += num;
// 当前事件的事件对象
console.log(e);
}
},
mounted(){
window.items = this.items;
}
}
</script>
这样之后不管你怎么点击a标签也只会响应click事件不会跳转页面了
常用的修饰符
- stop 阻止冒泡
- prevent 阻止默认事件
- capture 捕获阶段执行事件
- self 大致理解为捕获或者冒泡到这个元素的事件不会执行,只有直接出发此元素的事件才会被执行
- once 代表只执行一次
- passive 这个东西代表立即触发默认事件,同时会使prevent无效
按键修饰符 --> 可以通过按键修饰符绑定keyboard事件时监听某一个键
- enter
- tab
- delete (捕获“删除”和“退格”键)
- esc
- space
- up
- down
- left
- right
这是一个使用按键修饰符的小例子
<template>
<div id="app">
<h1>{{counter}}</h1>
<a href="https://baidu.com" target="_blank" v-on:click.prevent="add">点我+1</a>
<br />
<input type="text" placeholder="回车+10w" v-on:keyup.enter="keyHandle">
<router-view/>
</div>
</template>
<script>
export default {
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(e,num){
num = num || 1;
// this代表vue的实例
this.counter += num;
// 当前事件的事件对象
console.log(e);
},
keyHandle(){
this.counter+=100000;
}
},
mounted(){
window.items = this.items;
}
}
</script>
就轻松实现了按下特定的键做某些特定的事情了
同时我们也可以自定义按键修饰符
我们可以使用
Vue.config.keyCodes.w= 87
定义一个w的按键修饰符键盘码为87
<template>
<div id="app">
<h1>{{counter}}</h1>
<a href="https://baidu.com" target="_blank" v-on:click.prevent="add">点我+1</a>
<br />
<input type="text" placeholder="回车+10w" v-on:keyup.w="keyHandle">
<router-view/>
</div>
</template>
<script>
export default {
mounted(){
Vue.config.keyCodes.w= 87
},
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(e,num){
num = num || 1;
// this代表vue的实例
this.counter += num;
// 当前事件的事件对象
console.log(e);
},
keyHandle(){
console.log("按下了w")
}
},
mounted(){
window.items = this.items;
}
}
</script>
image.png
效果显而易见,同时vue2.1新增了系统修饰键(个人觉得就是几个特殊的键)
- .ctrl
- .alt
- .shift
- .meta 键盘上的有win图标的键,mac就是花键
所以我们可以这样玩
<template>
<div id="app">
<h1>{{counter}}</h1>
<a href="https://baidu.com" target="_blank" v-on:click.prevent.ctrl="add">按住ctrl点击+1</a>
<router-view/>
</div>
</template>
<script>
export default {
mounted(){
Vue.config.keyCodes.w= 87
},
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(e,num){
num = num || 1;
// this代表vue的实例
this.counter += num;
}
},
mounted(){
window.items = this.items;
}
}
</script>
这样的话就是不按住ctrl键不会响应点击事件的,还是一个非常亲名的功能
我这样就可以制作一个按住alt+c清楚输入的内容的一个输入框
<template>
<div id="app">
<h1>{{counter}}</h1>
<a href="https://baidu.com" target="_blank" v-on:click.prevent.ctrl="add">按住ctrl点击+1</a>
<br />
<input type="text" v-on:keyup.alt.67="clear($event)">
<router-view/>
</div>
</template>
<script>
export default {
mounted(){
Vue.config.keyCodes.w= 87
},
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(e,num){
num = num || 1;
// this代表vue的实例
this.counter += num;
},
clear(e){
e.target.value = "";
}
},
mounted(){
console.log(1);
window.items = this.items;
}
}
</script>
时候我们就可以骄傲的说我们的输入框有快捷键了
这里说一下上面绑定keyup时使用的是.alt.67
后面的67是键盘上的c键的键盘码我们绑定事件时也可以直接将键盘码点上去,然后就是
v-on有一个缩写 比如v-on:click.prevent.ctrl
--->@:click.prevent.ctrl
可以直接缩写为@
vue2.5新增了一个用于键盘事件的修饰符
- exact
我这里直接就将官方的说明粘过来了,一看就懂
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
vue2.2新增了鼠标的按键修饰符
- .left
- .right
- .middle
那么我们就可以实现一个右键点击+1的操作了
<template>
<div id="app">
<h1>{{counter}}</h1>
<a href="https://baidu.com" target="_blank" v-on:click.prevent.right.exact="add">点击右键+1(以使用exact不要按其他的键否则无效)</a>
<br />
<input type="text" v-on:keyup.alt.67="clear($event)">
<router-view/>
</div>
</template>
<script>
export default {
mounted(){
Vue.config.keyCodes.w= 87
},
data(){
return {
counter:0
}
},
methods:{
shangxiashu(){
this.isShow++;
},
add:function(e,num){
num = num || 1;
// this代表vue的实例
this.counter += num;
},
clear(e){
e.target.value = "";
}
},
mounted(){
console.log(1);
window.items = this.items;
}
}
</script>
那么这时候就发现只有右键点击才能够+1了(而左键点击又坑爹的跳转了),并且当我们按住ctrl之类的系统修饰键是无效的
v-model --> 数据的双向绑定
我们快速实现一个表单认证
<template>
<div id="app">
<div>
<form action="#">
<p>
<input type="text" v-model="username" placeholder="please type username">
</p>
<p>
<input type="password" v-model="userpwd" placeholder="please type password">
</p>
<input type="submit" value="sumit" @click.prevent="submit">
</form>
</div>
<router-view/>
</div>
</template>
<script>
export default {
data() {
return {
username: "",
userpwd: ""
};
},
methods: {
submit() {
console.log(`username:${this.username}--->password:${this.userpwd}`);
}
}
};
</script>
同时此指令还有一些修饰符就如v-on一样
- .lazy 默认使用input中的input事件进行双向绑定,使用此修饰符可将双向绑定的事件改为change(当输入框失去焦点时更新)
- .number 默认输入的任何字符都是字符串,如果你需要让用户输入的数字转换成number类型可加
- .trim 可以自动去除用户输入字符串中的首尾空格
其他指令介绍
(v-if,v-model,v-for,v-on)
v-text --> 指令用于绑定后面值到元素的innerText中
<template>
<div id="app">
<div v-text="msg"></div>
<router-view/>
</div>
</template>
<script>
export default {
data() {
return {
msg:"<h1>Hello Vue</h1>"
};
},
methods: {
}
};
</script>
这样就完成了绑定,这个指令会替换掉原本元素中的内容(元素中的所有内容都会被替换包括子元素)
你会发现这个指令解析出来就是一个单纯的字符串
v-html
这个指令可以解决上面的问题,v-text中的内容无论是什么都会被解析为字符串
v-html与v-text类似,但是会将类似于html的代码解析为html嵌入元素中
<template>
<div id="app">
<div v-html="msg">
<p>lfjlsfsd </p>
</div>
<router-view/>
</div>
</template>
<script>
export default {
data() {
return {
msg:"<h1>Hello Vue</h1>"
};
},
methods: {
}
};
</script>
image.png
上述两个指令都会替换掉元素中的所有内容,但是很灵活可以通过函数或者一些简单的表达式动态的求值,同时替换元素中的所有内容也算是一个缺点,我们可以使用vue插值表达式就是双大括号{{}}进行动态解析某些值,插值表达式不会替换原宿所有内容,解析后的值就会替换在插值表达式所在的位置,插值表达式计算出的值为纯字符串
<template>
<div id="app">
<div>
{{msg}}
</div>
<router-view/>
</div>
</template>
<script>
export default {
data() {
return {
msg:"<h1>Hello Vue</h1>"
};
},
methods: {
}
};
</script>
image.png
有一种情况我们可以预见
<template>
<div id="app">
<div>
<h1>Hello {{msg}}</h1>
</div>
<router-view/>
</div>
</template>
<script>
export default {
data() {
return {
msg:"Vue"
};
},
methods: {
}
};
</script>
这时候渲染是没有任何问题的
image.png
但是当我们进入调试模式调低网速时
这个地方更改为slow 3G
image.png
无语了,我这里是vue cli服务的情况
image.png
模拟不了那种情况,我就直接说了吧,大家应该能够明白是什么情况吧
当网速慢的时候我们写的插值表达式可能就会原样输出(就是该咋样咋样的输出,因为这个时候网速慢vue.js这个文件可能还没有请求过来,因为肯定是优先载入html文件再从html文件中的script标签中载入vuejs文件)这个时候就暴露了,等到vuejs请求下来才会更新这些插值表达式,但是这样就有点坑了,不是很美观,所以vue提供了一个指令用来控制此等情况
v-cloak
这个指令就非常简单,就是插值表达式暴露到屏幕的时候可以为对应的元素暴露一个v-cloak属性
(我这个真的不行,模拟不了
image.png
从图片中可以看出,这是服务端渲染html结束然后返回给客户端的,这应该是服务端渲染的(个人猜测,但是8,9了))
就是当有了这个v-cloak属性时我们就可以写类似的样式
因为这个属性在插值表达式解析完毕之后会自动去除(removeAttr..)
v-once
顾名思义不说了
(就是当这个元素第一次渲染之后,之后的数据变化触发render方法更新视图时这个元素不会在被刷新了永远也不会了)
<template>
<div id="app">
<div>
<h1 v-once>Hello {{msg}}</h1>
<input type="button" value="改变msg的值" @click="changeValue">
</div>
<router-view/>
</div>
</template>
<script>
export default {
data() {
return {
msg:"Vue"
};
},
methods: {
changeValue(){
console.log(1)
this.msg += "zzz"
console.log(this.msg);
}
}
};
</script>
<style lang="less">
div[v-cloak]{
display: none;
}
</style>
你就会发现无论你怎么点击按钮,试图也不会刷新因为初始化生命周期已经执行过一次render了,所以以后不会刷新视图了,哪怕数据怎么发生变化
image.png
除非你直接人肉修改此选项
<template>
<div id="app">
<div>
<h1 v-once ref="aa">Hello {{msg}}</h1>
<input type="button" value="改变msg的值" @click="changeValue">
</div>
<router-view/>
</div>
</template>
<script>
export default {
data() {
return {
msg:"Vue"
};
},
methods: {
changeValue(){
console.log(1)
this.msg += "zzz"
console.log(this.msg);
// 暴力修改
this.$refs.aa.innerText = "Hello World"
}
}
};
</script>
<style lang="less">
div[v-cloak]{
display: none;
}
</style>
哈哈,一般是不会这么做的,因为这是不被vue提倡的
v-pre
跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
例子就是
<template>
<div id="app">
<div>
<h1 v-pre ref="aa" v-text="这是一个测试的文字">Hello {{msg}}</h1>
<p>你看到Vue算我输</p>
<input type="button" value="test" @click="changeValue">
</div>
<router-view/>
</div>
</template>
<script>
export default {
data() {
return {
msg:"Vue"
};
},
methods: {
changeValue(){
console.log(1)
this.msg += "zzz"
console.log(this.msg);
// 暴力修改
console.log(this.$refs.aa);
}
}
};
</script>
<style lang="less">
div[v-cloak]{
display: none;
}
</style>
就像于Vue不会管这个元素了,这个元素的所有都跟普通html一样了,这些在vue中认识的指令也无用了,浏览器不能认识的一切都不会起作用了
image.png
v-slot
这个东西后期完善更新,需要先了解组件
(date:2019-06-23 13:21:54)
组件基础
声明一个组件的最简单的方法就是通过Vue.component方法,然后就是我想说的就是因为vue-cli声明组件用的是另外一种方法所以我们只有新建一个文件夹不用打包工具来写了,我这里直接去node_modules文件夹下的vue中将dist目录下的vuejs复制过来的,然后直接新建一个index.html文件夹如下
image.png
大家像我这样写完这个index.html之后使用file协议在浏览器中打开这个文件就会发现我之前所说的这种情况了,现在我们将网络改为slow 3G然后刷新,反正你就是会看到插值表达式会一闪而过就这个情况
算了其他的不多说了,我们首先看一下最基础的定义组件
<body>
<div id="app">
{{msg}}
<hello-c></hello-c>
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("hello-c",{
template:"<div>hello 组件</div>"
})
new Vue({
data:{
msg:"Hello Vue"
}
}).$mount("#app");
</script>
</body>
然后我们就可以在浏览器中看见hello组件了,这样便意味着我们已经自己制作了一个组件了,大家可能在制作组件的时候有一点不爽的就是为什么template写html代码没有代码提示什么都要手写就是不爽对吧,所以我们可以使用这种方法定义template
<body>
<div id="app">
{{msg}}
<hello-c></hello-c>
</div>
<template id="hello-c">
<div>
<h1>hello组件</h1>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("hello-c",{
template:"#hello-c"
})
new Vue({
data:{
msg:"Hello Vue"
}
}).$mount("#app");
</script>
</body>
我们可以直接将组件的模板写在template标签中,这样就又有了我们熟悉的代码提示了(是不是非常棒),同时我们自定义的组件就跟新建Vue的实例一样大多数关于组件的方法都会有比如说data啊methods啊都有
这样就继承了Vue的大多数属性
<body>
<div id="app">
{{msg}}
<Counter></Counter>
</div>
<template id="counter">
<div>
<button type="button" @click="count++">你一共点击了我{{count}}次</button>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("Counter",{
template:"#counter",
data(){
return {
count:0
}
}
})
new Vue({
data:{
msg:"Hello Vue"
}
}).$mount("#app");
</script>
</body>
组件的生命周期
我觉得关于Vue组件的生命周期只需要一张图就可以明白了
直接就将Vue官方的一张图fetch过来了
- beforeCreate() 在组件创建之前此时无法访问vue实例(this)
- created() 创建完成之后已经可以访问this实例和数据
- beforeMount() 在组件挂载到视图之前
- mounted() 组件已经挂载到视图上以后
- beforeUpdate() 改变数据触发视图更新之前
- updated() 视图已经更新后
- beforeDestroy() 组件将要被销毁之前(下树,display:none不算,是销毁)
- destroyed() 组件销毁之后
这就是Vue组件的生命周期了
<body>
<div id="app">
{{msg}}
<Counter></Counter>
</div>
<template id="counter">
<div>
<button type="button" @click="count++">你一共点击了我{{count}}次</button>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("Counter",{
template:"#counter",
data(){
return {
count:0
}
},
beforeCreate(){
console.log("beforeCreate");
},
created(){
console.log("created");
},
beforeMount(){
console.log("beforeMount");
},
mounted(){
console.log("mounted");
},
beforeUpdate(){
console.log("beforeUpdate");
},
updated(){
console.log("updated");
},
beforeDestroy(){
console.log("beforeDestroy");
},
destroyed(){
console.log("destroyed");
}
})
new Vue({
data:{
msg:"Hello Vue"
}
}).$mount("#app");
</script>
</body>
image.png
关于组件的生命周期就到这里了
组件的传参
那么我们使用一个自定义组件肯定是需要动态的显示某些内容的,这时候我们就需要传递一些参数过去了,那么组件之间是怎么传参的呢?
通过props就可以实现组件之间的参数传递
我们看一下
<body>
<div id="app">
<h1>{{count}}</h1>
<!--
第一个count为props中声明的可被接收的props,值为父组件中的count
这里使用v-bind将不然count中的内容不会被当做js代码解析
v-bind:count 缩写为 :count 缩写 :
-->
<input type="button" value="点我+1" @click="count++">
<Counter :count="count"></Counter>
</div>
<template id="counter">
<div>
<button type="button" @click="count++">你一共点击了我{{count}}次</button>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("Counter",{
template:"#counter",
// props属性列举了可被接收的参数
props:['count']
})
new Vue({
data:{
count:0
}
}).$mount("#app");
</script>
</body>
那么vue是不建议也不可以在子组件中直接操作props的,因为此props为父组件传递过来的,子组件无论怎么改变此props的值,当父组件触发虚拟DOM刷新页面时都会重新传递props意味着子组件改变的props并不会长久的储存下去,所以Vue提供了一个类似于自定义事件的这种机制
<body>
<div id="app">
<h1>{{count}}</h1>
<!--
第一个count为props中声明的可被接收的props,值为父组件中的count
这里使用v-bind将不然count中的内容不会被当做js代码解析
v-bind:count 缩写为 :count 缩写 :
-->
<input type="button" value="点我+1" @click="count++">
<!-- 通过在父组件中使用v-on指令监听自定义事件名 -->
<Counter :count="count" @mclick="count++"></Counter>
</div>
<template id="counter">
<div>
<!-- 使用$emit触发一个自定义事件的事件名 -->
<button type="button" @click="$emit('mclick')">你一共点击了我{{count}}次</button>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("Counter",{
template:"#counter",
// props属性列举了可被接收的参数
props:['count']
})
new Vue({
data:{
count:0
}
}).$mount("#app");
</script>
</body>
兄弟们看注释吧,代码结合注释这还不懂
(废话一大堆:说明不适合自学啊,这里说一下我是自学的前端开发的,不是不愿意去培训机构,相反其实还是挺想去的,只是因为家庭的原因(最主要是没钱啊)所以只能苦逼的自学了,其实我想说的是不管是去培训机构还是自学还是在学校我想说的就一点,你永远也不可能会一直有老师带着你,技术是在不断的迭代的,谁也不知道明天又有哪种框架哪种解决方案火了,就比如webassembly一样,谁知道那天会火说不定到时候的前端真的可以说又是一场革命了,你想到时候的前端有接近原生的代码执行效率,无论是开发网站还是配合electron或者nw开发跨平台的桌面应用或者搭配react native或uni-app或weex等等开发移动端的软件,毫无疑问的一点就是以后的前端谁也说不准,所以我觉得自学反而是好的,自学的过程中虽然很难但反而是最容易学通学透的因为我们没有人为我们解答问题,每一个问题都是需要自己去研究解决的,可以说每一个人都在学习,不过有的人是在学校学习有的人在工作的时候学习,或者说每个人一直都在学习,比如你刚出生你不学走路,不学说话,等等...)
咳咳扯远了,总而言之大家可以想象一下以后的前端是一个怎么样的情况,现在webassembly已经支持c/c++/rust语言编译为asm.js这种编译的JavaScript代码进行在浏览器端执行了
我们接着说一下,子组件通过自定义事件将自己的需求暴露给父组件,由父组件负责数据的更新(值是由父组件传递下来的,不父组件维护谁维护)
那么我想要求子组件点击按钮不跟父组件的按钮一样了,我要点击子组件的按钮我想+多少就多少应该怎么办呢?
那么我们继续完善代码
<body>
<div id="app">
<h1>{{count}}</h1>
<!--
第一个count为props中声明的可被接收的props,值为父组件中的count
这里使用v-bind将不然count中的内容不会被当做js代码解析
v-bind:count 缩写为 :count 缩写 :
-->
<input type="button" value="点我+1" @click="count++">
<!-- 通过在父组件中使用v-on指令监听自定义事件名 -->
<!-- 这里的$event就是传递过来的参数 -->
<Counter :count="count" @mclick="count+=$event"></Counter>
</div>
<template id="counter">
<div>
<!-- 使用$emit触发一个自定义事件的事件名 -->
<!-- 添加一个输入框,输入多少就+多少的count -->
<p><input type="text" v-model.number="sCount"></p>
<!-- 可以通过第二个参数给父组件的handle传递参数 -->
<button type="button" @click="$emit('mclick',sCount)">你一共点击了我{{count}}次</button>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("Counter",{
template:"#counter",
// props属性列举了可被接收的参数
props:['count'],
data(){
return {
sCount:0
}
}
})
new Vue({
data:{
count:0
},
methods:{
addHandle(num){
this.count+=num;
}
}
}).$mount("#app");
</script>
</body>
这时候我们就可以通过自定义事件传参的方式,来实现这个需求了,如果传递了多个参数请在父组件中@自定义事件后传入一个函数接收多个参数
这里我又想实现一个自定义的输入框,但是当我输入v-model的时候我发现这个指令失效了?
<body>
<div id="app">
<h1>{{msg}}</h1>
<custom-input v-model="msg"></custom-input>
</div>
<template id="cinput">
<input type="text">
</template>
<template id="counter">
<div>
<!-- 使用$emit触发一个自定义事件的事件名 -->
<!-- 添加一个输入框,输入多少就+多少的count -->
<p><input type="text" v-model.number="sCount"></p>
<!-- 可以通过第二个参数给父组件的handle传递参数 -->
<button type="button" @click="$emit('mclick',sCount,'hello')">你一共点击了我{{count}}次</button>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("Counter",{
template:"#counter",
// props属性列举了可被接收的参数
props:['count'],
data(){
return {
sCount:0
}
}
})
Vue.component("custom-input",{
template:"#cinput",
props:['value']
})
new Vue({
data:{
count:0,
msg:"Hello"
},
methods:{
addHandle(num,c){
console.log(c);
this.count+=num;
}
}
}).$mount("#app");
因为我们不能直接给自定义组件绑定v-model而是应该给组件里面的元素绑定,但是如果我们给子组件中的input标签绑定v-model的话就会出现(因为双向绑定的值是父亲的,但是与之绑定的视图确实儿子的)父组件一但刷新props重新赋值为父组件的传递过来的值了,这个时候应该怎么班呢?我们就应该自己实现一个v-model了
通过打印发现我们是能够得到v-model绑定的msg值的
image.png
然后我们就需要思考如何让这个值与之产生关联,首先无论如何我们肯定需要先v-bind先单向将数据与视图建立联系才行,通过v-bind我们已经能够使input标签显示msg的值了
image.png image.png
然后我记得v-model的时候我有提到过,v-model的双向数据绑定是通过元素的input事件进行绑定的可以通过lazy修饰符改为change事件,所以这个时候我要干什么大家应该都知道了吧
<body>
<div id="app">
<h1>{{msg}}</h1>
<custom-input v-model="msg"></custom-input>
</div>
<template id="cinput">
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
</template>
<template id="counter">
<div>
<!-- 使用$emit触发一个自定义事件的事件名 -->
<!-- 添加一个输入框,输入多少就+多少的count -->
<p><input type="text" v-model.number="sCount"></p>
<!-- 可以通过第二个参数给父组件的handle传递参数 -->
<button type="button" @click="$emit('mclick',sCount,'hello')">你一共点击了我{{count}}次</button>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("Counter",{
template:"#counter",
// props属性列举了可被接收的参数
props:['count'],
data(){
return {
sCount:0
}
}
})
Vue.component("custom-input",{
template:"#cinput",
props:['value'],
created(){
console.log(this.value);
}
})
new Vue({
data:{
count:0,
msg:"Hello"
},
methods:{
addHandle(num,c){
console.log(c);
this.count+=num;
}
}
}).$mount("#app");
</script>
</body>
使用v-on绑定input事件在代码中emit一个input事件到自定义组件身上,这样就可以被v-model接收到了,从而完成双向数据绑定,懂了吗?
Vue提供了一个自定义组件用于动态显示我们的自定义组件的
<body>
<div id="app">
<h1>{{msg}}</h1>
<!--
真的不想写注释了,但是我还是要写,vue指令后面引号中的字符会被当做js代码解析就像执行eval一样
is属性制定了一个要显示的自定义组件名称
-->
<input type="button" value="点我切换组件" @click="type == 'custom-input' ? type = 'Counter' : type = 'custom-input'">
<component :is="type"></component>
</div>
<template id="cinput">
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
</template>
<template id="counter">
<div>
<!-- 使用$emit触发一个自定义事件的事件名 -->
<!-- 添加一个输入框,输入多少就+多少的count -->
<p><input type="text" v-model.number="sCount"></p>
<!-- 可以通过第二个参数给父组件的handle传递参数 -->
<button type="button" @click="$emit('mclick',sCount,'hello')">你一共点击了我{{count}}次</button>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("Counter",{
template:"#counter",
// props属性列举了可被接收的参数
props:['count'],
data(){
return {
sCount:0
}
}
})
Vue.component("custom-input",{
template:"#cinput",
props:['value'],
beforeDestroy(){
console.log('beforeDestroy')
},
destroyed(){
console.log("destroyed")
}
})
new Vue({
data:{
count:0,
msg:"Hello",
type:"custom-input"
},
methods:{
addHandle(num,c){
console.log(c);
this.count+=num;
}
}
}).$mount("#app");
</script>
</body>
component标签用于控制渲染我们的组件通过is属性动态渲染不同的组件已达到更好的交互效果,这里组件的切换是上树下树级别的
image.png
因为组件执行了生命周期中的destroyed方法
(那么关于Vue的基础就已经说完了,接下来就是Vue深入组件的一个部分了)
深入了解组件
组件注册
首先关于组件命名的要求,按照Vue官方所说的那样组件命名有两种方式
第一种:custom-input 就是字母之间或者组件名之中必须出现一个横线用以区分原生标签
第二种:首字母大写 CustomInput 驼峰命名法,大家随意喜欢那种都是可以的
然后就是到目前为止我们注册组件还是使用的是Vue.component这种注册组件的方式是全局注册的,全局注册的组件无论在哪一种层级还是哪一个Vue实例都是可以使用的,接下来给大家说说如何局部注册组件,局部注册的组件只能在当前注册的组件中使用
<body>
<div id="app">
{{msg}}
<!-- 这里Vue会自动将后面首字母大写的字母改为小写并且加上横线 -->
<Custom-div></Custom-div>
</div>
<script src="./lib/vue.js"></script>
<script>
var CustomInput = {
template:`
<div>我是自定义input组件</div>
`
}
var CustomDiv = {
template:`
<div>我是自定义div组件</div>
`,
components:{
CustomInput
}
}
var vm = new Vue({
el:"#app",
data() {
return {
msg:"Hello Vue"
}
},
components:{
// 键值对相同可以直接写一个在这里
CustomDiv
}
})
</script>
</body>
然后我们在这里一共定义了两个组件其中一个组件注册为另一个组件的子组件,且这个组件注册到Vue实例上,那么我们接下来直接在html中访问这个CustomInput组件看看会是什么情况,你会发现Vue不出意外的报错了
image.png
<div id="app">
{{msg}}
<!-- 这里Vue会自动将后面首字母大写的字母改为小写并且加上横线 -->
<Custom-div></Custom-div>
<Custom-input></Custom-input>
</div>
那么我们接下来将这个组件放到customdiv组件中试试
<body>
<div id="app">
{{msg}}
<!-- 这里Vue会自动将后面首字母大写的字母改为小写并且加上横线 -->
<Custom-div></Custom-div>
</div>
<script src="./lib/vue.js"></script>
<script>
var CustomInput = {
template: `
<h1>我是自定义Input组件</h1>
`
}
var CustomDiv = {
// 子组件放在html代码中
template: `
<div>
<p>我是自定义div组件</p>
<Custom-input></Custom-input>
</div>
`,
components: {
CustomInput
}
}
var vm = new Vue({
el: "#app",
data() {
return {
msg: "Hello Vue"
}
},
components: {
// 键值对相同可以直接写一个在这里
CustomDiv
}
})
</script>
</body>
image.png
那么我们发现不出意外组件已经正常渲染了,因为Vue实例的template已经挂在到div#app上了,所以我们的子组件一定要写在template上而不是那个标签中的内部
Prop
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 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>
那么这个限制在字符串模板是没有限制的,前面也说了template属性可以给一个选择器用来定位html template标签中的内容,这就是所谓的DOM模板,字符串模板就是我下面这样template之后直接手写这个模板,但是你也知道的没有代码提示纯手写贼难受
<body>
<div id="app">
{{msg}}
<!-- 这里Vue会自动将后面首字母大写的字母改为小写并且加上横线 -->
<Custom-div></Custom-div>
</div>
<script src="./lib/vue.js"></script>
<script>
var CustomInput = {
template: `
<h1>我是自定义Input组件</h1>
`,
props:["mData"],
// 字符串模板中不需要m-data
mounted() {
console.log(this.mData);
},
}
var CustomDiv = {
// 子组件放在html代码中
template: `
<div>
<p>我是自定义div组件</p>
<Custom-input mData="Hello"></Custom-input>
</div>
`,
components: {
CustomInput
}
}
var vm = new Vue({
el: "#app",
data() {
return {
msg: "Hello Vue"
}
},
components: {
// 键值对相同可以直接写一个在这里
CustomDiv
}
})
</script>
</body>
那么关于props首先要了解的就是组件中props属性的类型,前面我们写的props都是数组的形式出现的就像我上面贴的代码一样,其实props属性还可以是一个对象,且看下方代码
<body>
<div id="app">
<!-- 使用v-bind -->
<my-component :username="username" :password="password" :age="age" ></my-component>
</div>
<template id="mcom">
<div>
我接受的username是{{username}} 我接受的password是{{password}} <br>
我接受的age是{{age}}
</div>
</template>
<script src="./lib/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data() {
return {
username:"小钢炮",
password:"xiaogangpao",
// 故意写一个字符串
age:"18"
}
},
components:{
// 我这里只是把对象直接写在这里的,不会看不懂吧
myComponent:{
template:"#mcom",
props:{
// 那么这里就有三个props需要传递过来
// 名称就是键,类型就是值
username:String,
password:String,
age:Number
}
}
}
})
</script>
</body>
那么我这里故意将age设置为字符串不出所料的是果真是报错了,出乎意料的是值还是显示出来了
image.png
那么我将age重新改为number类型的时候报错就消失了
image.png
就不贴代码了,这是应该知道怎么改的
那么我们可以直接这样传递对象
<my-component :obj="mobj"></my-component>
<script>
var vm = new Vue({
el: "#app",
data() {
return {
mobj: {
username: "小钢炮",
password: "xiaogangpao",
// 故意写一个字符串
age: 18
}
}
},
components: {
// 我这里只是把对象直接写在这里的,不会看不懂吧
myComponent: {
template: "#mcom",
props: ["obj"],
mounted() {
console.log(this.obj)
},
}
}
})
</script>
然后可能我们需要传递对象的属性进去,那么vue就提供了一个非常简便的方法,那么可能我们正常需要这样写
<my-component :username="mobj.username" :password="mobj.password" :age="mobj.age" ></my-component>
那么Vue就提供了一个简单的方法快速通过props传递一个对象的所有属性
<my-component v-bind="mobj" ></my-component>
<script>
var vm = new Vue({
el: "#app",
data() {
return {
mobj: {
username: "小钢炮",
password: "xiaogangpao",
// 故意写一个字符串
age: 18
}
}
},
components: {
// 我这里只是把对象直接写在这里的,不会看不懂吧
myComponent: {
template: "#mcom",
// 这里就可以接收所有对象的属性
props: ["username","password","age"],
mounted() {
// 这里就可以进行打印了
console.log(this.username,this.password,this.age)
},
}
}
})
</script>
大家记住,这种方法一定要在元素中props中写对象的属性,不要傻乎乎的写对象的名字在哪里啊
单向数据流
大家应该也知道我之前有说过从父组件传递下来的props是不能进行修改的,因为哪怕你接收的props如何修改,当父组件触发虚拟DOM时props又会进行重新传递导致子组件重新渲染新的props值,这就是所谓的数据的单向流动,即数据总是自顶向下一层一层传递的(可能这时候大家就会想了,如果层级非常多的话岂不是从顶级组件传递下来会有那么几个组件只负责props传递数据到目标组件上,那么这个问题我们需要使用vuex进行解决,后面说),这样我们就非常清楚数据的一个流动,并且如果子组件修改了props那么数据的流向也不是很明确了,那么如果确实需要修改props呢?这里有两种解决方案,要么就是直接将传递过来的props绑定到自己的data上
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
另一种方法呢就是直接通过计算属性依赖这个props的值计算我们需要的值
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
两种方法各有各的应用场景,这里就不深入了解了
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
props验证
这个验证跟react的prop-types
库非常相似
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
// username:String,
// 多个可能的类型
// age:[String,Number],
age: {
type: Number,
// 指定默认值
default: 18
},
password: {
// 指定类型
type: String,
// 必须传递
required: true
},
// 自定义验证函数
username: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
// 也可以是一个带有默认值的对象
/* obj:{
type:Object,
// 对象或者数组的默认值必须从一个factory function(工厂函数)中取
default(){
return ["a",18,"c"]
}
} */
},
那么这个时候定义之后我们回到浏览器刷新就会发现
image.png
报错了,但是值也取到了,那么我们修改一下这个函数
// 自定义验证函数
username: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['小钢炮', 'warning', 'danger'].indexOf(value) !== -1
}
}
这样就ok了
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
类型检查
type 可以是下列原生构造函数中的一个:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:
function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
//你可以使用:
Vue.component('blog-post', {
props: {
author: Person
}
})
//来验证 author prop 的值是否是通过 new Person 创建的。
如果有这么一种情况,我们的组件是用来发布出去的,那么不知道用户会怎么样的去传递属性,那么如果假如说我使用我这个组件,我传递了一个我根本就没有在props中定义过的属性会怎么样呢?
<body>
<div id="app">
<!-- 直接使用v-bind等于你需要传递所有属性的对象 -->
<my-component v-bind="mobj" class="red" data-data-picker="activated"></my-component>
</div>
<template id="mcom">
<div class="black">
Hello my
</div>
</template>
<script src="./lib/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data() {
return {
mobj: {
username: "小钢炮",
password: "xiaogangpao",
age: 18
}
}
},
components: {
myComponent: {
template: "#mcom",
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
// username:String,
// 多个可能的类型
// age:[String,Number],
age: {
type: Number,
// 指定默认值
default: 18
},
password: {
// 指定类型
type: String,
// 必须传递
required: true
},
// 自定义验证函数
username: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['小钢炮', 'warning', 'danger'].indexOf(value) !== -1
}
}
// 也可以是一个带有默认值的对象
/* obj:{
type:Object,
// 对象或者数组的默认值必须从一个factory function(工厂函数)中取
default(){
return ["a",18,"c"]
}
} */
},
mounted() {
console.log(this.username, this.password, this.age)
},
}
}
})
</script>
那么你会发现,上面我的这个自定义组件的根元素有一个已有的类名,我在使用的时候传递了两个没有定义的属性过去,我们看看html架构是个什么情况
image.png
你会发现没有被props定义的属性都会直接继承到组件的唯一根元素身上,如果你不想组件的根元素继承这些属性的话,你可以在定义组件的时候加上
inheritAttrs: false
那么我这里加一下,刷新的时候发现
image.png
我们没有被props接收的属性都被忽略了,class合并了后面有说原因,那么我们使用了这个选项之后就没办法使用传递过来的属性了吗?其实Vue将没有被props接收的自定义属性都封装到了一个组件实例的$attrs属性上,我们修改打印一下
mounted() {
console.log(this.$attrs)
},
image.png
<body>
<div id="app">
<base-input
v-model="username"
required
placeholder="type your username"
></base-input>
</div>
<template id="baseInput">
<!-- 将所有的$attrs属性都传递到props上 -->
<label>
<input type="text"
v-bind="$attrs"
:value="value"
@input="$emit('input',$event.target.value)"
>
</label>
</template>
<script src="./lib/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data() {
return {
username:"suiuye"
}
},
components: {
baseInput:{
template:"#baseInput",
inheritAttrs:false,
props:["value"]
}
}
})
</script>
</body>
image.png
Vue官方说过的一句话还是非常哲学的,对哲学:
这个模式允许你在使用基础组件的时候更像是使用原始的 HTML 元素,而不会担心哪个元素是真正的根元素,没错你会发现上面的baseInput这个自定义的组件我完全当成input再用,因为属性什么的都是用的input的属性
注意 inheritAttrs: false 选项不会影响 style 和 class 的绑定。意思就是说加了这个属性,class和style还是会被合并,不会被这个属性影响,我上面就是一个活生生的例子
自定义事件
不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称.
(事件名不存在组件名那样的自动转换大写字母的名称加横线,事件名必须$emit()什么事件名就必须@什么事件名称)
借用Vue官方的一句话
(不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。因此,我们推荐你始终使用 kebab-case 的事件名。)
意思就是在template中@的事件名如果有大写字母会被自动转换成小写导致事件永远不会被监听到,所以可以说是自定义事件必须使用短横线命名法类似于 CustomEvent ---> custom-event ,除非是字符串模板否则可以说是必须使用短横线命名法
自定义组件的v-model --> vue2.2新增特性
比如说我们自定义一个文本输入框
<body>
<div id="app">
<!-- 这里将msg传入了组件内 -->
<!-- <base-input v-model="msg"></base-input> <br> -->
<base-input v-model="msg"></base-input>
{{msg}}
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("base-input",{
model:{
// 监听根元素的value
prop:'value',
// 当监听根元素的input事件,如果改为change就会无效,因为下面并没有$emit(change),这里写什么下面就要emit什么
event:'input'
},
props:['value'],
template:`
<input type="text" :value="value" @change="$emit('input',$event.target.value)" />
`
})
new Vue({
data: {
msg:"Hello Vue"
},
methods: {
}
}).$mount("#app");
</script>
</body>
这样我们就自定义了一个input输入框,但是个人感觉好像没什么用处似的,有的时候我们可能很想监听我们的自定义组件的原声事件,那么v-model提供了一个修饰符
- .native --> 用于绑定原生事件
可以这么认为,native就是一个把组件变回原生DOM的一种方法,给vue组件绑定事件的时候,一定要加上native,如果是普通元素就不需要,默认vue会将原生事件绑定到组件的根元素上,如果根元素不支持此事件就会静默的失败,例如
<div id="app">
<base-input @focus.native="onFocus"></base-input>
{{msg}}
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("base-input",{
// 我们就可以直接将$listeners属性绑定到对应的元素上
template:`
<div>
<input type="text" v-on="$listeners" />
</div>
`
})
因为div获取不了焦点那么绑定事件就会静默失败,所以vue官方提供了一个$listeners属性,我们可以将我们定义的处理函数添加到这个属性上然后绑定到一个特定的元素上,下面是vue官方的一个演示
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>
`
})
vue2.3新增的一个修饰符
- .sync
这是一个v-bind的修饰符用于将props进行双向绑定
<div id="app">
<base-input v-bind:value.sync="msg"></base-input>
{{msg}}
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("base-input",{
props:['value'],
template:`
<input type="text"
:value="value"
@input="$emit('update:value', $event.target.value)"
/>
`
// 这里的update:是一个固定写法后面跟着你要更新的属性名
// 我这里要更新input的value属性就写value
})
插槽
关于插槽就要说一下前面的v-slot指令了,前面的v-slot就是跟插槽有关的,那么我们需要先理解一下什么是插槽,先看一下下面的一个小例子
<div id="app">
<base-div></base-div>
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("base-div",{
template:`<div></div>`
})
我这样声明使用这个组件是没有什么问题的,但是如果我要在组件中添加某些内容呢,肯定是这样的!
<base-div>
Hello World
</base-div>
然后我们就慢慢等待这页面打印出Hello World但是,页面真的能够打印出来吗?
image.png
那么就会发现实际的渲染结果可以说是差强人意,什么都没有,相信大家都会犯这样一个错误吧(我都犯过)总是把我们自定义的组件当成html元素一样往里面插入内容甚至,有时候插入子组件也插入在这里面,这肯定是不对的,正确的做法是
//清空组件中的内容
<base-div></base-div>
//模板中写Hello World
template:`<div>Hello World</div>`
这是渲染结果
最终的渲染结果就非常正确了,那么我们肯定希望能够在组件标签中直接插入内容了,方便啊,还有就是非常直观,因为你一个组件包括子组件还有内容全在DOM模板或者字符串模板中,那么我们定位组件的时候都必须在DOM模板或者字符串模板中进行编辑,这样的话乍一看html代码会非常懵逼,我们维护的时候就必须一层一层的去寻找模板查看了,大家应该懂这个意思吧?
所以Vue提供了插槽的特性,插槽就可以让我们直接渲染组件标签中的内容,我们立马尝试吧
我们首先将组件改会原本的状态然后我们继续在组件标签中添加Hello World内容,然后我们只需要在组件模板中添加一个标签就行了
Vue.component("base-div",{
template:`
<div>
//只需要添加一个slot标签就行了
<slot></slot>
</div>
`
})
然后我们就会惊奇的发现内容成功被渲染出来了
渲染成功这就是插槽了,就是提供了一个接口用于防止组件标签中的内容,关于插槽的默认值,某些情况下我们总希望我们没有给插槽数据的时候插槽能够渲染一个默认的值,如下
一个case
这样的渲染是常有的,我们可能就是希望组件按钮的值能够动态的改变,但是我们还是希望当我们不传递任何值的话能够出现一个默认情况,那么我们可以这样做
image.png
那么插槽其实是可以访问其所在层级的内容的,例如
插槽访问数据是有作用域的,插槽的作用域其实就相当于自定义组件的作用于,你就当那么自定义组件标签就一个div,那么作用域就是外层父亲了,这里插槽的作用域就是外边的#app,插槽无法访问同组件中的内容,例如这里我传递一些数据进去
<body>
<div id="app">
<button-submit :mdataa="mdata">
<!-- 这里访问不了button-submit组件的作用域 -->
<!-- 失败 -->
{{mdataa}}
</button-submit>
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("button-submit", {
props:["mdataa"],
// 如果没有传递值,就会是提交,如果传递了那么是什么就是什么
template: `
<div>
<button type="button">
<slot>提交</slot>
</button?>
{{mdataa}}
</div>
`
})
new Vue({
data: {
msg: "Hello Vue",
mdata:"这是数据,传递的props插槽无法访问"
}
}).$mount("#app");
</script>
</body>
像这个例子
Vue毫不客气的报错并且不会获得任何数据
具名插槽
听名字就知道就是有具体名字的插槽了,什么意思呢?具名插槽有什么用?
有时我们需要向一个组件中传递多个插槽(一个slot就是一个插槽),那么肯定这样是不对的
<body>
<div id="app">
<button-submit :mdataa="mdata">
<!-- 这里访问不了button-submit组件的作用域 -->
<!-- 失败 -->
我
</button-submit>
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("button-submit", {
props:["mdataa"],
// 如果没有传递值,就会是提交,如果传递了那么是什么就是什么
template: `
<div>
<button type="button">
<slot>提交</slot>
<slot>提交</slot>
<slot>提交</slot>
<slot>提交</slot>
</button?>
{{mdataa}}
</div>
`
})
new Vue({
data: {
msg: "Hello Vue",
mdata:"这是数据,传递的props插槽无法访问"
}
}).$mount("#app");
</script>
</body>
这样的话就相当于重复了几次插槽的内容而已,完全无法分隔开各插槽之间的内容,这个时候具名插槽的作用就来到了,我们可以这样书写代码实现不同插槽之间的分隔
<body>
<div id="app">
<base-layout>
header
nav
section
footer
</base-layout>
</div>
<template id="base-layout">
<div>
<!--
对于一下组件我们可能需要往不同的标签中插入不同的内容
所以我们在每个标签中放一个插槽进去,但是这样根本无法实现我们的需求
这样只会让一个插槽重复四次罢了
-->
<header>
<slot></slot>
</header>
<nav>
<slot></slot>
</nav>
<section>
<slot></slot>
</section>
<footer>
<slot></slot>
</footer>
</div>
</template>
<body>
<div id="app">
<base-layout>
<!--
为了对应插槽的具体名字我们需要使用DOM模板
模板使用v-slot指令,参数就是对应插槽的名字
这个指令是下面这样写,跟平常的指令不一样
-->
<template v-slot:header>
header
</template>
<template v-slot:nav>
nav
</template>
<template v-slot:section>
section
</template>
<template v-slot:footer>
footer
</template>
</base-layout>
</div>
<template id="base-layout">
<div>
<!--
对于一下组件我们可能需要往不同的标签中插入不同的内容
所以我们在每个标签中放一个插槽进去,但是这样根本无法实现我们的需求
这样只会让一个插槽重复四次罢了,所以插槽提供了一个name属性用于指定具体的名字
-->
<header>
<slot name="header"></slot>
</header>
<nav>
<slot name="nav"></slot>
</nav>
<section>
<slot name="section"></slot>
</section>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("base-layout", {
template: "#base-layout"
})
new Vue({
data: {
msg: "Hello Vue",
mdata: "这是数据,传递的props插槽无法访问"
}
}).$mount("#app");
</script>
</body>
image.png这样我们就分隔开了每一个插槽,这就是具名插槽了,那么其实默认的
image.png<slot></slot>
也是有名字的叫做default,我们也可以指令v-slot:default或者不指定名字的标签当会传入到默认的插槽中,如果没有提供默认插槽那么就会放弃对应默认插槽中的内容
关于具名插槽就介绍到此了,接下来说一下作用于插槽
作用域插槽
简单来说就是让插槽中的内容访问对应组件中的内容
<body>
<div id="app">
<base-layout>
<!-- 但是插槽中访问不了此数据,访问的是父亲#app中的数据所以会报错 -->
{{username}}
</base-layout>
</div>
<template id="base-layout">
<div>
<!-- 这里是能够访问自己组件中的数据的 -->
{{username}}
<slot></slot>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("base-layout", {
template: "#base-layout",
data(){
return {
username:"suiyue"
}
}
})
new Vue({
data: {
msg: "Hello Vue",
mdata: "这是数据,传递的props插槽无法访问"
}
}).$mount("#app");
</script>
</body>
image.png
这就代表着默认插槽的作用域与其所在的组件同级(不是父子是同级)所以无法访问其所在组件的一些数据
那么我们就需要这么玩了
<body>
<div id="app">
<base-layout>
<!-- 但是插槽中访问不了此数据,访问的是父亲#app中的数据所以会报错 -->
<!-- 需要使用DOM模板标签接受插槽props -->
<!--
通过v-slot参数为对应的插槽名称 等号后面的制定一个变量用于接受插槽props
所有的插槽props都会变成属性绑定到此变量上
-->
<template v-slot:default = "my_props" >
{{my_props.username}}
</template>
</base-layout>
</div>
<template id="base-layout">
<div>
<!-- 这里是能够访问自己组件中的数据的 -->
{{username}}
<!--
使用v-bind指令将username属性绑定在特定的插槽上
通过这样的方式传递数据给插槽,这样的方式叫做插槽props
-->
<!-- 不用v-bind后面是字符串 -->
<slot :username="username"></slot>
</div>
</template>
<script src="../lib/vue.js"></script>
<script>
Vue.component("base-layout", {
template: "#base-layout",
data(){
return {
username:"suiyue"
}
}
})
new Vue({
data: {
msg: "Hello Vue",
mdata: "这是数据,传递的props插槽无法访问"
}
}).$mount("#app");
</script>
</body>
作用域插槽效果
这样我们就成功的将数据fetch到了,以上就是作用于插槽的使用讲解了
上述作用于插槽在提供组件中如果没有具名插槽时可以简化书写为
image.png
还可以进一步简化
image.png上述简写方法只能在组件中没有具名插槽中使用,如果组件中有具名插槽还请不要使用简写,否则语法会无效并且Vue会发出警告,切记
解构插槽prop
作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里
function (slotProps) {
// 插槽内容
}
所以我们可以直接对插槽进行解构,像这样
浏览器正常渲染输出,同时我们可以在解构时重命名插槽
image.png
我们设置可以直接给插槽赋默认值
image.pngvue2.6+ 新增动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
就是可以通过一个变量或者计算属性函数等等动态计算插槽名字
具名插槽的缩写,跟v-on和v-bind一样具名插槽也是有缩写的,具名插槽的缩写 v-slot:header ---> #header v-slot:缩写为#,跟其他指令的缩写一样该缩写只有当插槽有参数才有用,这意味着一下语法是无效的
<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
我们必须明确制定他的参数才行
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
异步组件
image.png在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染.
这一个简单的例子演示了如何异步加载组件,你可以在此函数中执行任何异步的请求最后通过resolve传递给vue
断断续续才发现居然写了这么多,所以我决定拆分开来写了,因为vue还有差不多这么一篇文章的知识还没有接触,所以我决定将另外的一些知识什么的都写在下一篇文章中,同时发现自己写得有那么一点点的乱,我决定最后来一个总结吧
最后来一个总结
- 计算属性 -->obj
需要依赖于组件的data才能够进行数据的更新,如果data不更新,计算属性永远不更新
- 监听 --->obj
监听的属性为函数名,每一次这个属性的改变都会当做参数传给对应的函数,然后自定义逻辑
- class与style的绑定
对象法 class: key --> 类名 value-->bool(决定是否绑定此类名)
style: key --->样式名(驼峰) value---> 样式的值
数组法 class: 每一个变量都表示类名,值为bool
style -->每一个变量必须是一个对象,key-value代表样式名和样式值
同时也可以使用三元运算符,或者直接引用data中的对象或者数组等等
- 指令
- v-if
- v-else-if (必须在v-if后,否则无效)
- v-else (必须在上面两个指令后,否则无效)
- v-show
- v-for
- v-on --> @
-->事件修饰符
- stop
- prevent
- capture
- self
- once
- passive
-->按键修饰符 - enter
- tab
- delete (捕获“删除”和“退格”键)
- esc
- space
- up
- down
- left
- right
-->系统修饰键 - .ctrl
- .alt
- .shift
- .meta
--->2.5新增键盘事件修饰符 - .exact
--->2.2新增鼠标按键修饰符 - .left
- .right
- .middle
--->组件的修饰符 - .native
- v-bind --> (:)
-->修饰符
- .sync
<div id="app">
<base-input v-bind:value.sync="msg"></base-input>
{{msg}}
</div>
<script src="../lib/vue.js"></script>
<script>
Vue.component("base-input",{
props:['value'],
template:`
<input type="text"
:value="value"
@input="$emit('update:value', $event.target.value)"
/>
`
// 这里的update:是一个固定写法后面跟着你要更新的属性名
// 我这里要更新input的value属性就写value
})
- v-model
--->修饰符
- .lazy
- .number
- .trim
-
v-text
-
v-html
-
v-choak
-
v-once
-
v-pre
-
v-slot
-
数组更新检测
data中数组的某一些方法也会触发vue的视图更新
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
- 组件的生命周期
- beforeCreate()
- created()
- beforeMount()
- mounted()
- beforeUpdate()
- updated()
- beforeDestroy()
- destroyed()
- 自定义事件
通过组件内$emit一个事件,外部通过v-on接受事件
- 组件的全局和局部注册
- 组件prop
- 单向数据流
- props验证
- 插槽
- 具名插槽
- 作用域插槽
- 解构插槽prop
- vue2.6+ 动态插槽名 v-slot:[] v-bind:[]
好了差不多就是这样了,接下来我会写得非常简洁了,下一章见!
最后如果你有什么学习困惑或者对于前端有什么想跟我探讨的可以加下QQ群联系我 , 78484------5854
网友评论