当前位置: 首页 > news >正文

VUE-第二季-02

3.Vue组件化

3.1 什么是组件

(1) 传统方式开发的应用

一个网页通常包括三部分:结构(HTML)、样式(CSS)、交互(JavaScript)

传统应用存在的问题:
① 关系纵横交织,复杂,牵一发动全身,不利于维护。
② 代码虽然复用,但复用率不高。

(2) 组件化方式开发的应用

使用组件化方式开发解决了以上的两个问题:
① 每一个组件都有独立的js,独立的css,这些独立的js和css只供当前组件使用,不存在纵横交错。更加便于维护。
② 代码复用性增强。组件不仅让js css复用了,HTML代码片段也复用了(因为要使用组件直接引入组件即可)。

(3) 什么是组件?

① 组件:实现应用中局部功能的代码和资源的集合。凡是采用组件方式开发的应用都可以称为组件化应用。
② 模块:一个大的js文件按照模块化拆分规则进行拆分,生成多个js文件,每一个js文件叫做模块。凡是采用模块方式开发的应用都可以称为模块化应用。
③ 任何一个组件中都可以包含这些资源:HTML CSS JS 图片 声音 视频等。从这个角度也可以说明组件是可以包括模块的。
(4) 组件的划分粒度很重要,粒度太粗会影响复用性。为了让复用性更强,Vue的组件也支持父子组件嵌套使用。

子组件由父组件来管理,父组件由父组件的父组件管理。在Vue中根组件就是vm。因此每一个组件也是一个Vue实例。

3.2 组件的创建、注册和使用

(1) 创建组件

①	const userComponent = Vue.extend({这个配置项和创建Vue实例的配置项几乎是一样的,只是略有差异})
②	需要注意的是:
1)	el不能用。组件具有通用性,不特定为某个容器服务,它为所有容器服务。
2)	data必须使用函数形式:return {}
3)	使用template配置项配置页面结构:HTML。

(2) 注册组件

①	局部注册
  1. 使用components配置项:components : {user : userComponent},user就是组件名。
    ② 全局注册
  1. Vue.component(‘user’, userComponent)

(3) 使用组件

①	直接在页面需要使用组件的位置:<user></user>②	也可以这样使用:<user/> (不在脚手架环境中使用这种方式会出现后续元素不渲染的问题。)

(4) 创建组件对象也有简写形式:Vue.extend() 可以省略。直接写:{}

(5) 组件的命名细节:

① 全部小写
② 首字母大写,后面全部小写
③ kebab-case串式命名法
④ CamelCase驼峰式命名法(这种方式需要在脚手架环境中使用)
⑤ 不要使用HTML内置的标签作为组件名称。
⑥ 可以使用name配置项来指定Vue开发者工具中显示的组件名。

编写的第一个组件代码

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="../../js/vue.js"></script></head><body><div id="app"><h1>{{msg}}</h1><!--3.使用组件 --><userlist></userlist><userlist></userlist></div><script>//1.创建组件const userCompont = Vue.extend({template : `<ul><li v-for="(user,index) in users" :key='user.id'>{{user.id}} : {{user.username}}</li></ul>`,//data必须是函数的形式data(){return {users : [{ id: '001', username: 'hax1' },{ id: '002', username: 'hax2' },{ id: '003', username: 'hax3' },]}}})const vm = new Vue({el: '#app',data: {msg: '第一个组件练习',// users: [//     { id: '001', username: 'hax1' },//     { id: '002', username: 'hax2' },//     { id: '003', username: 'hax3' },// ]},//2.注册组件components : {userlist : userCompont}})</script></body></html>

全局组件和各种细节

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>第一个组件</title><script src="../js/vue.js"></script></head><body><!--组件的使用分为三步:第一步:创建组件Vue.extend({该配置项和new Vue的配置项几乎相同,略有差别})区别有哪些?1. 创建Vue组件的时候,配置项中不能使用el配置项。(但是需要使用template配置项来配置模板语句。)2. 配置项中的data不能使用直接对象的形式,必须使用function。第二步:注册组件局部注册:在配置项当中使用components,语法格式:components : {组件的名字 : 组件对象}全局注册:Vue.component('组件的名字', 组件对象)第三步:使用组件小细节:1. 在Vue当中是可以使用自闭合标签的,但是前提必须在脚手架环境中使用。2. 在创建组件的时候Vue.extend()可以省略,但是底层实际上还是会调用的,在注册组件的时候会调用。3. 组件的名字第一种:全部小写第二种:首字母大写,后面都是小写第三种:kebab-case命名法(串式命名法。例如:user-login)第四种:CamelCase命名法(驼峰式命名法。例如:UserLogin),但是这种方式只允许在脚手架环境中使用。不要使用HTML内置的标签名作为组件的名字。在创建组件的时候,通过配置项配置一个name,这个name不是组件的名字,是设置Vue开发者工具中显示的组件的名字。--><div id="app"><h1>{{msg}}</h1><!-- 3. 使用组件 --><userlogin></userlogin><userlist></userlist><userlist></userlist><userlist></userlist><userlogin></userlogin><!-- <userlogin/> --></div><div id="app2"><userlogin></userlogin><hello-world></hello-world><!-- <form></form> --></div><script>/* // 创建组件const abc = {template : `<h1>测试组件的名字????</h1>`}// 全局注册组件Vue.component('HelloWorld', abc) */Vue.component('hello-world', {name : 'Xxxxx',template : `<h1>测试组件的名字%%%%%</h1>`})/* Vue.component('form', {template : `<h1>测试组件的名字%%%%%</h1>`}) */// 1.创建组件(结构HTML 交互JS 样式CSS)/* const myComponent = Vue.extend({template : `<ul><li v-for="(user,index) of users" :key="user.id">{{index}},{{user.name}}</li></ul>`,data(){return {users : [{id:'001',name:'jack'},{id:'002',name:'lucy'},{id:'003',name:'james'}]}}}) */const myComponent = {template : `<ul><li v-for="(user,index) of users" :key="user.id">{{index}},{{user.name}}</li></ul>`,data(){return {users : [{id:'001',name:'jack'},{id:'002',name:'lucy'},{id:'003',name:'james'}]}}}// 1. 创建组件/* const userLoginComponent = Vue.extend({template : `<div><h3>用户登录</h3><form @submit.prevent="login">账号:<input type="text" v-model="username"> <br><br>密码:<input type="password" v-model="password"> <br><br><button>登录</button></form></div>`,data(){return {username : '',password : ''}},methods: {login(){alert(this.username + "," + this.password)}},}) */const userLoginComponent = {template : `<div><h3>用户登录</h3><form @submit.prevent="login">账号:<input type="text" v-model="username"> <br><br>密码:<input type="password" v-model="password"> <br><br><button>登录</button></form></div>`,data(){return {username : '',password : ''}},methods: {login(){alert(this.username + "," + this.password)}},}// 全局注册Vue.component('userlogin', userLoginComponent)const vm2 = new Vue({el : '#app2'})// Vue实例const vm = new Vue({el : '#app',data : {msg : '第一个组件'},// 2. 注册组件(局部注册)components : {// userlist是组件的名字。myComponent只是一个变量名。userlist : myComponent,//userlogin : userLoginComponent}})/* let data = {counter : 1} */function data(){return {counter : 1}}let x = data();let y = data();</script></body></html>

3.3 组件嵌套

代码学习:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>组件嵌套</title><script src="../../js/vue.js"></script></head><body><div id="root"></div><script>// 创建Y1组件const y1 = {template : `<div><h3>Y1组件</h3></div>`}// 创建X1组件const x1 = {template : `<div><h3>X1组件</h3></div>`}// 创建Y组件const y = {template : `<div><h2>Y组件</h2><y1></y1></div>`,components : {y1}}// 创建X组件const x = {template : `<div><h2>X组件</h2><x1></x1></div>`,components : {x1}}// 创建app组件const app = {template : `<div><h1>App组件</h1><x></x><y></y></div>`,// 注册X组件components : {x,y}}// vmconst vm = new Vue({el : '#root',template : `<app></app>`,// 注册app组件components : {app}})</script></body></html>

嵌套结构:这种结构更加贴切实际项目的开发

3.4 VueComponent & Vue

3.4.1 this

new Vue({})配置项中的this和Vue.extend({})配置项中的this他们分别是谁?

1.	<!DOCTYPE html>  
2.	<html lang="en">  
3.	<head>  
4.	    <meta charset="UTF-8">  
5.	    <title>vm与vc</title>  
6.	    <script src="../js/vue.js"></script>  
7.	</head>  
8.	<body>  
9.	    <div id="app">  
10.	        <mc></mc>  
11.	    </div>  
12.	    <script>  
13.	        const myComponent = Vue.extend({  
14.	            template : `<h1></h1>`,  
15.	            mounted(){  
16.	                console.log('vc', this)  
17.	            }  
18.	        })  
19.	  
20.	        const vm = new Vue({  
21.	            el : '#app',  
22.	            components : {  
23.	                mc : myComponent  
24.	            },  
25.	            mounted() {  
26.	                console.log('vm', this)  
27.	            },  
28.	        })  
29.	    </script>  
30.	</body>  
31.	</html> 

测试结果:

new Vue({})配置项中的this就是:Vue实例(vm)。
Vue.extend({})配置项中的this就是:VueComponent实例(vc)。
打开vm和vc你会发现,它们拥有大量相同的属性。例如:生命周期钩子、methods、watch等。

3.4.2 vm === vc ???

只能说差不多一样,不是完全相等。
例如:
vm上有el,vc上没有。
另外data也是不一样的。vc的data必须是一个函数。
只能这么说:vm上有的vc上不一定有,vc上有的vm上一定有。

3.4.3 Vue.extend()方法做了什么?

每一次的extend调用返回的都是一个全新的VueComponent函数。
以下是Vue.extend()的源码:
 

注意:是每一次都会返回一个全新的VueComponent构造函数。是全新的!!!

构造函数有了,什么时候会调用构造函数来实例化VueComponent对象呢?

Vue在解析<mc></mc>时会创建一个VueComponent实例,也就是:new VueComponent()

3.4.4 通过vc可以访问Vue原型对象上的属性

