简介
是一种渐进式JavaScript框架,Vue被设计为可以自底向上逐层应用
MVC模式(后端概念)
M:model 数据模型(数据保存)
V:View 视图 (用户界面)
C:controller 控制器(业务逻辑)
MVVM模式(前端概念)
M:model 数据模型(数据保存,处理数据业务逻辑)
V:View 视图 (用户界面,展示数据,与用户交互)
VM:View Model 数据模型和视图的桥梁
MVVM模式最大特点是支持数据的双向传递:M -> VM ->V,V -> VM -> M
Vue采用MVVM模式:
被控制的区域:View
Vue实例对象:View Model
实例对象中的data:Model
可在浏览器中安装插件 Vue-devtools 来配合开发使用
github网址:https://github.com/vuejs/vue-devtools
兼容性:
Vue不支持IE8及以下的版本(Vue使用的IE8无法模拟的ES5特性)
官网:https://cn.vuejs.org/
完整的学习Vue:HTML+CSS,JavaScript,CSS3,HTML5,第三方库,网络通信,ES6+(包括ES2015,ES2018,...),webpack,模块化(常见的有CommonJS,ES6 Module,AMD,CMD,UMD),包管理器,CSS预编译器
Vue特点:渐进式,组件化,响应式
Vue的应用场景:前台的部分页面和后台的全部页面
搭建Vue项目的脚手架:Vue-cli,webpack-simple,手动搭建
引入Vue.js
script标签引入:<script src='vue.js'></script>
CDN引入:<script src="https://cdn.jsdelivr.net/npm/vue"></script> (生产环境版本,优化了尺寸和速度)
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> (开发环境版本,包含了有帮助的命令行警告)
声明式渲染
Vue实例对象:
let vue=new Vue({ //创建一个Vue实例对象
el:'xxx(id名,class名...)', //告诉Vue的实例对象,将来需要控制界面的哪个区域
data:{ //告诉Vue实例对象,被控制区域的数据是什么
...
},
})
e.g.
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
注入:配置对象中的部分内容会被提取到vue实例中:data,methods,...
注入的两个目的:
1. 完成数据响应式:vue是怎么知道数据被更改了呢?(vue2.0是通过Object.defineProperty方法完成数据响应式。vue3.0是通过Class Proxy完成的数据响应式)
Object.defineProperty无法感知属性的新增和删除等。Class Proxy解决了这些缺陷。
2. 绑定this:this指向vue实例
有关虚拟DOM树
为提高渲染效率,vue会把模板编译成为虚拟DOM树,然后再生成真实的DOM
当数据更改时,将重新编译成虚拟DOM树,然后对前后两棵树进行对比,仅将差异部分反映到真实DOM,这样既可最小程度改动真实DOM,提升页面效率
对于Vue而言,提升效率重点是两方面:
1. 减少新的虚拟DOM生成
2. 保证对比之后,只有必要的节点有变化
Vue提供多种方式生成虚拟DOM树:
1. 在挂载的元素内部直接书写,此时将使用元素的outerHTML作为模板
2. 在template配置中书写
3. 在render配置中用函数直接创建虚拟节点树,此时完全脱离模板,将省略编译步骤:render(h){return h('元素节点','xxx ')},render(h){return h('元素节点',[h('子节点','xxx')])}
以上步骤从上到下,优先级逐渐提升
虚拟节点树必须是单根的
将生成的真实DOM树,放置到某个元素位置,称为挂载
挂载的方式:
1. 通过el:'css选择器'进行配置
2. 通过vue实例.$mount('css选择器')进行配置
Vue指令
1. v-if,v-else,v-else-if:条件语句
<div v-if='xxx'>
xxxxxxx
</div>
<div v-else-if='xxx'>
xxxxxxx
</div>
<div v-else='xxx'>
xxxxxxx
</div>
2. v-show:显示(改变display)
<div v-show='xxx(结果为true就显示,false就不显示)'>
xxxxxxx
</div>
3. v-for:循环(数组,对象,数字,字符)
<div v-for="item in items">
{{ item.xxx }}
</div>
<div v-for="(item, index) in items1">...</div>
<div v-for="(val, key) in object">...</div>
<div v-for="(val, name, index) in object">...</div>
var app = new Vue({
el: '#app',
data: {
items:['1','2','3'],
items1:[
{
name:'xx',
age:'xx'
},
{
name:'xx',
age:'xx'
},
],
object:{
name:'xxx',
age:'xx',
work:'xxx',
married:'xx'
}
}
})
v-for注意点:
1. v-for在渲染元素时,会先查看缓存中有没有需要渲染的元素
2. 若缓存中没有需要渲染的元素,就会创建一个新的放到缓存中。
若缓存中有需要渲染的元素,就不会创建一个新的,而是直接复用原有的
3. 在vue中只要数据发生了改变,就会自动重新渲染
可在for循环中绑定一个唯一的标识以防重新渲染新数据时发生错误:<div v-for="(item, index) in items1" :key='xxx(最好不要把index也作为唯一key,也会改变数据)'>...</div>
4. v-text和v-html:更新元素的 textContent和innerHTML
<span v-text="msg"></span> => <span>{{msg}}</span>
<div v-html="html语句"></div>
5. v-on:event 绑定事件(缩写@)
<button v-on:click='alert('1')'>add</button>
<button v-on:click='add'>add</button> / <button @click='add'>add</button>
<button @click='aa(2,$event)'>add</button>
var app = new Vue({
el: '#app',
methods:{
add(){...},
aa(n,e){...}
}
})
事件event上的修饰符:
.stop - 调用 `event.stopPropagation()`阻止冒泡发生
.prevent - 调用 `event.preventDefault()`阻止默认事件发生
.capture - 让默认情况下是事件冒泡变成事件捕获
.self - 只有当事件是从侦听器绑定的元素本身触发时才触发回调
.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调
.native - 监听组件根元素的原生事件
.once - 只触发一次回调
.left - (2.2.0) 只当点击鼠标左键时触发
.right - (2.2.0) 只当点击鼠标右键时触发
.middle - (2.2.0) 只当点击鼠标中键时触发
.passive - (2.3.0) 以 `{ passive: true }` 模式添加侦听器
部分用法:<input @keyup.enter="xxx">
<button v-on:click.once="xxx"></button>
6. v-model:在表单控件或者组件上创建双向绑定
<input type='text' v-model='num'>
var app = new Vue({
el: '#app',
data:{
num:2
}
})
此时输入框的文本值为2
7. v-bind:给元素属性绑定数据。动态地绑定一个或多个 attribute,或一个组件 prop 到表达式(缩写::)
<img :src='imgSrc'> (绑定属性)
<img :src="'/path/xx'+filename">(内联字符串拼接)
<div :class='xxx'>(class的绑定)
...
一些修饰符:
.prop - 作为一个 DOM property 绑定而不是作为 attribute 绑定。
.camel - (2.1.0+) 将 kebab-case attribute 名转换为 camelCase。(从 2.1.0 开始支持)
.sync (2.3.0+) 语法糖,会扩展成一个更新父组件绑定值的 `v-on` 侦听器。
8. v-pre:跳过这个元素和它的子元素的编译过程(原样输出)
<span v-pre>{{ this will not be compiled }}</span>
9. v-cloak:这个指令保持在元素上直到关联实例结束编译(渲染完成后显示)
当网络较慢,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码。我们可以使用 v-cloak 指令来解决这一问题
若和CSS`[v-cloak] { display: none }` 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕
css:[v-cloak] {
display: none;
}
HTML: <div v-cloak>
{{ message }}
</div>
10. v-once:只渲染元素和组件一次
<span v-once>This will never change: {{msg}}</span>
11. v-slot:插槽。提供具名插槽或需要接收 prop 的插槽。(缩写:#)
<!-- 具名插槽 -->
//组件调用时
<MyFooter v-red :age.sync="age">
<template v-slot:footer> //这里v-slot:后边的值与组件内的slot的name属性对应,也就是插槽的名称。
<div>list</div>
</template>
</MyFooter>
//书写组件时
<template>
<div>
{{age}}
<div>
<slot name='footer' /> //这里name的值就是这个插槽的名称。
</div>
</div>
</template>
<!-- 接收 prop 的具名插槽 -->
//组件调用
<ul>
<myli :title="val.title">
<template v-slot:footer="message(自定义名字)">
<div>{{message.t}}</div>
</template>
</myli>
</ul>
//书写组件时
<template>
<li>
<slot name='footer' :t="title">
</slot>
</li>
</template>
12. 绑定类名:
想要在style中找对应的类并绑定到元素中::class=['xxx',...]
<div :class="['xxx',...]"></div>
可用三目运算符实现按需绑定类名:<div :class="['xxx',flag ? 'xx' : 'xxxx',...]"></div>
可通过对象来决定是否要绑定类名:<div :class="['xxx',{'xx' : true/false},...]"></div>
可在Vue实例对象的data中添加一个对象,含需要绑定的类名值:<div :class="obj"></div>
data{
obj:{
'xxx':true,
'xx':false,
'xx':true,
...
}
}
13. 绑定样式:
和绑定类名类似(若属性名中有'-',则必须用''把属性名引起来)
<div :style="{属性名:'属性值',...}"></div>
也可好像绑定类名一样,在data定义一个对象(含属性名和属性值):<div :style="obj"></div>
若有多个对象,则把其放进数组中:<div :style="[obj,obj1,...]"></div>
特殊属性
key:该属性可以干预diff算法,在同一层级,key值相同的节点会进行比对,key值不同的节点则不会
可应用到的场景:
1. 切换登录方式(应用key不会只改变label,会让label改动,旧节点移除,新节点产生)
<div v-if="loginType==='mobile'">
<label>手机号</label>
<input type='text' key='1'>
</div>
<div v-else>
<label>邮箱</label>
<input type='text' key='2'>
</div>
2. 循环生成的节点中,给予每个节点唯一且稳定的key值
<div v-for="(item, index) in items1" :key='xxx(最好不要把index也作为唯一key,也会改变数据)'>...</div>
Vue自定义指令
自定义全局指令:
Vue.directive('指令名',(el,binding,vnode)=>{});
其中各参数的解析:
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 编译生成的虚拟节点
e.g. <div v-changecolor='red'></div>
Vue.directive('changecolor',(el,binding,vnode)=>{el.style.background=binding.value})
一个指令定义对象提供的几个钩子函数:
bind:function(){}:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
inserted:function(){}:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
update:function(){}:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)
componentUpdated:function(){}:指令所在组件的 VNode 及其子 VNode 全部更新后调用
unbind:function(){}:只调用一次,指令与元素解绑时调用。
Vue.directive('xxx',{
bind:function(){}, //指令被绑定到元素上时执行
inserted:function(){}, //绑定指令的元素被添加到父元素上时调用
update:function(){},
componentUpdated:function(){},
unbind:function(){}
})
自定义局部指令:
new Vue({
el:'xxx(要求在此元素中局部应用)',
directives:{
'指令名':{
bind:function(el,obj){
....
}
}
}
})
Vue 扩展实例构造器
Vue.set(target, propertyName/index, value)
解决不能通过索引设置数组的值的问题
e.g. 写一个方法来改变数组arr中的第二个值
methods:{
change:function(){
//this.arr[1]='x';(错误写法)
Vue.set(this.arr,1,'x');(正确写法)
};
}
Vue 定义方法(函数)methods
methods:{} (用来存储(定义)一些监听事件的回调函数(方法))
new Vue({
methods:{
xx(){
...
}
}
})
Vue 计算属性 computed
computed:{} (专门用于定义计算属性的)
new Vue({
computed:{
//仅访问器
xx(){
return ...
}
//访问器+设置器
xxx:{
get(){ //得到值
return ...
},
set(val){ //设置值
...
}
}
}
})
computed(计算属性)和methods(方法)的区别:
计算属性可以赋值,而方法不行
计算属性会进行缓存,如果依赖不变,则直接使用缓存结果,不会重新计算
凡是根据已有数据计算得到新数据的无参数函数,都应尽量写成计算属性,而不是方法
特点:只要计算返回的结果没有发生变化,那么计算属性就只会被执行一次
应用场景:由于计算属性会将返回的结果缓存起来,所以如果返回的数据不经常发生变化,那么使用计算属性(computed)的性能会比使用函数(methods)的性能高
Vue 过滤器
过滤器和函数,计算属性一样也是用来处理数据的
但过滤器一般用于格式化插入的文本数据
过滤器只能在插值语法和v-bind中使用
过滤器可连续使用
过滤器的定义:
自定义全局过滤器:
Vue.filter('过滤器名',过滤器处理函数function(value){... return xxx}) (默认情况下处理数据的函数要接收一个参数,就是当前要被处理的数据)
自定义局部过滤器:
new Vue({
el:'xxx(绑定要局部使用的元素区域)'
filters:{
'过滤器名':function(value){...}
}
})
过滤器的使用:(Vue会把要处理的数据交给指定的过滤器处理,把处理之后的结果插入到指定的元素中)
<div>{{处理的数据 | 过滤器名}}</div> / <div v-bind:id="处理的数据 | 过滤器名"></div>
过滤器连续使用:<div>{{处理的数据 | 过滤器名1 | 过滤器名2 | ...}}</div>
可在过滤器中添加参数:<div>{{处理的数据 | 过滤器名('xxx')}}</div>
e.g. 用过滤器对时间进行格式化
<div id='app'>{{time | formatDate('yyyy-mm-dd')}}</div>
Vue.filter('formatDate',function(value,fmstr){
let date=new Date(value);
let year=date.getFullYear();
let month=date.getMonth() + 1 + '';
let day=date.getDate() + '';
let hour=date.getHours() + '';
let minute=date.getMinutes() + '';
let second=date.getSeconds() + '';
if(fmstr && fmstr === 'yyyy-mm-dd'){
return `${year}-${month.padStart(2,'0')}-${day.padStart(2,'0')}`;
}
return `${year}-${month.padStart(2,'0')}-${day.padStart(2,'0')}-${hour.padStart(2,'0')}-${minute.padStart(2,'0')}-${second.padStart(2,'0')}`;
})
new Vue({
el:'#app',
data:{
time:Date.now();
}
})
Vue 过渡动画
将需要执行动画的元素放到transition组件中
对于使用css属性来过渡动画的:
当transition组件中的元素显示时会自动查找 .v-enter/.v-enter-active/.v-enter-to 类名
当transition组件中的元素隐藏时会自动查找 .v-leave/.v-leave-active/.v-leave-to 类名
动画初始化的设置(第一次进入时就有动画):可通过给transition添加appear属性
给多个不同的元素指定不同的动画:可通过gettransition指定name的方式来指定'进入之前/进入之后/进入过程中',离开之前/离开之后/离开过程中'对应的类名来实现不同的元素执行不同的过渡动画
过渡动画的实现:
1. 利用css属性:
<style>
.v-enter{
opacity:0;(或其他属性)
}
.v-enter-to{
opacity:1;
}
.v-enter-active{
opacity:all 3s;
}
.v-leave{
opacity:1;
}
.v-leave-to{
opacity:0;
}
.v-leave-active{
opacity:all 3s;
}
</style>
<transition appear>
<div class='box' v-show='isshow'></div>
</transition>
多个transition同时使用:
<style>
.one-enter{
opacity:0;(或其他属性)
}
.one-enter-to{
opacity:1;
}
.one-enter-active{
opacity:all 3s;
}
.two-enter{
opacity:0;(或其他属性)
}
.two-enter-to{
opacity:1;
}
.two-enter-active{
opacity:all 3s;
}
</style>
<transition appear name='one'>
<div class='box' v-show='isshow'></div>
</transition>
<transition appear name='two'>
<div class='box' v-show='isshow'></div>
</transition>
2. 在属性中声明javascript钩子函数:
<transition v-bind:css='false'(不会去查找css类名而受到影响) v-on:before-enter='beforeEnter' v-on:enter='enter' v-on:after-enter='afterEnter'>
<div class='box' v-show='isshow'></div>
</transition>
new Vue({
methods:{
beforeEnter(el){
el.style.opacity='0';
...
},
enter(el,done){ //动画执行完毕后一定要调用done回调函数,否则后续的afterEnter钩子函数不会被执行
//若是通过JS钩子函数来实现过渡动画,那必须在动画执行过程中加上el.offsetWidth/el.offsetHeight
el.offsetWidth;
//el.offsetHeight;
el.style.transition='all 3s';
//若想要第一次进入时就有动画,可设置setTimeout
setTimeout(function(){
done();
},0);
...
},
//若enter中调用了done(),就会执行enterAfter
enterEnter(el){
el.style.opacity='1';
el.style.marginTop=100+'px';
...
},
}
})
3. 配合使用第三方 JavaScript 动画库Velocity.js
引入Velocity.js的路径:<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.5.0/velocity.min.js"></script>
new Vue({
methods:{
enter(el,done){
//Velocity(对应的元素,属性设置,过渡时长)
Velocity(el,{opacity:1,...},3000);
}
}
})
4. 自定义类名动画:
可以通过以下 attribute 来自定义过渡类名:
enter-class
enter-active-class
enter-to-class (2.1.8+)
leave-class
leave-active-class
leave-to-class (2.1.8+)
<style>
//自定义类名
xxx-enter/xxx{
...
}
xxx-enter-to/xxx{
...
}
xxx-enter-active/xxx{
...
}
</style>
<transition appear enter-class='上面自定义的类名' enter-active-class='上面自定义的类名' enter-to-class='上面自定义的类名'>
</transition>
5. 配合使用第三方 CSS 动画库Animate.css
引入Animate.css的路径:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<transition appear enter-class='animated animated上对应的类名' enter-active-class='animated animated上对应的类名' enter-to-class='animated animated上对应的类名'>
</transition>
transition里只能放一个标签元素,想要在transition里面放多个标签元素(多个元素有过渡效果):可利用<transition-group></transition-group>
使用transition-group时默认会把内容放到span中,要想放到指定的标签元素里,可用:<transition-group tag='xxx(指定的包裹标签元素)'>
Vue 组件
每个组件包含:功能(JS代码),内容(模块代码),样式(CSS代码)
全局组件:
1. 创建组件构造器
let xxx = Vue.extend({
template:`...`; //在创建组件指定组件的模板时,模板只能有一个根元素(可用一个div包裹)
})
2. 注册已经创建好的组件
Vue.component('组件名',xxx(上面组件构造器的名字))
或
Vue.component('组件名',{
//data通过函数返回的原因:每创建一个新组件,都会调用一次这个方法将这个方法返回的数据和当前创建的组件绑定在一起,有效避免了数据混乱
data(){
return{...}
}
template:`...`,
methods:{
xxx(){...}
},
props:[...]
})
3. 使用注册好的组件
<组件名></组件名>
局部组件:(推荐使用)
new Vue({
el:'xxx(绑定局部区域)',
components:{
'组件名':{
template:`...`,
...
}
}
})
组件的属性props:
<组件名 :p1='a' :p2='b'></组件名> //传递属性
Vue.component('组件名',{
props:['p1','p2'], //定义属性
template:'<div>{{p1}},{{p2}}</div>',
})
//props包含的一些设置
props:{
属性名:{
type:xxx, //约束该属性的类型是xxx
required:xxx, //约束该属性是否必须要传递
default:xxx //默认值(若返回的默认值是对象,则要写成函数,把对象作为返回结果。如default:function(){return {}}或default:()=>({}))
validator:function(){...} //自定义验证函数
}
}
在组件中,属性是只读的,绝对不可以更改,叫做单向数据流
组件的切换:
可通过v-if实现组件切换
<div>
<tmpone v-if='isshow'></temone>
<tmptwo v-else></temtwo>
</div>
<template id='temone'>
<div>aaa</div>
</template>
<template id='temtwo'>
<div>bbb</div>
</template>
Vue.component('temone',{
template:'#temone'
})
Vue.component('temtwo',{
template:'#temtwo'
})
动态组件:
<div>
<keep-alive> //可保存组件的状态
<component v-bind:is='组件名'></component>
</keep-alive>
</div>
组件动画:
单个组件就使用transition
多个组件就使用transition-group
和上面的元素过渡动画一样
不过默认情况下进入动画和离开动画是同时执行的,如果想要一个做完后再做另一个,则需要指定动画模式(mode='out-in')
<transition mode='out-in'>
<component v-bind:is='组件名'></component>
</transition>
父子组件:
局部组件就是典型的父子组件
子组件只能在父组件中使用
以下为定义方式:
第一种:
new Vue({
el:'xxx(绑定局部区域)',
components:{
'父组件名':{
template:`...`,
components:{
'子组件名':{
tenplate:'...'
}
}
...
}
}
})
第二种:
Vue.component('父组件名',{
template:'...',
components:{
'子组件名':{
tenplate:'...'
}
}
})
父子组件的数据传递:
父组件传递数据给子组件:
第一步:在父组件中通过v-bind传递数据:v-bind:自定义接收名称='要传递的数据'
第二步:在子组件中通过props接收数据:props:['自定义接收名称']
<template id='父组件名'>
<div>{{name}}</div>
<子组件名 :parent-name(自定义的接收名称)='name'></子组件名>
</template>
<template id='子组件名'>
<div>{{parentname(自定义的接收名称)}}</div>
</template>
Vue.component('父组件名',{
template:'...',
data(){
return {
name:'ckx',
}
}
components:{
'子组件名':{
tenplate:'...',
props:['parentName(自定义的接收名称)',...]
}
}
})
兄弟组件之间不能直接传递数据,如果兄弟组件间要传递数据,那么必须借助父组件(但这方法很麻烦,可用Vuex简化)
普通的实现方法:
1. 父亲给儿子传递一个方法
2. 在儿子中修改数据
3. 儿子中修改完数据,调用父亲传递过来的方法,并且将修改后的数据通过此方法传递给父亲
4. 在父亲的方法中保存最新数据
<template id='父组件名'>
<div>
<子组件名1 @parentchange='change'></子组件名1>
<子组件名2 :parentnum='num'></子组件名2>
</div>
</template>
<template id='子组件名1'>
<div>{{count}}</div>
</template>
<template id='子组件名2'>
<div>{{parentnum}}</div>
</template>
Vue.component('父组件名',{
template:'...',
data(){
return {
num:0,
}
},
methods:{
change(newCount){
this.num=newCount;
}
},
components:{
'子组件名1':{
tenplate:'...',
data(){
return {
count:0,
}
},
methods:{
add(){
this.count++;
this.$emit('parentchange',this.count);
}
}
},
'子组件名2':{
tenplate:'...',
props:['parentnum']
},
}
})
父子组件的方法传递:
第一步:在父组件中通过v-on传递方法:v-on:自定义接收名称='要传递的方法'
第二步:在子组件中自定义一个方法,再在自定义方法中通过this.$emit('自定义接收名称'):触发传递过来的方法
<template id='父组件名'>
<div>
<button @click='aaa'></button>
</div>
<子组件名 @parent-method('自定义接收名称')='aaa'></子组件名>
</template>
<template id='子组件名'>
<div>
<button @click='xxx'></button>
</div>
</template>
Vue.component('父组件名',{
template:'...',
methods:{
aaa(){...}
}
components:{
'子组件名':{
tenplate:'...',
methods:{
xxx(){
this.$emit('parent-method('自定义接收名称')',...(函数参数)); //获取此参数时可用$event
}
}
}
}
})
组件的命名:
组件名的命名方式有两种:
短横线分隔命名(xx-xx-xx)
首字母大写命名(XxxXxxx)
注意点:
1. 注册组件时使用了'驼峰命名',那么在使用时需要转换成'短横线分割命名'
注册时:myFather -> 使用时:my-father
2. 传递参数时如果想使用'驼峰命名',那么就必须写'短横线分割命名'
传递时:parent-name='name' -> 接收时:props['parentName']
3. 传递方法时不能使用'驼峰命名',只能用'短横线分割命名'
@parent-say='say' -> this.$emit('parent-say')
组件中数据和方法的多级传递:
Vue中儿子想要使用爷爷的数据,则要一层一层往下传递
Vue中儿子想要使用爷爷的方法,则要一层一层往下传递
<template id='爷组件名'>
<div>{{name}}</div>
<button @click='aaa'></button>
<父组件名 :parent-name(自定义的接收名称)='name' @parent-method('自定义接收名称')='aaa'></父组件名>
</template>
<template id='父组件名'>
<div>{{parent-name}}</div>
<button @click='xxx'></button>
<子组件名 :parent-name1(自定义的接收名称)='parent-name' @parent-method1('自定义接收名称')='xxx'></子组件名>
</template>
<template id='子组件名'>
<div>{{parentname1(自定义的接收名称)}}</div>
<button @click='xxxx'></button>
</template>
组件中的插槽:
使用子组件时给子组件动态的添加内容,那么必须使用插槽<slot></slot>(相当于把slot比作一个坑,然后我们来填坑)
Vue2.6之后使用v-slot代替slot
插槽可以指定名称,默认情况下若没有指定名称,则称之为匿名插槽
匿名插槽的特点:有多少个匿名插槽,则填充的数据就会被拷贝几份(推荐只用一个匿名插槽)
<template id='子组件名'>
<div>{{parentname1(自定义的接收名称)}}</div>
<slot></slot>
</template>
<子组件名 :parent-name1(自定义的接收名称)='parent-name' @parent-method1('自定义接收名称')='xxx'>
<div>填坑的内容</div>
</子组件名>
具名插槽:
通过插槽的name属性给插槽指定名称
使用时可通过slot='name'方式指定当前内容用于替换哪一个插槽
<template id='子组件名'>
<div>{{parentname1(自定义的接收名称)}}</div>
<slot name='xxx'></slot>
</template>
<子组件名 :parent-name1(自定义的接收名称)='parent-name' @parent-method1('自定义接收名称')='xxx'>
<div slot='xxx'>填坑的内容</div>
</子组件名>
v-slot(缩写#)的用法:
<template v-slot:one(#one)>
<div>追加内容</div>
</template>
<slot name='one'></slot>
作用域插槽:
带数据的插槽(让父组件在填充子组件插槽内容时也能使用子组件的数据)
应用场景:子组件提供数据,父组件决定如何渲染
使用:
在slot中通过v-bind:数据名称='数据名称'方式暴露数据
在父组件中通过<template slot-scope='作用域名'> / <template v-slot:default='作用域名'>接收数据
在父组件的<template></template>中通过作用域名,数据名的方式使用数据
<template id='父组件名'>
<div>{{parent-name}}</div>
<button @click='xxx'></button>
<子组件名>
<template slot-scope='xxx'> //接收子组件插槽暴露的数据
<div>{{xxx.name}}</div>
</template>
</子组件名>
</template>
<template id='子组件名'>
<slot v-bind:name='name'>{{name(子组件的数据)}}</slot> //通过v-bind把name暴露给父组件
</template>
Vue Vuex共享数据
下载Vuex.js并引入(在引入Vuex前需要先引入Vue)
CDN引入:https://unpkg.com/vuex@3.6.2/dist/vuex.js
github下载:https://github.com/vuejs/vuex
创建Vuex对象
const store = new Vuex.Store({
state: { //相当于组件中的data,专门用来保存共享数据
count: 0,
...
},
mutations: {
increment (state) {
state.count++
}
}
})
<template id='父组件名'>
<div>{{this.$store.state.count}}</div>
<子组件名></子组件名>
</template>
<template id='子组件名'>
<div>{{this.$store.state.count}}</div> //使用Vuex中保存的共享数据时,要使用this.$store.state.xxx来获取数据
</template>
const store = new Vuex.Store({
state: {
count: 0,
...
}
})
Vue.component('父组件名',{
template:'...',
store:store, //祖先组件中添加store的key保存Vuex对象,其后代组件就可以使用Vuex中保存的共享数据
components:{
'子组件名':{
tenplate:'...'
}
}
})
修改共享数据:
const store = new Vuex.Store({
state: { //相当于组件中的data,专门用来保存共享数据
count: 0,
...
},
mutations: { //用于保存修改共享数据的方法
xxx (state) { //系统会自动给这些方法传递一个state的参数
state.count++; //统一修改共享数据
}
}
})
Vue.component('父组件名',{
template:'...',
store:store,
components:{
'子组件名':{
tenplate:'...',
methods:{
this.$store.commit(xxx(上面mutations中执行的方法)) //调用执行store中mutations的方法来修改共享数据
}
}
}
})
Vuex的getters属性:
const store = new Vuex.Store({
state: { //相当于组件中的data,专门用来保存共享数据
count: 0,
...
},
mutations: { //用于保存修改共享数据的方法
xxx (state) { //系统会自动给这些方法传递一个state的参数
state.count++; //统一修改共享数据
}
},
getters:{ //用于计算数据并返回(相当于computed)
xxx(){
...
return xxx
}
}
})
调用:<div>{{this.$store.getters.xxx}}</div>
Vue Vue-Router 路由
通过哈希(#/xxx)来切换组件的显示,还能在切换时传递参数
下载Vue-router.js并引入(在引入Vue-router前需要先引入Vue)
通过CDN引入:https://unpkg.com/vue-router/dist/vue-router.js
github下载:https://github.com/vuejs/vue-router
<template id='one'>
<div>第一个界面</div>
</template>
<template id='two'>
<div>第二个界面</div>
</template>
定义切换的规则(定义路由规则):
const routers=[
//每一个对象就是每一条规则
{path:'/one(自定义路径)',component:one(组件名)},
{path:'/two',component:two}
]
根据自定义的切换规则,创建路由对象:
const router = new VueRouter({
routes: routes
})
将创建好的路由对象绑定到Vue实例上:
new Vue({
el:'xxx',
router:router
})
使用:
<div>
<a href='#/one'>切换到第一个界面</a>
<a href='#/two'>切换到第二个界面</a>
//路由出口(路由匹配的组件会在此渲染)
<router-view></router-view>
</div>
Vue-router传递参数:
只要将Vue-router挂载到Vue实例对象上,我们就可以通过vue.$route拿到路由对象,从而能拿到传递的参数
传递参数的两种方式:
1. 通过rul参数(?key=value$key=value),在生命周期中通过this.$route.query获取
<div>
<router-link to='/one?name='xxx''>切换到第一个界面</router-link>
//路由出口(路由匹配的组件会在此渲染)
<router-view></router-view>
</div>
const one={
template:'#one',
created:function(){
console.log(this.$route.query)
}
}
2. 通过占位符传递(路由规则中的/:key,路径中的/value),在生命周期中通过this.$route.params获取
const routers=[
{path:'/one/:name',component:one},
{path:'/two',component:two}
]
<div>
<router-link to='/one/xxx'>切换到第一个界面</router-link>
//路由出口(路由匹配的组件会在此渲染)
<router-view></router-view>
</div>
const one={
template:'#one',
created:function(){
console.log(this.$route.params)
}
}
嵌套路由:
<template id='one'>
<div>
<p>我是第一个界面</p>
<router-link to='/one/onechild1' tag='button'>切换到第一个子界面</router-link>
<router-link to='/one/onechild2' tag='button'>切换到第二个子界面</router-link>
</div>
<router-view></router-view>
</template>
<template id='onechild1'>
<div>
<p>我是第一个界面的子界面1</p>
</div>
<router-view></router-view>
</template>
<template id='onechild2'>
<div>
<p>我是第一个界面的子界面2</p>
</div>
<router-view></router-view>
</template>
const one={
template:'#one',
components:{
onechild1:onechild1,
onechild2:onechild2
}
}
const routers=[
{
path:'/one',
component:one,
children:[
{
path:'onechild1',
component:onechild1
},
{
path:'onechild2',
component:onechild2
}
]
},
]
<div>
命名视图(router-view):
当路由地址被匹配时同时指定多个出口,并且每个出口中显示的内容不同
和匿名插槽一样,若指定了多个router-view,那么当路由地址被匹配后,多个router-view中显示的内容是一样的。若想要每个router-view显示不同的内容则使用name属性
<div>
<router-view name='name1'></router-view>
<router-view name='name2'></router-view>
</div>
<template id='one'>
<div>
第一界面
</div>
</template>
<template id='two'>
<div>
第二界面
</div>
</template>
const routers=[
{
path:'/',
components:{
name1:one;
name2:two
}
},
]
Vue-router的watch属性:
专门监听数据的变化,只要数据发生变化,就会自动调用对应数据的回调方法
不仅能监听数据,还能监听路由地址的变化
在企业开发中,还能通过watch来判断当前界面是从哪个界面跳转过来的
监听数据:
<div id='app'>
<input type='text' v-model='num1'>
<span>+<span>
<input type='text' v-model='num2'>
<span>=<span>
<input type='text' disabled v-model='res'>
</div>
let vue=new Vue({
el:'#app',
data:{
num1:0,
num2:0,
res:0
},
watch:{
num1:function(newValue,oldValue){
this.res=parseInt(this.num1)+paresInt(this.num2)
},
num2:function(newValue,oldValue){
this.res=parseInt(this.num1)+paresInt(this.num2)
}
}
})
监听路由地址变化:
<div>
<a href='#/one'>切换到第一个界面</a>
<a href='#/two'>切换到第二个界面</a>
</div>
let vue=new Vue({
el:'#app',
data:{
num1:0,
num2:0,
res:0
},
watch:{
"$route.path":function(newValue,oldValue){
console.log(newValue,oldValue); //前一个路由地址,转换后的路由地址
}
}
})
Vue 生命周期
生命周期的钩子函数:(在Vue实例对象和组件中都可添加对应的方法)
创建期间的生命周期方法:
beforeCreate(){} (实例创建前):实例初始化后(new Vue())被调用 (此时还没有初始化好data和methods属性)
created(){} (实例创建后):在这一步,实例已完成的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调 (此时data和methods属性已经初始化完,但没开始编译模板)
(此时开始编译模板,根据data中的数据和指令生成HTML,不过此时还没有渲染到页面上,还存在内存中)
beforeMount(){} (实例挂载前):相关的 render 函数首次被调用 (完成了模板的编译,但还没有挂载到页面上)
mounted(){} (实例挂载后):注意 mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick (已经将编译好的模板挂载到页面指定的容器中显示)
运行期间的生命周期方法:
beforeUpdate(){} (实例更新前):这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器 (此时data的状态是最新的,但是页面上显示的还是旧状态数据)
updated(){} (实例更新后):此时组件 DOM 已经更新,可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。注意 updated 不会保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 updated 里使用vm.$nextTick (此时data中的状态值和页面上显示的数据都已经完成了更新)
销毁期间的生命周期方法:
beforeDestroy(){} (实例销毁前):在这一步,实例仍然完全可用(是最后能够访问到组件数据和方法的函数)
destroyed(){} (实例销毁后):实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁,无法使用
Vue 特殊特性
在Vue中可通过ref来获取DOM元素
ref的使用:
<div>
<button @click='change'></button> //通过此按钮获取DOM元素和组件
<p ref='myp'>原生DOM</p>
<one ref='myone'></one>
</div>
<template id='one'>
<div>我是组件</div>
</template>
Vue.component('one',{template:'#one'})
new Vue({
methods:{
change(){
console.log(this.$refs.xxx(ref名))
}
}
})
Vue渲染组件的两种方式:
1. 先定义注册组件,然后再Vue实例中当做标签来使用(此方式会把组件渲染到对应的控制区域中)
2. 先定义注册组件,然后通过Vue实例的render方法来渲染(此方式会直接利用组件替换掉控制的区域中)
使用render来渲染组件:
<template id='one'>
<div>我是组件</div>
</template>
new Vue({
render:function(createElement){
let html=createElement('one');
return html;
}
})
Vue Vue-cli 脚手架
会搭建好一套利用webpack管理vue的项目结构
安装Vue-cli:
在cmd中输入命令:npm install -g @vue/cli
查看是否安装成功的命令:vue --version
在cmd中通过以下命令使用Vue-cli:
创建项目:vue create xxx(项目名)
然后到下一步
第一个是Vue2.0的默认配置
第二个是Vue3.0的默认配置
第三个是手动配置
(若选择的是默认配置的,则直接等待项目创建完毕即可)
若选择的是手动配置,则跳转到下图:
可选择如图选中的几项(也可按需自行选择)
Choose Vue version:选择Vue版本
babel:可以让我们在当前的项目中随意的使用这些新最新的es6,甚至es7的语法。就是把各种JavaScript千奇百怪的语言统统转为浏览器可以识别的语言
TypeScript:支持所有的JavaScript语法,还有一些扩展的语法,在编译期会去掉类型和特有语法,生成纯粹的JavaScrip。他的优点有:静态类型检查,IDE 智能提示,代码重构,可读性
PWA Support:间接式的网页增强
Router:路由
Vuex:状态管理
CSS Pre-processors:预编译的CSS
Linter/Formatter:代码的检测/格式化
Unit Testing:单元测试
E2E Testing:E2E测试
然后到下一步,跳转到下图(一步一步进行选择,也可按需自行选择):
是否使用history路由模式:Y
选择css预处理器:Sass/Scss(dart-sass)
把Babel,PostCSS等配置放到哪里:In package.json
需不需要将这些配置做为预设:no
然后就等待项目创建完毕,跟着其提示进入创建好的Vue网站即可
创建完项目后,会自动生成一个项目的文件夹
(在Vue2.x中生成的项目结构中我们能看到build文件夹和config文件夹,但在Vue3.x以后生成的项目结构中已经没有了build文件夹和config文件夹)
每个文件夹对应的作用:
1. node_modules文件夹:存储了以来的相关包
2. public文件夹:任何防止在public文件夹的静态资源都会被简单的复制,而不经过webpack,你需要通过绝对路径来引用它们。一般用于存储一些永远不会改变的静态资源或webpack不支持的第三方库
3. src文件夹:代码文件夹。
其子文件夹及文件:assets文件夹:存储项目中自己的一些静态文件(图片/字体等)
components文件夹:存储项目中的自定义组件(小组件,公共组件)
views文件夹:存储项目中的自定义组件(大组件,页面级组件,路由级别组件)
router文件夹:存储VueRouter相关文件
store文件夹:存储Vuex相关文件
App.vue:根组件
main.js:整个项目的入口(创建整个项目入口的Vue实例,并绑定相关的东西)
打包项目的命令:npm run build(打包后的东西会放到新建的dist文件夹)
运行项目的命令:npm run serve
Vue文件的基础模板:
//template用于编写当前组件的结构代码
<template>
...
</template>
//script用于编写当前组件的业务代码
<script>
export default{
name:'',
data(){
return xxx
},
...
}
</script>
//style用于编写当前组件的样式代码(设置scoped是规定这些样式只对这个区域(组件)的元素有效)
<style scoped>
...
</style>
在项目中使用Vuex进行数据状态管理:
在store文件夹中新建index.js文件,并编写以下内容:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store=new Vuex.Store({
state:{
name:'xxx'
}
})
export default store
然后在main.js文件中导入store:import store from './store/index.js'(store的路径)
并添加到Vue上:new Vue({
store:store
})
在项目中使用Vue-router配置相应的路由:
在router文件夹中新建一个index.js文件,并编写以下内容:
import Vue from 'vue'
import Router from 'vue-router'
//导入相应的组件
import one from '../components/one.vue' (组件的路径)
import two from '../components/two.vue'
Vue.use(Router)
const routers=[
{path:'/one',component:one},
{path:'/two',component:two},
]
const router=new Router({
routers
})
export default router
然后在main.js文件中导入router:import router from './router/index.js'(router的路径)
并添加到Vue上:new Vue({
router:router
})
在Vue-cli创建的项目中修改webpack配置:
可新建vue.config.js文件的方式来修改配置,然后通过此文件中的configureWebpack属性来新增webpack配置
大概编写内容:(具体可查看vuejs官网的配置参考)
const path=require('path')
const webpack=require('webpack')
module.exports={
//为了方便起见对webpack原有的属性进行一层封装,若我们需要修改webpack的配置,那么可以在项目中新建一个vu.config.js的文件,然后去查询Vue-cli的封装是否能满足我们的需求,若满足我们的需求,就可以使用Vue-cli封装的属性来修改webpack的配置
outputDir:'bundle';
//若不可以满足我们的需求,那么我们可通过configureWebpack的属性来编写原生的webpack配置
configureWebpack:{
plugins:[
new webpack.BannerPlugin({ //添加版权信息的注释
banner:'xxxx(信息内容)'
})
]
}
}
Vue ELementUI
是一款基于Vue的桌面端UI框架
官网:https://element.eleme.cn/#/zh-CN
(在官网中可查看各组件的用法,代码)
ElementUI的安装:
npm安装:npm i element-ui -S
ElementUI的引入:
1. CDN引入:<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">(引入样式)
<script src="https://unpkg.com/element-ui/lib/index.js"></script>(引入组件库)
2. 在main.js文件中引入ElementUI:
//完整引入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI); //告诉Vue要使用ElementUI
对ElementUI的优化:
因为在默认情况下,无论有没有使用到某个组件,在打包时都会将elementUI中所有的组件打包到我们的项目中,对导致项目体积比较大,用户访问比较慢,所以需要进行优化
优化:按需导入,按需打包
需要安装安装 babel-plugin-component的插件:npm install babel-plugin-component -D
配置babel.config.js文件:
module.exports = {
presets: [
['@vue/cli-plugin-babel/preset',{ "modules": false }]
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
按需导入:import {Row,Button,...(需要用到的组件)} from 'element-ui';
Vue.use(Row)
Vue.use(Button)
MintUI:
一款基于Vue的移动端UI框架
官网:https://mint-ui.github.io/docs/#/zh-cn2
MintUI安装:npm i mint-ui -S
MintUI的引入:
1. CDN引入:
<link rel="stylesheet" href="https://unpkg.com/mint-ui/lib/style.css">(引入样式)
<script src="https://unpkg.com/mint-ui/lib/index.js"></script>(引入组件库)
2. 在main.js文件中引入MintUI:
//完整引入
import MintUI from 'mint-ui'
import 'mint-ui/lib/style.css'
Vue.use(MintUI)
3. 也可按照ElementUI一样按需引入
不过在使用组件时不能用Vue.use(),要用Vue.component()
e.g. import {Button,Switch} from 'mint-ui';
//以下步骤和按需引入ElementUI有区别
import 'mint-ui/lib/style.css'
Vue.component(Button.name,Button)
Vue.component(Switch.name,Switch)
Vant:
也是一款基于Vue的移动端UI框架(相比于MintUI,推荐使用这个)
官网:https://youzan.github.io/vant/#/zh-CN/
Vant的安装:
Vue 2 项目,安装 Vant 2:npm i vant -S
Vue 3 项目,安装 Vant 3:npm i vant@next -S
Vant引入:
1. CDN引入:<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@2.12/lib/index.css"/>(引入样式文件)
(引入 Vue 和 Vant 的 JS 文件)
<script src="https://cdn.jsdelivr.net/npm/vue@2.6/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vant@2.12/lib/vant.min.js"></script>
2. 按需引入:
安装babel-plugin-import:npm i babel-plugin-import -D
配置babel.config.js文件:
module.exports = {
{
presets: [
'@vue/cli-plugin-babel/preset'
],
"plugins": [
["import", {
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}]
]
}
}
import {Button,...} from 'vant';
import 'vant/lib/button/style';
Vue.use(Button)
Vue 有关Plugin
Vue.use()的作用是注册一个Vue插件(注册插件),必须在new Vue之前使用
若想要通过Vue.use的方式来注册组件,那么必须先将组件封装成插件,然后再Vue.use(引入的插件名)
封装成插件:在src文件夹中新建plugin文件夹,在此目录下新建对应的文件夹(文件),然后把编写好的vue组件放进来,再在同级目录创建一个JS文件
在此JS文件中编写:import Vue from 'vue'
import xxx from '对应组件的路径'
export default {
install:function(){
Vue.component(xxx.name,xxx);
}
}
什么时候需要定义插件?
当某一个组件或者功能经常需要被使用到时,我们就可以将这个组件或者功能定义成一个插件
定义插件的相关文档:https://cn.vuejs.org/v2/guide/plugins.html
获取数据时的跨域问题
新建文件vue.config.js并编写相关的代理配置:
module.exports={
devServer:{
proxy:{ //配置代理
'xxx':{ //凡是以xxx开头的请求,进行代理
target:'xxxx', //代理的目标域
//代理开始时,开发服务器向服务器发送请求时会运行此函数
onProxyReq(proxyReq, req, res) {
// add custom header to request(添加消息头(分别是origin,referer),在控制台中的Headers有显示)
proxyReq.setHeader('origin', 'xxxx');
proxyReq.setHeader('referer', 'xxxx');
}
}
}
}
}
以上是对Vue的部分整理和总结,希望有用,有什么建议欢迎提出哦!
大家一起进步~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。