初识VUE
一、vue的基础概念
1. 是一套用于构建用户界面的渐进式框架
2. 渐进式框架
类库或者框架都是都是重量级的,里面包含很多方法,但是实际项目中我们用不到这么多,所以在开发他们的时候,会根据功能分模块开发,使用者按需导入。
二、 vue全家桶
1. vue + components(vue element/iview...) + vue-router + vuex + vue-cli
2. VUE基础模块:包含基础语法,核心实现,组件开发,相关指令等都在这里
3. vue-router:构建SPA单页面用的路由
4. vuex:公共状态管理
5. vue-cli:vue脚手架
6. components:vue element、iview、vuex
三、 vue的特点
1. 易用
2. 灵活
3. 高效
四、 开发思想
1. 传统操作DOM模式
2. MVC
-
model view controller
MVC是单向数据绑定,数据更该可以重新渲染视图,但是视图重新渲染不会引发数据的更改
-
REACT
需要我们在控制层基于change事件实现数据的更改
- MVVM
- model view viewmodel
- MVVM是双向数据绑定:VUE本身实现了数据和视图的相互影响和监听。
五、VUE的MVVM的思想
1. M
model :数据层
2. V
view:视图层
3. VM
viewModel:数据和视图的监听层
4.当视图或者数据发生改变时,VM层会监听到,同时把另外一层也跟着重新渲染或者改变
例如:数据层改变 --- vm会帮我们重新渲染视图 视图层改变 --- vm会帮我们重新更改数据
VUE的基础语法
定义
每当创建一个VUE实例,就相当于创建了一个viewmodel监听器:可以监听对应视图和对应数据的相互改变
使用
引入
`<script src="./node_modules/vue/dist/vue.js"></script>`
注:引入的时候尽可能引用为压缩的版本,这样有错误会抛出异常
`let vm(变量) = new Vue({
el:"#app(选择器)",
data:{
属性名:属性值
}
methods:{
属性名:属性值
}
})
`
1. el
- element:当前监听器监听的视图
- 相当于告诉VUE,只处理当前视图下的内容
- 不能是html和body这两个元素
2. data
当前监听器监听的数据(这些监听的数据会挂载到VM的实例上,也就是可以用vm.xxx=xxx来操作了);
当数据更改时,视图会自动更新
3. mothods
放视图中需要使用的方法
注意
-
data上的属性和methods上的属性都会挂载到vm实例上
-
data中的属性名和methods中属性名不能重复
-
data 写的都是vue的变量,methods写的都是vm用到的方法
-
methods中方法里的this指的都是当前的实例,data中的this 不是当前实例
小胡子语法
1. mustache
2.使用
{{数据变量}}
3.注意
-
只能编写表达式(变量,赋值表达式,函数执行,三元表达式等)
-
不能写语句(比如for循环,if 判断)
-
小胡子中最终展示出来的就是表达式的结果
-
在小胡子语法中绑定的是对象类型时,会基于JSON,stringfy把其编译成字符串在再呈现出来(而不是直接用tostring处理)
视图自动渲染
原理
视图和数据更新的前提是这个数据被VUE劫持了(对应数据有 get 和 set ),并不是所有数据更改后都会通知视图渲染。
情况一
初始值是一个对象
对象中没有健值对,后期新增的健值对是不会让视图重新渲染的(但是数据会变)
解决
-
增加一个无关变量,每次更数据的时候把这个无关变量也更新,
例如: 初始值: t:0,更新数据时同时设定 t = Math.random()
原理:虽然没有渲染视图,但是数据已经更改,手动触发视图渲染
-
最好在初始化数据的时候,就把视图需要的数据提起申明好
例如:c : undefined(虽然是空值,但是要有这个属性)
原理:只有data中初始化过的属性才有get和set
-
不要修改某个属性名,而是把对象的值整体替换(指向新的堆内存)
例如:可以使用Object.assign() ----> 注意要把新对象放在前面
也可以使用...obj,把原对象在新对象中展开。
原理:整个对象的替换,没一会后代属性都会被劫持。
- 可以基于vm.set(对象,属性名,属性值)
- $forceUpdate强制通知视图重新渲染,但是不设置set和get
情况二
如果初始值是一个数组
我们修改数据基于arr[n]=xxx或者arr.lenght等操作方式是无法让视图重新渲染的。
解决
- vm.$set
- push, shift, unshift, pop, splice, sort, reverse ---这7个方法可以触发视图重新渲染
- 重新把arr的值重写(指向新的对内存)
VUE的常用指令
定义
- directive
- 在Vue中v-xxx的行内属性,我们统称为vue指令,这些指令实际上都是基于自定义属性设置的,只不过在VUE上有特殊的含义
- 当vue加载成功并进行处理的时候,会按照相关的规则解析和渲染视图,遇到对应的指令实现对应的功能
有原生对应的
v-text
- 定义: 给非表单元素设置内容,相当于小胡子语法,v-text会把所有内容都当作文本
- 使用: v-text='变量'
- 场景: 传统的胡子语法,在vue没有加载完成之前,会把{{xxx}}展示在页面中,当vue加载完成以后才会出现真正的内容,这样用户体验不好,可以使用这种方法代替小胡子语法
v-html
- 定义:给非表单元素设置内容,相当于原生的innerHTML,v-html支持对于标签的自动识别
- 使用:v-html='变量'
- 注意:只在可信任的内容上使用该指令,用户可以操作的一般不添加这个指令
v-for
-
定义:循环展示标签-- 想循环谁就给谁设置v-for
-
使用:v-for='(值,index)'in vue 变量
1. 值代表数组中的当前项 2. index 是对应的索引 -
注意:
1. 值和key 是自己定义的变量,两个都写需要加上()。 2. 这两个变量只能用在当前使用v-for的标签上及其内部 -
应用范围:可以用来循环数组,字符串,数字,对象,多用于数组。
无原生对应的
v-bind
- 定义:给元素的内置属性动态绑定数据,例如:给img绑定动态的图片路径地址
- 使用:v-bind:src='变量',可以简写为:src=' 变量'
- 注意:是专门用来处理行内属性(src class style...)的指令,一般简写成 冒号(:)
v-cloak
-
定义:为了解决小胡子的显示问题
-
使用:v-cloak,需要配合css使用,有这个v-cloak行内属性的元素会有这个样式(属性选择器)
[v-cloak]{display:none;} -
原理:当vue模版渲染之前,执行的是普通的html,这时,我们写的css样式起作用,让元素display:none,就看不见小胡子了 ,当模版渲染完成以后,vue会自动把这个行内属性移除,这时在页面上展示的是渲染好多的html了。
v-once
- 定义:绑定的数据是一次性的,后面不论数据怎么改变,视图都不会重新渲染
- 使用:v-once
- 注意:vue对有这个指令的元素只渲染一次
v-pre
- 定义:告诉vue这个标签及其子标签都不用vue渲染
- 使用:v-pre
- 作用:可以用来提神vue的变异效率
if、show、model
v-if
-
定义:控制当前元素是否在结构中加载(它控制的是组件的加载和卸载操作 = > DOM的增加和删除)
-
使用:v-if= ‘判断体’
-
注意:如果判断体的结构是true,当前元素会在架构中显示,如果是false ,当前元素会在结构中移除,还有对应的 v-else-if/v-else等指令,如原生的if...else if...else使用时中间不能穿插其他标签
-
扩展:template
-
定义:多个元素条件一样,又不想在结构中添加父标签,把他们包裹起来
-
使用: 、 let vm = new Vue({
el:'#app',
template:'标签',
data:{......} }).$mount('#app');
、 -
解析:在这里template的作用:指定当前渲染的模版
顺序:可能性1: 步骤1. 是否指定“el”(元素)选项? --> 是 --> 执行步骤2. 则继续询问是否指定“template”(模版)选项?--> 是 --> 则将template编译,到render函数中渲染,-->否 --> 将el外部的HTML作为template编译。
可能性2:步骤1. 是否指定“el”(元素)选项?--> 否 --> 当调用vm.$mount(el)函数时。执行步骤2.
-
v-show
- 定义:基于display:none控制元素的显示隐藏
- 使用:v-show = ‘变量’
- 注意:v-show后边跟的是一个布尔类型(其他类型会默认转成布尔),true是显示,false是display:none.
v-if 和 v-show 的区别
-
条件不成立时:v-if是整个标签都不加载,v-show是加载了标签的同时设置了display:none的属性。
-
性能消耗方面:v-if有比较大的切换开销,v-show有比较大的初始加载开销(频繁切换时用v-show)
-
在过于频繁的切换操作中,v-if明显要比v-show性能低一些
v-model
-
定义:一般给表单元素设置的,实现表单元素和数据之间的相互绑定
-
使用:v-model = '变量'
-
效果:视图 〈=〉数据:数据改变表单元素的内容跟着改变,内容改变,数据也会跟着更新。
-
原理:
- 先把数据绑定给表单元素,一般把数据赋值给表单元素的value.
- 监听表单元素的内容改变
- 内容改变后,会把对应的数据也改变
- 对应的数据改变,视图中所有用到数据的地方都会重新渲染
表单v-model的使用
- 定义:
- 按照v-model进行分组,单选框准备的数据是一个值,复选框准备的数据是一个数组。
- 每一个框都有自己的value,谁被选中,数据值就是被选中元素的value值;相反,值是多少,对应value的元素也会被默认选中;
- 例如:
-
复选框
-
`
<input value='1' type="checkbox" v-model='ary'>
<input value='2' type="checkbox" v-model='ary'>
<input value='3' type="checkbox" v-model='ary'> `
@1 ary:[1,3]:就是默认选中value为1和3的元素
@2 ary中存放的是被选中的那些checkbox的value值
2. 单选框 `
<input type = "radio" value='女' v-model = 'sex'>`
@1 sex:'女':就是默认选中的是value为女的元素
@2 sex存储就是选中的那个radio的value值
3. 下拉框 ` <option value = 'rice'>米</option> <option value = 'apple'>苹果</option> <option value = 'banana'>香蕉</option> </select> ` @1 food存储的是里边option对应的value值 @2 food :'rice':默认展示的是value为rice的元素 vue中的事件处理 指令 定义:用来实现时间绑定的指令 原理:DOM2级事件绑定 使用:v-on 简写 @ 使用 语法:v-on:事件类型=‘函数’ ,简写 @事件类型=‘函数’ 注意: 例如:v-on:click=‘fn’---> @click='fn' 绑定的函数从是否传参、是否需要事件源,可以分为以下几种情况: fn : 不需要传参的时,小括号可以省略 fn(a,b) : 需要传递实参时,注意形参和实参的个数相对应 fn(event,a,b) : 实参中event位置不限,无论在第几个都代表事件对象。需要使用时间对象的时候,用$event接收事件对象,且只能用其接收。 常规修饰符 @xxx.prevent = '函数名' : 阻止了默认行为,相当于原生:preventDefault @xxx.stop = '函数名' :阻止冒泡传播,相当于原生:stopPropergation @xxx.once = '函数名':当前函数只会执行一次 @xxx.self = '函数名':只有点击元素本身才会出发这个函数 @xxx.capture = '函数名':控制函数在捕获阶段执行,相当于原生:ele.addEventListener('click',函数名,{capture:false}) @xxx.passive = '函数名',先执行默认,再执行函数 ele.addEventListener('click',函数名,{capture:false,passive:true}) passive:true 优先执行默认 注意: 修饰符可以串联:例如: 使用修饰符串联时,顺序很重要:用v-on:click.prevet.self会阻止所有的点击;而v-on:click.self.prevent只会阻止对元素自身的点击 按键修饰符 使用:@keydown.按键(或键盘码)= ‘函数名’ 支持以下按键、及其对应的键盘码@keydown = '函数名'操作 .enter .tab .delete(捕获“删除”和“退格”键) .esc .space .up/.down/.left/.right filter过滤器方法 基础 作用:把需要在视图中渲染的数据进行二次或者多次的处理 语法: 过滤器应该被添加在JavaScript表达式的尾部,有‘管道‘符号指示 在哪里定义过滤器 可以在一个组件的选项中定义本地的过滤器 filters:{ capitalize:function(value){ if(!value)return'' value = value.toString() return value.charAt(0).toUpperCase()+ value.slice(1) } } 在创建Vue实例之气全局定义过滤器 Vue.filter('capitalize',function(value){ if(!value)return'' value = value.toString() return value.charAt(0).toUpperCase()+vaule.slice(1) }) new Vue({ //.... }) 注意 当全局过滤器和局部过滤器重名时,会采用局部过滤器 过滤器中的方法没有挂载到实例上:this是window 过滤器方法的名称可以和methods中的方法名称一样,不会冲突; 过滤器用在两个地方 双括号插值:{{message|capitalize}} v-bind表达式(从2.1.0+开始支持)---> 使用:过滤器函数总接收表达式的值(之前的操作链的结果)作为第一个参数 举例:拿上面的双括号插值的表达式举例:capitalize过滤器函数将会抽到message的值作为第一个参数。 过滤器可以串联 单参数:例:{{message|filterA|filterB}} filterA被定义为接收单个参数的过滤器函数,表达式message的值将作为参数传到函数中。 然后继续调用同样被定义为接收单个参数的过滤器函数filterB,将filter A的结果传递到filterB中。 多参数:例:{{message|filterA|('arg1','arg2')}} filterA被定义为接收三个参数的过滤器函数 其中message 的值作为第一个参数,普通字符串‘arg1’作为第二个参数,表达式arg2的值作为第三个参数。 computed计算属性 定义 定义:它不是一个方法,是一个属性,所以在视图中调取的时候不能加括号执行 注意:computed中的属性会挂在到实例上,函数中的this是当前实例,它存储的是对应方法返回的结果(getter函数处理的结果) 特点 计算属性是基于它们的响应式依赖进行缓存的,只要依赖不发生改变,这个函数就不会执行 依赖:就是在这个函数中使用的this上的属性(必须是同步使用) 例如: <h2>{{personType}}</h2> data:{ name:'巴拉巴拉', age:0 }, computed:{ personType(){ comsole.log(this.name) return this.age >= 18?"adult":"child" } }, 只要age和name没有发生改变,计算属性都会立即返回之前的计算结果,而不必再次执行函数。 应用场景 当小胡子中用到比较复杂的表达式的时候,我们一般用计算属性先处理一下,把计算属性的返回结果放在小胡子中 使用:有明显的依赖关系时,建议使用计算属性 computed和methods区别 methods : 是方法,可以传递参数 每一次重新渲染都会执行 computed: 是属性,不能传递参数 有明显的依赖关系,在依赖不变时,不让对应的方法执行 计算属性中必须要关联一个响应式数据,否则getter函数只执行一次 异步的时候,尽量不要选用computed computed 全写 计算属性默认只有getter,不过在需要时也可以提供一个setter 简写:相当于只写了getter函数 computed:{ //=>getter函数 reverseComputed(){ return this.text.split('').reverse().join(''); } } 全写 reverseComputed:{ get(){ //=>getter:只要获取这个属性值就会触发get函数执行 return this.text.split('').reverse().jion(''); }, set(value){ //=>setter:给函数设置属性值时会触发set函数,value是给这个函数设置的值 console.log('ok',value); } } } getter函数:只要获取这个属性值就会出发get函数执行 setter函数:给属性设置值的时候会触发set函数 watch 监听 定义 watch监听响应式数据的改变 语法 data:{ name:"Max", obi:{ a:123 } }, watch:{ name(newV, oldV){ //newV本次新值,oldV上次的老值 //监听name,此时的name是值类型 console.log(newV. oldV) }, obj(newV,oldV){ //此时的obj是应用数据类型,只有obj这个变量对应的值改变时,才会出发这个函数 //obj里面的内容改变的时候,这个函数没有反应 console.log(newV,oldV) } } 引用类型的深度监听 deep:true 深度监听 immediate:true 刚进来时先执行一次handler回调函数 handler函数:有两个参数,newV,oldV obj:{ deep:true,//深度监听 immediate:true,//刚进来时先执行一次handler回调函数 handler(newV,oldV){ //这个函数名必须叫做handler //深度监听一个对象的时候,这两个形参对应的都是新值 console.log(newV,oldV) } } 作用 当需要在数据变化时执行异步或开销较大的操作时应用监听器 特点 watch中监听的响应式数据必须在data中初始化 只要属性发生改变,里边对应的函数就会执行 watch可以编写异步操作:computed中的getter不支持异步获取数据 和computed中setter类似 只不过computed是自己单独设置的计算属性(不能和data中的冲突) 而watch只能监听data中有的属性 双向数据绑定---数据驱动视图 声明式和命令式 命令式编程 定义:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现 例如:for循环 声明式编程 定义:告诉‘机器’你想要的是什么(what),让机器想出如何去做(how) 例如:数组内置方法forEach等 两大函数 Observe函数 劫持并监听所有属性:在Observe函数中:完成getter和setter操作 本质: vue2.0:主要采用Object.defineProperty() vue3.0:主要采用Proxy() 区别: Proxy 优势在于不用每一个属性都进行劫持,他是整个对象的劫持 defineProperty:是一个一个劫持 监听后根据属性的变化,进行相关处理 complie函数 进行视图的处理 实现双向绑定的步骤 实现一个监听器Observer: 对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()对属性都加上setter和getter. 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。 实现一个解析器Compile: 解析Vue模版指令,将模版中的变量都替换成数据,然后初始化渲染页面视图 并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。 实现一个订阅者Watcher: Watcher 订阅者是Observer和Complie之间通信的桥梁 主要的任务是订阅Observer中属性值变化的消息,当收到属性值变化的消息时,,触发解析器Complie中对应的更行函数 实现一个订阅器Dep: 订阅器采用发布-订阅设计模式,用来收集订阅者Watcher,对监听器Observer和订阅者Watcher进行统一管理 Observe原理模拟 观察者:把数据进行劫持,对对象的深层次处理 function observer(obj){ if(obj && typeof obj ==="object"){ for(let key in obj){ if (!obj.hasOwnProperty(key))break; defineReactive(obj,key,obj[key]); } } } 数据劫持 function defineReactive(obj,attr,value){ observe(value); Object.defineProperty(obj,attr,{ get(){ return value; }, set(newValue){ observer(newValue )r; if(newValue === value)return; value = newValue; } }); } Object.defineProperty() 作用 可以监测数据的改变 语法 Object.defineProperty(obj,prop,descriptor) obj:要定义属性的对象 prop:要定义或修改的属性的名称 descriptor: 要定义或修改的属性描述符(是一个对象) 属性描述符 数据描述符:数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的 存取描述符:存取描述符是有getter 函数和setter函数所描述的属性 注意:一个描述符只能是这两者其中之一,不能同时是两者,这两种描述符都是对象 属性描述符-共享键值对 默认值是值在使用Object.defineProperty()定义属性时的默认值 enumerable 默认值:false 定义:当且仅当该属性的enumerable键值为true时,该属性才会出现在对象的枚举属性中 enumerable定义了对象的属性是否可以在for...in循环和Object.keys()中枚举 configurable 默认值:fasle 定义:当且仅当该属性的configurable键值为true时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除 数据描述符可选键值 value 默认值:undefined 定义:该属性对应的值,可以是任何有效的JavaScript值(数值,对象,函数等)。 writable 默认值:false 当且仅当该属性的writable键值为true时,属性的值,也就是上面的value,才能被赋值运算符改变。 存取描述符可选键值 get 默认值:undefined 属性的getter函数,如果没有getter,则为undefined 特点: 当访问该属性时,会调用吃函数 执行时不传入任何参数,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象) 该函数的返回值会被用作属性的值 set 默认值:undefined 属性的setter函数,如果没有setter,则为undefined 特点: 当属性值被修改时,会调用此函数 给方法接收一个参数(也就是被赋予的新值),会传入赋值时的this对象。 proxy()构造函数 定义 监听某个对象,把要监听的对象克隆一份给Proxy代理,愿对象没有变化 该Proxy()构造函数用于创建Proxy对象 语法 new Proxy(target,handle) target :要包装的目标对象Proxy.它可以是任何类型的对象 handler:一个对象,其属性是定义对Proxy执行操作时的行为函数 区别 Proxy 优势在于不用每一个属性东欧进行劫持,他是整个对象的劫持 defineProperty:是一个一个劫持 特点 只有通过实例获取,才能触发 视图驱动数据的原理 视图更新无外乎就是表单元素的处理 v-model 首先可以把响应数据绑定在文本框中,并且可以监听文本框内容的改变 内容改变后会修改响应数据,响应数据一更改,视图还会重新渲染 v-molde 原理模拟 let data = { text:'hello' }; let temp = { ...data }; Object.defineProperty(data,'text',{ set(newValue){ temp.text = newValue; render(); } }); 根据数据去渲染视图 function render(){ inpBox.value = temp.text; conBox.innerHTML = temp.text; } render(); 视图更新控制数据的更新 inpBox.addEventListener('input',function(){ let val = inpBox.Value; data.text = val; }); class和style绑定 vue 思想转变 页面中的东西想要变,先到JS中准备数据 在基于数据去渲染视图,后期直接改数据即可 动态控制class类名 静态样式直接编写即可: <div class="box" style="margin :20px auto;"> 在项目中基于业务需求动态控制元素样式的时候,需要一些特殊的处理 对象的处理方式 直接写在结构上 语法: :class="{样式类名:响应式数据,...}" 响应式数据为true则有这个样式类,反智则没有, 例如: <div class = "static" v-bind:class="{active:isActive,'text-danger':hasError}"> </div> 和如下data : data:{ isActive:true, hasError:false } 结果渲染为: <div class="static active"></div> 注意:v-bind:class指令也可以与普通的class共存,如上 写在响应数据中 语法: :class="响应数据(对象)" 例如: <div v-bind:class="classObject"></div> data:{ classObject:{ active:true, 'text-danger':false } } 渲染的结果为: <div class="active"></div> 写在计算属性中 语法::class="返回值时对象的计算属性" -->这里绑定一个返回对象的计算属性 例如: <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' } } } 这是一个常用且强大的模式 数组控制样式类 语法: 1. :class = "[响应式数据1,...]" 2. 控制响应式数据的值是对应的样式类或者没有值,来控制是否有这个样式 例如: <div v-bind:class="[activeClass,errorClass]"></div> data:{ activeClass:'active', errorClass:'text-danger' } 渲染为: <div class = "active text-danger"></div> 注意 1. 如果想根据条件切换列表中class,可以用三元表达式:较为繁琐 <div v-bind:class="[isActive?activeClass:'',errorClass]"></div> 2. 在数组语法中也可以使用对象语法: < div v-bind:class="[{acytive:isActive},errorClass]"></div> 动态控制style样式 与动态控制class类名基本一致 注意:可以用驼峰式(camelCase)或烤串式命名法(kabab-case) Vue 生命周期函数 定义 生命周期函数(钩子函数),是根据VUE的整个渲染机制,在渲染的咩一个关节点上,提供对应的函数,让我们进行一些相关的操作 创建实例阶段 从new Vue()开始:代表我们准备创建一个实例,让这个实例来完成双向处理操作 初始化事件和生命周期管控 beforeCreat() 作用时期:创建实例之前:实例还没有创建出来呢,此时实例都没有(或者数据都没有挂载呢),做什么都没用 应用: 根据一些信息的判断,决定是否继续向下渲染,如果不需要向下,则跳转到其他的组件 可能会做一些组件是否渲染的权限校验(路由中的导航守卫可能更适合干这个事情) 初始化data/methods/filter...等数据的处理 此时我们的实例才算创建完成 complie阶段 created() 作用时期:实例创建完成:data/methods等已经挂载到实例上了 场景: 页面中的数据一般需要从服务器中获取,动态绑定 因为获取数据是异步的,在没有获取数据之前,我们就会向下渲染组件 这样导致第一次渲染组件其实是没有拿到数据的; 拿到数据后通过修改数据控制组件重新渲染 应用:为了让页面展示数据的速度更快一些,我们尽可能把获取数据提前(一般都是在created发送一部请求数据) 实例创建好后,查找是否指定了“el” 否:继续查找是否执行vm.$mount(el)函数,-->是:相当于指定了el 是:查找是否有template(因为组件用的是template) 是:将template编译到了render函数中 否:将el外部的HTML,作为template编译 beforeMount() 作用时期:第一次渲染DOM之前:此时页面中还没有DOM元素呢 应用: 基本不做什么事,有的人也会在这里发送异步数据 第一次渲染完成 把template语法编译(生成一个虚拟DOM vm._vnode) 原理:把template编译为虚拟的DOM对象,是基于一个插件(vue-template-loader),也是基于webpack处理的 vue底层开始把虚拟DOM 变成真实的DOM (DOM 对象【don diff】,最后把真实DOM渲染到页面中) mounted() 作用时期:第一次渲染DOM之后,:真实DOM渲染完了 应用:第一次页面已经渲染完了,页面中已经有我们想要的DOM信息了,所以我们经常在这里做一些DOM的处理或者监听工作的 当data修改时 beforeUpdate() 作用时期:修改新的DOM之前,新数据渲染之前 应用:基本不做什么事 重新生成虚拟DOM 拿本次生成的虚拟DOM与上次的虚拟DOM进行比较,找到两次的区别(深度优先原则) 将本次与上次不一样的地方,生成补丁包【dom diff】 把这些补丁生成真的DOM,放在页面进行重新渲染 updated() 作用时期:修改新的DOM之后,新数据渲染之后 应用:和 mounted类似 注意:但是这里不能做一件事:修改响应数据(这样会导致修改操作的死循环) 组件(实例)销毁 beforedestroyed() 作用时期:销毁之前 应用:该保存保存,该提示提示,一些事情此时不处理以后就你没有机会了 销毁的时候,页面已经渲染的真实DOM不动(路由跳转除外),其他之前挂载的东西都消失了 destroyed() 作用时期:销毁之后 应用:基本不做什么事 vue中的组件基础 定义 不论是vue还是react,我们后期都是基于组件化开发的 vue中组件是可复用的Vue实例,且带有一个名字,我们可以通过new Vue创建的Vue根实例中,把这个组件作为自定义元素来使用 特点 每一个组件就是一个自定义标签 可复用 方便维护,方便拆分 每个组件作用域隔离(互不干扰) 有完整的生命周期 有自己的响应式数据和各种方法(事件) 组件优点 基于组件开发,我们提取公共的组件(封装) 有助于实现代码或者组件的服用 方便项目快速开发 而且能够构建敏捷化开发平台 方便团队协作开发:拆分:单独开发,单独维护 在vue中每一个组件都是一个独立的个体 都是一个单独的实例 都有自己的data/methods/生命周期等 两种命名规范 kebab-case模式(烤串法) 特点:当使用kebab-case定义一个组件时,在引用这个自定义元素时只能还是这种方式调用 例如:base-info 调用 <base-info></base-info> PasalCase模式(驼峰法) 特点: 当使用PasalCase定义一个组件时,在引用这个自定义元素时两种方法都可以用 页面在vue中渲染的时候,所有组件大写的名字都会变为小写 例如:BaseInfo <baseinfo></baseinfo> <base-info></base-info> 注意 组件名设置为kabab-case模式或者PasalCase模式,在new Vue渲染的视图中都只能有 <base-info></base-info> 这种方式调取(因为页面在new Vue中渲染的时候,所有所有大写的名字都会变为小写) 在其他的template语法中,基于 <Btnlist></Btnlist> 或者 <btn-list></btn-list> 都可以正常的调用(只有在new Vue 基于el 指定的视图中,才只能用这种模式) 两种注册方案 注册特点: 基于template指定组件渲染的视图 只能有一个根元素 模版字符串方式(写在组件中) template标记方式,写在结构中,组件中直接调取ID名即可 指定响应的数据(data必须时一个函数) 为了保证每个组件中的DATA时独立的,需要让每个组件的DATA是一个必包函数 只有这样最后把N多组件放在一起渲染,相互之间的DATA才不会冲突 data:function(){....}=>data(){...}return 的对象包含指定的响应数据 注意 因为每一个组件都是一个单独的可复用的Vue 实例,所以他们与new Vue 接收相同的选项 例如:data,computed,watch.methods以及生命周期钩子,除了像el这样根实例特有的选项 全局注册组件 特点:穿件组件后直接在页面中调取使用即可 语法: Vue.components([name],[options]) name :是组件的名字 options:是组件的配置项 注意:全局注册的组件可以相互调用 局部注册组件 特点:需要在页面渲染(new Vue)的时候,基于components注册一下才能使用 语法:const[name]=[options] name :是组件的名字 options:是组件的配置项 注意:局部注册的组件在其子组件中不可用 注意:在vue视图中调取组件需要采用双闭合的方式,因为单闭合的方式调用,会导致下面的代码不在进行渲染 vue组件中的slot插槽 定义 双闭合调用组件,可以在组件的内部扩展其他的内容 slot插槽的概念,我们可以在双闭合标签内在编写一些其他的内容,把这些内容作为组件的扩充内容 预留插槽 可以让组件可以自定义扩展内容 基础操作 具名插槽 定义:在模版给预留的插槽设置名字 例如: 具名插槽:在模版中给预留的插槽设置名字 <template id="pageTemplate"> <div class="box"> <slot name="header"></slot> <slot></slot> <p>补习班</p> <slot name="footer"></slot> </div> </template> 具名插槽指定内容的时候需要用template把内容包起来,v-slot:[name]把模版中的内容放置到指定的命名插槽中,剩余不指定的都是插入到默认的插槽中 <page> <template v-slot:header> <div>我是页眉</div> </template> <p>含糊函数和黑色</p> <template v-slot:footer> <div>我是页尾</div> </template> </page> 注意 具名插槽指定内容的时候需要template把内容包起来 v-slot:[name]把模版中的内容放置到指定的命名插槽下,剩余不指定的都是插入到默认的插槽中 作用域插槽 编译作用域 在组件调用指定的插槽区域,所处作用域依然是外层公共作用域,而不是组件的私有作用域 所以不能在插槽中直接用组件中私有的响应式数据 例如: <p>{{context}}</p> 现在这样是不行的 注意:父级模版里所有内容都是在父级作用域中编译的;子模版里的所有内容都是在自作用域中编译的。 定义:在插槽中想要拿到组件中的数据是,需要利用作用域插槽 例如: <template id="pageTemplate"> <div class="box"> <p>{{context}}</p> //给插槽中的信息提供数据 <slot :context="context" :age="age"></slot> </div> <template> defult是默认插槽的名字(插槽不设置名字,默认就是它) v-slot:default=“Max”=> v-slot="Max" <page> <template v-slot="Max"> <p>SLOT:{{Max.context}}</p> <p>SLOT:{{Max.age}}</p> </template> </page> 组件信息通讯值父传子 定义 在父组件中调用子组件,想让子组件具备不同的信息(每个子组件都是单独的实例) “父传子”:基于prop属性来完成 直接写在组件标签上的,就叫做传给当前子组件的属性 想让两个组件中的信息不同,可以通过属性值不同来完成 例如: 图例: 属性规范 属性传递的时候,属性名尽可能不要出现大写(因为你设置为大写,最后也是基于小写传递的)--> supNum => supnum 传递的属性名设置为kabab-case模式,在子组件注册用的是camalCase来注册接收 传递的属性值默认都是字符串,如果需要传递其他数据类型,我们需要基于v-bind处理 =>:supnum="10",这样传递过去就是Number类型 操作步骤 在父组件调用到的时候,给组件传递属性 子组件想要父组件传递的属性,需要注册一下:props:["title","oppnum"],这里指注册需要用到的属性即可 注意 注册完成以后,会把当前属性挂载到(当前组件的)实例上,获取这个值的方法:{{title}}/this.title props属性校验 在子组件中进行 属性注册时候的校验规则: 即使校验是失败,组件还是会正常渲染,只不过控制台会有错误信息提示 指定属性的类型:props:{xxx:String,...} 指定属性的默认值:props:{xxx:{type:String,default:'xxx',required:true}} 注意: type如果是一个数组,意为指定的多类型皆可以 default可以是一个函数,函数返回值是默认值 required:true设置当前项为必传项 validator自定义验证规则函数 实参:函数的实参接收的是当前属性的值 返回值:必须符合函数中指定的规则,返回true/false 组件信息通讯之子改父 需求 子组件中某些操作,要修改父组件中的一些信息-->“子改父” 解决 基于vue自带的发布订阅实现 this.$on(xxx,function(){......}):自定义事件名,并且像事件池中加入方法 this.$emit(xxx,params......):通知指定自定义事件池中的方法进行 操作步骤 在父组件中定义一个方法,这个方法可以修改父组件的状态信息 我们需要把这个方法放入到事件池中(每个vm实例有一个自己的事件池),因为我们要在子组件中操作这个方法,所以这个方法我们需要放入到子组件的事件池中 只要这样我们在子组件中,可以根据某些操作,控制事件池中的方法执行 对应代码 在父组件中设置方法 methods:{ //设置子组件能够触发执行的方法(为了修改状态的) handle(type){ type === "SUP"?this.supNum++:this.oppNum++; } } 把父组件的方法放入子组件的事件池 template: `<div class="container"> <header class="headerBox"> <h3>{{title}}<h3> <span>【{{sumNum+oppNum}}】</span> <header> <vote-main :supnum="supNum" :oppnum="oppNum"></vote-main> <vote-footer @xxx-"handle"></vote-footer> </div>`, //@xxx="handle"调用子组件的时候,我们这样写,就相当于创建一个自定义事件xxx,并且向事件池追加一个叫做handle的方法,而且是把方法放入到vote-footer的事件池中,类似于:footer实例.$on('xxx',handle) 点击子组件中的按钮,修改父组件中的值 const VoteFooter = { template : `<footer class ="footerBox"> <button @click="func('SUP')">支持</button> <button @click ="func('OPP')">反对</button> </footer>`, methods:{ func(type){ //通知子组件事件池中的handle自定义事件执行 this.$emit("xxx",type); } } }; 举例 图解: vue中的单向数据流 定义 所有的prop都是的其父子prop之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是返回过则不行 通俗解释:父组件可以基于属性吧信息传给子组件,但是反过来子组件无法基于属性把信息传递给父组件 例如: 原因:只有调取组件的时候才能基于属性传递:父组件可以调用子组件,子组件不能调用父组件(会造成死循环) 钩子函数顺序 Vue的父组件和子组件生命周期钩子函数执行顺序可以归类为以下4部分:深度优先原则 加载渲染过程: 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子Mounted -> 父Mounted 子组件更新过程: 父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated 父组件更新过程: 父beforeUpdate -> 父updated 销毁过程: 父beforeDestory -> 子beforeDestory -> 子destroyed -> 父destroyed 图解: 注意 每次父组件发生更新时,子组件中所有的prop都会被刷新为最新值:这意味着你不应该在一个子组件内部改变prop.如果你这样做了,Vue会在浏览器的控制台中发出警告 子组件想修改prop时,只能通过$emit派发一个自定义事件,父组件接收到后,有父组件修改 有两种常见的试图改变一个prop的情形 这个prop用来传递一个初始值;这个子组件接下来希望将其作为一个本地的prop数据来使用 解决:在这种情况下,最好定义一个本地的data属性并将这个prop用作其初始值 这个prop亿一种原始的值传入且需要进行转换 解决:在这种情况下,最好使用这个prop的值来定义一个计算属性 组件信息通信---EventBus 定义 利用EventBus(事件总线)进行整体管控,完全的发布订阅模式 原理: 不论父子还是兄弟,还是不想干的组件,只要保证每个组件都可以获得整个事件池即可 操作步骤 创建一个总的事件池(vm实例) 父组件中有自己的状态信息,也有修改状态的办法,子组件中也有自己的状态信息,和修改状态的办法 ==============各组件呈现的内容完全基于自己的状态信息来渲染即可 我们会把子组件和父组件的方法全部放到事件池中 点击操作时,只要通知事件池中的方法执行即可 对应代码 创建总的事件池 let EventBus = new Vue 创建各自的状态和方法 把所有的方法放到事件池 const VoteMain = { template:`<main class="mainBox"> <p>支持人数:{{supNum}}人</p> <p>反对人数:{{oppNum}}人</p> <p>支持率:{{ratio}}人</p> </main> `, //创建自己的状态和方法 data(){ return{ supNum:0, oppNum:0 }; } methods:{ handleMain(type){ type === "SUP"?this.supNum++:this.oppNum++; } }, created(){ //创建完实例,把方法加入到事件池中 EventBus.$on('xxx',this.handleMain); } }; 点击操作,统一事件池执行 const VoteFooter = { template:`<footer class="footeBox"> <button @click = "handle('SUP')">支持</button> <button @click = "handle('OPP')">反对</button> </footer>`, methods:{ handle(type){ //通知事件池中的方法执行 EventBus.$emit('xxx',type); } } }; 举例 图解: 组件信息通讯祖先与后代(provide和inject) 定义 让所有需要通信的组件共同举杯一个祖先元素,这样就可以基于provide和inject实现祖先和后代之间的通信----执行上下文方案 原理 把需要通信的“公共信息”,放在祖先元素上管控,以及需要修改这些“公共信息”的方法也在祖先元素上 后代元素想要使用,直接基于inject注册使用即可 操作步骤 祖先组件基于provide注册需要供后代足迹啊使用的数据和方法 后代组件基于inject声明需要使用的数据并调取使用 对应代码 provide注册数据 data(){ return { obj:{ supNum:0, oppNum:0 } }; }, provide(){ //此方法只有第一次加载组件的时候执行一次 return{ obj:this.obj, handle:this.handle }; }, methods:{ handle(type){ type === "SUP"?this.obj.supNum++ : this.obj.oppNum++; } }, provide中的信息没有放在vm实例上,不是响应式数据;数据存在在this._provided属性中 inject声明调取 const VoteMain = { template:`<main class="mainBox"> <p>支持人数:{{obj.supNum}}人</p> <p>反对人数:{{obj.oppNum}}人</p> </main>`, //想用哪些公共信息,直接注册即可(而且直接挂载到实例上) inject:["obj"] }; 注意 我们会创建响应式状态信息先存储公共信息,让provide中存储的是状态信息:以后只要我们把状态信息修改了,存储在祖先provide中的信息也会跟着修改 需要保证provide中存储的数据事可被监控的,所以data中存储的数据组要以对象的方式存储,这样才能保证对象中的每个数据也是被监控的 组件信息通信(ref parent children) ref 在DOM元素上使用 const VoteFoote = { template:`<footer class="footerBox"> <button type="button" class="btn btn-primary" ref="supBtn" @click="handle(1)">支持</button> <button type="button" class="btn btn-primary" ref="oppBtn" @click="handle(0)">反对</button> </footer>`, methods:{ handle(type){ type == 1?this.$parent.supNum++ : this.$parent.oppNum++; } }, mounted(){ //this.$refs是一个对象,对象中存储了所有页面中设定的ref和对应的元素对象(ref帮助我们获取知道的DOM元素对象) //{supBtn:DOM元素对象,oppBtn:DOM元素对象...} //console.log(this.$refs); } }; ref如果在普通的DOM元素上使用,引用指向的就是DOM元素 在组件上使用 const MyVote ={ template:`<div class ="container"> <header class="headerBox"> <h3>{{title}}</h3> <span>[{{sumNum+oppNum}}]</span> </header> <vote-main ref="AAA"></vote-main> <vote-footer></vote-footer> </div>`, components:{... props:["title"], data(){... mounted(){ //给组件设置ref,最后存储的结果是当前组件的实例(这样我们就可以修改其中的信息了) //console.log(this.$refs.AAA); } }; 如果用在子组件上,引用就指向组件实例,基于此可以快速获取和操作子组件中的数据 children children 是获取当前父组件中所有子组件的实例 const MyVote ={ template:`<div class ="container"> <header class="headerBox"> <h3>{{title}}</h3> <span>[{{sumNum+oppNum}}]</span> </header> <vote-main ref="AAA"></vote-main> <vote-footer></vote-footer> </div>`, components:{... props:["title"], data(){... mounted(){ //=>this.$children存储了当前父组件中用到的所有子组件的实例(顺序自己去记住即可) //console.log(this.$children); } }; $children是一个数组集合,需要我们记住顺序才可以 parent 在子组件中使用,获取其所在父组件的实例 const VoteFoote = { template:`<footer class="footerBox"> <button ref="supBtn" @click="handle(1)">支持</button> <button ref="oppBtn" @click="handle(0)">反对</button> </footer>`, methods:{ handle(type){ type == 1?this.$parent.supNum++ : this.$parent.oppNum++; } }, mounted(){ //this.$parent获取其所在父组件的实例 //console.log(this.$parent); } }; vuex的基础及使用 定义 vuex是为了解决组件间的数据交互;专门针对vue的一种数据管理模式:vue中实现公共状态管理的插件 前提: SPA单页面(实现的事同一个页面中,各组件的信息通信) 步骤 创造一个vuex的实例 let store = new Vuex.Store({配置项}) 配置项 state、mutations、actions、getters、modules 把创建的实例,注入到根组件中 let vm= new Vue({ el:'#app', //属性名:store是vue规定的(必须用store) //属性值:是创建的vuex实例的名称 store:store, }); 注入完成之后每一个组件中都多了一个$store的属性 注意: $store中的state中存储的就是我们的数据源 配置项 state 作用 存放组件之间需要共享的公共状态:即Vuex的基本数据 使用 state:{ //这里存放的都是公共数据 color:'red', bro1Color:'blue', bro2Color:'green', }, 尽量通过mutatios中的方法修改这些数据,不要直接修改 调用 `<h1 :style='{color:$store.state.bro1Color}'>这是一个bro1组件</h1>` 直接使用state在视图中渲染 mutations 作用 这个属性中存储的都是用来更改state中数据的同步方法 使用 mutations:{ //函数名自定义的 //这里存放的都是用来修改state中属性的方法 changeBro1Color(state,option){ //state就是vuex中的那个state; //option是调用这个函数是传递的参数 state.bro1Color = option; } } 这些方法都至少有一个参数,最多两个 参数 第一个参数是vuex默认传的state(数据源) 第二个参数是我们通过commit调用mutations中对应方法的时候传递的参数 调用 this.$store.commit('changeBro1Color','max') 注: this.$store.commit('mutation中的方法名',传过去的参数) 扩展 这里的函数必须是同步函数(它是约定俗成的不是技术规定)为什么呢? --->是为了方便状态可控,修改状态,有迹可循 actions 作用 由于直接在mutation方法中进行异步操作,将会引起数据失效,所以提供了Action来专门进行异步操作,最终提交mutation方法 这个属性中存储的一般都是一些异步执行的方法,为了在异步执行完成之后再去通过commit触发mutations中的对应函数 使用 actions:{ //这里边也都是一些函数 // mutations中的函数需要通过commit触发 // actions中的函数需要通过dispatch触发 // actions中的函数一般都会再去触发mutations中的方法 asyncAdd(store,option){ setTimeout(()=>{ store.commit('add',option) //请求成功之后,再去触发mutaition中add方法 },1000); } }, 参数 context(与store实例具有相同方法和属性的context对象) 第二个参数是我们通过dispatch调用时传递的参数 调用 this.$store.dispatch('actions中的方法名',传递给他的参数) getters 作用 可以对state中的成员加工后传递给外界:理解成vuex的计算属性即可 什么时候选择使用getters? 当多个组件功用一套处理逻辑的时候选择使用 若这个逻辑只在一个组件使用,不建议写在getters中,因为这会增大vuex的体积; 使用 getters:{ nameInfo(state){ return"姓名:"+state.name }, fullInfo(state,getters){ return getters.nameInfo+'年龄:'+state.age } } 如不需要,第二个参数可以省略 参数 state当前vuex对象中的状态对象 getters当前getters对象,用于将getters下的其他getters拿来用 调用 this.$store.getter.fullInfo modules 作用 使用单一状态树,导致应用的所有状态集中到一个很大的对象,但是,当应用变得很大时,store对象会变的臃肿不堪 为了解决以上问题,vuex允许我们将store分割到模块(module) 每个模块拥有自己的state,nutation,action,getter,甚至是嵌套子迷失----从上至下进行同样方式的分割 使用 const moduleA = { state:()=>({...}), mutations:{...}, actions:{...}, getters:{...} } const moduleB = { state:()=>({...}), actions:{...}, getters:{...} } const store = new Vuex,Store({ modules:{ a:moduleA, b:moduleB } }) 注意 对于模块内部的nutation和getter,接收的第一个参数是模块的局部状态对象 对于模块内部的getter和actions,根节点状态会作为第三个参数暴露出来 命名空间 默认情况下,模块内部的action,mutation和getter是注册在全局命名空间的,这样是的多个模块能够对同一mutation或action做出响应 解决 在根模块下加namespaced:true属性,会把当前模块作为一个封闭空间 调用:this.$store.commit('login/chengCount',10)(调用指定模块下mutations中的方法) 辅助函数 Vuex.mapState(['state中的状态名称']) 在哪个组件使用,就相当于把state中的属性挪到了哪个组件中(一般挪到计算属性中使用) Vuex.maoMutations(['mutations中的方法名称']) 在哪个组件中使用,就相当于把mutations中的方法移到了本组件的methods中 Vuex.mapActions() 在哪个组件中使用,就相当于把actions中的方法移到那个组件中 Vuex.mapGetters() 注意 注意名字“[名字]” 不能与组件中的属性吗重复 如果本组件中有了这个属性吗,我们还想用辅助函数的时候就需要如下命名方式 Vuex.mapState({ count123:'count'}),之后在本组件中使用时,用count123这个名字即可 vue-cli脚手架基础 脚手架 定义 根据业务需求或者功能需求,把需要配置的webpack以及一些每一次都要做的事情进行封装 以后根据脚手架安装,就可以快速吧把东西处理好 脚手架一般都是基于命令操作的(研发脚手架需要把Node学好/Npm包管理学好) vue-cli就是一个脚手架 vue工程化 项目中需要基于工程化开发来管理vue项目 =>_vNode把template语法基于vue-template-loader解析成虚拟DOM 基于vue-cli打造vue工程化 vue-cli 官网:https://cli.vuejs.org/zh/guide/ 定义: vue cli 是一个基于vue.js进行快速开发的完整系统 安装: 在全局下:$npm install @vue/cli -g mac全局安装必须在前面加sudo $ vue --version 查看当前版本号 $ npm root -g查看全局安装目录 创建项目 文件目录 配置完成预览页面 执行命令 : $npm run serve 成功展示: 不成功可能是因为丢包,重新跑下环境应该能好 vue-cli 创建项目 $vue-create(要创建的项目名称) 名称遵循npm 包命名规则,可以时中杠 项目名称不能时中文,大写字母,特殊字符 出现default(bable,eslint)窗口,我们一把选择默认(上下键切换选项,enter键开始安装) 上下键切换,空格选中,enter下一步 路由中,是否选用history模块,一般用哈希路由,所以写n css预处理选择什么?一般选择less ESlint 选择了默认的第一个 是否用开发下的词法解析,选默认第一个即可 把上面选中的文件是单独生成文件夹,还是全都放在package.json里面,一般我们选择放在package.jsonzhong 这次的选择是否保存,我这里选择n (no) vue-cli 文件目录 node_modules 安装的各个模块 public 存放的是最后编译项目时候的页面模版(一个或者多个) index.html 在webpack打包编译的时候,页面模版也会跟着进行编译 把HTML压缩 把编译后的CSS和JS插入到页面模版中 注意事项: 单独自己倒入一些资源信息,把资源信息放置到当前目录下(public),基于<%=BASE_URL%>知道相对引入的地址 我们需要在页面中留一个#APP容器,因为最后编译的时候,会把所有的内容插入到这个容器中 对于不需要打包合并在一起的资源,我们可以单独在模版中自己导入 有一些公共资源,这些公共资源我们不行最后根据webpack都打包在一起,我们想单独导入 还有一些模块不支持commonJS/ES6Module模块过饭,这样无法在项目中进行导入导出,此时我们需要单独导入到模版页面中(一般此处导入进来的模块,对应的方法都会暴露到window上) 因为模版页面中的东西很少,我们完全可以先自己写一个loadding等待的效果(放置一顶端资源加载过慢导致的白屏) 包括一下特殊需要先执行的JS或者样式,做好也是单独卸载这里(这样不要最后合并打包到一起),例如:REM等比缩放的计算 src 包含整个项目所需要的内容和代码(脚手架生产的webpack配置,只对src目录进行编译处理,其他目录是不处理的) main.js 项目的入口(单页面一个入口,多页面多个入口) 导入需要渲染的组件,在new vue的时候,把组件进行编译,最后放置在模版页面中#APP容器中 import Vue from 'vue'; import App from './App.vue' Vue.config.productionTip = false; new Vue({ render:h => h(App), }).$mount('#app') App.vue 在工程化的项目中,创建组件只需要构建xxx.vue即可(文件中包含当前组件自己的结构模版,js数据状态方法,自己的样式等) 当前index.html这个页面的主要入口 <template> //组件的视图模版 <div id="app">Max</div> </template> <script> //当前组件的js管控 export default{ //相当于私有组件中的内容 } </script> <style lang="less" scoped> 当前组件的样式 问题:直接这样写,最后所有组件中的样式合并到爆的时候容易产生冲突 1. 手动避免冲突:基于less/sass中的浅滩,指定很长的前缀可以避免,但是生成css性能不好 2. 直接设置scoped,到边当前组件的样式是私有的(webpack帮助我们重新命名),最后打包在一起的时候就不会冲突了 </style> components 存放多个组件中需要用到的公共组件的 xxx.vue pages 每一个单独的业务板块或者页面都在这里创建,自己创建的文件夹 assets 存放项目需要的静态资源文件 images,css, lib api 存放axios的二次配置或者一些借口的书就管理 store 管理vuex中的一套信息 router 管理路由中的一套信息 views 主组件入口 babel.config.js Babel的配置信息(编译解析js的) package.json 当前项目的模块依赖清单(脚手架生成的项目中,我们还会把一下webpack需要用到的配置信息,也卸载这里) “scripts” 配置可执行的命令 “scripts”:{ "serve":"vue-cli-service serve", "build":"vue-cli-service bulid", "lint":"vue-cli-service lint" }, npm run serve/yarn serve 开发环境下预览项目 (webpack会打包编译并且基于dev-server创建一个本地预览服务,并且监听代码变化,一旦有变化会重新编译) $ npm run bulid 生成环境部署之前,基于这个命令,把项目进行打包 (打包之后的内容放到dist或者bulid目录中,我们把这个目录中的内容正统部署到服务器即可) “dependencies”:{....} 生产依赖 “devDependencies”:{...}开发依赖 “eslintConfig”:{...} 词法解析规则 可以在此处定义ESLINT词法检测规则 “browserslist”:{....} 配置浏览器兼容的列表 vue.congig.js vue-cli的进阶配置 vue-cli进阶处理 创建 在当前文件夹的根目录下创建vue.config.js文件 在这个文件中进行二次配置脚手架 语法 官网 https://cli.vuejs.org/zh/guide/ module.exports = {配置项1,配置项2....} 常用配置 publicPath 作用 在服务器打包的部署的时候,我们往往会根据服务器部署的地址,指定所有插入资源的统一前缀路径(默认/代表部署到服务器根目录) publicPath:'/dist/' pages 作用:配置多页面 代码 pages:{ index:{ //page的入口 entry:'src/main.js', //模版来源 template:'public/index.html', //指定左后打包完成生成的文件名 filename:'index.html', //template中title标签需要是<title><%= htmlWebpackPlugin.options.title %></title> title:'首页', //需要依托哪些JS chunks:["chunk-vendors","chunk-common","index"] }, login:{ entry:'src/login.js', template:'public/login.html', filename:'login.html', chunks:["chunk-vendors","chunk-common","login"] } }, lintOnSave 作用 在开发环境下,是否每次代码编译都执行词法检测(开启后:编译会变慢,而且以后在xxx.vue中可能多加一个空格它都编译不过去) 默认是true lintOnSave:false,一般都禁掉 productionSourceMap 作用 如果你不需要生产环境的source map ,可以将其设置为false以加速生产环境构建 source map:相当于压缩后的文件对照圆满的映射,方便调试 devServer type:objext 所有webpack-dev-server的选项都支持。注意: 有些值像 host,port和https可能会被命令行参数复写 有些值像publicPath和historyApiFallback不应该被修改,因为它们需要和开发服务器的publicPath同步异保障正常的工作。 //webpack-dev-server devServer:{ port:3000} 例如:改变端口号 devServer.proxy 作用 如果你的前端应用和后端API服务器没有运行在同一个主机上,你需要在开发环境下将API请求代理到API服务器 这个问题可以通过vue.config.js中的devServer.proxy选项来配置 使用 devServer.proxy可以是一个执行开发环境API服务器的字符串: module.exports = { devServer:{ proxy:'http://localhost:4000' } } devServer.proxy可以是一个对象 devServer:{ proxy:{ '/api':{ target:BASE_URL, changeOrigin:true } } } 环境变量和模式 .env.development-->NODE_ENV = development BASE_URL="http://127.0.0.1:8080" .env.production --> NODE_ENV = production BASE_URL="http://www.baidu.com" 不同的环境下走不同的前缀URL地址 let NODE_ENV = process.env.NODE_ENV, BASE_URL = process.env.BASE_URL; module.exports = { //在服务器打包部署的时候,我们往往会根据服务器部署的地址,指定左右插入资源的统一前缀路径(默认/代表部署到服务器根目录) publicPath:NODE_ENV === "production"?'/dist':'/', 浏览器兼容处理 在package.json中配置 “browserslist”:[ ">0.5%", "last 2 versions", "ie 10-11" ] 在主入口配置(在main.js) /兼容处理/ import 'core-js/stable'; import 'regenerator-runtime/runtime'; 在babel.comfig.js文件中配置 module.exports = { presets:[ ['@vue/cli-plugin-babel/preset', { useBuiltInts:'entry' } ] ] }; vue-router 基础概括 下载 $npm i vue-router 或者 $yarn add vue-router 路由使用 导入路由 在main.js主入口中 import Vue from 'vue'; import App from './App.vue'; /导入路由表/ Vue.config.productionTip = false; new Vue({ 使用路由:所有基于路由管控的组件中将会包含两个属性 this.$router => 实现路由的切换等 this.$route => 包含路由的一些基本信息 router, render:h => h(App), }).$mount('#app') this.$router => 提供一系列操作路由的方法 this.$route => 提供一系列路由的属性 配置项 mode 路由的方式:HASH(哈希路由)&& HISTORY(BROWSER路由) HASH路由 在URL地址末尾加入#/--->哈希值/ 栗(🌰): #/userlist --> 哈希值/userlist #/useradd --> 哈希值/useradd 原理 vue-router 监听当前页面HASH值的改变,根据不同的HASH值,在“路由视图容器”中渲染不同的组件 路由容器 router-view 路由容器 根据路由表的匹配规则,佩佩到不同的组件,每一次都可以吧匹配到的自建房之灾容器中进行渲染 routes 配置路由表 在不同的HASH值下匹配不同的组件 语法 routes:[{key:val,...},{...}...] 栗子(🌰) import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); const router = new VueRouter({ //路由的方式:hash&& history mode:'hash', routes:[{ //按照从上到下的顺序一次查找,找到对应的停止 path:'/',//路由地址为“/”时,跳到名为Home的组件 component:Home },{ path:'/custome', component:Custome },{ path:'./custome', component:Custome },{ path:'*',// *代表除了上面路径外的所有路径 //component:Page404 redirect:'/'-->重定向 }] }); 注: 1. 需要把用到的组件先导入,路由懒加载则用不到 2. path:'/' + 路由路径(后期跳转个匹配的时候需要) 3. name:'xxx' + 命名路由(后期可基于<router-link :to='{name:'xxx' }'>跳转) 4. component:Home + 组件的渲染 5. redirect:'/' + 路径重定向为'/' 6. children:[{...}] + 子路由 每一次URL地址后面的HASH值改变,程序就监听到了。程序会重新从路由表中第一个开始向下一次进行匹配,直到找到符合的那一项为止 命名视图 内容容器不设置名字 默认名字是default <router-view></router-view> <router-view name="AAAA"></router-view> routes:[{ path:'/', name:'nhome', components:{ default:Home, AAA:Active} }] 不指定视图。默认渲染Home组件,Active组件渲染到AAA视图中 路由懒加载 在路由表中首先要导入需要用到的全部组件,在 路由表处理的时候,首先会把这些组件全部编译处理。然后在向下逐一执行代码,如果组件过多就会大大影响性能 作用: 处于性能优化考虑,利用路由懒加载,实现按需加载 使用:不需要在开始导入全部组件,在路由表中使用组件的时候再导入 component:()=>{ //路由懒加载 return import('../pages/Home.vue'); } 路由跳转 子路由 子路由的值是一个数组,每一项都是一个对象,对象中包含内容与routes中一致 routes:[{ path:'/', name:'home', component:Home, children:[{ path:'/list', name:'list', component:List, children:[{ path:'/system', name:'system', component:system },{ path:'/active', name:'active', component:Active }] }] }] vue-cli 实现路由跳转 两大方案 router-link 编程式导航 基于JS中的this.$router下的方法实现路由跳转 router-link 最后渲染出来的是a标签 跳转方式 to地址 to='[path]' <router-link to='/'> to="{[path]/[name],传参}" 这种方式不仅能够实现传参,还可以传递参数 传参形式 path <router-link :to="{ path:'/custome', query:{ id:100 } }">客户管理</router-link> 基于query显示传参(传递的参数信息回忆问号的方式显示在URL地址中) path或者name 实现路由跳转都可以基于这个方式处理 基于$route.query可以获取这个值,而且不论是点击传递还是页面再刷新,只要地址栏中有问号参数,query中就可以获取到这个值 name <router-link :to="{ path:'/nasystem', params:{ id:100 } }">系统设置</router-link> 基于params传参是隐性传参(传递的信息不会再地址栏中体现) 只有基于命名路由跳转才可以使用params传参 进入到路由后,可以基于$route.params获取传递参数信息 页面一但刷新,则传递的参数信息就没有了 动态路由 { path:'/active/:is/:name', //动态路由的提前占位: =》跳转的地址/active/xxx,则会把xxx当作值传递给id($rpute.params中可以获取到) =》项目中,如果需要传递参数,我们一般的都是基于这种方式,而很少用问好传参(因为它丑) name:'nactive', component:Home} 动态路由 第二步:在router-link中传入参数即可 可以根据参数不同跳转相同组件的不同操作 问题 点击多个按钮跳转到同一个路由,路由管控的组件并不会从源头开始渲染(也就是created/mounted等周期函数只执行一次),触发的只是更新 解决 先切换到其他路由组件,路由会默认把上一次渲染的组件销毁 当后期重新切换回来的时候,触发当前组件的从头开始重新渲染 但是两次连着要渲染一个组件,那么组件不会被销毁 watch监听路由改变 export default{ created(){ //第一次加载组件创建实例后 console.log(this.$route.params); }, watch:{ //监听路由信息的改拜年,从而实现某些操作(只有路由信息有变化,我们就可以监听到, 第一次加载组件时监听不到,所以和created一起使用 $route(from,to){ //from上一个路由的信息 to 当前路由的信息 console.log(this.$route.params); } } } 编程式导航 基于JS中的this.$router下的方法实现路由跳转 定义 通过JS实现路由的跳转和传参,相当于把 router-link中的规则转移到JS中,利用JS实现 实现跳转的方式 push 类似于,用法基本相同 go 路由池:每一次的路由跳转都会记录在路由池中 go(-1):在路由池中找打上一次的路由地址,并跳转到这个地址 go(1)在路由池中找到下一次的路由地址,并跳转 back 与go(-1)效果相同 栗子(🌰) <router-link to='/xxx'></router-link> //=>传递参数 <router-link to='{name:'xxx',params:{xxx:xxx}}'></router-link> /编程式导航:JS中实现跳转/ this.$router.push('/xxx'); this.$router.push(name:'xxx',params:{id:100});=>对应router-link中的“params”传参 this.$router.push({path:'/xxx/100'});=>对应router-link中的“动态路由” this.$router.push({path:'/xxx',query:{id:100}});=>/xxx?id=100,获取this.$router.query.id,对应“query”传参 this.$router.go(-1);