通过vc可以访问Vue原型对象上的属性:
1. Vue.prototype.counter = 100
2. console.log(vc.counter) // 100

为什么要这么设计?代码复用。Vue原型对象上有很多方法,例如:mount(),对于组件VueComponent来说就不需要再额外提供了,直接使用vc调用mount(),代码得到了复用。

Vue框架是如何实现以上机制的呢?
1.      VueComponent.prototype.proto = Vue.prototype
测试:

1.	<!DOCTYPE html>  
2.	<html lang="en">  
3.	<head>  
4.	    <meta charset="UTF-8">  
5.	    <title>测试</title>  
6.	    <script src="../js/vue.js"></script>  
7.	</head>  
8.	<body>  
9.	    <div id="app"></div>  
10.	    <script>  
11.	        const userlist = Vue.extend({  
12.	            template : `<div><h1>用户列表</h1></div>`,  
13.	        })  
14.	        const vm = new Vue({  
15.	            el : '#app',  
16.	            template : `<userlist></userlist>`,  
17.	            components : {userlist}  
18.	        })  
19.	        console.log(userlist.prototype.__proto__ === Vue.prototype) // true  
20.	    </script>  
21.	</body>  
22.	</html>  
3.4.4.1 回顾原型对象

prototype称为:显示的原型属性,用法:函数.prototype,例如:Vue.prototype
proto__称为:隐式的原型属性,用户:实例.proto,例如:vm.proto
无论是通过prototype还是__proto,获取的对象都是同一个,它是一个共享的对象,称为:XX的原型对象。
如果通过Vue.prototype获取的对象就叫做:Vue的原型对象。
如果通过User.prototype获取的对象就叫做:User的原型对象。
请看下图:

3.4.4.2 原理剖析

VueComponent.prototype.proto = Vue.prototype

这样做的话,最终的结果就是:Vue、vm、VueComponent、vc都共享了Vue的原型对象(并且这个Vue的原型对象只有一个)。

代码学习:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>vm与vc</title><script src="../../js/vue.js"></script></head><body><div id="app"><h1>{{msg}}</h1><user></user><user></user><user></user></div><script>// 这个不是给Vue扩展counter属性。// 这个是给“Vue的原型对象”扩展一个counter属性。Vue.prototype.counter = 1000// 创建组件const user = Vue.extend({template : `<div><h1>user组件</h1></div>`,mounted(){// this是VueComponent实例// user是什么呢????是一个全新的构造函数 VueComponent构造函数。//console.log('vc', this === user)// 为什么要这样设计?为了代码复用。// 底层实现原理:// VueComponent.prototype.__proto__ = Vue.prototypeconsole.log('vc.counter', this.counter)// 这个访问不了,因为msg是vm实例上的属性。//console.log('vc.msg', this.msg)}})console.log('user.prototype.__proto__ === Vue.prototype' , user.prototype.__proto__ === Vue.prototype)console.log(user)// vmconst vm = new Vue({el : '#app',data : {msg : 'vm与vc'},components : {user},mounted() {// this是Vue实例console.log('vm', this)},})console.log('vm.counter', vm.counter)// 本质上是这样的:console.log('vm.counter', vm.__proto__.counter)/* function test(){var Sub = function User(){this.name = 'admin'}return Sub}let a = test()// 通过构造函数创建对象console.log(new a()) *//* console.log(a)let b = test()console.log(b)console.log(a === b) */// prototype __proto__// 构造函数(函数本身又是一种类型,代表Vip类型)function Vip(){}// Vip类型/Vip构造函数,有一个 prototype 属性。// 这个prototype属性可以称为:显式的原型属性。// 通过这个显式的原型属性可以获取:原型对象// 获取Vip的原型对象let x = Vip.prototype// 通过Vip可以创建实例let a = new Vip()/* let b = new Vip()let c = new Vip() */// 对于实例来说,都有一个隐式的原型属性: __proto__// 注意:显式的(建议程序员使用的)。隐式的(不建议程序员使用的。)// 这种方式也可以获取到Vip的原型对象let y = a.__proto__/* b.__proto__c.__proto__ */// 原型对象只有一个,其实原型对象都是共享的。console.log(x === y) // true// 这个不是给Vip扩展属性// 是在给“Vip的原型对象”扩展属性Vip.prototype.counter = 1000// 通过a实例可以访问这个扩展的counter属性吗?可以访问。为什么?原理是啥?// 访问原理:首先去a实例上找counter属性,如果a实例上没有counter属性的话,会沿着__proto__这个原型对象去找。// 下面代码看起来表面上是a上有一个counter属性,实际上不是a实例上的属性,是a实例对应的原型对象上的属性counter。console.log(a.counter)//console.log(a.__proto__.counter)</script></body></html>

3.5 单文件组件

  1. 什么是单文件组件?
    (1) 一个文件对应一个组件(之前我们所学的是非单文件组件,一个html文件中定义了多个组件)
    (2) 单文件组件的名字通常是:x.vue,这是Vue框架规定的,只有Vue框架能够认识,浏览器无法直接打开运行。需要Vue框架进行编译,将x.vue最终编译为浏览器能识别的html js css。
    (3) 单文件组件的文件名命名规范和组件名的命名规范相同:
    ① 全部小写:userlist
    ② 首字母大写,后面全部小写:Userlist
    ③ kebab-case命名法:user-list
    ④ CamelCase命名法:UserList(我们使用这种方式,和Vue开发者工具呼应。)
  1. x.vue文件的内容包括三块:
    (1) 结构:<template>HTML代码</template>
    (2) 交互:<script>JS代码</script>
    (3) 样式:<style>CSS代码</style>
  1. export和import,ES6的模块化语法。
    (1) 使用export导出(暴露)组件,在需要使用组件的x.vue文件中使用import导入组件
    ① 默认导入和导出
    1) export default {}

2) import 任意名称 from ‘模块标识符’

② 按需导入和导出
1) export {a, b}
2) import {a, b} from ‘模块标识符’
③ 分别导出
export var name = ‘zhangsan’
export function sayHi(){}
4. VSCode工具可以安装一些插件,这样在编写x.vue的时候有提示。例如:vetur插件。
(1) 使用该插件之后,有高亮显示,并且也可以通过输入 <v 生成代码。
5. 把之前“组件嵌套”的例子修改为单文件组件
![file-20250803120222466.png](https://gitee.com/javaxubo/work_note-images/raw/master//image/202508031202121.png

auto rename tag插件

这个 name: 'App' 表示该组件的名称是 "App",在其他地方可以通过这个名称来引用或识别该组件。如果没有显式设置 name,Vue 会使用文件名作为默认名称(在单文件组件中)。

记住一个要领:不管是单文件组件还是非单文件组件,永远都包括三步:创建组件、注册组件、使用组件。

创建vm的代码就不是一个组件了,这个js代码写到一个js文件中即可,一般这个起名:main.js。寓意:入口

还剩最后的一点HTML代码,一般这个文件叫做index.html,代码如下:

如上图,注意引入顺序。
代码执行原理:

① 第一步:浏览器打开index.html页面,加载容器
② 第二步:加载vue.js文件,有了Vue
③ 第三步:加载main.js1)	import App from ‘./App.vue’2)	import X from './X.vue'3)	import X1 from './X1.vue'4)	import Y from './Y.vue'5)	import Y1 from './Y1.vue'
这样就完成了所有组件以及子组件的创建和注册。
④ 第四步:创建Vue实例vm,编译模板语句,渲染。
写完之后不能直接运行,浏览器不认识.vue文件,不认识ES6的模块化语法。需要安装Vue脚手架。

3.6 Vue脚手架

3.6.1 确保npm能用(安装Node.js)

Node.js的下载地址:
Node.js — Download Node.js®
安装步骤参考地址:Node安装 - 哩个啷个波 - 博客园

打开dos命令窗口,输入npm命令。

3.6.2 Vue CLI(脚手架安装)

  1. Vue的脚手架(Vue CLI: Command Line Interface)是Vue官方提供的标准化开发平台。它可以将我们.vue的代码进行编译生成html css js代码,并且可以将这些代码自动发布到它自带的服务器上,为我们Vue的开发提供了一条龙服务。脚手架官网地址:Vue CLI

注意:Vue CLI 4.x需要Node.js v8.9及以上版本,推荐v10以上。
2. 脚手架安装步骤:
① 建议先配置一下npm镜像:
1) npm config set registry https://registry.npm.taobao.org
2) npm config get registry 返回成功,表示设置成功
② 第一步:安装脚手架(全局方式:表示只需要做一次即可)
1) npm install -g @vue/cli
如果这一步安装出现问题 参考这里的文章解决 npm ERR! code CERT_HAS_EXPIRED:解决证书过期问题 - 哩个啷个波 - 博客园
2) 安装完成后,重新打开DOS命令窗口,输入vue命令可用表示成功了
③ 第二步:创建项目(项目中自带脚手架环境,自带一个HelloWorld案例)
1) 切换到要创建项目的目录,然后使用 vue create vue_pro

这里选择Vue2,
babel:负责ES6语法转换成ES5。
eslint:负责语法检查的。
回车之后,就开始创建项目,创建脚手架环境(内置了webpack loader),自动生成HelloWorld案例。

④ 第三步:编译Vue程序,自动将生成html css js放入内置服务器,自动启动服务。
1) dos命令窗口中切换到项目根:cd vue_pro
2) 执行:npm run serve,这一步会编译HelloWorld案例

ctrl + c停止服务。
3) 打开浏览器,访问:http://localhost:8080

3.6.3 认识脚手架结构

使用VSCode将vue_pro项目打开:

package.json:包的说明书(包的名字,包的版本,依赖哪些库)。该文件里有webpack的短命令:
serve(启动内置服务器)
build命令是最后一次的编译,生成html css js,给后端人员
lint做语法检查的。

3.6.4 分析HelloWorld程序

可以看到在index.html中只有一个容器。没有引入vue.js,也没有引入main.js
Vue脚手架可以自动找到main.js文件。(所以main.js文件名不要修改,位置也不要随便移动)


接下来就是将之前写的程序拷贝到脚手架中,进行测试。
需要拷贝过来的是:App.vue、X.vue、Y.vue、X1.vue、Y1.vue。
main.js和index.html都不需要了,因为脚手架中有。

