Vue知识点整理(待更新)
参考Vue.js中文官网,Vue 知识点汇总(上)–附案例代码及项目地址,Vue 知识点汇总(下)–附案例代码及项目地址,Vue知识点汇总【持更】
文章目录
一、基础知识
-
Note:
-
attribute
是HTML
标签上的特性,它的值只能够是字符串;attribute
可以简单理解成dom节点自带的属性,例如html中常用的id、class、title、align
。attributes是属于property的一个子集。attribute的赋值://注意参数1,2均是字符串 div1.setAttribute('class', 'a'); div1.setAttribute('title', 'b'); div1.setAttribute('title1', 'c'); div1.setAttribute('title2', 'd');
-
property是DOM中的属性,是JavaScript里的对象;property取值如下:
//取任何属性的只,用“.”即可 var id = div1.id; var className = div1.className; var childNodes = div1.childNodes; var attrs = div1.attributes; //此处再次强调: //1) class特性在变成属性时,名字改成了“className”,因此div1.className和div1.getAttrbute('class')相同。 //2) 上面代码中的div1.attributes是取的attributes这一属性,取出来保存到attrs变量中,attrs就成了一个NamedNodeList类型的对象,里面存储了若干个Attr类型。
-
在js中,attribute和property关系如下:
-
property能够从attribute中得到同步;
-
attribute不会同步property上的值;
-
attribute和property之间的数据绑定是单向的,attribute->property;
-
更改property和attribute上的任意值,都会将更新反映到HTML页面中;
-
-
-
Note:
-
Vue (读音类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。
-
Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
-
-
Note:
-
MVVM 是 Model - View - ViewModel 的缩写 可以看到他和之前的MVC很像,的确有人称之为一个加强优化版的MVC。 是一种模块化开发代码分层的思想或者框架!
-
模型(
Model
):模型和业务数据绑定,方便数据的使用和传递。 -
视图(
View
): 视图是应用程序中用户界面相关的部分,是用户看到并与之交互的界面。 -
ViewModel
:首先它的创建需要将Model中的数据绑定在他身上(为模型到视图的映射提供桥梁)。将原来MVC中的业务逻辑剥离出来写在ViewModel中,简化view
和controller
。
-
-
使用步骤
1、模块中需要的数据,通过网络层请求得到 ,然后将数据绑定到Model层中。
2、将model
层中的数据转化到ViewModel
中,然后在ViewModel中处理一些逻辑问题。
3、将ViewModel中的数据绑定到控制器的View上,然后更新界面。MVVM模型适合作为前端的框架,用来实现前后端分离。
-
Vue官网对Vue的MVVM模型做出了解释:参考Vue中的MVVM
各层的作用:
-
View层:
①视图层
②在前端开发中就是DOM层
③主要作用是给用户展示各种信息 -
Model层:
①数据层
②数据可能是固定的死数据,更多是来自于服务器,从网络上请求下来的数据。 -
ViewModel层:
①视图模型层
②视图模型层是View和Model沟通的桥梁
③一方面它实现了Data Bindings来进行数据的绑定,将Model的改变实时反映到View中
④另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击,滚动,touch)时,可以监听到,并在需要的情况下改变对应的Data。
-
-
-
【问】Vue实例的生命周期分为哪几个阶段?(创建、运行和销毁;常见的生命周期函数包括
created
,mounted
和updated
等)Note:
-
生命周期概念:生命周期是指一个组件从创建 > 运行 > 销毁的整个过程,强调的是一个时间段。
生命周期函数概念:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点 。
-
组件生命周期函数的分类:
-
官网提供的Vue实例生命周期解释如下:
-
-
【问】关于Vue的特性有哪些?(数据驱动视图,双向数据绑定)
Note:
-
数据驱动视图:数据(
model
)的变化会驱动视图(View
)自动更新。 -
双向数据绑定:在网页中,form负责采集数据,Ajax负责提交数据。数据源的变化,会被自动渲染到页面上;页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到数据源中。
-
-
【问】如何编写一个简单的Vue应用?(Vue实例挂载到某个元素上)
Note:
-
Vue.js
的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> </head> <body> <div id="app"> {{ message }} <!-- 声明式渲染 --> </div> </body> <script> var app = new Vue({ //创建Vue实例 el: '#app', //挂载点为id = "app"的DOM元素 data: { message: 'Hello Vue!' } }) </script> </html>
现在数据和 DOM 已经被建立了关联,所有东西都是响应式的,即在当前页面的控制台上修改
app.message = "Hello world"
,html页面会更新内容。
-
二、基础语法(渲染和绑定)
-
Note:
-
el:""
:指定vue所操作的dom
范围,属性值是你获取的节点; -
data:{}
:就是vue的model
,是存放数据的,属性值是一个对象或者是一个函数,在组件中的data是一个函数; -
watch:{}
:vue
中的侦听器 -
computed:{}
:vue
中的计算属性,看起来像methods
,用起来像data
; -
methods:{}
:是vue中的事件方法;
-
-
【问】内容渲染指令有哪些?(插值表达式,v-text=‘’,v-html=‘’)
Note:
-
内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:
-
{{}}
插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容。注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中; -
v-text
:会覆盖元素内部原有的内容。 -
v-html
:不仅会覆盖原来的内容,而且可以把带有标签的字符串,渲染成真正的HTML内容;
比如:
<div id = "box1"> <p v-text="text2">这是我原本的内容</p> <div v-html="text3">这是我原本的内容</div> </div> <script> const vm1=new Vue({ el:'#box1', data:{ text1:'插值表达式的结果', text2:'v-text的结果', text3:'a href="http://www.baidu.com">v-html的结果</a>' } </script>
-
-
-
【问】属性绑定指令如何使用?(
v-bind:属性=
,常配合class,style,src和href属性使用)Note:
-
v-bind
:可为元素的属性动态绑定属性值,可简写为:
(v-bind:
语法糖)。 -
在开发中,有哪些属性需要动态进行绑定:比如图片的链接
src
、网站的链接href
、动态绑定一些类、样式等等,比如<!--html--> <a :href="'https://www.runoob.com/vue2/'+url">点击跳转vue菜鸟教程</a> <!--script--> const vm2=new Vue({ el:'#box2', data:{ url:'vue-tutorial.html' } })
-
v-bind:class=
:动态切换class
,比如:- 当数据为某个状态时,字体显示红色。
- 当数据另一个状态时,字体显示黑色。
绑定class有两种方式:
-
对象语法:
class
后面跟的是一个对象用法一:直接通过{}绑定一个类 <h2 :class="{'active': isActive}">Hello World</h2> 用法二:也可以通过判断,传入多个值 <h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
-
数组语法:
class
后面跟的是一个数组用法一:直接通过{}绑定一个类 <h2 :class="['active']">Hello World</h2> 用法二:也可以传入多个值 <h2 :class="['active', 'line']">Hello World</h2>
-
v-bind:style
来绑定一些CSS内联样式,绑定style
有两种方式:-
对象语法:style后面跟的是一个对象类型,对象的
key
是CSS属性名称,
对象的value
是具体赋的值,值可以来自于data中的属性::style="{color: currentColor, fontSize: fontSize + 'px'}"
-
数组语法:style后面跟的是一个数组类型,多个值以“,“分割即可
<div v-bind:style="[baseStyles, overridingStyles]"></div>
-
-
-
Note:
-
vue提供了
v-on
事件绑定指令,用于为DOM元素绑定事件监听。原生DOM对象有onclick
、oninput
、onkeyup
等原生事件,替换为vue的事件绑定形式后,分别为v-on:click
、v-on:input
、v-on:keyup
; -
v-on:
可以用@
进行简写(v-on:
语法糖) -
在
v-on
指令所绑定的事件处理函数,可以接收事件参数对象event
; -
$event
是vue提供的特殊变量,用来表示原生的事件参数对象event
; -
参考代码如下:
<button @click="add">自增</botton> <button @click="changeColor">变色</botton> data(){ return{ count:'', } } methods:{ add(){ this.count++; }, changeColor(e){ e.target.style.backgroundColor='red'; } }
-
-
Note:
-
事件修饰符:在事件处理函数中调用
event.preventDefault()
(取消在浏览器中事件的默认动作)或event.stopPropagagation()
(阻止事件冒泡和捕获)是非常常见的需求。参考stopPropagation, preventDefault的区别
vue提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的5个事件修饰符如下:参考[事件冒泡概念]((https://baike.baidu.com/item/%E4%BA%8B%E4%BB%B6%E5%86%92%E6%B3%A1/4211429?fr=aladdin).prevent 阻止默认行为(例如:阻止a连接的跳转、阻止表单的提交等) .stop 阻止事件冒泡(即阻止传播到责任链中下一个事件处理器) .capture 以捕获模式触发当前的事件处理函数 .once 绑定的事件只触发1次 .self 只有在event.target是当前元素自身时触发事件处理函数
-
-
按钮修饰符:在监听键盘事件时,我们经常需要判断详细的按键。此时,可以为键盘相关的事件添加按键修饰符,例如
<!--只有在"key"是'Enter'时调用'vm.submit()'...> <input @keyup.enter="submit"> <!--只有在'key'是'Esc'时调用'vm.clearInput()'...> <input @keyup.esc="clearInput">
-
【问】双向绑定指令如何使用?(
v-model
原理为v-on:input
+v-bind:value
;v-model对radio,checkbox,select等进行双向绑定)Note:
-
vue提供了
v-model
双向数据绑定指令,用来辅助开发者在不操作DOM的前提下,快速获取表单的数据。v-model
其实是一个语法糖,它的背后本质上是包含两个操作:-
v-bind
绑定一个value
属性 -
v-on
指令给当前元素绑定input
事件。在普通input
对应的input
事件中,vue
已经帮我们写好逻辑了。
也就是说下面的代码:等同于下面的代码:
<input type="text" v-model="message"> 等同于 <input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
-
-
v-model:radio
:复选框分为两种情况:单个勾选框和多个勾选框单个勾选框:
v-model
即为布尔值。此时input
的value
并不影响v-model
的值。参考https://jiuaidu.com/jianzhan/832729/<template> <div id="app"> <input type="radio" id="male" value="male" v-model="gender"> male <input type="radio" id="female" value="female" v-model="gender"> femalea <p>{{gender}}</p> </div> </template> <script> export default { name: 'app', data () { return { gender: '' } } } </script>
-
v-model:checkbox
:-
单个勾选框:
-
v-model
即为布尔值。 -
此时input的value并不影响
v-model
的值。
-
-
多个复选框:当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。当选中某一个时,就会将
input
的value
添加到数组中。代码:
<div id = "app"> <!--单个复选框--> <label for="check"> <input type="checkbox" v-model="checked" id="check">同意协议 </label> <p>是否选中:{{checked}} <p> <!--多个复选框--> <label><input type="checkbox" v-model="hobbies"value="篮球">篮球</Label> <label><input type="checkbox" v-model="hobbies"value="足球">足球</Label> <label><input type="checkbox" v-model="hobbies"value="台球">台球</Label> <p>您选中的爱好:{{hobbies}}</p> </div> <script> let app = new Vue ({ el:'#app', data:{ checked : false , hobbies: [] } }) </script >
-
-
v-model:select
:和checkbox
一样,select也分单选和多选两种情况。-
单选:只能选中一个值。
-
v-model
绑定的是一个值。 -
当我们选中option中的一个时,会将它对应的value赋值到mySelect中
代码:
<!--选择一个值--> <select v-model="mySelect"> <option value="apple">苹果</option> <option value="orange">橘子</option> <option value="banana">香蕉</option> </select> <p>您最喜欢的水果:{{mySelect}} </p> ...
-
-
多选:可以选中多个值。
-
v-model
绑定的是一个数组。 -
当选中多个值时,就会将选中的option对应的value添加到数组mySelects中
代码:
<select v-model="mySelect" multiple> <option value="apple">苹果</option> <option value="orange">橘子</option> <option value="banana">香蕉</option> </select> <p>您最喜欢的水果:{{mySelect}} </p> <script> let app = new Vue ({ el:'#app', data:{ mySelect:'apple', mySelects : [] } }) </script>
-
-
-
-
【问】条件渲染指令如何使用?它们之间有什么区别(
v-if
动态添加DOM元素,有更高的切换开销;v-show
动态添加样式,有更高的初始渲染开销)Note:
-
条件渲染指令包括:
v-if
和v-show
-
实现原理不同:
-
v-if
指令会动态地创建或移除DOM元素,从而控制元素在页面上的显示与隐藏; -
v-show
指令会动态为元素添加或移除style="display:none;"
样式,从而控制元素的显示与隐藏;
-
-
性能消耗不同:
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此:- 如果需要非常频繁地切换,则使用
v-show
较好 - 如果在运行时条件很少改变,则使用
v-if
较好
- 如果需要非常频繁地切换,则使用
-
两者使用的区别:
-
v-if
、v-else
、v-else-if
条件性的渲染某元素,判定为true时渲染,否则不渲染; -
v-show
条件不满足令其display
为none
。
<div v-if="score<60">不及格</div> <div v-else-if="60<=score&&score<90">中等</div> <div v-else="score>=90">优秀</div> <div v-show="true">display:block显示</div> <div v-show="false">display:none隐藏</div>
-
-
-
【问】列表渲染指令如何使用?(
v-for
常配合checkbox、options、li标签使用)Note:
-
vue
提供了v-for
列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。v-for
指令需要使用item in items
形式的特殊语法,其中:items
是待循环的数组item
是被循环的每一项
代码:
<ul id='app'> <li v-for="item in list"> 姓名是:{{item.name}} </li> </ul> <script> let app = new Vue ({ el:'#app', data:{ list: [ {'id' : 1, 'name': 'wang'}, {'id' : 2, 'name': 'liu'} ] } }) </script>
-
v-for
指令还支持一个可选的第二个参数,即当前项的索引。语法格式为(item,index) in items
,示例代码如下(item
,index
变量名可随便取):<ul id='app'> <li v-for="(item,index) in list"> 第{{index + 1}}个同学,姓名是:{{item.name}} </li> </ul>
-
【问】如何提高列表渲染指令的性能?(
v-for
在vue2.2+之后必须使用:key
来提高渲染效率),参考列表渲染 — Vue.jsNote:
-
当列表的数据变化时,默认情况下,vue会尽可能的复用已存在的DOM元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。
-
为了给vue一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲染的性能。此时,需要为每项提供一个唯一的
key
属性:<ul id='app'> <li v-for="(user,i) in list" :key="user.id"> 姓名是:{{item.name}} </li> </ul>
-
-
-
Note:
-
keep-alive
是什么-
当组件被缓存时,会自动触发组件的
deactivated
生命周期函数。 -
当组件被激活时,会自动触发组件的
activated
生命周期函数。 -
当组件第一次被创建,会执行
created
生命周期函数,也会执行activated
生命周期函数。之后组件再被激活,只会触发activated
而不会触发created
。
-
-
keep-alive
的基本使用以及基本属性:<keep-alive> <组件名></组件名> <keep-alive>
-
include包含的组件(可以为字符串,数组,以及正则表达式,只有名称匹配的组件会被缓存)。
// 只缓存组件name为a和b的组件 <keep-alive include="a,b"> <component /> </keep-alive>
-
exclude排除的组件(可以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)。
// 组件name为c的组件不缓存(可以保留它的状态或避免重新渲染) <keep-alive exclude="c"> <component /> </keep-alive> // 如果同时使用include,exclude,那么exclude优先于include, 下面的例子只缓存a组件 <keep-alive include="a,b" exclude="b"> <component /> </keep-alive>
-
max缓存组件的最大值(类型为字符或者数字,可以控制缓存组件的个数);
// 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件 <keep-alive exclude="c" max="5"> <component /> </keep-alive>
-
-
-
【问】侦听器watch的作用及如何使用?(侦听器主要用于监听data中存储的数据对象是否发生变化;设置deep得到嵌套侦听器;设置immediate得到即时回调侦听器),参考侦听器 | Vue.js,《Vue 入门教程》Vue 侦听器
Note:
-
watch
侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。有两种创建方式:-
方法格式的侦听器:
watch : { key : function(new,old){} }
; -
对象格式的侦听器:
watch : {key : {handler(new,old){}, deep, immediate }
,支持创建嵌套侦听器和回调侦听器。
-
-
侦听器简单例子:
-
侦听器
watch
实际是vue
实例上的一个对象属性。当我们需要对vue
实例上某个属性进行侦听时,我们以需要被侦听的属性名作为watch
对象的键,以一个函数function
作为该键的值。 -
函数
function
接收两个参数:侦听数据变化之后的值newValue
;侦听数据变化之前的值oldValue
:
var vm = new Vue({ el: '#app', data() { return { count: 0 } }, watch: { count: function(newVal, oldVal) { // 具体处理逻辑 }, } })
-
-
深层侦听器:
watch
默认是浅层的,仅在被赋新值时,才会触发回调函数。如果想侦听所有嵌套的变更需要将deep
选项设置为true
,得到使用深层侦听器;export default { watch: { question : { handler(newValue, oldValue) { // 注意:在嵌套的变更中, // 只要没有替换对象本身, // 那么这里的 `newValue` 和 `oldValue` 相同 }, deep: true } } }
-
即时回调的侦听器:
watch
默认是懒执行的,仅当数据源变化时,才会执行回调。但在某些场景中,举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据,即在创建侦听器时,立即执行一遍回调。这里可以为侦听器设置immediate: rue
,强制回调函数立即执行:export default { // ... watch: { question: { handler(newQuestion) { // 在组件实例创建时会立即调用 }, // 强制立即执行回调 immediate: true } } // ... }
-
侦听器综合案例(对字典、整型进行监听):参考Vue基础之侦听器详解
const app = createApp({ data() { return { a: 1, b: 2, c: { d: 4 }, e: 5, f: 6 } }, watch: { // 侦听顶级 property a(val, oldVal) { console.log(`new: ${val}, old: ${oldVal}`) }, // 字符串方法名 b: 'someMethod', // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深 c: { handler(val, oldVal) { console.log('c changed') }, deep: true }, // 侦听单个嵌套 property 'c.d': function (val, oldVal) { // do something }, // 该回调将会在侦听开始之后被立即调用 e: { handler(val, oldVal) { console.log('e changed') }, immediate: true }, // 你可以传入回调数组,它们会被逐一调用 f: [ 'handle1', function handle2(val, oldVal) { console.log('handle2 triggered') }, { handler: function handle3(val, oldVal) { console.log('handle3 triggered') } /* ... */ } ] }, methods: { someMethod() { console.log('b changed') }, handle1() { console.log('handle 1 triggered') } } }) const vm = app.mount('#app') vm.a = 3 // => new: 3, old: 1
-
-
【问】计算属性computed是什么?(如果相关数据发生改变,computed会重新计算并返回结果,有点类似watch侦听的意思,应该也是采用观察者设计模式;默认走缓存),参考计算属性
Note:
-
关于
computed
计算属性的理解:1、computed 和 data同级,计算属性写在computed中;
2、写起来像方法,用起来像属性;
3、计算属性虽然称为属性,但其本质是一个函数;
4、虽然计算属性本质是一个函数,但是在页面中使用计算属性时,不要加
()
;5、一定要有返回值;
6、可以使用
data
中的已知值;7、只要跟计算属性相关的数据发生了变化,计算属性就会重新计算,不相关的属性无论如何变化,都不会导致计算属性变化;
8、计算属性名不能和
data
中的数据重名(因为要使用data中的数据)。 -
使用例子:
<template> {{reversedMsg}} </template> export default { data(){ return{ msg : "" } }, computed: { reversedMsg(){ return this.msg.split('').reverse().join('') } } // ... }
-
-
【问】computed与watch的区别和使用场景?,参考Computed 和 Watch 的区别
Note:
-
computed
计算属性的作用:-
1)解决模板中放入过多的逻辑会让模板过重且难以维护的问题。例如两个数据的拼接或字体颜色的判断。
-
2)它支持缓存,只有依赖的数据发生了变化,才会重新计算。例如模板中多次用到数据拼接可以用计算属性,只执行一次计算,除非数据发生变化。
-
3)不支持异步,如果有异步操作,无法监听数据的变化。
-
4)如果属性值是函数,默认使用
get
方法,函数的返回值就是属性的属性值。还有一个set
方法,当数据变化时就会调用set
方法。 -
5)
computed
的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
-
-
watch
侦听器的作用:-
1)它不支持缓存,数据变化时,它就会触发相应的操作。
-
2)支持异步监听。
-
3)接受两个参数,第一个是最新的值,第二个是变化之前的值。
-
4)监听
data
或者props
传来的数据,发生变化时会触发相应操作。有两个参数:-
immediate
:立即触发回调函数。 -
deep
:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。
-
-
-
computed
与watch
的使用场景:-
computed
:是多对一,多个数据影响一个。当需要进行数值计算,并且依赖于其它数据时,应该使用computed
,因为可以利用computed
的缓存特性,避免每次获取值时都要重新计算(缓存多个值)。 -
watch
:是一对多,一个数据发生变化,执行相应操作会影响多个数据。当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
-
-
三、组件化 & 模块化开发(组件化系统、全局和局部组件、父子组件通讯 - 传参 & 事件监听与回调、兄弟通讯-共享变量绑定多个事件、插槽的使用)
Note:不使用Vue
的CDN
源也可以创建父子组件,并实现通信,参考不用vue-cli 创建vue组件
-
【问】Vue组件化系统的基础概念
Note:-
组件系统是
Vue
的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。其实在HTML中,几乎任意类型的应用界面都可以抽象为一个组件树。
-
在
Vue
里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。
-
-
【问】Vue中如何注册组件?(
Vue.extend()
创建组件,Vue.component()
注册组件)Note:
-
组件的使用分成三个步骤:
- 创建组件构造器;
- 注册组件;
- 使用组件;
-
组件使用的简单案例(这里的组件是全局的组件):
Vue.extend()
这个方法 如果发现vue.extend
不是方法,换其他cdn
源试试,之前试了https://unpkg.com/vue@3/dist/vue.global.js
没效果。<html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app1"> <my-cpn></my-cpn> </div> <div id="app2"> <my-cpn></my-cpn> </div> </body> <script> //1.创建组件构造器 const myComponent = Vue.extend({ template: `<div> <h2>组件标题</h2> <p>我是组件中的一个段落内容</p> </div>` }); //2.注册组件,并且定义组件标签的名称 Vue.component('my-cpn',myComponent) let app = new Vue({ el:'#app1' }) let app1 = new Vue({ el:'#app2' }) </script>
效果如下:
-
Vue.extend()
:调用Vue.extend()
创建的是一个组件构造器。-
通常在创建组件构造器时,传入
template
代表我们自定义组件的模板。 -
该模板就是在使用到组件的地方,要显示的HTML代码。
-
事实上,这种写法在
Vue2.x
的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
-
-
Vue.component()
:调用Vue.component()
是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。所以需要传递两个参数:-
注册组件的标签名
-
组件构造器
-
组件必须挂载在某个Vue实例下,否则它不会生效。
-
-
-
【问】什么是全局组件和局部组件?(局部组件在Vue实例中通过
components :{组件标签 : 组件名}
注册,全局组件通过Vue.component()
全局注册)Note:
-
当我们通过调用
Vue.component()
注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。 如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件。 -
全局组件看上一问代码;
-
局部组件代码如下(效果为
app2
对应的div
没有被渲染):<!-- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> --> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app1"> <my-cpn></my-cpn> </div> <div id="app2"> <my-cpn></my-cpn> </div> </body> <script> //1.创建组件构造器 const myComponent = Vue.extend({ template: `<div> <h2>组件标题</h2> <p>我是组件中的一个段落内容</p> </div>` }); let app = new Vue({ el:'#app1', components : { 'my-cpn' : myComponent } }) let app1 = new Vue({ el:'#app2' }) </script>
效果如下:
my-cpn
只在挂载了app1
的Vue
实例上可见
-
-
Note:
-
父组件和子组件:组件和组件之间存在层级关系,而其中一种非常重要的关系就是父子组件的关系。我们来看通过代码如何组成的这种层级关系(在父组件创建(
Vue.extend()
)时通过components
属性引入子组件):<!-- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> --> <!-- vue的template中只能有一个根节点 --> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app1"> <parent-cpn></parent-cpn> <p>Hello world</p> <child-cpn></child-cpn> </div> <div id="app2"> <child-cpn></child-cpn> </div> </body> <script> //1.创建组件构造器 const childComponent = Vue.extend({ template: `<div> <h2>组件标题</h2> <p>我是子组件</p> </div>` }); const parentComponent = Vue.extend({ template: `<div> <h2>组件标题</h2> <p>我是父组件</p> <child-cpn></child-cpn> </div>`, components : { "child-cpn" : childComponent } }); let app = new Vue({ el:'#app1', components : { 'parent-cpn' : parentComponent } }) let app1 = new Vue({ el:'#app2', components : { "child-cpn" : childComponent } }) </script>
效果如下:
app1
无法识别<child-cpn>
,app2
可以识别<child-cpn>
-
父子组件错误用法:以子标签的形式在Vue实例中使用
-
因为当子组件注册到父组件的
components
时,Vue会编译好父组件的模块 -
该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
-
<child-cpn></child-cpn>
是只能在父组件中被识别的。类似这种用法,<child-cpn></child-cpn>
是会被浏览器忽略的。
-
-
-
【问】父组件和子组件之间如何通讯?(渲染父组件时会同时渲染子组件;父传子用
props
,子组件通过props
定义要接收到参数;子传父用$emit
:子组件定义事件,父组件定义方法)Note:
-
父传子用
props
:子组件通过props
定义要接收到参数-
第一步:引入子组件。
-
第二步:在数据源中定义要传入子组件的数据
parentMsg
。 -
第三步:在使用
child
组件时传入parentMsg
。<child :自定义属性名="parentMsg"></child>
。 -
第四步:在子组件中,要
props:['自定义属性名']
来接收传过来的参数。
父组件参考代码:
<template> <div> <h2>parent</h2> <!--3、传入parentMsg--> <child :visible="visible"></chld> </div> </template> <script> //1、引入子组件 import child from './child.vue' export default { data() { return { //2、定义要传入子组件的数据parentMsg visible:'true' } }, components:{ child } } </script>
子组件参考代码:
<template> <div> {{visible}} </div> </template> <script> export default { name:'child', props:['visible']//接收 } </script>
如果使用
vue
的cdn
源,将所有Vue
实例(封装着template
属性)写在一个html
里,则可以在同一个html
页面上实现父组件向子组件传值:<html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app1"> <parent-cpn></parent-cpn> </div> <div id="app2"> <child-cpn></child-cpn> </div> </body> <script> //1 创建子组件 const childComponent = Vue.extend({ name : 'child', props : ['visible'], template: `<div> <h3>{{visible}}</h3> <p>我是子组件</p> </div>` }); //2 创建父组件 const parentComponent = Vue.extend({ data() { return { //2、定义要传入子组件的数据parentMsg visible : '进入子组件' } }, template: `<div> <h2>组件标题</h2> <p>我是父组件</p> <child-cpn :visible=visible></<child-cpn>> </div>`, components : { "child-cpn" : childComponent } }); let app = new Vue({ el:'#app1', components : { 'parent-cpn' : parentComponent } }) let app1 = new Vue({ el:'#app2', components : { "child-cpn" : childComponent } }) </script>
效果图如下:
-
-
子传父用
$emit
:子组件定义事件标识(sendmsg
),通过this.$emit(‘sendmsg’,所需要传的值)
向父组件发送该事件标识,父组件通过方法(getmsg
)来监听指定事件标识中绑定的值(事件监听)。emit
使用方法:this.$emit(‘自定义事件名’,所需要传的值)
-
第一步:首先在子组件中定义一个事件,并且使用
emit
发送给父组件,在示例中子组件使用的click
事件触发发送自定义事件(sendmsg)
。 -
第二步:在父组件中需要定义方法(
getmsg
)接受自定义事件(sendmsg
): -
第三步:在使用子组件时,
<child @sendmsg="getmsg">
。
【子组件】发送值代码:
<template> <div> <button @click="childmsg">点我试试</button> </div> </template> <script> export default { name:'child', data() { return { msg:"This is the first word from child" } }, methods:{ //点击按钮则向父组件传自定义事件sendmsg,childmsg childmsg(){ this.$emit('sendmsg',this.msg) } } } </script>
【父组件】接收值代码:
<template> <div> <child @sendmsg="getmsg"></child> </div> </template> <script> import child from './child.vue' export default { data() { return { } }, components:{ child }, methods:{ getmsg(val){ console.log(val) } } } </script>
如果使用
vue
的cdn
源,将所有Vue
实例(封装着template
属性)写在一个html
里,则可以在同一个html
页面上实现子组件向父组件传值:<html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app1"> <parent-cpn></parent-cpn> </div> <div id="app2"> <child-cpn></child-cpn> </div> </body> <script> //1 创建子组件 const childComponent = Vue.extend({ name : 'child', props : ['visible'], template: `<div> <h3>{{visible}}</h3> <p>我是子组件</p> <button @click="sendMsg">点击我向父组件传值</button> </div>`, methods : { sendMsg(){ this.$emit("getChildMsg","child出来了"); //"getChildMsg"是子组件向父组件暴露的参数方法标识 } } }); //2 创建父组件 const parentComponent = Vue.extend({ data() { return { //2、定义要传入子组件的数据parentMsg childMsg : "", visible : '进入子组件' } }, template: `<div> <h2>组件标题</h2> <p>我是父组件</p> <child-cpn :visible=visible @getChildMsg="getMsg"></child-cpn> <p>{{childMsg}}</p> </div>`, components : { "child-cpn" : childComponent }, methods : { getMsg(val){ this.childMsg = val; } } }); let app = new Vue({ el:'#app1', components : { 'parent-cpn' : parentComponent } }) let app1 = new Vue({ el:'#app2', components : { "child-cpn" : childComponent } }) </script>
实现效果如下:
-
-
-
【问】兄弟组件是什么?其之间如何进行数据共享?(可通过EventBus事件总线对象实现,即
$emit
发送数据(等价notify
),$on
监听数据(等价wait
),基于事件驱动通信,参考Vue 兄弟组件之间的通信Note:
-
Vue
中实现兄弟组件的通讯一般为2种方式:-
一种方法是让父组件允当两个子组件之间的中间件(中继);
-
另一种就是使用EventBus(事件总线),它允许两个子组件之间直接通讯,而不需要涉及父组件。
由于中继方式比较简单,这里不做赘述,只讲
EventBus
方式。 -
-
EventBus
事件总线方式:-
第一步:在兄弟组件同目录下创建
eventBus.js
,然后创建vue实例:import Vue from 'vue' export default new Vue()
-
第二步:在【兄弟组件A】中,引入
eventBus.js
(定义为bus
对象),接着定义数据msg
,编写方法用于在监听到share
事件后发送msg
。import bus from './eventBus.js' <button @click="sendMsg"> export default{ data(){ return{ msg:'hello' } }, methods:{ sendMsg(){ bus.$emit('share',this.msg); } } }
-
第三步
:在【兄弟组件B】中,引入eventBus.js
,定义数据newMsg,编写方法用于接收msg
和赋值给newMsg
:import bus from './eventBus.js' <button @click="sendMsg"> export default{ data(){ return{ newMsg:[] } }, created:{ bus.$on('share',val=>{ this.newMsg=val; }) } }
-
如果使用
vue
的cdn
源,将所有Vue
实例(封装着template
属性)写在一个html
里,则可以在同一个html
页面上实现子组件向父组件传值:
<!-- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> --> <!-- vue的template中只能有一个根节点 --> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app1"> <bro1-cpn></bro1-cpn> </div> <div id="app2"> <bro2-cpn></bro2-cpn> </div> </body> <script> //1. 创建总线对象 bus1 = new Vue(); //兄弟1向兄弟2传值 或者 兄弟2向兄弟1传值 // bus2 = new Vue(); //兄弟2向兄弟1传值 //事件监听 bus1.$on('getMsg1', function (val) { alert(val) }) bus1.$on('getMsg2', function (val) { alert(val) }) //2. 创建兄弟组件1 const brotherComp1 = Vue.extend({ template: `<div> <p>我是兄弟组件1</p> <button @click="sendMsg">点击我向兄弟组件2传值</button> </div>`, methods : { sendMsg(){ bus1.$emit("getMsg1","兄弟2,我来了"); //"getChildMsg"是子组件向父组件暴露的参数方法标识 } } }); //3. 创建兄弟组件2 const brotherComp2 = Vue.extend({ template: `<div> <p>我是兄弟组件2</p> <button @click="sendMsg">点击我向兄弟组件1传值</button> </div>`, methods : { sendMsg(){ bus1.$emit("getMsg2","兄弟1,我来了"); } } }); let app = new Vue({ el:'#app1', components : { 'bro1-cpn' : brotherComp1 } }) let app1 = new Vue({ el:'#app2', components : { "bro2-cpn" : brotherComp2 } }) </script>
实现效果如下:
-
-
父子组件通信 vs 兄弟组件通信:
- 这里声明事件为
share
字符串, 兄弟组件之间通过Vue
实例对象进行通信,通过obj.$emit
向其他兄弟组件发送关于share
事件的消息,其他兄弟组件通过obj.$on
监听share
事件的消息(即对象的wait
和notify
操作) - 和父子组件通信不同,兄弟组件可以通过一个共享
Vue
实例(bus
),该实例可以绑定多个事件标识(share1
,share2
)来实现通信,而父组件向子组件传参时,由于其自身嵌套关系:- 父组件传值给子组件时相当于函数调用,父组件不用使用
$emit
,子组件不用使用$on
; - 子组件传值给父组件时相当于函数回调,父组件通过对指定的事件标识进行监听(不用使用
$on
),而子组件通过$emit
进行回调,告知父组件已经处理好值了。
- 父组件传值给子组件时相当于函数调用,父组件不用使用
- 这里声明事件为
-
-
【问】插槽是什么?插槽的用法(需要先理解父子组件之间如何通讯;默认插槽,具名插槽,作用域插槽(默认,具名,解构),动态插槽名),参考插槽 Slots | Vue.js
Note:
- 在学习插槽之前需要先理解父子组件之间如何通讯;
- 总体思想是父组件在子组件的指定槽位中定制自己的模板(
template
),然后由子组件渲染模板内容(子组件中的slot
起到占位符的作用)。
-
v-slot
的基本用法:父组件可以通过子组件中的插槽显示自定义的内容-
vue
官方规定:每一个slot
插槽都要有一个name
名称
如果省略了slot
的name
则有一个默认名称default
默认情况下,使用组件时提供的内容会被填充到name
为default
的插槽内。 -
使用
v-slot:xxx
, 其中xxx
为插槽name
值,只能放在父组件标签内 -
v-slot
是一个虚拟标签,只起到包裹性质的作用,不会被渲染为实质性的html
元素,v-slot:xxx
可以简写为#xxx
-
v-slot
在父子组件中的使用:【父组件声明了子组件为Left,并使用具名插槽】 <Left> <template v-slot:mySlot> <p>这是在 Left 组件声明的p标签</p> </template> </Left> 【子组件定义了mySlot插槽】 <div style="color:#33e;background:#ee2"> <slot name="mySlot"></slot> </div>
-
-
默认插槽(
default
):-
当使用组件指定了插槽内容时,优先显示指定的内容
当没有指定内容时,渲染 slot 标签内的默认内容 -
简单演示:父组件显式提供的内容会取代子组件的默认内容
//父组件声明子组件为SubmitButton <SubmitButton>Save</SubmitButton> //子组件默认内容 <button type="submit"> <slot> Submit <!-- 默认内容 --> </slot> </button>
-
-
具名插槽(
name
):-
在一个组件中包含多个插槽出口是很有用的,为了将内容置入组件的不同插槽中,要用带有
name
属性的插槽 -
简单演示:没有提供
name
的<slot>
出口会隐式地命名为 “default”(默认插槽) 。//子组件定义多个插槽 <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
在父组件中使用
<BaseLayout>
时,我们需要一种方式将多个插槽内容传入到各自目标插槽的出口。此时就需要用到具名插槽了//父组件将子组件声明为<BaseLayout> <BaseLayout> <template v-slot:header> <!-- header 插槽的内容放这里 --> </template> </BaseLayout>
-
具名插槽传入内容时需要使用一个含
v-slot
指令的<template>
元素;v-slot
有对应的简写#
,因此<template v-slot:header>
可以简写为<template #header>
,意思是:将这部分模板片段传入子组件的 header 插槽中
-
-
渲染作用域 & 作用域插槽(通过
v-slot
传入props
获取子组件插槽的属性):-
渲染作用域:插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。
<span>{{ message }}</span> <FancyButton>{{ message }}</FancyButton>
这里的两个
{{ message }}
插值表达式渲染的内容都是一样的。插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:
父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
-
作用域插槽:在上面的渲染作用域可知,插槽的内容无法访问到子组件的状态。然而在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。这里可以像对组件传递
props
那样,向一个插槽的出口上传递attributes
-
条件:在封装组件时,为预留的
<slot>
提供属性对应的值 -
格式:
1)子组件:
<slot v-bind:username='username' name='box1'></slot>
2)父组件:
<template v-slot:box1='username_props'>
在封装组件时,为预留的<slot>
提供属性对应的值,叫做作用域插槽。这些属性对应的值可以在父组件中访问到,默认为空对象。 -
默认作用域插槽演示:
//父组件 <MyComponent v-slot="slotProps"> {{ slotProps.text }} {{ slotProps.count }} </MyComponent>
v-slot="slotProps"
可以类比这里的函数签名,和函数的参数类似,我们也可以在v-slot
中使用解构://父组件 <MyComponent v-slot="{text,count}"> {{ text }} {{ count }} </MyComponent>
-
具名作用域插槽演示:
具名作用域插槽的工作方式也是类似的,插槽props
可以作为v-slot
指令的值被访问到:v-slot:name="slotProps"
,当使用缩写时是这样#name="slotProps"
//子组件 <slot name="header" message="hello"></slot> //父组件 <MyComponent> <template #header="headerProps"> {{ headerProps }} </template> <template #default="defaultProps"> {{ defaultProps }} </template> <template #footer="footerProps"> {{ footerProps }} </template> </MyComponent>
最终
headerProps
的结果是{ message: 'hello' }
-
-
作用域插槽的例子:
子组件在其作用域下完成与
user
对象的绑定<div> <slot v-bind:user="user" name="box3"></slot> <slot v-bind:msg="hello world" name="box4"></slot> </div> data(){ return:{ user:{ firstname:'lan', lastname:'chun' } } }
父组件通过向插槽传props,可以获取到子组件的user对象值
<子组件名> <template v-slot:box3="slotProps1"> {{slotProps1.user.firstname}} {{slotProps1.user.lastname}} </template> <template v-slot:box4="slotProps2"> {{slotProps2.msg}} </template> </子组件名>
-
动态插槽名(在插槽
v-slot
上使用动态参数)-
动态参数:参考模板语法 | Vue.js
同样在指令参数上也可以使用一个
JavaScript
表达式,需要包含在一对方括号内:<!-- 注意,参数表达式有一些约束, 参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释 --> <a v-bind:[attributeName]="url"> ... </a> <!-- 简写 --> <a :[attributeName]="url"> ... </a>
这里的
attributeName
会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性attributeName
,其值为"href"
,那么这个绑定就等价于v-bind:href
。相似地,你还可以将一个函数绑定到动态的事件名称上:
<a v-on:[eventName]="doSomething"> ... </a> <!-- 简写 --> <a @[eventName]="doSomething">
在此示例中,当
eventName
的值是"focus"
时,v-on:[eventName]
就等价于v-on:focus
。 -
动态插槽名:
动态指令参数在
v-slot
上也是有效的,即可以定义下面这样的动态插槽名:<base-layout> <template v-slot:[dynamicSlotName]> ... </template> <!-- 缩写为 --> <template #[dynamicSlotName]> ... </template> </base-layout>
注意这里的表达式和动态指令参数受相同的语法限制。
-
-
四、Vue Cli和Webpack(打包压缩静态资源&依赖模块,热加载)
参考webpack看这一篇就够了,webpack详解,webpack官网
-
Note:
-
为什么要进行前端的模块化开发? 参考Vue 知识点汇总(上)
-
前端模块化的一些方案:AMD、CMD、
CommonJS
、ES6
。 -
在
ES6
之前,要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发,并且在通过模块化开发完成了项目后,还需要处理模块化间的各种依赖,并且将其进行整合打包。 -
此时出现webpack,其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系,而不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用。
-
-
Note:
-
webpack
本身是node
的一个第三方模块包, 用于打包代码 -
webpack
是javascript
应用程序的静态资源模块的打包器 (module bundler
),打包的过程中,还可对资源进行处理,比如压缩图片,将scss
转成css
,将ES6
语法转成ES5
语法,将TypeScript
转成JavaScript
等等操作。 -
把很多文件打包整合到一起, 缩小项目体积, 提高加载速度,常见功能如下:
-
less/sass
处理成css
-
ES6/7/8
处理成ES5
-
支持
js
模块化 -
处理
css
兼容性 -
将
html/css/js
进行压缩合并
-
-
-
【问】如何理解前端的打包过程?(文件压缩,模块间的依赖关系处理)
Note:
-
前端打包工具包括:
webpack
、grunt
、gulp
。 -
前端的打包过程可以通过
webpack
来理解:webpack
帮助我们进行模块化,并且处理模块间的各种复杂关系。
-
-
【问】Webpack的基本使用?(打包命令
npm run build
)Note:
-
Webpack
项目结构-
新建项目目录,目录结构和之前的规范不同
-
根目录创建
public
,创建index.html
-
创建
src
存放代码资源文件,创建index.js
-
将逻辑进行模块化,并在
index.html
引入模块化的js
文件
-
-
Webpack
的使用-
初始化包环境
yarn init
-
安装依赖包
yarn add webpack webpack-cli -D
-
配置
scripts
(自定义命令)scripts: { "build": "webpack" }
-
运行打包命令
yarn build #或者 npm run build
-
-
-
Note:
-
webpack打包时指定的入口文件是哪个?默认情况下,
webpack
会将项目根目录下的src/index.js
文件作为入口文件。可以在 webpack 的配置文件webpack.config.js
中通过entry
属性来指定入口文件(ChatGPT
给出的答案):module.exports = { entry: './src/index.js' };
如果项目中存在多个入口文件,可以将
entry
属性设置为一个对象,并为每一个入口文件指定对应的名称:module.exports = { entry: { app: './src/app.js', login: './src/login.js' } };
通过这种方式,
webpack
会将多个入口文件打包成多个捆绑(bundle),并生成对应的静态资源文件。
总之,webpack
打包时的入口文件可以通过配置文件中的entry
属性来指定,默认情况下会将根目录下的src/index.js
文件作为入口文件进行打包。 -
webpack
的入口和出口:告诉webpack
从哪开始打包, 打包后输出到哪里-
默认入口为
./src/index.js
,默认出口为./dist/main.js
-
webpack配置:在
src
的并列处新建webpack.config.js
,填入配置项const path = require("path") module.exports = { entry: "./src/main.js", // 入口 output: { path: path.join(__dirname, "dist"), // 出口路径 filename: "bundle.js" // 出口文件名 } }
修改
package.json
,自定义打包命令 ,让webpack使用配置文件"scripts": { "build": "webpack" }
-
-
打包流程图:所有要被打包的资源都要跟入口产生直接/间接的引用关系
-
-
【问】Webpack如何在打包后生成html,并自动引入打包好的js?(
html-webpack-plugin
)Note:
-
html-webpack-plugin
插件, 让webpack
打包后生成html
文件并自动引入打包后的js
,html-webpack-plugin插件地址 -
配置步骤如下:
-
下载插件
yarn add html-webpack-plugin -D
-
配置
webpack.config.js
// 引入自动生成 html 的插件 const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // ...省略其他代码 plugins: [ new HtmlWebpackPlugin() ] }
-
打包后的
index.html
自动引入打包后的js
文件 -
自定义打包的
html
模版,和输出文件名字plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', filename: 'index.html' }) ] ] }
-
-
-
Note:
-
mode
模式分为开发阶段和发布阶段-
development
开发阶段,简易打包,打包速度快 -
production
发布阶段,打包精细,打包速度慢(但是没关系不会经常production)
mode: 'development || production'
-
-
-
【问】什么是Webpack开发服务器?(
Webpack-dev-server
对更新的文件进行打包,其他内容用缓存的即可)Note:
-
webpack-dev-server
是一个小型的 Node.js Express 服务器,可以用来快速开发应用程序。它主要提供了以下功能(ChatGPT
的答案):- 自动重新加载:当
webpack
打包后的文件发生变化时,webpack-dev-server
会自动重新加载页面。 - 热更新:当项目中的源代码发生变化时,
webpack-dev-server
会自动将变更的代码模块替换成新的版本,而不需要手动重新加载页面。 - 支持
HTTPS
:可以通过配置选项来启用 HTTPS 协议。
使用
webpack-dev-server
可以提高开发效率,但是它并不适用于生产环境。因为它不会将打包后的文件写入硬盘,而是存储在内存中,所以无法直接访问打包后的文件。
如果需要在生产环境中使用webpack
打包后的文件,则应该使用webpack
的默认模式来打包文件,而不是使用webpack-dev-server
。 - 自动重新加载:当
-
Webpack-dev-server
打包服务器要解决的问题:-
每次在修改代码时, 都需要重新
yarn build
打包, 才能看到最新的效果, 实际工作中, 打包yarn build
非常费时 (30s - 60s
) 之间。之所以耗时,是因为:- 构建依赖
- 磁盘读取对应的文件到内存, 才能加载
- 将处理完的内容, 输出到磁盘指定目录
-
因此可以起一个开发服务器, 在电脑内存中打包, 缓存一些已经打包过的内容, 只重新打包修改的文件, 最终运行加载在内存中给浏览器使用。
-
webpack-dev-server
的目的:启动本地服务, 可实时更新修改的代码, 打包变化代码到内存中, 然后直接提供端口和网页访问。
-
-
Webpack-dev-server
如何配置:-
修改
Packge.json
"scripts": { "build": "webpack", "serve": "webpack serve --port 8083 --open" },
-
在
webpack.config.js
中添加服务器配置,更多配置参考这里https://webpack.docschina.org/configuration/dev-server/#devserveraftermodule.exports = { // ...其他配置 devServer: { port: 3000, // 端口号 open: true } }
-
-
-
【问】Webpack如何对css文件进行加载和打包?(配置css加载器:
style-loader
,css-loader
和less-loader
)Note:
-
如果直接将准备好的
css
文件, 引入到webpack
入口,使用webpack
进行打包会存在问题:原因是
webpack
默认只认识js
文件和json
文件 -
css
加载器配置步骤:style-loader文档,css-loader文档-
安装依赖
yarn add style-loader css-loader -D
-
webpack.config.js
配置const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // ...其他代码 module: { rules: [ // loader的规则 { test: /\.css$/, // 匹配所有的css文件 // use数组里从右向左运行 // 先用 css-loader 让webpack能够识别 css 文件的内容并打包 // 再用 style-loader 将样式, 把css插入到dom中 use: [ "style-loader", "css-loader"] } ] } }
-
实现原理:当模块引到入口时才会被
webpack
打包;css
打包进js
中, 然后被嵌入在style
标签插入dom
上
-
-
less
加载器步骤:less-loader文档-
安装
less-loader
yarn add less less-loader -D
-
webpack.config.js
配置module: { rules: [ // loader的规则 // ...省略其他 { test: /\.less$/, // 使用less-loader, 让webpack处理less文件, 内置还会用less翻译less代码成css内容 use: [ "style-loader", "css-loader", 'less-loader'] } ] }
-
-
-
【问】Webpack如何对图片文件进行加载和打包?(
webpack5
使用asset module
;webpack4
使用url-loader
和file-loader
)Note:
-
使用
asset module
方式(webpack5
版本新增)对图片文件进行加载,asset module文档{ test: /\.(png|jpg|gif|jpeg)$/i, type: 'asset' }
-
如果是
webpack4
及以前,使用url-loader
和file-loader
,配置如下:url-loader文档,file-loader文档-
下载依赖包
yarn add url-loader file-loader -D
-
webpack.config.js
配置{ test: /\.(png|jpg|gif|jpeg)$/i, use: [ { loader: 'url-loader', // 匹配文件, 尝试转base64字符串打包到js中 // 配置limit, 超过8k, 不转, file-loader复制, 随机名, 输出文件 options: { limit: 8 * 1024, }, }, ], }
-
src/assets/
准备2个图文件 -
在
css/less/index.less
中,把小图片用做背景图body{ background: url(../assets/logo_small.png) no-repeat center; }
-
在
src/main.js
中,把大图插入到创建的img
标签上, 添加body
上显示// 引入图片-使用 import imgUrl from './assets/1.gif' const theImg = document.createElement("img") theImg.src = imgUrl document.body.appendChild(theImg)
-
打包运行
dist/index.html
观察2个图片区别 -
实现原理:
url-loader
把文件转base64
打包进js
中, 会有30%
的增大,file-loader
把文件直接复制输出。
-
-
-
【问】Webpack如何对字体文件进行加载和打包?(
webpack5
使用asset module
,webpack4
使用url-loader
和file-loader
)Note:
-
在
webpack5
中,可以使用asset module
技术,asset/resource
直接输出到dist
目录下,配置步骤:asset module文档-
src/assets/
中放入字体库fonts文件夹 -
在
main.js
引入iconfont.css
// 引入字体图标文件 import './assets/fonts/iconfont.css'
-
在
public/index.html
使用字体图标样式<i class="iconfont icon-weixin"></i>
-
-
webpack4
及以前使用下面的配置-
配置
webpack.config.js
{ // 处理字体图标的解析 test: /\.(eot|svg|ttf|woff|woff2)$/, use: [ { loader: 'url-loader', options: { limit: 2 * 1024, // 配置输出的文件名 name: '[name].[ext]', // 配置输出的文件目录 outputPath: "fonts/" } } ] }
-
执行打包命令,观察打包后网页效果
-
-
-
Note:
-
以
8kb
进行区分,小于8kb
图片转成 base64 字符串- 好处就是浏览器不用发请求了,直接可以读取
- 坏处就是如果图片太大,再转
base64
就会让图片的体积增大 30% 左右
-
-
【问】Webpack如何处理高版本的js?(
babel-loader
)Note:
-
实现原理:
babel-loader
通过调用babel
的api
让webpack对高版本的js
代码, 降级处理后打包 -
写代码演示: 高版本的js代码(箭头函数、类), 打包后, 直接原封不动打入了js文件中, 遇到一些低版本的浏览器就会报错
-
原因:
webpack
默认仅内置了模块化的兼容性处理import export
-
解决:
babel
的介绍可以用于处理高版本js
语法的兼容性(babel官网)让webpack
配合babel-loader
对js
语法做处理。babel-loader文档@babel/core
:@babel/core
是babel
的核心库,所有的核心Api都在这个库里,这些Api供babel-loader
调用;@babel/preset-env
:这是一个预设的插件集合,包含了一组相关的插件,Bable中是通过各种插件来指导如何进行代码转换。该插件包含所有es6转化为es5的翻译规则
-
-
配置过程如下:
-
安装依赖包:
yarn add -D babel-loader @babel/core @babel/preset-env
-
配置规则:
module: { rules: [ { test: /\.js$/, exclude: /(node_modules)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] // 预设:转码规则(用bable开发环境本来预设的) } } } ] }
-
-
-
【问】Vue Cli是什么?怎样创建一个项目?(以
Vue-Cli2
为例),参考Vue-cli官网,从零搭建vue2项目Note:
-
CLI
是Command-Line Interface
, 翻译为命令行界面, 但是俗称脚手架,Vue CLI是一个官方发布 vue.js 项目脚手架,使用vue-cli
可以快速搭建Vue
开发环境以及对应的webpack
配置. -
Vue CLI
使用前提-
安装
Node.js
:NPM
的全称是Node Package Manager
是一个NodeJS
包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。 -
使用了
webpack
模板:完成对所有的资源压缩等优化的操作。
-
-
安装
Vue cli
过程如下:- 1)先下载node和npm,并分别使用
node -v
和npm -v
查看相应版本。(如果npm
下载慢,参考npm下载缓慢解决方法) - 2)安装vue-cli后,通过
vue —version
查看vue-cli版本 - 3)创建一个新项目(创建时可以选择是
Vue3
或者Vue2
):vue create hello-world
- 4)运行项目:
cd hello-world
后,npm run serve
启动前端服务,通过http://localhost:8080/访问首页
Note:究竟是npm run serve
还是npm run dev
得看package.json
中scripts配置vue-cli-service serve
命令的key
是什么,参考npm run dev 和 npm run serve区别)。
- 1)先下载node和npm,并分别使用
-
-
Note:
-
vue-cli 3
与vue-cli 2
版本有很大区别(项目目录结构不同):-
vue-cli 3
是基于webpack 4
打造,vue-cli 2
还是webapck 3
-
vue-cli 3
的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录 -
vue-cli 3
提供了vue ui
命令,提供了可视化配置,更加人性化:
移除了static文件夹,新增了public文件夹,并且index.html
移动到public中
-
-
Vue CLI2初始化项目:
vue init webpack my-project
,目录结构如下:
vue-cli2
项目的结构如下(ChatGPT
给出的答案):├── build │ ├── build.js │ ├── check-versions.js │ ├── dev-client.js │ ├── dev-server.js │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ └── prod.env.js ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── assets │ ├── components │ ├── main.js │ ├── router │ └── store ├── static └── test
下面是每个文件的作用:
build/
:用于存放构建相关的文件。config/
:用于存放项目配置文件。index.html
:项目的入口文件。package.json
:项目的配置文件,用于定义项目的依赖和脚本。src/
:用于存放源代码。src/App.vue
:项目的根组件。src/assets/
:用于存放静态资源。src/components/
:用于存放组件。src/main.js
:项目的入口文件。src/router/
:用于存放路由配置。src/store/
:用于存放状态管理。static/
:用于存放静态资源。test/
:用于存放测试相关文件。
Vue CLI3初始化项目:
vue create my-project
,目录结构如下:
-
vue-cli2
和vue-cli3
在搭建 Vue 项目时存在一些差异。主要区别如下(chatGPT
给的答案):vue-cli2
是基于webpack 1
和webpack-simple
模板搭建 Vue 项目,项目结构比较简单,默认不支持单文件组件。
vue-cli3
是基于webpack 4
和webpack-chain
模板搭建 Vue 项目,项目结构比较复杂,默认支持单文件组件。vue-cli2
中,webpack 配置文件在项目根目录下,可以直接修改配置文件进行定制化配置。
vue-cli3
中,webpack 配置文件分为开发环境和生产环境两部分,位于项目根目录下的vue.config.js
文件中,可以通过链式语法修改配置。vue-cli2
中,默认使用的是webpack-dev-server
,支持热更新。
vue-cli3
中,默认使用的是webpack-dev-server
,也支持热更新。但是,它还支持webpack-dev-middleware
,可以在服务端进行开发。
总之,
vue-cli2
和vue-cli3
在搭建 Vue 项目时存在一些区别,包括模板类型、项目结构、webpack 配置、开发服务器等方面。
-
-
【问】Webpack如何配置vue.js?(使用babel-loader,style-loader,css-loader,less-loader,file-loader,url-loader等进行打包;搭建webpack-dev-server),参考webpack打包Vue项目
Note:webpack
通过vue-loader
和vue-template-compiler
等插件来配置vue.js
(ChatGPT
给出的答案)。vue-loader
是一个webpack
的加载器,用于将Vue
组件的代码转换为JavaScript
模块。它可以解析Vue
组件中的 HTML 模板、CSS 样式和 JavaScript 代码,并将它们打包为JavaScript
模块,安装命令如下:
然后在配置文件npm install vue-loader vue-template-compiler --save-dev
webpack.config.js
中配置vue-loader
:module.exports = { module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' } ] } };
vue-template-compiler
是一个Vue
模板编译器,用于将Vue
模板转换为JavaScript
代码。它可以在编译时解析 Vue 模板中的语法,并生成对应的JavaScript代码。
然后在配置文件npm install vue-template-compiler --save-dev
webpack.config.js
中配置vue-template-compiler
:
上面的配置中,const VueLoaderPlugin = require('vue-loader/lib/plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' } ] }, plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: './src/index.html' }) ] };
VueLoaderPlugin
用于启用vue-loader
,HtmlWebpackPlugin
用于将 Vue 组件打包成 HTML 文件。
总之,webpack
通过vue-loader
和vue-template-compiler
等插件来配置vue.js
,可以解析 Vue 组件的 HTML 模板、CSS 样式和 JavaScript 代码,并将它们打包成 HTML 文件。
-
Note:
-
main.js
是入口文件,主要作用是初始化vue
实例(App
主组件对象,router
对象),并通过Vue.use()
使用需要的插件。import Vue from 'vue' import ElementUI from 'element-ui'; import'element-ui/lib/theme-chalk/index.css'; import App from './App.vue' import router from '@/router/index.js' import Main from '@/components/Main.vue' Vue.config.productionTip=false Vue.use(ElementUI); new Vue ( { render:h=>h(App), router //router }).mount('#app') //"el"等价于$mount
-
App.vue
是我们的主组件,所有页面都是在App.vue
下进行切换的。其实你也可以理解为所有的路由也是App.vue
的子组件,即所有的组件都在App.vue上完成注册。所以这里router(hello
)标示为App.vue
的子组件。<template> <div id="app"> <img src="./assets/logo.png"> <hello></hello> </div> </template> <script> import Hello from './components/Hello' export default { name: 'app', components: { Hello } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
-
Vue
项目结构小总结:-
1)
index.html
文件入口; -
2)
src
放置组件和入口文件; -
3)
node_modules
为依赖的模块; -
4)
config
中配置了路径端口值等; -
5)
build
中配置了webback
的基本配置、开发环境配置、生产环境配置等。 -
6)
main.js
是我们的入口文件,主要用于初始化vue
实例,并使用需要的插件。 -
7)
App.vue
是我们的主组件,所有的页面都在App.vue下进行切换。我们可以将router
标示为App.vue
的子组件。 -
8)在每个模板中给
style
设置一个scoped
属性,意为该style
的属性只对这个组件起作用,不会影响到其他组件中含有相同class的元素。
-
-
五、vue-router的使用(路由跳转、路径与组件的映射、嵌套路由、动态路由、路由传参、路由守卫、promise异步操作与链式编程)
参考 Vue Router官网,异步Promise及Async/Await可能最完整入门攻略,ES6 Promise 和 Async/await的使用,在 Vue 中用 Axios 和异步模式请求API
-
【问】前端路由的概念和原理?(前端路由通过监听
url
中hash
值的变化情况(href
属性),渲染对应的组件)Note:
-
一般来说,在浏览器中,我们通过输入URL并点击回车来访问不同的页面。这种方式称为后端路由(
ChatGPT
的回答)- 但是,在单页应用中,所有的页面都是由同一个HTML文件加载的,因此不能通过更改URL来实现页面切换。这时,就需要在前端实现路由功能。
- 前端路由通常是通过
js
来实现的,它会监听浏览器的地址栏,并在用户点击浏览器的前进或后退按钮时,动态渲染页面内容。 - 通过前端路由,可以让单页应用具有与多页应用(在应用的每个页面中,为每个链接添加一个
href
属性,指向对应的HTML文件。这样,用户点击链接时,就可以跳转到目标页面)相同的路由功能,提高用户体验。
此外,前端路由还可以提供其他一些优点,比如:
- 可以使用
history
模式,使url
看起来更美观,不会出现hash
值。 - 可以通过路由重定向(
redirect
)实现页面重定向。 - 可以通过路由别名(
alias
)为某个路由定义多个别名。
-
路由器提供了两种机制: 路由和转送。
- 路由是决定数据包从来源到目的地的路径.
- 转送将输入端的数据转移到合适的输出端.
路由中有一个非常重要的概念叫路由表,路由表本质上就是一个映射表, 决定了数据包的指向。
-
前端路由:前端路由的本质是一个关于路径和组件的映射表,其核心是改变URL,但是页面不进行整体的刷新,具体流程如下:
1)用户点击页面上的路由链接
2)导致
url
地址的Hash
值变化3)前端路由监听到
Hash
地址的变化4)前端路由把当前
Hash
地址对应的组件渲染到浏览器中
- 实现原理:
url
的hash
也就是锚点(#
), 本质上是改变window.location
的href
属性,可以通过直接赋值location.hash
来改变href
, 但是页面不发生刷新。
-
-
【问】路由的基本使用?(创建路由组件对象;创建路由对象;使用路由对象),参考Router-view路由出口,Vue 知识点汇总(下)
Note:
-
使用
vue-router
的基本步骤:-
1)创建路由组件
-
2)配置路由映射: 组件和路径映射关系;
-
3)使用路由: 通过
<router-link to="">
(路由入口,渲染成<a>
标签)和<router-view>
(路由出口)实现页面跳转;
-
-
配置步骤如下:
1)安装
vue-router
包npm install vue-router --save
2)创建路由组件:
Prize.vue
,Main.vue
和parent.vue
(省略)3)在
router
文件夹下建立index.js
,定制路由规则。//1、导入Vue和VueRouter的包,定义 (路由) 组件 import Vue from 'vue' import VueRouter from 'vue-router' import Prize from '@/components/Prize.vue' import Home from "@/components/Main.vue"; import parent from '@/components/parent' //2、调用Vue.use()函数,把VueRouter安装为vue插件 Vue.use(VueRouter); //3、创建路由的实例对象 const router =new VueRouter({ routes:[ { path:'/home', component:Home }, { path:'/prize', component:Prize, }, { path:'/parent', component:parent } ] }) //4、向外共享路由的实例 export default router
包括如下步骤:a.导入
Vue
路由组件对象;b.调用Vue.use()
函数使用VueRouter
插件;c.创建路由的实例对象;d.向外共享路由的实例。4)把
router
组件对象挂载到main.js
上:import Vue from 'vue' import ElementUI from 'element-ui'; import'element-ui/lib/theme-chalk/index.css'; import App from './App.vue' import router from '@/router/index.js' import Main from '@/components/Main.vue' Vue.config.productionTip=false Vue.use(ElementUI); new Vue ( { render:h=>h(App), router //router }).mount('#app') //"el"等价于$mount
5)在
App.vue
中通过<router-link to="">
和<router-view>
(路由出口),来使用router
实例对象,参考Router-view路由出口<template> <div id="app"> <h1>我是网站的标题</h1> <router-link to="/Home">首页</router-link> <router-Link to="/Prize">价位</router-link> <router-view></router-view> <h2>我是APP中一些底部版权信息</h2> </div> </template> <script> export default { name : ' App ' , components : { } } </script>
-
<router-link>
: 该标签是一个vue-router
中已经内置的组件, 它会被渲染成一个<a>
标签;<router-view>
: 该标签会根据当前的路径, 动态渲染出不同的组件。 -
网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和
<router-view>
处于同一个等级。 -
在路由切换时, 切换的是
<router-view>
挂载的组件, 其他内容不会发生改变.
-
-
-
【问】路由的重定向(在配置路径和组件时,使用
redirect
实现重定向,跳转至其他path
,进而加载相应component
)Note:
-
路由重定向(
redirect
)是指在访问某个路由时,自动跳转到另一个路由(ChatGPT
的回答)。
在Vue
中,可以通过在路由配置中配置redirect
属性来实现路由重定向。const router = new VueRouter({ routes: [ { path: "/user/:id", redirect: "/user/:id/profile", }, { path: "/user/:id/profile", component: UserProfile, }, ], });
在上面的例子中,当用户访问
/user/:id
路由时,会自动重定向到/user/:id/profile
路由,并加载对应的组件。 -
路由重定向指的是:用户在访问地址
A
的时候,强制用户跳转到地址C
,从而展示特定的组件页面;通过路由规则的redirect
属性,指定一个新的路由地址,可以很方便地设置路由的重定向:const router = new VueRouter({ //在routes数组中,声明路由的匹配规则 routes: [ //当用户访问/的时候,通过redirect属性跳转到/home对应的路由规则 { path : '/', redirect:'/home'}, { path : '/home' , component : Home}, { path : '/movie', component : Movie}, { path : '/about', component : About} ] })
-
-
【问】什么是路由的懒加载(打包
js
时将路由对应的组件打包成一个个的js
代码块,在组件被访问到的时候才加载,=>
异步加载)Note:
-
为什么要进行路由的懒加载:当打包构建应用时,
Javascript
包会变得非常大,影响页面加载。 如果将路由对应的组件打包成一个个的js
代码块,只有在这个路由被访问到的时候, 才加载对应的组件。
-
懒加载的方式:
-
结合
Vue
的异步组件和Webpack
的代码分析:const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
在 Vue.js 中,
=>
符号是一种箭头函数(arrow function)的简写形式(lambda表达式用来表示函数)。它的完整写法是:const getters = { sidebar: state => state.app.sidebar } 等价于 const getters = { sidebar : (state) => { return state.app.sidebar } }
-
AMD写法:
const About = resolve => require(['../components/About.vue'], resolve);
-
在ES6中, 我们可以有更加简单的写法来组织
Vue
异步组件和Webpack
的代码分割:const Home = () => import('../components/Home.vue')
-
-
-
【问】什么是嵌套路由(在主组件、子组件下的
<router-link to=“”>
中使用index.js
配置好的路径)Note:
-
Vue 嵌套路由又称子路由,是指在 Vue 应用中,一个路由组件会渲染出其他路由组件。在实际应用中,通常由多层嵌套的组件组合而成。
-
需求在
apptest.vue
下路由到about
,在about
组件下路由到tab1
、tab2
-
第一步:路由配置
index.js
{ path: '/about', component: About, // redirect: '/about/tab1', children: [ // 子路由规则 // 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由” { path: '', component: Tab1 }, { path: 'tab2', component: Tab2 } ] },
-
第二步:在
apptest.vue
下路由到/about
,则会加载About
组件<router-link to="/about">关于</router-link> <!-- 作用很单纯:占位符,给要显示的组件预留位置的 --> <router-view></router-view>
-
第三步:在
about
组件下路由到/about/tab1
、/about/tab2
,则会加载Tab1 、Tab2
组件<router-link to="/about/tab1">关于</router-link> <router-link to="/about/tab2">关于</router-link> <router-view></router-view>
-
-
-
【问】什么是动态路由匹配(通过
:
定义路径的参数项,动态渲染组件,提高路由规则的复用性)Note:
-
Vue
的动态路由指的是在定义路由时,可以使用参数的形式定义一个路由,并且在路由跳转时可以传递参数来动态渲染对应的组件,这样做可以让同一个组件显示不同的内容。 -
在
vue-router
中使用英文的冒号(:
)来定义路由的参数项。index.js
示例代码如下:假设路由地址为/user/:id
,则可以动态匹配hash
值为/user?id=xxx
的url
。const User = { template: '<div>User</div>' } const router = new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: '/user/:id', component: User } //相当于/user?id=xxx ] })
-
动态路由的使用场景(
ChatGPT
回答)- 举个例子,假设我们有一个用户管理系统,其中需要根据用户的
id
来显示不同的用户信息。我们可以使用动态路由实现这个功能。例如,我们可以定义一个路由规则/user/:id
,:id
就是动态部分。这样,当用户访问http://www.example.com/user/123
时,Vue Router
会自动将:id
的值设为123
,并将用户导航到对应的路由组件中。 - 比如,在一个博客应用中,用户可能想要访问不同的文章,每一篇文章都对应一个不同的
URL
,如/articles/:articleId
。通过使用动态路由,开发者可以让应用根据用户输入的:articleId
匹配到相应的文章组件,并展示给用户。
- 举个例子,假设我们有一个用户管理系统,其中需要根据用户的
-
-
【问】什么是声明式导航和编程式导航(声明式导航即点击
<a>
标签上的链接;编程式导航则通过调用router
对象api
实现(push
和replace
)),参考vue-router的push和replace的区别,VUE的两种跳转push和replace对比区别Note:
-
在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:
普通网页中点击<a>
链接、vue项目中点击<router-link:to="...">
都属于声明式导航;在浏览器中,调用
API
方法实现导航的方式,叫做编程式导航。例如:
普通网页中调用location.href
跳转到新页面的方式,属于编程式导航;router.push(...)
方法的参数可以是一个字符串路径,或者一个描述地址的对象。// 字符串 router.push('home') // 对象 this.$router.push({path: '/login?url=' + this.$route.path}); // 带查询参数,变成/backend/order?selected=2 this.$router.push({path: '/backend/order', query: {selected: "2"}}); // 命名的路由 router.push({ name: 'user', params: { userId: 123 }})
-
vue-router
中的编程式导航API,常用的导航API有:-
1)
this.$router.push('hash地址')
:跳转到不同的url
,但这个方法会向history
栈添加一个记录,点击后退会返回到上一个页面。 -
2)
this.$router.replace('hash地址')
:同样是跳转到指定的url
,但是这个方法不会向history
里面添加新的记录,点击返回,会跳转到上上一个页面,上一个记录是不存在的。 -
3)
this.$router.go(数值n)
:相对于当前页面向前或向后跳转多少个页面,类似window.history.go(n)
。n可为正数可为负数。正数返回上一个页面 -
4)如果在行内使用编程式导航跳转的时候,
this
必须要省略,否则会报错!比如:<button @click="$router.back()">back 后退</button> <button @click="$router.forward()">forward 前进</button>
参考代码:
<template> <div class="movie-container"> <button @click="gotoLk">通过 push 跳转到“洛基”页面</button> <button @click="gotoLk2">通过 replace 跳转到“洛基”页面</button> <button @click="goback">后退</button> <!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错! --> <button @click="$router.back()">back 后退</button> <button @click="$router.forward()">forward 前进</button> </div> </template> <script> export default { name: 'Movie', methods: { gotoLk() { // 通过编程式导航 API,导航跳转到指定的页面 this.$router.push('/movie/1') }, gotoLk2() { this.$router.replace('/movie/1') }, goback() { // go(-1) 表示后退一层 // 如果后退的层数超过上限,则原地不动 this.$router.go(-1) } } } </script>
-
-
-
【问】vue中
$router
和$route
的区别,参考vue中$router
和$route
的区别
Note:this.$route
:当前激活的路由的信息对象。每个对象都是局部的,封装着当前路由的path
,name
,params
,query
等属性,比如:<script> export default { created() { const { params, query } = this.$route //获取`$route`对象中的`params`和`query` const { path } = params //编程式导航,但不会添加到历史记录中 this.$router.replace({ path: '/' + path, query }) }, render: function(h) { return h() // avoid warning message } } </script>
$route
对象中的params
路由参数和query
查询参数有什么区别(ChatGPT
的回答):
$route
对象封装的query
和params
主要用于获取URL
中的查询参数和路由参数。- 查询参数指的是URL中以
?
开头的部分,以&
符号分隔的多个键值对。比如,在URL为http://www.example.com/user?id=123&type=admin
中,id
和type
就是查询参数。 - 路由参数指的是路由规则中定义的动态部分,例如,在路由规则
/user/:id
中,:id
就是一个路由参数。 $route
对象的query
属性包含了URL中的查询参数,以对象的形式存储。比如,在上面的例子中,$route.query
对象的值就是{ id: '123', type: 'admin' }
。$route
对象的params
属性包含了路由参数,以对象的形式存储。比如,在上面的例子中,$route.params
对象的值就是{ id: '123' }
。
- 查询参数指的是URL中以
this.$router
:全局的 router 实例。通过vue
根实例中注入router
实例,然后再注入到每个子组件,从而让整个应用都有路由功能。其中包含了很多属性和对象(比如history
对象),任何页面也都可以调用其push()
,replace()
,go()
等方法。
-
【问】什么是路由守卫?(通过路由守卫函数和
token
进行路由跳转前的访问权限控制:to
,from
和next
),参考导航守卫Note:
-
Vue
的路由守卫是指在某些情况下,路由跳转前会触发指定的函数,用来拦截路由,或者在路由跳转后做一些额外的操作。它可以用来实现权限验证、路由拦截等功能。Vue
提供了beforeEach
(全局前置守卫)和beforeResolve
(全局后置守卫) 等守卫函数,它们可以在路由跳转前进行拦截和额外处理。路由守卫可以控制路由的访问权限(ChatGPT
答案)。 -
全局前置守卫
beforeEach
:在index.js
中-
to
表示将要访问的路由的信息对象 -
from
表示将要离开的路由的信息对象 -
next()
函数表示放行的意思 -
具体逻辑如下:
- 1)拿到用户将要访问的
hash
地址 - 2)判断
hash
地址是否等于/main
。- 如果等于
/main
,证明需要登录之后,才能访问成功 - 如果不等于
/main
,则不需要登录,直接放行next()
。(除了main
页面其他都是白名单)
- 如果等于
- 3)如果访问的地址是
/main
。则需要读取localStorage
中的token
值。- 如果有
token
,则放行; - 如果没有
token
,则强制跳转到/login
登录页
- 如果有
- 1)拿到用户将要访问的
实例代码如下:
// 为 router 实例对象,声明全局前置导航守卫 // 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数 router.beforeEach(function(to, from, next) { // to 表示将要访问的路由的信息对象 // from 表示将要离开的路由的信息对象 // next() 函数表示放行的意思 // 分析: // 1. 要拿到用户将要访问的 hash 地址 // 2. 判断 hash 地址是否等于 /main。 // 2.1 如果等于 /main,证明需要登录之后,才能访问成功 // 2.2 如果不等于 /main,则不需要登录,直接放行 next() // 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值 // 3.1 如果有 token,则放行 // 3.2 如果没有 token,则强制跳转到 /login 登录页 if (to.path === '/main') { // 要访问后台主页,需要判断是否有 token const token = localStorage.getItem('token') if (token) { next()//如果有token(登录)就放行 } else { // 没有登录,强制跳转到登录页 next('/login') } } else { next() } })
-
-
next
函数的 3 种调用方式:
-
当前用户拥有后台主页的访问权限,直接放行:
next()
-
当前用户没有后台主页的访问权限,强制其跳转到登录页面:
next("/login')
-
当前用户没有后台主页的访问权限,不允许跳转到后台主页:
next(false)
-
-
-
【问】怎样进行路由传参?(
query
,params
和props
传参),参考Vue 知识点汇总(下)Note:
-
query
传参(path
和query
属性,拼接路径带?
):- 配置路由格式:
/router
, 也就是普通配置 - 传递的方式: 对象中使用
query
的key
作为传递方式 - 传递后形成的路径:
/router?id=123
,/router?id=abc
//方式1:路由跳转并携带query参数,to的字符串写法 messageData是一个变量 <router-link :to="`/home/news?id=001&message=${messageData}`" ></router-link> //方式2:路由跳转并携带query参数,to的对象 <router-link :to="{ path:"/home/news", //index.js配置的路径 query:{ //将query的k-v作为参数拼接到path中 id:001, message:messageData } }" > </router-link>
获取参数:
this.$route.query.id
、this.$route.query.message
- 配置路由格式:
-
params
传参(name
和params
属性,拼接后端path
不带?
):路由跳转并携带
param
参数,to
的字符串写法 ,首先我们要在路由文件(index.js
)中定义我们要传递的参数-
配置路由格式:
/router/:id
-
传递的方式: 在
path
后面跟上对应的值 -
传递后形成的路径:
/router/123
,/router/abc
index.js
配置:{ name:'HomeNews' path:'news/:id/:message',//二级路由,定义参数,表示第一个参数是id,第二个是message component:News },
传参:
//方式1:路由跳转并携带query参数,to的字符串写法 messageData是一个变量 <router-link :to="`/home/news?id=001&message=${messageData}`" ></router-link> //方式2:路由跳转并携带params参数,to的对象写法,不需要在路由文件中定义参数 <router-link :to="{ name:"HomeNews", //使用params传参时,必须使用name属性进行路由跳转,不能使用path配置项跳转 params:{ id:001, message:messageData } }" ></router-link>
获取参数:
this.$route.params.id
、this.$route.params.message
-
-
路由
props
配置:传参配置:
src/router/index.js
{ name:'HomeNews' path:'news/:id/:message',//二级路由,定义参数,表示第一个参数是id,第二个是message component:News, // 第一种写法:props值为对象,该对象中所有的key-value最终都会通过props传递给组件news // props:{a:1}, // 第二种写法(只能params):props值为Boolean,为true时把路由收到的`params`参数通过props传递给组件news // props:true, // 第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传递给组件news props:function(route){ return { id:route.query.id, message:route.query.message } }, },
使用:
New.vue
export default{ prors:['id','message'] }
-
-
Note:
-
Promise
是JavaScript
中的一个对象,它用于表示一个异步操作的最终完成(或失败)及其结果值。 当一个异步操作成功完成时,会将结果值作为参数传递给Promise
对象的回调函数 。 当一个异步操作失败时,会将失败的原因作为参数传递给Promise
对象的回调函数。Promise
对象配合async/await
调用回调函数获得返回值,实现异步操作。 -
async/await
是JavaScript
中的一种语法,可以让异步操作看起来像同步操作。它通过在函数前面加上async
关键字来定义一个异步函数,在异步函数内部,可以使用await
关键字来等待异步操作完成,并在操作完成后获取结果。例如(ChatGPT
答案):async function getData() { const response = await fetch('https://example.com/data.json'); const data = await response.json(); return data; } // 使用异步函数 getData().then(data => { console.log(data); });
上面的代码定义了一个异步函数
getData
,在函数中使用await
关键字来等待fetch
(也可以使用axios
等其他http
库)和response.json()
操作完成,然后在调用该函数时使用then
方法来处理函数的返回值。 -
在处理异步请求时,容易出现回调地狱的问题:
如果要获取最终的数据
data4
:-
我们需要通过一个
url1
从服务器加载一个数据data1
,data1
中包含了下一个请求的url2
-
我们需要通过
data1
取出url2
,从服务器加载数据data2
,data2
中包含了下一个请求的url3
-
我们需要通过
data2
取出url3
,从服务器加载数据data3
,data3
中包含了下一个请求的url4
-
发送网络请求
url4
,获取最终的数据data4
而在使用
axios
处理异步请求时,可以将异步处理结果封装成一个Promise
对象 -
-
promise
的基本使用:new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('Hello World') reject('Error Data') },1000) }).then(data =>{ console.log(data); }).catch(error =>{ console.log(error); }
定时器异步事件解析
-
new Promise
很明显是创建一个Promise
对象 -
小括号中
((resolve, reject) => {})
也很明显就是一个函数,而且我们这里用的是之前刚刚学习过的箭头函数。在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)
resolve
和reject
它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个。-
如果是成功的,那么通常我们会调用
resolve(messsage)
,这个时候,我们后续的then
会被回调。 -
如果是失败的,那么通常我们会调用
reject(error)
,这个时候,我们后续的catch
会被回调。
-
-
-
-
【问】Promise有哪三种状态?(
pending
,fulfill
和reject
)Note:
-
当我们进行异步操作时,可以给异步操作包装一个
Promise
,异步操作之后会有三种状态。-
pending
:等待状态(进行中),比如正在进行网络请求,或者定时器没有到时间。 -
fulfill
:满足状态(已完成),当我们主动回调了resolve
时,就处于该状态,并且会回调.then()
-
reject
:拒绝状态(已失败),当我们主动回调了reject
时,就处于该状态,并且会回调.catch()
-
-
-
Note:
-
Pomise
的链式调用简化写法如下(将return Promise.resovle(data)
改成了return data
):new Promise((resolve, reject setTimeout(function(){ resolve('Hello World') },1000) }).then(data =>{ console.log(data);//=>Hello World return data +'111' }).then(data =>{ console.log(data);//=>Hello World111 return data+'222' }).then(data=>{ console.log(data);//=>Hello World111222 return Promise.reject(data+'error' }).then(data=>{ console.log(data);//这里没有输出,这部分代码不会执行 return data + '333' }).catch(data = console.log(data);//=>Hello World111222error return data +'444' }).then(data=>{ console.log(data);//=>Hello World111222error444 })
-
六、状态管理(全局状态store和事件驱动,函数式编程,lambda表达式)
参考Vuex详解,一文彻底搞懂Vuex,Vuex详解(五种状态),Vuex 是什么? | Vuex
-
【问】什么是状态管理?Vuex是什么?(通过维护一个全局的变量,简化不同组件之间的传参过程)
Note:
-
在状态管理的实际场景中,有哪些状态可以由
vuex
来管理:比如用户的登录状态、用户名称、头像、地理位置信息等等;比如商品的收藏、购物车中的物品等等。这些状态信息,我们都可以放在统一的地方,对它进行保存和管理。 -
Vuex
是一个专为Vue.js
应用程序开发的状态管理模式库。-
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
-
Vuex
也集成到Vue
的官方调试工具devtools extension
,提供了诸如零配置的time-travel
调试、状态快照导入导出等高级调试功能。
-
-
组件之间的传值有哪些?有父子通讯,兄弟组件通讯…但是传参对于多层嵌套就显得非常繁琐,代码维护也会非常麻烦。因此
vuex
就是把组件共享状态抽取出来以一个全局单例模式管理 ,把共享的数据函数放进vuex
中,任何组件都可以进行使用。
-
-
【问】Vuex中核心状态有哪些?怎样进行管理的?(Vuex的核心思想:
action
异步提交mutation
任务,mutation
修改state
状态实现页面更新;mutation
相似于methods
,stage
相似于data
,getter
相似于computed
),参考Vuex 是什么? | Vuex,Vue知识点汇总【持更】Note:
-
存储在
store
里的都是全局变量,可以通过方法提交更新,其他页面和组件也会同步更新,拿到最新的值。 -
vuex
中一共有五个状态State
,Getter
,Mutation
,Action
和Module
下面进行详细讲解:-
在
store
文件夹中新建index.js
,进行如下配置,并在main.js
(初始化vue
实例)中进行引入。-
index.js
配置如下:import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) //使用Vuex插件 export default new Vuex.Store({ //数据,相当于data state: { }, getters: { }, //里面定义方法,操作state方发 mutations: { }, // 操作异步操作mutation actions: { }, modules: { }, })
-
在
main.js
中配置如下:import Vue from 'vue' import App from './App.vue' import router from './router' import store from ' ./store ' Vue.config.productionTip = false new Vue ( { router, store, render:h=>h(App) }).$mount('#app')
-
-
1)
State
:提供唯一的公共数据源(驱动应用的数据源),所有共享的数据统一放到store
的state
进行储存,相似与data
;import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ //数据,相当于data state: { name:"张三", age:12, count:0 }, })
如何在组件中调用
vuex
中state
的属性值:-
方法一:使用插值表达式
{{}}
<p>{{ $store.state.name }}</p> <p>{{ $store.state.age }}</p>
-
方法二:
this.$store.state.全局数据名称
-
方法三:从
vuex
中按需导入辅助函数mapstate()
import { mapState } from "vuex";
当前组件需要的全局数据,映射为当前组件
computed
属性computed:{ ...mapState(["name","age","sex"]), },
通过插值表达式直接获取值
<p>辅助函数 {{ name }}</p> <p>辅助函数 {{ age }}</p>
-
-
2)
Mutation
:更改Vuex
的store
中的状态的唯一方法是提交mutation
,类似vue
中的methods
;每个mutation
都有一个字符串的事件类型 (type
)和一个回调函数 (handler
)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受state
作为第一个参数;mutations : { addcount(state,num){ state.count=+state.count+num }, reduce(state){ state.count-- } },
如何在组件中使用
vuex
中mutation
定义的方法:-
在组件中定义两个按钮进行加减操作
<button @click="btn">点我增加store仓库中的数据</button> <button @click="btn1">点我减少store仓库的数据</button>
-
方法一:使用
commit
触发Mutation
操作methods:{ //加法 btn(){ this.$store.commit("addcount",10) //每次加十 } //减法 btn1(){ this.$store.commit("reduce") } }
-
方法二:使用辅助函数
mapMutations()
进行操作,具体方法stage
methods : { ...mapMutations(["addcount","reduce"]), btn(){ this.addcount(10); }, btn1(){ this.reduce(); }, }
-
-
3)
Action
:响应在 view 上的用户输入导致的状态变化,action可以提交mutation,在action中可以执行store.commit()
,而且action
中可以有任何的异步操作。action
处理异步操作,由于mutation
都是同步事务,在mutation
中混合异步调用会导致你的程序很难调试。action
类似于mutation
,不同在于Action
提交的是mutation
,而不是直接变更状态。//操作异步操作mutation actions : { asyncAdd(context){ //异步 setTimeout(()=>{ context.commit("reduce") },1000); } },
如何在组件中使用
vuex
中action
定义的方法:-
方法一:直接使用
dispatch
触发Action
函数methods : { ...mapMutations(["addcount","reduce"]), btn(){ this.addcount(10); }, btn1(){ this.reduce(); }, btn2(){ this.$store.dispatch("asynAdd") }, }
-
方法二:使用辅助函数
mapActions()
methods : { ...mapActions(["asynAdd"]), btn2(){ this.asynAdd(); }, }
-
-
4)
Getter
:类似于vue
中的computed
,当我们需要对数据进行处理可以使用getter
,getter
会接收state
作为第一个参数,而且getter
的返回值会根据它的依赖被缓存起来,只有getter
中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算(Vue 3.0
后不支持缓存);参考Getter | Vuex如果我们想在
Store
中获取学生年龄大于20的个数,getter
中定义state.students
处理方法如下。const store = new Vuex.Store({ state:{ students:[ {id:110,name:'why',age:18}, {id:111,name:'kobe',age:21} {id:112,name:'lucy',age:25} {id:113,name:'lilei',age:30}, ] } getters:{ greaterAgesCount: state => { return state.students.filter(s=>s.age>=20).length } } })
如何在组件中使用
vuex
中getter
定义的方法:-
方法一:通过
store.getters
访问computed: { doneTodosCount () { return this.$store.getters.doneTodosCount } }
-
方法二:通过辅助函数`mapGetters()``访问
import { mapGetters } from 'vuex' export default { // ... computed: { // 使用对象展开运算符将 getter 混入 computed 对象中 //...mapGetters([ // 'doneTodosCount', // 'anotherGetter', ]) //或者想重新命名 ...mapGetters({ // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount` doneCount: 'doneTodosCount' }) click(){ return doneCount(); } } }
-
-
5)
Modules
:当遇见大型项目时,数据量大,store
就会显得很臃肿,为了解决以上问题,Vuex
允许我们将 store 分割成模块(module)。每个模块拥有自己的state
、mutation
、action
、getter
、甚至是嵌套子模块——从上至下进行同样方式的分割;modules : cityModules:{ namespaced:true, state:{ cityname:"中国", }, mutations:{ cityfunction(state){ state.cityname="日本" } } }, userinfo:{ state:{ username:"张启楠", }, },
默认情况下,模块内部的
action
和mutation
仍然是注册在全局命名空间的,这样使得 多个模块能够对同一个action
或mutation
作出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加
namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有getter
、action
及mutation
都会自动根据模块注册的路径调整命名。获取指定模块中的
state
的属性值:methods : { btn3(){ console.log(this.$store.state.cityModules.cityname); }, }
-
-
-
【问】Vuex核心思想是什么?(
State
是提供唯一的公共数据源(相当于data
);通过提交mutation
是更改Vuex
中store
状态的唯一方法(相当于methods
)Note:
Vuex
的核心思想是:当我们在页面上点击一个按钮:-
它会触发(
dispatch
)一个action
; -
action
随后会执行(commit
)一个mutation
-
mutation
立即会改变state
,state
改变以后,我们的页面会state 获取数据,页面发生了变化。
-