只需要将App.vue中的路径修改一下即可:

打开VSCode终端:ctrl + `, 注意这里命令没有,只是因为md文件中才特意转的
在终端中执行:npm run serve
报错了:

导致这个错误的原因是:组件的名字应该由多单词组成。这是eslint进行的es语法检测。
解决这个问题有两种方案:
第一种:把所有组件的名字修改一下。
第二种:在vue.config.js文件中进行脚手架的默认配置。配置如下:

在终端中ctrl + c 两次,终止之前的服务,再次运行命令:npm run serve

3.6.5 脚手架默认配置

脚手架默认配置在vue.config.js文件中进行。
main.js、index.html等都是可以配置的。
配置项可以参考Vue CLI官网手册,如下:

例如配置这两项:
第一个:保存时不检查语法 lintOnSave : false
第二个:配置入口

3.6.6 解释main.js中的render函数

将render函数更换为:template配置项,你会发现它是报错的。说明引入的Vue无法进行模板编译。
原因:Vue脚手架默认引入的是精简版的Vue,这个精简版的Vue缺失模板编译器。

实际引入的vue.js文件是:dist/vue.runtime.esm.js(esm版本是ES6模块化版本)
为什么缺失模板编译器?
Vue包含两部分:一部分是Vue的核心,一部分是模板编译器(模板编译器可能占整个vue.js文件的一大部分体积)。程序员最终使用webpack进行打包的时候,显然Vue中的模板编译器就没有存在的必要了。为了缩小体积,所以在Vue脚手架中直接引入的就是一个缺失模板编译器的vue.js。
这样就会导致template无法编译(注意:<template>标签可以正常编译[package.json文件中进行了配置],说的是template配置项无法编译),解决这个问题包括两种方式:
第一种方式:引入一个完整的vue.js
第二种方式:使用render函数
关于render函数,完整写法:

这个函数被vue自动调用,并且传递过来一个参数createElement。
简写形式可以使用箭头函数:

3.7 props配置

使用props配置可以接收其他组件传过来的数据,让组件的数据变为动态数据,三种接收方式:
(1) 简单接收
props : [‘name’,’age’,’sex’]
(2) 接收时添加类型限制
props : {
name : String
age : Number
sex : String
}
(3) 接收时添加类型限制,必要性限制,默认值
props : {
name : {
type : Number,
required : true
},
age : {
type : Number,
default : 10
},
sex : {
type : String,
default : ‘男’
}
}
其他组件怎么把数据传过来?
<User name=”jack” age=”20” sex=”男”></User>
注意事项:
① 不要乱接收,接收的一定是其它组件提供的。
② props接收到的数据不能修改。(修改之后会报错,但页面会刷新。)可以找个中间变量来解决。
代码学习:
Car.vue

<template><div><h3>品牌:{{brand}}</h3><h3>价格:{{cprice}}</h3><h3>颜色:{{color}}</h3><button @click="add">价格加1</button></div></template><script>export default {name : 'Car',data() {return {cprice : this.price}},methods : {add(){this.cprice++}},/* data() {return {brand : '宝马520',price : 10,color : '黑色'}}, */// 在Car这个子组件当中使用props配置项进行数据的接收。// 第一种:简单的接收方式,直接采用数组接收。//props : ['brand','color','price']// 第二种:添加类型限制/* props : {brand : String,color : String,price : Number} */// 第三种:添加类型限制,并且还可以添加默认值,还可以添加必要性// 避免直接更改prop,因为每当父组件重新渲染时,该值都会被覆盖// 注意:不要修改prop中的数据。props : {brand : {type : String,required : true},color : {type : String,default : '红色'},price : {type : Number,required : true}}}</script>

App.vue

<template><div><h1>{{msg}}</h1><!-- 在App这个父组件当中,找到子组件Car,然后给Car这个子组件传数据:通过属性的形式传数据 --><Car brand="宝马520" color="黑色" :price="10"></Car><hr><Car brand="比亚迪汉" color="红色" :price="20"></Car></div></template><script>import Car from './components/Car.vue'export default {name : 'App',data() {return {msg : '汽车信息'}},components : {Car}}</script>

浏览器效果:

3.8 从父组件中获取子组件

在组件上使用ref属性进行标识:

`<User ref=”userJack”></User>`

在程序中使用$refs来获取子组件:

this.$refs.userJack

访问子组件的属性:

this.$refs.userJack.name

访问子组件的子组件属性:

this.$refs.userJack.$refs.name

ref也可以使用在普通的HTML标签上,这样获取的就是这个DOM元素:

`<input type=”text” ref=”username”>`
this.$refs.username

代码演示

<template><div><h1 ref="hh">{{msg}}</h1><!-- 在App这个父组件当中,找到子组件Car,然后给Car这个子组件传数据:通过属性的形式传数据 --><Car brand="宝马520" color="黑色" :price="10" ref="car1"></Car><hr><Car brand="比亚迪汉" color="红色" :price="20" ref="car2"></Car><hr><button @click="printCarInfo">打印汽车信息</button></div></template><script>import Car from './components/Car.vue'export default {name : 'App',data() {return {msg : '汽车信息'}},methods : {printCarInfo(){// 获取子组件console.log(this.$refs.car1)console.log(this.$refs.car2)console.log(this.$refs.car1.brand)console.log(this.$refs.car1.color)console.log(this.$refs.car1.price)// 这个就不是组件了。console.log(this.$refs.hh.innerText)}},components : {Car}}</script>

3.9 mixins配置(混入)

运行效果:

可以看到以上Vip.vue和User.vue代码中都有相同的methods,这个代码可以复用吗?可以使用mixins配置进行混入。实现步骤:

第一步:提取

单独定义一个mixin.js(一般和main.js在同级目录),代码如下:

第二步:引入并使用

以上演示的是方法methods的混入,实际上混入时没有限制,之前所学的配置项都可以混入。
混入时会产生冲突吗?已经有一个方法a了,如果再混入一个a方法会怎样?
 

通过测试,如果冲突了,会执行组件自身的,不会执行混入的。(这是原则:混入的意思就是不破坏)
但对于生命周期周期钩子函数来说,混入时,会采用叠加方式:

执行结果:

通过测试得知:对于生命周期钩子函数来说,都有的话,采用叠加,先执行混入的,再执行自己的。
以上的混入属于局部混入,只混入到指定的组件当中。
全局混入:

执行结果:

一共四个组件,所以输入四次:mixin mounted
main.js

// 等同于引入vue.js文件import Vue from 'vue'// 导入App组件(根组件)import App from './App.vue'import {mix1} from './mixin.js'import {mix2} from './mixin.js'import {mix3} from './mixin.js'// 全局混入Vue.mixin(mix1)Vue.mixin(mix2)Vue.mixin(mix3)// 关闭生产提示信息Vue.config.productionTip = false// 创建Vue实例new Vue({el : '#app',// 您正在使用Vue的仅运行时版本,其中模板编译器不可用。// 目前使用的vue.js是一个缺失了模板编译器的vue.js文件。// 怎么解决,两种方案:// 第一种:使用完整版的vue.js: import Vue from 'vue/dist/vue.js'// 第二种:使用render函数。// 为什么采用缺失模板编译器的vue.js?// 目的:减小体积。Vue.js包括两块:Vue的核心 + 模板编译器(模板编译器可能占用vue.js文件体积的三分之一。)// 将来程序员使用webpack进行打包处理之后,模板编译器就没有存在的必要了。//template : '<h1>render函数</h1>'// 看不懂,一会再说。//render: h => h(App),// render函数被自动调用// 这个函数被调用的时候会自动传过来一个参数:createElement,createElement是一个函数。// createElement函数可以用来创建元素/* render(createElement){// 创建了一个div元素//return createElement('div', 'render函数')return createElement(App)} */render : h => h(App)})

mixins

export const mix1 = {methods: {printInfo(){console.log(this.name, ',' , this.age)}}}export const mix2 = {methods: {a(){console.log('mixin.js a.....')}},}export const mix3 = {mounted() {console.log('mixin.js mounted...')}}

User

<template><div><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年龄:{{age}}</h3><button @click="printInfo">打印用户信息</button><button @click="a">用户a</button></div></template><script>// import {mix1} from '../mixin.js'// import {mix2} from '../mixin.js'// import {mix3} from '../mixin.js'export default {name : 'User',mounted() {console.log('User mounted...')},data() {return {msg : '用户信息',name : '张三2',age : 20}},// mixins : [mix1,mix2, mix3],/* methods: {a(){console.log('user a....')}}, */}</script>

Vip

<template><div><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年龄:{{age}}</h3><button @click="printInfo">打印会员信息</button><button @click="a">会员a</button></div></template><script>// import {mix1} from '../mixin.js'// import {mix2} from '../mixin.js'export default {name : 'Vip',data() {return {msg : '会员信息',name : '李四2',age : 21}},// mixins : [mix1,mix2],methods: {a(){console.log('vip a....')}},}</script>

App

<template><div><User></User><Vip></Vip></div></template><script>import User from './components/User.vue'import Vip from './components/Vip.vue'export default {name : 'App',components : {User, Vip}}</script>

3.10 plugins配置(插件)

给Vue做功能增强的。
怎么定义插件?以下是定义插件并暴露插件。插件是一个对象,对象中必须有install方法,这个方法会被自动调用。

插件一般都放到一个plugins.js文件中。
导入插件并使用插件:

插件对象的install方法有两个参数:
第一个参数:Vue构造函数
第二个参数:插件使用者传递的数据
先学会用插件,后面我们做项目的时候会使用很多插件。到时再体会插件存在的意义。

3.11 局部样式scoped

默认情况下,在vue组件中定义的样式最终会汇总到一块,如果样式名一致,会导致冲突,冲突发生后,以后来加载的组件样式为准。怎么解决这个问题?

另外vue组件的style样式支持多种样式语言,例如:css、less、sass等。如何选择使用呢?

使用less注意安装less-loader:npm i less-loader
App根组件中的样式style不建议添加scoped。

App

<template><div><User></User><Vip></Vip></div></template><script>//如果不加scoped 并且两个组件当中css样式属性名一样,就会出现覆盖的情况,并且是按照谁后引入就会覆盖先引入的文件样式import Vip from './components/Vip.vue'import User from './components/User.vue'export default {name : 'App',components : {User, Vip}}</script><style>/* 一般在App根组件当中样式不会添加scoped,因为根组件的样式还是希望采用全局的方式处理。 */</style>

User

<template><div class="s"><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年龄:{{age}}</h3></div></template><script>export default {name : 'User',data() {return {msg : '用户信息',name : '张三2',age : 20}}}</script><style scoped>.s {background-color: aquamarine;}</style>

Vip

<template><div class="s"><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年龄:{{age}}</h3></div></template><script>export default {name : 'Vip',data() {return {msg : '会员信息',name : '李四2',age : 21}}}</script><style scoped>.s {background-color:bisque}</style>

样式效果:

3.12 BugList案例

  1. 先使用静态组件的方式把页面效果实现出来。
    (1) App.vue
    (2) BugHeader.vue
    (3) BugList.vue
    (4) BugItem.vue
    (5) BugFooter.vue

动态的判断单选框的值是否默认勾选写法:

删除按钮颜色的写法:


实现效果:

表格的写法:


实现效果:

第一版代码:

App.vue

<template><div><BugHeader></BugHeader><br><BugList></BugList><BugFooter></BugFooter></div></template><script>//如果不加scoped 并且两个组件当中css样式属性名一样,就会出现覆盖的情况,并且是按照谁后引入就会覆盖先引入的文件样式import BugHeader from './components/BugHeader.vue'import BugList from './components/BugList.vue'import BugFooter from './components/BugFooter.vue'// import User from './components/User.vue'export default {name : 'App',components : {BugHeader,BugList,BugFooter}}</script><style>/* 一般在App根组件当中样式不会添加scoped,因为根组件的样式还是希望采用全局的方式处理。 *//* 共享 */.button{display: inline-block;*display: inline;zoom: 1;padding: 6px 20px;margin: 0;cursor: pointer;border: 1px solid #bbb;overflow: visible;font: bold 13px arial, helvetica, sans-serif;text-decoration: none;white-space: nowrap;color: #555;background-color: #ddd;background-image: -webkit-gradient(linear, to right top, to right bottom, from(rgba(255,255,255,1)), to(rgba(255,255,255,0)));background-image: -webkit-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));background-image: -moz-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));background-image: -ms-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));background-image: -o-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));background-image: linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));-webkit-transition: background-color .2s ease-out;-moz-transition: background-color .2s ease-out;-ms-transition: background-color .2s ease-out;-o-transition: background-color .2s ease-out;transition: background-color .2s ease-out;background-clip: padding-box; /* Fix bleeding */-moz-border-radius: 3px;-webkit-border-radius: 3px;border-radius: 3px;-moz-box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;-webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;text-shadow: 0 1px 0 rgba(255,255,255, .9);-webkit-touch-callout: none;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;}.button:active{background: #e9e9e9;position: relative;top: 1px;text-shadow: none;-moz-box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;}.button.red{color: #fff;text-shadow: 0 1px 0 rgba(0,0,0,.2);background-image: -webkit-gradient(linear, to right top, to right bottom, from(rgba(255,255,255,.3)), to(rgba(255,255,255,0)));background-image: -webkit-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));background-image: -moz-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));background-image: -ms-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));background-image: -o-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));background-image: linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));}.button.red{background-color: #ca3535;border-color: #c43c35;}.button.red:hover{background-color: #ee5f5b;}.button.red:active{background: #c43c35;}.button.green{background-color: #57a957;border-color: #57a957;}.button.green:hover{background-color: #62c462;}.button.green:active{background: #57a957;}</style>

BugHeader.vue

<template><div class="header" ><textarea rows="3" cols="60" v-model="bugInfo" place="请输入bug的信息"></textarea><br></br><button class="small red button" @click="save">保存</button></div></template><script>export default {data(){return {bugInfo : '',}},methods:{save(){console.log("保存用户输入的bug信息",this.bugInfo);}}}</script><style scoped>/* header */.header {margin-bottom: 20px;margin-top: 20px;}</style>

BugList.vue

<template><div><table><thead><tr><th class="c1">全选<input type="checkbox"> </th><th>bug描述</th><th class="c2">操作</th></tr></thead><tbody><BugItem v-for="bug in bugList" :key="bug.id" :bug="bug"></BugItem></tbody></table></div></template><script>import BugItem from './BugItem.vue'export default {components : {BugItem},data() {return {bugList: [{ id: '0001', bugInfo: '问题1', isResolve: true },{ id: '0002', bugInfo: '问题2', isResolve: true },{ id: '0003', bugInfo: '问题3', isResolve: false },]}}}</script><style scope>/* list */table {width: 760px;border-collapse: collapse;}table caption {font-size: 1em;font-weight: bold;margin: 1em 0;}.c1,.c2 {width: 100px;}th {border: 1px solid #999;text-align: center;padding: 5px 0;}table thead tr {background-color: #008c8c;color: #fff;}</style>

BugItem.vue

<template><!-- <div></div>  --><tr><td><input type="checkbox" :checked="bug.isResolve"></td><td><span  class="desc">{{ bug.bugInfo }}</span></td><td><button class="small red button">删除</button></td></tr></template><script>export default {props : ['bug'],}</script><style >/* item */table tbody tr:nth-child(odd){background-color: #eee;}table tbody tr:hover{background-color: #ccc;}table tbody tr td:first-child{color: #f40;}td{border: 1px solid #999;text-align: center;padding: 5px 0;}/* 鼠标指针变成手型(👆) */.desc {cursor: pointer;}</style>

BugFooter.vue

<template><div class="footer"><button class="small red button">清除已解决</button><span>当前BUG总量是{{ 2 }}个,已解决1个。</span></div></template><script>export default {}</script><style >/* footer */.footer{margin-top: 10px;}.footer span{font-size: 12px;}</style>

初步页面效果:

  1. 在BugList.vue中提供bugList数据,实现动态数据展示。
  1. 保存bug:
    (1) 获取用户输入的信息采用双向数据绑定。
    ① 通过Date.now()获取时间戳的方式来搞定id。
    (2) 将BugList.vue中的bugList数据提升到父组件App.vue中。
    (3) 父组件向子组件传递,采用 :bugList=”bugList”,在子组件当中使用props接收。
    (4) 子组件向父组件传递,父组件可以提前定义一个函数,将函数传递给子组件,在子组件中调用这个函数即可。
    (5) 该功能的小问题:
    ① 保存完成后自动清空。
    ② 输入为空时不能保存(可以加trim去除空白),并且提示必须输入。
  1. 修改bug的状态
    (1) 勾选和取消勾选,会触发click事件或者change事件。
    (2) 事件发生后,获取bug的id,将id传递给App组件中的回调函数,遍历数组,拿到要修改的bug对象,更改bug对象的resolved属性值。
  1. 删除bug
    (1) 删除时可以调用数组的filter方法进行过滤,将过滤之后的新数组赋值给this.bugList
  1. 统计bug
    (1) 第一种:普通计数器统计。
    (2) 第二种:数组的reduce方法完成条件统计。
  1. 全选和取消全选
    (1) 全选复选框的状态维护:
    ① 已解决的数量 === 总数量 时,勾选。
    ② 全部删除后,应该取消勾选。
    (2) 全部删除了可以将footer隐藏。v-show
    (3) 全选和取消全选
  1. 清除已解决
    (1) 调用数组的filter方法进行过滤,生成新数组,将其赋值给this.bugList
  1. 实现编辑功能
    (1) 功能描述
    ① 鼠标移动到描述信息上之后,光标变成小手。
    ② 点击描述信息之后,将描述信息放入文本框。并且同时让文本框获得焦点。
    ③ 用户开始修改描述信息(要注意避免将信息修改为空)
    ④ 输入修改信息之后,文本框失去焦点,显示修改后的描述信息。
    (2) 实现功能的核心技术:
    ① 给bug对象扩展一个具有响应式的editState属性,如果是true表示处于编辑状态,false表示处于未编辑状态:this.$set(bug, ‘editState’, true)
    ② 获得焦点的动作如何完成:
  1. 在文本框上添加ref=”inputDesc”,然后通过this.$refs.inputDesc获取到dom元素,调用focus()让其获取焦点。
  1. 以上操作需要在下一次渲染DOM完成后执行:nextTick
    a. this.nextTick(function(){this.refs.inputDesc.focus()})

3.13 localStorage和sessionStorage

window.localStorage 浏览器关闭,数据还在。
getItem removeItem setItem clear
JSON.stringify
JSON.parse
存储大小5mb
Window.sessionStorage 浏览器关闭清空存储。
getItem的key不存在的话返回null。JSON.parse(null),结果还是null。
改造项目。用本地存储来改造。使用监视属性watch,并且要开启深度监视。

3.14 使用本地存储改造BugList案例

3.15 组件自定义事件

click、keydown、keyup,这些事件都是内置事件。
Vue也支持给组件添加自定义事件。
包括两种方式:
第一种方式:直接在组件标签上绑定事件
第二种方式:通过代码来给组件绑定事件

3.15.1 直接在组件标签上绑定事件

<Car @event1=”doSome”>

表示给Car这个组件vc实例绑定event1事件,当event1事件发生时,doSome方法执行。
事件绑定在谁的身上,谁就负责触发这个事件,怎么触发?在Car组件中定义methods:
methods : {
triggerEvent1(){
// 触发事件并且给事件传数据
this.$emit(‘event1’, this.name, this.age, this.gender)
}
}
然后,在Car的父组件中编写doSome方法:
methods : {
doSome(name, age, gender){}
// 或者可以这样
doSome(name, ...parameters){} // ...parameters表示采用一个数组接收参数
}
通过这种方式可以轻松完成子组件向父组件传递数据。
<Car @event1.once=”doSome”> 表示只触发一次。 <Car @click.native=”doSome”> 使原生事件生效。

3.15.2 通过代码给组件绑定事件

在父组件当中:

mounted(){ // 表示挂载完毕后给组件绑定事件。
// 这种方式更加灵活。例如:希望AJAX请求响应回来数据之后再给组件绑定事件。
this.refs.car.on(‘event1’, this.doSome)
}
this.refs.car.once(‘event1’, this.doSome) 表示只触发一次。
绑定时要注意:
this.refs.car.on(‘event1’, function(){
//这里的this是子组件实例(Car组件实例)
})
this.refs.car.on(‘event1’, ()=>{
// 这里的this是父组件实例(App组件实例)
})
this.doSome这个回调函数写成普通函数时:函数体中this是子组件实例。(Car组件实例)
this.doSome这个回调函数写成箭头函数时:函数体中this是父组件实例。(App组件实例)

3.15.3 解绑事件

哪个组件绑定的就找哪个组件解绑:
methods : {
unbinding(){
this.off(‘event1’) // 这种方式只能解绑一个事件。
this.off([‘event1’, ‘event2’]) // 这种方式解绑多个事件。
this.$off() // 解绑所有事件。
}
}
注意:vm和vc销毁的时候,所有组件以及子组件当中的事件会全部解绑。

3.16 全局事件总线

原理:给项目中所有的组件找一个共享的vc对象。把这个共享的对象vc叫做全局事件总线。所有的事件都可以绑定到这个共享对象上。所有组件都通过这个全局事件总线对象来传递数据。这种方式可以完美的完成兄弟组件之间传递数据。这样的共享对象必须具备两个特征:
(1) 能够让所有的vc共享。
(2) 共享对象上有on、off、emit等方法。
第一种解决方案:
在main.js文件中:
// 获取VueComponent构造函数
const VueComponentConstructor = Vue.extend({})
// 创建vc
const vc = new VueComponentConstructor()
// 让所有的vc都能够使用这个vc
Vue.prototype.bus = vc
第二种解决方案:建议的。
在main.js文件中:
new Vue({
el : '#app',
render : h => h(App),
beforeCreate(){
Vue.prototype.$bus = this
}
})
永远需要记住的:A组件向B组件传数据,应该在B组件中绑定事件(接)。应该在A组件中触发事件(传)。

数据发送方:触发事件
methods : {
triggerEvent(){
this.bus.emit(‘eventx’, 传数据)
}
}
数据接收发:绑定事件
mounted(){
this.bus.on(‘eventx’, this.doSome)
}
养成好习惯:组件实例被销毁前,将绑定在bus上的事件解绑。
beforeDestroy(){
this.bus.off(‘eventx’)
}

3.17 BugList案例改造

3.17.1 使用组件自定义事件改造BugList案例

所有从父向子传递函数的位置,都可以修改为自定义事件方式。

父组件向子组件传递数据的写法

主要改造子向父传数据的功能。

3.17.2 使用全局事件总线改造BugList案例

主要改造爷孙之间数据的传递。
自定义事件在Vue开发者工具当中是可以在看到的。
组件销毁的时候,记得把全局事件总线对象上绑定的事件解绑。

3.18 消息订阅与发布

使用pubsub-js库完成消息订阅与发布。该库可以在任意前端框架中实现消息的订阅与发布。
安装pubsub-js:npm i pubsub-js
程序中引入pubsub:import pubsub from ‘pubsub-js’
引入了一个pubsub对象,通过调用该对象的subscribe进行消息订阅,调用publish进行消息发布。
订阅:subscribe
mounted(){
this.pubsubId = pubsub.subscribe(‘message’, (messageName, data) => {
// 两个参数:第一个是消息的名字。第二个参数是消息发布时传过来的数据。
// 要使用箭头函数。这样才能保证this的使用。
})
}
beforeDestroy(){
pubsub.unsubscribe(this.pubsubId )
}
发布:publish
pubsub.publish(‘message’, ‘zhangsan’, 20)

组件间的通信方式总结:
① props:可以完成父向子传数据
② 父向子传一个函数:可以完成子向父传数据
③ 组件自定义事件:可以完成子向父传数据。
④ 全局事件总线
⑤ 消息订阅与发布

3.19 使用消息订阅与发布改造BugList案例

组件销毁时,记得取消订阅。

4.Vue与AJAX

4.1 回顾发送AJAX异步请求的方式
发送AJAX异步请求的常见方式包括:

  1. 原生方式,使用浏览器内置的JS对象XMLHttpRequest
    (1) const xhr = new XMLHttpRequest()
    (2) xhr.onreadystatechange = function(){}
    (3) xhr.open()
    (4) xhr.send()
  1. 原生方式,使用浏览器内置的JS函数fetch
    (1) fetch(‘url’, {method : ‘GET’}).then().then()
  1. 第三方库方式,JS库jQuery(对XMLHttpRequest进行的封装)
    (1) .get()
    (2) .post()
  1. 第三方库方式,基于Promise的HTTP库:axios (对XMLHttpRequest进行的封装)
    (1) axios.get().then()
    axios是Vue官方推荐使用的。
    4.2 回顾AJAX跨域
  1. 什么是跨域访问?
    (1) 在a页面中想获取b页面中的资源,如果a页面和b页面所处的协议、域名、端口不同(只要有一个不同),所进行的访问行动都是跨域的。
    (2) 哪些跨域行为是允许的?
    ① 直接在浏览器地址栏上输入地址进行访问
    ② 超链接


    <link href=”其它网站的css文件是允许的”>
    <script src=”其它网站的js文件是允许的”>
    ⑥ ......
    (3) 哪些跨域行为是不允许的?
    ① AJAX请求是不允许的
    ② Cookie、localStorage、IndexedDB等存储性内容是不允许的
    ③ DOM节点是不允许的
  1. AJAX请求无法跨域访问的原因:同源策略
    (1) 同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
    (2) AJAX请求不允许跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
  1. 解决AJAX跨域访问的方案包括哪些
    (1) CORS方案(工作中常用的)
    ① 这种方案主要是后端的一种解决方案,被访问的资源设置响应头,告诉浏览器我这个资源是允许跨域访问的:response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
    (2) jsonp方案(面试常问的)
    ① 采用的是<script src=””>不受同源策略的限制来实现的,但只能解决GET请求。
    (3) 代理服务器方案(工作中常用的)
    ① Nginx反向代理
    ② Node中间件代理
    ③ vue-cli(Vue脚手架自带的8080服务器也可以作为代理服务器,需要通过配置vue.config.js来启用这个代理)
    (4) postMesssage
    (5) websocket
    (6) window.name + iframe
    (7) location.hash + iframe
    (8) document.domain + iframe
    (9) ......
  1. 代理服务器方案的实现原理
    同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略的。

4.3 演示跨域问题
Vue脚手架内置服务器的地址:http://localhost:8080
我们可以额外再开启一个其它的服务器,这个服务器随意,例如:node server、Apache服务器、JBOSS服务器、WebLogic服务器、WebSphere服务器、jetty服务器、tomcat服务器......我这里选择的是基于Java语言的一个服务器Tomcat,这个web服务器开启了一个8000端口,提供了以下的一个服务,可以帮助我们获取到一个Bug列表:
http://localhost:8000/bugs/

打开BugList案例的代码,在mounted钩子函数中发送ajax请求,获取bug列表。
vue-cli安装axios库:npm i axios。使用时:import导入axios

以上的访问表示:在8080服务器中发送AJAX请求访问8000服务器,必然会出现AJAX跨域问题:

4.4 启用Vue脚手架内置服务器8080的代理功能

  1. 简单开启
    vue.config.js文件中添加如下配置:
    devServer: {
    proxy: 'http://localhost:8000' // 含义:Vue脚手架内置的8080服务器负责代理访问8000服务器
    }
    发送AJAX请求时,地址需要修改为如下:

原理:访问地址是http://localhost:8080/bugs,会优先去8080服务器上找/bugs资源,如果没有找到才会走代理。
另外需要注意的是:这种简单配置不支持配置多个代理。
2. 高级开启
支持配置多个代理。
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8000',
pathRewrite:{'^/api', ''},
ws: true, // 支持websocket
changeOrigin: true // true表示改变起源(让目标服务器不知道真正的起源)
},
'/abc': {
target: 'http://localhost:9000',
pathRewrite:{'^/abc', ''},
ws: true, // 默认值true
changeOrigin: true // 默认值true
}
}
}
4.5 使用AJAX改造BugList案例
mounted钩子中发送ajax请求即可。
4.6 Vue插件库vue-resource发送AJAX请求

  1. 安装:npm i vue-resource
  1. import vueResource from ‘vue-resource’
  1. 使用插件:Vue.use(vueResource)
  1. 使用该插件之后,项目中所有的vm和vc实例上都添加了:$http属性。
  1. 使用办法:
    (1) this.http.get(‘’).then() 用法和axios相同,只不过把axios替换成this.http
    4.7 天气预报
  1. 实现效果
  1. 接口来自:https://openweathermap.org/
  1. 开发者进行注册,获取api key

  1. 获取当前天气的接口

  1. 根据城市名字获取经度和纬度的接口
  1. 功能实现要点:
    (1) 首先实现静态组件

    ② App根组件下有两个子组件:Search、Weather
    (2) 根据城市名获取经度纬度的接口:
    http://api.openweathermap.org/geo/1.0/direct?q=${this.cityName}&appid=${apiKey}
    ② 以上红色字体采用了ES6的模板字符串。
    ③ 经度:const lon = response.data[0].lon
    ④ 纬度:const lat = response.data[0].lat
    (3) 获取天气信息的接口:
    https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}
    ② 天气图标:response.data.weather[0].icon
    ③ 温度:response.data.main.temp
    ④ 湿度:response.data.main.humidity
    ⑤ 风力:response.data.wind.speed
    (4) 以上免费接口已经在服务端解决了跨域的问题,可以直接用。
    (5) 兄弟组件之间通信采用全局事件总线。
    (6) 以上接口返回的图标的id,根据图标id动态拼接图片地址


    (7) 初次打开页面的时候,不应该显示天气的任何信息,Weather组件应该是隐藏的。
    (8) 查询天气过程中,显示“正在拉取天气信息,请稍后...”
    (9) 如果访问的城市不存在时,应该提示“对不起,请检查城市名”
    (10) 如果出现其它错误一律显示“网络错误,请稍后再试”
    (11) 在Search组件中触发事件的时候,将多个属性封装为对象的形式(语义化更明确)传给Weather组件。
    ① 在Weather组件中可以采用对象的形式一次性接收。
    ② 注意结构中的代码:

{{weather.windSpeed}} m/s

(12) ES6语法,合并对象
① this.weather = {...this.weather, ...weather}

5. Vuex

5.1 vuex概述

  1. vuex是实现数据集中式状态管理的插件。数据由vuex统一管理。其它组件都去使用vuex中的数据。只要有其中一个组件去修改了这个共享的数据,其它组件会同步更新。一定要注意:全局事件总线和vuex插件的区别:
    (1) 全局事件总线关注点:组件和组件之间数据如何传递,一个绑定on,一个触发emit。数据实际上还是在局部的组件当中,并没有真正的让数据共享。只是数据传来传去。

(2) vuex插件的关注点:共享数据本身就在vuex上。其中任何一个组件去操作这个数据,其它组件都会同步更新。是真正意义的数据共享。

  1. 使用vuex的场景是:
    (1) 多个组件之间依赖于同一状态。来自不同组件的行为需要变更同一状态。
    5.2 vuex环境搭建
  1. 安装vuex
    (1) vue2安装vuex3版本
    ① npm i vuex@3
    (2) vue3安装vuex4版本
    ① npm i vuex@4
  1. 创建目录和js文件(目录和文件名不是必须叫这个)
    (1) 目录:vuex
    (2) js文件:store.js
  1. 在store.js文件中创建核心store对象,并暴露
  1. 在main.js文件中关联store,这一步很重要,完成这一步之后,所有的vm和vc对象上会多一个$store属性

5.3 vuex实现一个最简单的案例

  1. 使用vuex实现一个点我加1的简单功能。
  1. 为什么这么折腾呢?
    (1) 通过以上案例,可以看出数据num可以被多个组件共享。(vuex可以管理多个组件共享的数据)
    (2) 通过on和emit这种全局事件总线不好吗?可以。但如果组件多的话,并且涉及到读和写的操作会导致混乱。
  1. actions中的回调函数,参数context
    (1) 如果业务逻辑非常负责,需要多个actions中的方法联合起来才能完成,可以在回调函数中使用context继续调用dispatch方法触发下一个action方法的执行。
    5.4 vuex工作原理

如果业务逻辑非常简单,也不需要发送AJAX请求的话,可以不调用dispatch方法,直接调用commit方法也是可以的。
5.5 多组件数据共享
实现以下案例:

5.6 getters配置项

  1. 如果想将state中的数据进行加工计算,并且这个计算逻辑复杂,而且要在多个位置使用,建议使用getters配置项。
  1. 怎么用?
    (1)
    (2)
    (3) 类似于Vue当中的:data和computed的关系。
    5.7 mapState和mapGetters的使用(优化计算属性)
  1. 组件中在使用state上的数据和getters上的数据时,都有固定的前缀:
    {{this.store.state.name}}
    {{this.store.getters.reverseName}}
    使用mapState和mapGetters进行名称映射,可以简化以上的写法。
  1. 使用mapState和mapGetters的前提是先引入
    (1) import {mapState, mapGetters} from ‘vuex’
  1. mapState如何使用,在computed当中使用ES6的语法
    (1) 第一种方式:对象形式
    ① ...mapState({name:’name’})
    (2) 第二种方式:数组形式
    ① ...mapState([‘name’])
    (3) 插值语法就可以修改为:{{name}}
  1. mapGetters如何使用,在computed当中使用ES6的语法
    (1) 第一种方式:对象形式
    ① ...mapGetters({reverseName:’reverseName’})
    (2) 第二种方式:数组形式
    ① ...mapGetters([‘reverseName’])
    (3) 插值语法就可以修改为:{{reverseName}}
    5.8 mapMutations和mapActions的使用(优化methods)
    import {mapMutations, mapActions} from ‘vuex’
    methods : {
    // 对象写法
    ...mapActions({add:’plusOne’,reverseName:’reverseName’})
    // 数组写法(前提是:保证methods中的方法名和actions中的方法名一致)
    ...mapActions([‘plusOne’, ‘reverseName’])
    }
    5.9 vuex的模块化开发
    5.9.1 未使用mapXxxx的模块化开发
    a模块

b模块

c模块

在store.js文件中引入各个模块

A组件

b组件

将A组件和B组件在App组件中注册

5.9.2 使用mapXxxx的模块化开发
a模块

b模块

在store.js中引入a和b模块

A组件

B组件

在APP组件中注册A和B组件

当然,在action中也可以发送AJAX请求:

6. 路由route

6.1 传统web应用vs单页面web应用
传统web应用
传统web应用,又叫做多页面web应用:核心是一个web站点由多个HTML页面组成,点击时完成页面的切换,因为是切换到新的HTML页面上,所以当前页面会全部刷新。

单页面web应用(SPA:Single Page web Application)
整个网站只有一个HTM页面,点击时只是完成当前页面中组件的切换。属于页面局部刷新。

单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。单页面的跳转仅刷新局部资源。因此,对单页应用来说模块化的开发和设计显得相当重要。
单页面应用的优点:
1、提供了更加吸引人的用户体验:具有桌面应用的即时性、网站的可移植性和可访问性。
2、单页应用的内容的改变不需要重新加载整个页面,web应用更具响应性和更令人着迷。
3、单页应用没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象
4、单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。
5、良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端
单页面应用的缺点:
1、首次加载耗时比较多。
2、SEO问题,不利于百度,360等搜索引擎收录。
3、容易造成CSS命名冲突。
4、前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂度很高,需要一定的技能水平和开发成本高。
单页面和多页面的对比

目前较为流行的是单页面应用的开发。
如果想使用Vue去完成单页面应用的开发,需要借助Vue当中的路由机制。
6.2 路由route与路由器router
路由:route
路由器:router
每一个路由都由key和value组成。
key1+value1===>路由route1
key2+value2===>路由route2
key3+value3===>路由route3
......
路由的本质:一个路由表达了一组对应关系。
路由器的本质:管理多组对应关系。
Vue中路由的工作原理:

6.3 使用路由

  1. 实现功能描述
  1. 根据静态页面提取两个组件:Tea.vue和Fruit.vue

  1. vue-router也是一个插件,安装vue-router
    (1) vue2要安装vue-router3
    ① npm i vue-router@3
    (2) vu3要安装vue-router4
    ① npm i vue-router@4
  1. main.js中引入并使用vue-router
    (1) 导入:import VueRouter from ‘vue-router’
    (2) 使用:Vue.use(VueRouter)
    (3) new Vue时添加新的配置项:一旦使用了vue-router插件,在new Vue的时候可以添加一个全新的配置项:router
  1. router路由器的创建一般放在一个独立的js文件中,例如:router/index.js
    (1) 创建router目录
    (2) 创建index.js,在index.js中创建路由器对象,并且将其暴露。然后在main.js文件中引入该路由器即可。
  1. 使用router-link标签代替a标签(App.vue中)

router-link标签最终编译之后的还是a标签。vue-router库帮助我们完成的。
7. 添加激活样式
使用active-class属性,在激活时添加样式:selected

  1. 指定组件的最终显示位置。
  1. 测试

注意事项:
① 路由组件一般会和普通组件分开存放,路由组件放到pages目录,普通组件放到components目录下。
② 路由组件在进行切换的时候,切掉的组件会被销毁。
③ 路由组件实例比普通组件实例多两个属性:route和router
1) route:属于自己的路由对象。
2) router:多组件共享的路由器对象。
6.4 多级路由

  1. 要实现的效果
  1. 主要实现

6.5 路由query传参
为了提高组件的复用性,可以给路由组件传参。
怎么传?

怎么接?

6.6 路由起名字
可以给路由起一个名字,这样可以简化to的编写。
怎么起名?

怎么使用?必须使用 :to=”{}” 的方式

6.7 路由params传参
怎么接?

怎么传?

需要注意的是,如果采用params传参,使用:to的时候,只能用name,不能用path。
6.8 路由的props
props配置主要是为了简化query和params参数的接收。让插值语法更加简洁。
第一种实现方式:

第二种实现方式:函数式

第三种实现方式:直接将params方式收到的数据转化为props

6.9 router-link的replace属性

  1. 栈数据结构:先进后出,后进先出原则。
  1. 浏览器的历史记录是存储在栈这种数据结构当中的。包括两种模式:
    (1) push模式(默认的)
    (2) replace模式
  1. 如何开启replace模式:
    (1)
    (2)
    6.10 编程式路由导航
    需求中可能不是通过点击超链接的方式切换路由,也就是说不使用如何实现路由切换。可以通过相关API来完成:
    (1) push模式:
    ① this.router.push({
    name : ‘’,
    query : {}
    })
    (2) replace模式:
    ① this.router.replace({
    name : ‘’,
    query : {}
    })
    (3) 前进:
    ① this.router.forward()
    (4) 后退:
    ① this.router.back()
    (5) 前进或后退几步:
    ① this.router.go(2) 前进两步
    ② this.router.go(-2) 后退两步
    (6) 使用编程式路由导航的时候,需要注意:重复执行push或者replace的API时,会出现以下错误:

这个问题是因为push方法返回一个Promise对象,期望你在调用push方法的时候传递两个回调函数,一个是成功的回调,一个是失败的回调,如果不传就会出以上的错误。所以解决以上问题只需要给push和replace方法在参数上添加两个回调即可。
6.11 缓存路由组件
默认情况下路由切换时,路由组件会被销毁。有时需要在切换路由组件时保留组件(缓存起来)。

 

这里的组件名称指的是:

不写include时:包含的所有路由组件全部缓存。
如何指定多个缓存路由,可以使用数组形式:

6.12 activated和deactivated
这是两个生命周期钩子函数。
只有“路由组件”才有的两个生命周期钩子函数。
路由组件被切换到的时候,activated被调用。
路由组件被切走的时候,deactivated被调用。
这两个钩子函数的作用是捕获路由组件的激活状态。
6.13 路由守卫
6.13.1 全局前置守卫
router/index.js文件中拿到router对象。
router.beforeEach((to, from, next)=>{ // 翻译为:每次前(寓意:每一次切换路由之前执行。)
// to 去哪里(to.path、to.name)
// from 从哪来
// next 继续:调用next( )
})

这种路由守卫称为全局前置路由守卫。
初始化时执行一次,以后每一次切换路由之前调用一次。

如果路由组件较多。to.path会比较繁琐,可以考虑给需要鉴权的路由扩展一个布尔值属性,可以通过路由元来定义属性:meta:{isAuth : true}

6.13.2 全局后置守卫
router/index.js文件中拿到router对象。
router.afterEach((to, from)=>{ // 翻译为:每次后(寓意:每一次切换路由后执行。)
// 没有 next
document.title = to.meta.title // 通常使用后置守卫完成路由切换时title的切换。
})
这种路由守卫称为全局后置路由守卫。
初始化时执行一次,以后每一次切换路由之后调用一次。
该功能也可以使用前置守卫实现:

该功能使用后置守卫实现更好:

解决闪烁问题:

6.13.3 局部路由守卫之path守卫

注意:没有afterEnter
6.13.4 局部路由守卫之component守卫

注意:只有路由组件才有这两个钩子。
6.13.5 前端项目上线

  1. 路径中#后面的路径称为hash。这个hash不会作为路径的一部分发送给服务器:
    (1) http://localhost:8080/vue/bugs/#/a/b/c/d/e (真实请求的路径是:http://localhost:8080/vue/bugs)
  1. 路由的两种路径模式:
    (1) hash模式
    (2) history模式
  1. 默认是hash模式,如何开启history模式
    (1) router/index.js文件中,在创建路由器对象router时添加一个mode配置项:
  1. 项目打包
    (1) 将Xxx.vue全部编译打包为HTML CSS JS文件。
    (2) npm run build
  1. 这里服务器端选择使用Java服务器:Tomcat,接下来教大家配置Tomcat服务器:
    (1) 下载JDK





    (2) 安装JDK



    (3) 配置环境变量:JAVA_HOME





    ⑥ 注意:如果你安装的路径和我安装的不一样,只要能够找到JDK bin目录的上一级目录即可。
  1. (4) 下载Tomcat



    (5) 安装Tomcat
    ① 解压就是安装。我这里直接解压到C盘的根目录下
  1. (6) 配置环境变量:CATALINA_HOME


    (7) 配置环境变量:PATH



    (8) 启动Tomcat
    ① 打开dos命令窗口,输入startup.bat,看到以下窗口表示tomcat启动成功(注意Tomcat服务器的端口号是8080,启动Tomcat服务时最好先关闭vue脚手架,因为vue cli使用的端口也是8080,如果启动了Tomcat服务器,再启动vue脚手架的话,脚手架会另外开启一个其他的端口。)
  1. 如果你启动tomcat控制台有乱码也无所谓,如果实在看不下去,可以修改以下配置文件内容:
    a.
    b.
    (9) 关闭Tomcat
    ① ctrl + c 或者:dos命令窗口中输入:shutdown.bat
    (10) 将前端项目部署到Tomcat。
    ① 找到tomcat服务器的webapps目录,并找到webapps目录下的ROOT目录,清空ROOT目录
    ② 将前端项目直接拷贝放到ROOT目录下即可。
    (11) 访问项目。
    ① 重启tomcat,并访问。
    ② http://localhost:8080


6. hash模式和history模式的区别与选择
(1) hash模式
① 路径中带有#,不美观。
② 兼容性好,低版本浏览器也能用。
③ 项目上线刷新地址不会出现404。
④ 第三方app校验严格,可能会导致地址失效。
(2) history模式
① 路径中没有#,美观。
② 兼容性稍微差一些。
③ 项目上线后,刷新地址的话会出现404问题。需要后端人员配合可以解决该问题。
7. 对于tomcat服务器来说,如何解决history带来的404问题,这需要后端人员写一段代码:
(1) 在ROOT目录下新建WEB-INF目录。
(2) 在WEB-INF目录下新建web.xml文件。
(3) 在web.xml文件中做如下配置:
 

404/index.html 7. Vue37.1 了解Vue31. vue3官网地址 (1) https://cn.vuejs.org/ 2. vue3发布时间 (1) 2020年9月18日。 翻译: 今天,我们很自豪地宣布Vue.js 3.0“海贼王”正式发布。这个新的主要版本的框架提供了改进的性能、更小的捆绑包大小、更好的TypeScript集成、用于处理大规模用例的新API,以及为框架未来的长期迭代奠定了坚实的基础。 3.0版本代表了两年多的开发工作,包括30多个RFC、2600多个提交、来自99个贡献者的628个拉取请求,以及核心回购之外的大量开发和文档工作。我们要向我们的团队成员表示最深切的感谢,感谢他们接受了这一挑战,感谢我们提出的撤回请求,感谢我们的赞助商和支持者提供的财政支持,感谢广大社区参与我们的设计讨论并为预发布版本提供反馈。Vue是一个为社区创建并由社区支持的独立项目,如果没有您的持续支持,Vue 3.0是不可能实现的。 3. 版本迭代历史 (1) https://github.com/vuejs/core/releases 4. vue3做了哪些改动 (1) 最核心的虚拟DOM算法进行了重写。 (2) 支持tree shaking:在前端的性能优化中,es6 推出了tree shaking机制,tree shaking就是当我们在项目中引入其他模块时,他会自动将我们用不到的代码,或者永远不会执行的代码摇掉 (3) 最核心的响应式由Object.defineProperty修改为Proxy实现。 (4) 更好的支持TS(Type Script:TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统。) (5) ...... 5. vue3比vue2好在哪 (1) (2) 翻译: ① 与Vue 2相比,Vue 3在捆绑包大小(通过树抖动可轻41%)、初始渲染(快55%)、更新(快133%)和内存使用(少54%)方面都有了显著的性能改进。 6. Vue3的新特性 (1) 新的生命周期钩子 ① (2) 键盘事件不再支持keyCode。例如:v-on:keyup.enter支持,v-on:keyup.13不支持。 (3) 组合式API(Composition API) ① (4) 新增了一些内置组件 ① (5) data必须是一个函数。 (6) ...... 7.2 Vue3工程的创建 7.2.1 vue-cli创建Vue3工程 1. 创建Vue3版本的工程,要求vue-cli最低版本4.5.0 (1) (2) 可以使用以下命令升级你的脚手架版本 ① npm install -g @vue-cli 2. 创建Vue3工程 (1) vue create vue3_pro 3. 启动工程 (1) 切换到工程根目录。 (2) npm run serve 7.2.2 vue-cli创建的vue3项目说明 1. 目录结构以及文件和vue2相同。 2. main.js文件说明 3. 查看App.vue组件 vue3中template标签下可以有多个根标签了。 7.2.3 create-vue创建Vue3工程1. create-vue是什么? (1) 和vue-cli一样,也是一个脚手架。 (2) vue-cli创建的是webpack+vue项目的脚手架工具。 (3) create-vue创建的是vite+vue项目的脚手架工具。 (4) webpack和vite都是前端的构建工具。 2. vite官网 (1) https://vitejs.cn/ 3. vite是什么?(vite被翻译为:快) (1) vite是一个构建工具,作者尤雨溪。 (2) 前端构建工具有哪些? 4. vite和传统构建工具的区别? (1) https://cn.vitejs.dev/guide/why.html 官方的说辞。 (2) 使用vite后,至少两方面是提升了: ① 服务器启动速度快。 ② 更新速度快了。 5. 使用create vue创建Vue3工程 (1) 官方指导:快速上手 | Vue.js (2) 安装 create-vue脚手架并创建vue3项目:npm init vue@latest 执行时,如果检测到没有安装create-vue脚手架时会安装脚手架。如果检测到已经安装过脚手架,则直接创建项目。 一路选择No: (3) cd vue3_pro (4) npm install (5) npm run dev (6) 访问5173端口 7.2.4 create-vue创建的vue3工程说明1. 目录结构 2. 和vue-cli脚手架创建的区别 (1) index.html文件不再放到public目录下了。 ① vite官方的解释是:让index.html成为入口。(vue-cli脚手架生成的项目入口是:main.js) (2) vue-cli的配置文件vue.config.js。create-vue脚手架的配置文件:vite.config.js ① vite.config.js 能配置什么?可以参考vite官网:https://cn.vitejs.dev/config/ 例如配置代理服务器和以前就不太一样了。 7.3 Proxy实现原理 Vue2的响应式核心:Object.defineProperty Vue3的响应式核心:Proxy 7.3.1 Object.defineProperty get做数据代理。set做数据劫持。在set方法中修改数据,并且渲染页面,完成响应式。 Object.defineProperty(data, ‘name’, { get(){ // 数据代理 }, set(val){ // 数据劫持 } }) 存在的问题:1) 通过数组下标修改数据,这个操作是没有响应式的。 2) 后期给对象新增属性、删除属性,这些操作都是没有响应式的。 导致以上问题最根本原因是:Object.defineProperty只能捕获到读和修改。 Vue2中怎么解决以上问题? 1) 对于数组来说调用数组的相关API。例如:push、shift、unshift.... 2) 新增属性、删除属性等操作通过:Vue.set或者this.$set 7.3.2 Proxy Proxy是ES6新增特性。window.Proxy Proxy是一个构造函数,参数传递一个目标对象,可以为目标对象生成代理对象。 通过访问代理对象上的属性来间接访问目标对象上的属性。 访问代理对象上的属性时,读属性、修改属性、删除属性、新增属性。Proxy都可以捕获到。 Vue3框架底层实际上使用了ES6的Reflect对象来完成对象属性的访问: 7.4 setup1. setup是一个函数,vue3中新增的配置项。 2. 组件中所用到的data、methods、computed、watch、生命周期钩子....等,都要配置到setup中。 3. setup函数的返回值: (1) 返回一个对象,该对象的属性、方法等均可以在模板语法中使用,例如插值语法。 (2) 返回一个渲染函数,从而执行渲染函数,渲染页面。 ① import {h} from ‘vue’ (引入渲染函数) ② return ()=>{h(‘h2’, ‘欢迎大家学习Vue3’)} 4. vue3中可以编写vue2语法,向下兼容的。但是不建议。更不建议混用。 5. setup中的this是undefined。所以setup中this是不可用的。 7.5 ref函数(实现响应式) 7.5.1 简单数据的响应式 1. ref是一个函数。完成响应式的。 2. ref使用时需要import (1) import {ref} from ‘vue’ 3. 凡是要实现响应式的data,需要使用ref函数进行包裹 (1) let name = ref(‘李四’) (2) 输出name,你会发现,它是RefImpl对象(引用实现的实例对象,简称引用对象),在这个引用对象当中有一个value属性,value属性的实现原理是:Object.defineProperty,通过它实现了响应式处理。 4. 修改数据的话必须这样做:name.value = ‘王五’ 7.5.2 对象数据的响应式 重点:如果ref函数中是一个对象,例如:ref({}),底层会为这个对象{}生成一个Proxy代理对象。通过这个代理对象完成了响应式。 如果代码是这样写,如下,实际上没有用到Proxy,还是使用了Object.defineProperty完成的响应式: 如果代码是这样写,如下,就是使用了Proxy完成的响应式: 7.6 Vue3中专门为对象做响应式的核心函数:reactive 以上代码中的Proxy是怎么创建的?底层是通过调用reactive函数来完成的。 当然这个函数我们也可以自己使用。 注意:这个函数不能为简单类型服务,只能为对象类型服务。 注意:reactive为响应式做了递归处理。对象中的对象中的对象的属性,也是响应式的。 重点1: 并且数组如果使用reactive函数包裹的话,数组中的数据,在通过下标去修改的时候,也是支持响应式的。(这个在Vue2中是不支持的。) 重点2: 在开发中一般很少使用ref,一般会使用reactive。即使是简单类型的数据,也会将其存放到一个对象中,使用reactive函数进行包括。 7.7 Vue3中的props 给组件User传数据 User组件使用props接收数据 在setup函数中如何使用props? setup的第一个参数就是props。可以直接拿来用。 7.8 Vue3的生命周期 vue2的生命周期图: vue3的生命周期图: 1. vue2中 (1) beforeDestroy (2) destroyed 2. vue3中 (1) beforeUnmount (2) unmounted 3. vue3中仍然可以使用配置项方式提供生命周期钩子函数。但也可以使用组合式API方式。如果采用组合式API方式,API名称规律如下: (1) beforeCreate => setup (2) created => setup (3) beforeMount => onBeforeMount (4) mounted => onMounted (5) beforeUpdate => onBeforeUpdate (6) updated => onUpdated (7) beforeUnmount => onBeforeUnmount (8) unmounted => onUnmounted 4. 当然如果需要使用beforeCreate和created钩子,可以采用vue2的语法:配置项形式。vue2和vue3语法可以共存,但不建议。 7.9 Vue3中的自定义事件 绑定事件 触发事件 7.10 Vue3的全局事件总线1. 安装mitt (1) npm i mitt 2. 封装event-bus.js文件 (1) (2) 3. 绑定事件 4. 触发事件 5. 移除所有事件和指定事件 7.11 vue3的计算属性 import {computed} from ‘vue’ //computed是一个组合式的API。 setup(){ // 简写 let reversedName = computed(()=>{ }) // 完整写法 let reversedName = computed({ get(){}, set(value){} }) } 计算属性最重要的特征是:只要计算属性关联的数据发生变化,计算属性的回调函数就会执行。所以计算属性关联的数据必须是具有响应式的。 7.12 自定义hook函数 将组合式API拿出来,封装成一个函数,在需要复用的位置,使用这个hook函数。 一般创建一个hooks目录,在hooks目录当中放hook函数。 代码复用。 在需要使用的位置导入: 7.13 vue3的监视属性 import {watch} from ‘vue’ //watch就是组合式API。 1. 监视ref定义的一个响应式数据 (1) watch(数据, (newValue, oldValue)=>{}) 2. 监视ref定义的多个响应式数据 (1) watch(数据1, (newValue, oldValue)=>{}) (2) watch(数据2, (newValue, oldValue)=>{}) 或者 (3) watch([数据1,数据2], (newValue, oldValue)=>{}) 3. immediate在哪里写? (1) 在watch的第三个参数位置上使用一个对象:{immediate : true, deep:true} 4. 监视reactive定义的响应式数据。注意:oldValue拿不到 (1) watch(数据, (newValue, oldValue)=>{}) 5. 如果监视的reactive定义的响应式数据。强制开启了深度监视,配置deep无效。默认就是监视对象的全部属性。 6. 如果要监视对象中的某个属性怎么办?被监视的数据必须是一个函数,将要监视的数据返回 (1) watch(()=>user.age, (newValue,oldValue)=>{}) 7. 如果要监视对象中的某些属性怎么办? (1) watch([()=>user.age, ()=>user.name], (newValue,oldValue)=>{}) 8. 如果要监视reactive定义的响应式数据中的某个属性,这个属性是一个对象形式,那么开启deep:true是可以的。 (1) watch(()=>user.address, (newVal, oldVal)=>{}) 9. 关于ref包裹对象时的value (1) watch(name.value, (newVal, oldVal){}) //监视无效 (2) watch(user.value, (newVal, oldVal)=>{})//监视有效 (3) watch(user,(newVal, oldVal)=>{}, {deep:true}) // 深度监视生效 7.14 watchEffect函数1. watchEffect函数里面直接写一个回调 (1) 回调中用到哪个属性,当这个属性发生变化的时候,这个回调就会重新执行。 (2) watchEffect(()=>{const a = data.counter1; const b = data.counter2 逻辑代码......}) 当counter1和counter2被修改后,这个回调就会执行。 2. computed和watchEffect的区别? (1) computed注重返回结果。 (2) watchEffect注重逻辑处理。 3. 示例代码 7.15 shallowReactive和shallowRef 浅层次的响应式。 shallowReactive:对象的第一层支持响应式,第二层就不再支持了。 shallowRef:只给基本数据类型添加响应式。如果是对象,则不会支持响应式。 以下是演示shallowRef 以下是演示shallowReactive 7.16 组合式API和选项式API对比 组合式API:Composition API 选项式API:Options API 选项式API:特点是 分散。 组合式API:特点是 集中 (一个hook是一个独立的功能,一个hook中有自己的data、methods、computed、watch) 7.17 深只读与浅只读 组合式API:readonly与shallowReadonly 应用场景:其它组件传递过来的数据,如果不希望你修改,你最好加上只读,以防以后不小心改了人家的数据。 深只读: 浅只读: 7.18 响应式数据的判断 isRef:检查某个值是否为 ref。 isReactive:检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。 isProxy:检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。 isReadonly:检查传入的值是否为只读对象。 7.19 toRef和toRefs 通过以上写法,可以看到模板语法中都有“data.”,这个是可以改善的。 大家可以看一下,下面的这个改善是否可以? 我们发现这样修改是不行的。丢失了响应式。什么原因?主要原因是因为以上的这种写法等同于: 显然这种写法和响应式对象data无关了。 再修改为以下这样行不行? 我们发现功能实现了。但是存在的问题是:data不会变。例如: 怎么解决这个问题:toRef,它可以让数据具有响应式,并且修改toRef生成的对象时,还能关联更新data: 测试结果: 还有一个更简单的:toRefs 运行结果: 7.20 转换为原始&标记为原始 toRaw与markRaw toRaw:将响应式对象转换为普通对象。只适用于reactive生成的响应式对象。 markRaw:标记某个对象,让这个对象永远都不具备响应式。比如在集成一些第三方库的时候,比如有一个巨大的只读列表,不让其具备响应式是一种性能优化。 toRaw: markRaw: 7.21 Fragment组件 fragment翻译为:碎片。片段。 在Vue2中每个组件必须有一个根标签。这样性能方面稍微有点问题,如果每一个组件必须有根标签,组件嵌套组件的时候,有很多无用的根标签。 在Vue3中每个组件不需要有根标签。实际上内部实现的时候,最终将所有组件嵌套好之后,最外层会添加一个Fragment,用这个fragment当做根标签。这是一种性能优化策略。 7.22 Teleport组件 teleport翻译为:远距离传送。用于设置组件的显示位置。

http://www.xdnf.cn/news/1245817.html

相关文章:

  • 数据结构(14)链式结构二叉树
  • Java技术栈/面试题合集(4)-Spring篇
  • ENSP防火墙安全策略简单案例
  • vue3通过按钮实现横向滚动或鼠标滚动横坐标滚动
  • MousePlus鼠标右键增强工具v5.5.25,支持鼠标轮盘功能
  • Linux驱动25 --- RkMedia音频API使用增加 USB 音视频设备
  • Windows 远程管理 (WinRM)问题详解包括c#与python例子
  • C++ 变量初始化方式总结 | 拷贝初始化 | 列表初始化 | 值初始化
  • YooAsset源码阅读-Downloader篇
  • 本地使用uv管理的python项目怎么部署到服务器?
  • 攻击实验(ARP欺骗、MAC攻击、报文洪水攻击、DNS欺骗)
  • Laravel The requested URL /hellowzy was not found on this server. 404 问题的解决
  • 2025年渗透测试面试题总结-01(题目+回答)
  • 《Node.js与 Elasticsearch的全文搜索架构解析》
  • 如何用分布式架构视角理解宇宙稳定性?从精细调参到微服务的类比思考
  • 【C++】模板深入进阶
  • 检索召回率优化探究四:基于LangChain0.3集成Milvu2.5向量数据库构建的智能问答系统
  • Sklearn 机器学习 数据聚类 层次聚类的两个重要属性
  • 编码器模型和解码器模型解析
  • GPT-5的诞生之痛:AI帝国的现实危机
  • LLM开发——语言模型会根据你的提问方式来改变答案
  • arp攻击(ettercap 版本0.8.3.1)
  • Physics Simulation - UE中Projectile相关事项
  • tensorRT配合triton部署模型
  • HTML 如何转 Markdown
  • 【Redis】string常用命令
  • 417页PDF | 2025年“人工智能+”行业标杆案例荟萃
  • 三款好用的PDF阅读器
  • 深入理解 Android SO 导出符号:机制与安全优化
  • Python高级编程与实践:Python高级数据结构与编程技巧