一、vue简介
Vue是一款用于构建用户界面的 JavaScript 框架。
它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。
无论是简单还是复杂的界面,Vue 都可以胜任。
二、vue3选项式API
绝大多数和vue2相同
1.vue3初探
1.1 MVX
目标:理解MVVM、MVC、MVP
MV系列框架中,M和V分别指Model层和View层,但其功能会因为框架的不同而变化。
Model层是数据模型,用来存储数据;
View层是视图,展示Model层的数据。
虽然在不同的框架中,Model层和View层的内容可能会有所差别,但是其基础功能不变,变的只是 数据的传输方式
。
1.1.1 MVC(Model-View-Controller)
MVC是模型-视图-控制器,它是MVC、MVP、MVVM这三者中最早产生的框架,其他两个框架是以它为基础发展而来的。
MVC的目的就是将M和V的代码分离,且MVC是单向通信,必须通过Controller来承上启下。
$ npx express express-app --view=ejs
# npx 项目生成器的管理工具
# express node项目的生成器
# epxress-app 项目名称-自己起名
# --view=ejs 前端的模版
# 如果npx 创建不成 cnpm i express-generator -g
# 如果npx 创建不成 express express-app --view=ejs
# 如果还不行 找一个创建的项目,拷贝过来,安装依赖
$ cd express-app
$ cnpm i
# npm i
$ cnpm run start
# http://localhost:3000
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UjUUhCVX-1673404830690)(assets/image-20221212141903204.png)]
Model:模型层,数据模型及其业务逻辑,是针对业务模型建立的数据结构,Model与View无关,而与业务有关。
View:视图层,用于与用户实现交互的页面,通常实现数据的输入和输出功能。
Controller:控制器,用于连接Model层和View层,完成Model层和View层的交互。还可以处理页面业务逻辑,它接收并处理来自用户的请求,并将Model返回给用户。
// express-app/mysql/db.js -- 可替换自己熟悉的写法
// 链接数据库
const mongoose = require('mongoose')
const DB_URL = "mongodb://localhost:27017/ty2206"
mongoose.connect(DB_URL)
mongoose.connection.on('connected', () => {
console.log('数据库连接成功')
})
mongoose.connection.on('disconnected', () => {
console.log('数据库连接断开')
})
mongoose.connection.on('error', (err) => {
console.log('数据库连接失败' + err)
})
module.exports = mongoose
// express-app/mysql/collections/User.js
const mongoose = require('../db')
const Schema = mongoose.Schema
// MVC 中 M
const userSchema = new Schema({
userId: {
required: true,
type: String
},
userName: {
type: String
},
password: String
})
// users 数据库中的集合名称
module.exports = mongoose.model(userSchema, 'users')
// express-app/routes/index.js
var express = require('express');
var router = express.Router();
// var User = require('../mysql/collections/User')
/* GET home page. */
// MVC 中的 C
router.get('/', function(req, res, next) {
// User.find().exec((err, data) => {
// if (err) throw err
// res.render 渲染哪一个页面
// res.render('index', { title: 'Express', data });
// })
// 模拟数据库操作
res.render('index', { title: 'Express', data: [
{ userId: 'user1', userName: '吴大勋' },
{ userId: 'user2', userName: '纪童伟' },
] });
});
module.exports = router;
<!-- express-app/views/index.ejs-->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
<h1>用户列表如下</h1>
<table>
<tr>
<th>序号</th>
<th>id</th>
<th>姓名</th>
</tr>
<% for(var i = 0; i < data.length; i++) { %>
<tr>
<td><%= i + 1 %></td>
<td><%= data[i].userId %></td>
<td><%= data[i].userName %></td>
</tr>
<% } %>
</table>
</body>
</html>
遇到条件控制语句 使用 <% %>包裹,遇到变量 使用 <%= %> 或者 <%- %>包裹
- <%= %> 原样输出 - 转义输出。---- innerText
- <%- %> 解析输出。 ---- innerHTML
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KsXSIosF-1673404830692)(assets/image-20220908001836530.png)]
上图可以看出各部分之间的通信是单向的,呈三角形状。
具体MVC框架流程图如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MmUazYE4-1673404830692)(assets/image-20220908002149336.png)]
从上图可以看出,Controller层触发View层时,并不会更新View层中的数据,View层的数据是通过监听Model层数据变化自动更新的,与Controller层无关。换言之,Controller存在的目的是确保M和V的同步,一旦M改变,V应该同步更新。
同时,我们可以看到,MVC框架大部分逻辑都集中在Controller层,代码量也集中在Controller层,这带给Controller层很大压力,而已经有独立处理事件能力的View层却没有用到;而且,Controller层与View层之间是一一对应的,断绝了View层复用的可能,因而产生了很多冗余代码。
MVC 房东 -房客 -中介
为了解决上述问题,MVP框架被提出
1.1.2 MVP(Model-View-Presenter)
MVP是模型-视图-表示器,它比MVC框架大概晚出现20年,是从MVC模式演变而来的。它们的基本思想有相同之处:Model层提供数据,View层负责视图显示,Controller/Presenter层负责逻辑的处理。将Controller改名为Presenter的同时改变了通信方向。
Model:模型层,用于数据存储以及业务逻辑。
View:视图层,用于展示与用户实现交互的页面,通常实现数据的输入和输出功能。
Presenter:表示器,用于连接M层、V层,完成Model层与View层的交互,还可以进行业务逻辑的处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QMNHRVJy-1673404830693)(assets/image-20220908002243164.png)]
上图可以看出各部分之间的通信是双向的。
在MVC框架中,View层可以通过访问Model层来更新,但在MVP框架中,View层不能再直接访问Model层,必须通过Presenter层提供的接口,然后Presenter层再去访问Model层。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0zrma4Kz-1673404830693)(assets/image-20220908002315234.png)]
从上图可以看出,View层和Model层互不干涉,View层也自由了很多,所以View层可以抽离出来做成组件,在复用性上就比MVC框架好很多。
但是,由于View层和Model层都需要经过Presenter层,导致Presenter层比较复杂,维护起来也会有一定的问题;而且,因为没有绑定数据,所有数据都需要Presenter层进行“手动同步”,代码量较大,虽然比起MVC框架好很多,但还是有比较多冗余部分。
为了让View层和Model层的数据始终保持一致,MVVM框架出现了。
1.1.3 MVVM(Model-View-ViewModel)
MVVM是模型-视图-视图模型。MVVM与MVP框架区别在于:MVVM采用双向绑定:View的变动,自动反映在ViewModel,反之亦然。
Model:数据模型(数据处理业务),指的是后端传递的数据。
View:视图,将Model的数据以某种方式展示出来。
ViewModel:视图模型,数据的双向绑定(当Model中的数据发生改变时View就感知到,当View中的数据发生变化时Model也能感知到),是MVVM模式的核心。ViewModel 层把 Model 层和 View 层的数据同步自动化了,解决了 MVP 框架中数据同步比较麻烦的问题,不仅减轻了 ViewModel 层的压力,同时使得数据处理更加方便——只需告诉 View 层展示的数据是 Model 层中的哪一部分即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F6R1CKLI-1673404830694)(assets/image-20220908002410800.png)]
上图可以看出各部分之间的通信是双向的,而且我们可以看出,MVVM框架图和MVP框架图很相似,两者都是从View层开始触发用户的操作,之后经过第三层,最后到达Model层。而关键问题就在于这第三层的内容,Presenter层是采用手动写方法来调用或修改View层和Model层;而ViewModel层双向绑定了View层和Model层,因此,随着View层的数据变化,系统会自动修改Model层的数据,反之同理。
具体MVVM框架流程图如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AhhZ1vns-1673404830694)(assets/image-20220908002444206.png)]
从上图可以看出,View层和Model层之间的数据传递经过了ViewModel层,ViewModel层并没有对其进行“手动绑定”,不仅使速度有了一定的提高,代码量也减少很多,相比于MVC框架和MVP框架,MVVM框架有了长足的进步。
从MVVM第一张图可以看出,MVVM框架有大致两个方向:
1、模型–>视图 ——实现方式:数据绑定
2、视图–>模型 ——实现方式:DOM事件监听
存在两个方向都实现的情况,叫做数据的双向绑定。双向数据绑定可以说是一个模板引擎,它会根据数据的变化实时渲染。如图View层和Model层之间的修改都会同步到对方。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s5nNSY19-1673404830695)(assets/image-20220908002601014.png)]
MVVM模型中数据绑定方法一般有四种:
- 数据劫持vue2 - Object.defineProperty
- 原生Proxy vue3
- 发布-订阅模式
- 脏值检查
Vue2.js使用的就是数据劫持和发布-订阅模式两种方法。了解Vue.js数据绑定流程前,我们需要了解这三个概念:
- Observer:数据监听器,用于监听数据变化,如果数据发生改变,不论是在View层还是在Model层,Observer都会知道,然后告诉Watcher。
- Compiler:指定解析器,用于对数据进行解析,之后绑定指定的事件,在这里主要用于更新视图。
- Watcher:订阅者。
首先将需要绑定的数据劫持方法找出来,之后用Observer监听这堆数据,如果数据发生变化,Observer就会告诉Watcher,然后Watcher会决定让那个Compiler去做出相应的操作,这样就完成了数据的双向绑定。
vue3.js使用更快的原生 Proxy,消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的很多限制:无法监听 属性的添加和删除、数组索引和长度的变更,并可以支持 Map、Set、WeakMap 和 WeakSet!
带来的特性:
vue3.0实现响应式
Proxy支持监听原生数组
Proxy的获取数据,只会递归到需要获取的层级,不会继续递归
Proxy可以监听数据的手动新增和删除
Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的~
1.2 vue特性
目标:理解声明式,对比传统DOM开发
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2mt1Rbt-1673404830695)(assets/image-20220913091144383.png)]
Vue从设计角度来讲,虽然能够涵盖这张图上所有的东西,但是你并不需要一上手就把所有东西全用上,都是可选的。
声明式渲染和组件系统是Vue的核心库所包含内容,而路由、状态管理、构建工具都有专门解决方案。这些解决方案相互独立,我们可以在核心的基础上任意选用其他的部件(以插件形势使用),不一定要全部整合在一起。
Vue.js的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进DOM的系统。
假设需要输出 “hello ty2206”
准备工作:cnpm
https://npmmirror.com/
$ npm install -g cnpm --registry=https://registry.npmmirror.com
以后就可以使用cnpm 代替 npm
如果遇到类似于以下**psl这种错误
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fSkQoCrA-1673404830696)(assets/image-20220914103517117.png)]
只需要找到这个文件删除即可(这个错误只会出现在windows电脑下)
补充:如果使用cnpm出现
randomUUID is not a function
,解决方法$ npm uninstall -g cnpm $ npm install cnpm@7.1.0 -g
传统开发模式的原生js,jQuery代码如下:
<div id="test"></div>
<!--原生js-->
<script>
const msg = "hello ty2206"
const test = document.getElementById('test')
test.innerHTML = msg
// test.innerText = msg
// test.textContent=""
</script>
<!--jQuery-->
<script>
var msg = 'hello ty2206'
$('#test').html(msg)
// $('#test').text(msg)
</script>
$ cnpm i vue jquery # 临时安装,不会出现package.json文件
拷贝
node_modules/vue/dist/vue.global.js
以及vue.global.prod.js
,还有jquery/dist/jquery.js
以及jquery.min.js
到lib文件夹
完整代码:01_base/01_before.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>传统的DOM操作</title>
</head>
<body>
<div id="jsDOM"></div>
<div id="jqDOM"></div>
</body>
<script src="../lib/jquery.min.js"></script>
<script>
const str = 'hello ty2206'
const jsDOM = document.getElementById('jsDOM')
// jsDOM.innerHTML = str
// jsDOM.innerText = str
jsDOM.textContent = str
// $('#jqDOM').html(str)
$('#jqDOM').text(str)
</script>
</html>
02_vue3.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>vue3解决DOM操作</title>
</head>
<body>
{{ str }}
<div id="app">
<div>{{ str }}</div>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const { createApp } = Vue
const app = createApp({
data () {
return {
str: 'hello ty2206',
list: ['a', 'b', 'c', 'd']
}
}
})
app.mount('#app')
</script>
</html>
01_base/03_vue2.html
$ cnpm i vue@2
拷贝 vue下的 vue.js 以及vue.min.js到lib文件夹
<!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>vue2解决DOM操作</title>
</head>
<body>
{{ str }}
<div id="app">
<div>{{ str }}</div>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</body>
<script src="../lib/vue.js"></script>
<script>
new Vue({
data: { // new Vue实例时使用对象,其余时刻使用函数
str: 'hello ty2206',
list: ['a', 'b', 'c', 'd', 'e']
}
}).$mount('#app')
</script>
</html>
1.3 vue3十大新特性
* setup ---- 组合式API
* ref ---- 组合式API
* reactive ---- 组合式API
* 计算属性computed ---- 组合式API 以及 选项式API
* 侦听属性watch ---- 组合式API 以及 选项式API
* watchEffect函数 ---- 组合式API
* 生命周期钩子 ---- 组合式API 以及 选项式API
* 自定义hook函数 ---- 组合式API
* toRef和toRefs ---- 组合式API 以及 选项式API
* 其他新特性
* shallowReactive 与 shallowRef ---- 组合式API
* readonly 与 shallowReadonly ---- 组合式API
* toRaw 与 markRaw ---- 组合式API
* customRef ---- 组合式API
* provide 与 inject ---- 组合式API 以及 选项式API
* 响应式数据的判断 ---- 组合式API 以及 选项式API
* 新的组件 ----- 类似于新增的HTML标签
* Fragment
* Teleport
* Suspense
* 其他变化
* data选项应始终被声明为一个函数
* 过渡类名的更改
* 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes --- 事件处理
* 移除v-on.native修饰符 --- 事件处理
* 移除过滤器(filter) --- 单独讲解vue2和vue3差异化
1.4 创建第一个vue3应用
每个 Vue 应用都是通过 createApp
函数创建一个新的 应用实例:
<div id="app"></div>
import { createApp } from 'vue'
const app = createApp({
/* 根组件选项 */
})
app.mount('#app')
简单计数器案例:01_base/04_counter.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>vue3示例</title>
</head>
<body>
<div id="app">
{{ msg }}
<div>
{{ count }}
<div>count的double: {{ count * 2 }}</div>
<!-- 体验点击事件 -->
<button @click="count++">加1</button>
</div>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
// 解构 创建应用实例的 函数
const { createApp } = Vue // 有的人喜欢写 window.Vue
// 创建应用实例
const app = createApp({ // 当前应用实例的选项
data () { // 书写形式为函数,表示vue实例中需要使用的 数据的变量,必须含有返回值,返回值为对象
return {
msg: 'hi ty2206',
count: 10
}
}
})
// 应用实例挂载
app.mount('#app')
</script>
</html>
1.我们传入
createApp
的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。应用实例必须在调用了
.mount()
方法后才会渲染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串:
1.5 API风格
参考链接
目标:选项式API以及组合式API如何选择
01_base/05_composition.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>vue3_组合式API</title>
</head>
<body>
<div id="app">
{{ msg }}
<div>
{{ count }}
<div>count的double: {{ count * 2 }}</div>
<!-- 体验点击事件 -->
<button @click="count++">加1</button>
</div>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
// 解构 创建应用实例的 函数
// ref 代表 组合式API 创建数据时的一个响应式的标识
const { createApp, ref } = Vue // 有的人喜欢写 window.Vue
// 创建应用实例
const app = createApp({ // 当前应用实例的选项
setup () { // 组合式API的标志
const msg = ref('hello ty2206!') // msg 的初始值
const count = ref(100) // count 的初始值
// 数据需要在views视图响应,需要将其返回去
return {
msg,
count
}
}
})
// 应用实例挂载
app.mount('#app')
</script>
</html>
使用组合式API可以
- 更好的逻辑复用
- 更灵活的代码组织
- 更好的类型推导
- 更小的生产包体积
选项式 API 确实允许你在编写组件代码时“少思考”,这是许多用户喜欢它的原因。然而,在减少费神思考的同时,它也将你锁定在规定的代码组织模式中,没有摆脱的余地,这会导致在更大规模的项目中难以进行重构或提高代码质量。在这方面,组合式 API 提供了更好的长期可维护性。
组合式 API 能够覆盖所有状态逻辑方面的需求
一个项目可以同时使用两种API
选项式API不会被抛弃
2.模板与指令
2.1 模板语法
学习:插值表达式、js表达式、v-cloak
Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。
在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。
2.1.1 文本插值
最基本的数据绑定形式是文本插值,它使用的是“Mustache[ˈmʌstæʃ]”语法 (即双大括号):
<span>Message: {{ msg }}</span>
双大括号标签会被替换为相应组件实例中
msg
属性的值。同时每次msg
属性更改时它也会同步更新。
2.1.2 js表达式
Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
这些表达式都会被作为 JavaScript ,以组件为作用域解析执行。
在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
- 在文本插值中 (双大括号)
- 在任何 Vue 指令 (以
v-
开头的特殊 attribute) attribute 的值中绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用,比如改变数据或触发异步操作。
2.1.3 v-cloak
用于隐藏尚未完成编译的 DOM 模板。
当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。
v-cloak
会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。配合像 [v-cloak] { display: none }
这样的 CSS 规则,它可以在组件编译完毕前隐藏原始模板。
[v-cloak] {
display: none;
}
<div v-cloak>
{{ message }}
</div>
直到编译完成前,
<div>
将不可见。
完整案例:02_template/06_mustache.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>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<!-- v-cloak 配合属性选择器可隐藏未 编译的 DOM模版 -->
<div id="app" v-cloak>
<div>{{ msg }}</div>
<div>{{ msg.split('').reverse().join('') }}</div>
<div>{{ msg.split('').reverse().join('-') }}</div>
<div>{{ flag ? '真' : '假' }}</div>
<div id="user100">1</div>
<!-- 绑定属性 指令 v-bind 简写形式为: -->
<!-- 属性值有变量 一定要使用绑定属性 -->
<div v-bind:id="'user' + id">2</div>
<div :id="'user' + id">22</div>
<div :id="`user${id}`">3</div>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const { createApp } = window.Vue
const app = createApp({
data () {
return {
msg: 'hello',
flag: true,
id: 100
}
}
})
app.mount('#app')
</script>
</html>
2.2 文本类指令
学习:v-text、v-html、v-pre
2.2.1 v-html & v-text
双大括号会将数据插值为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html
指令:
<div v-html="rawHTML"></div>
<div v-text="rawHTML"></div>
<div>{{rawHTML}}</div>
在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用
v-html
,并且永远不要使用用户提供的 HTML 内容(script也属于HTML内容)。
2.2.2 v-pre
元素内具有 v-pre
,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。
<div v-pre>{{ rawHTML }}</div>
完整案例: 02_template/07_text.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>
</head>
<body>
<div id="app">
<h1>v-html</h1>
<div>
<div v-html="msg"></div>
<div v-html="str"></div>
</div>
<h1>v-text</h1>
<div>
<div v-text="msg"></div>
<div v-text="str"></div>
</div>
<h1>v-pre</h1>
<div>{{ msg }}</div>
<!-- v-pre 跳过当前DOM下的 vue语法编译 -->
<div v-pre>{{ msg }}</div>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const { createApp } = Vue
// v-html v-text v-pre
const app = createApp({
data () {
return {
msg: 'hello',
str: '<h1>hi</h1>'
}
}
})
app.mount('#app')
</script>
</html>
2.3 属性绑定
学习:v-bind 以及简写形式
双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind
指令:
<div v-bind:id="myId"></div>
如果绑定的值是
null
或者undefined
,那么该 attribute 将会从渲染的元素上移除。
因为 v-bind
非常常用,提供了特定的简写语法:
<div :id="myId"></div>
开头为
:
的 attribute 可能和一般的 HTML attribute 看起来不太一样,但它的确是合法的 attribute 名称字符,并且所有支持 Vue 的浏览器都能正确解析它。
布尔型 attribute 依据 true / false 值来决定 attribute 是否应该存在于该元素上。disabled
就是最常见的例子之一。
<button :disabled="flag">Button</button>
当 flag
为真值或一个空字符串 (即 <button disabled="">
) 时,元素会包含这个 disabled
attribute。而当其为其他假值时 attribute 将被忽略。
如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:
data () {
return {
obj: {
a: 1,
b: 2
}
}
}
<div v-bind="obj"></div>
完整案例: 02_template/08_attribute.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>
</head>
<body>
<div id="app">
<div :id="myId"></div>
<div :id="`user${num}`"></div>
<!-- num - 字符串 -->
<div num="100"></div>
<!-- num - number -->
<div :num="1000"></div>
<button disabled="true"></button>
<button :disabled="true"></button>
<div arr="[1, 2, 3]"></div>
<div :arr="[1, 2, 3]"></div>
<div obj="{a:1, b:2 }"></div>
<div :obj="{a:1, b:2 }"></div>
<div class="null"></div>
<div :class="null"></div>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const { createApp } = Vue
// 如果属性的值是变量或者包含变量,number类型的数据,boolean类型数据,对象,数组,
// null,undefined,正则,需要使用绑定属性
const app = createApp({
data () {
return {
myId: 'user100',
num: 100
}
}
})
app.mount('#app')
</script>
</html>
如果属性的值是变量或者包含变量,number类型的数据,boolean类型数据,对象,数组,
null,undefined,正则,需要使用绑定属性
2.4 事件绑定
学习:v-on以及简写形式,methods应用
我们可以使用 v-on
指令 (简写为 @
) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="methodName"
或 @click="handler"
。
事件处理器的值可以是:
- 内联事件处理器:事件被触发时执行的内联 JavaScript 语句 (与
onclick
类似)。 - 方法事件处理器:一个指向组件上定义的方法的属性名或是路径。
·内联事件处理器·通常用于简单场景,例如:
<button @click="count++">加 1</button>
<p>Count is: {{ count }}</p>
随着事件处理器的逻辑变得愈发复杂,内联代码方式变得不够灵活。因此 v-on
也可以接受一个方法名或对某个方法的调用。
方法事件处理器
<button @click="greet">问候</button>
data () {
return {
name: 'Vue.js'
}
},
methods: {
greet(event) {
// 方法中的 `this` 指向当前活跃的组件实例
alert(`Hello ${this.name}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
}
除了直接绑定方法名,你还可以在内联事件处理器中调用方法。这允许我们向方法传入自定义参数以代替原生事件:
methods: {
say(message) {
alert(message)
}
}
<button @click="say('hello')">说 hello</button>
<button @click="say('bye')">说 bye</button>
内联事件处理器中访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的 $event
变量,或者使用内联箭头函数:
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('表单不能提交.', $event)">提交</button>
<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('表单不能提交.', event)">提交</button>
methods: {
warn(message, event) {
// 这里可以访问 DOM 原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
}
在处理事件时调用 event.preventDefault()
或 event.stopPropagation()
是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理 DOM 事件的细节会更好。
为解决这一问题,Vue 为 v-on
提供了事件修饰符。
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
<!-- 事件修饰符 -->
<!-- 阻止冒泡-原生 -->
<div class="parent" @click="clickParent1">
<div class="child" @click="clickChild1"></div>
</div>
<!-- 阻止冒泡-事件修饰符 -->
<div class="parent" @click="clickParent2">
<div class="child" @click.stop="clickChild2"></div>
</div>
methods: {
clickParent1 (event) {
console.log('parent1')
},
clickChild1 (event) {
event.stopPropagation()
console.log('child1')
},
clickParent2 (event) {
console.log('parent2')
},
clickChild2 (event) {
console.log('child2')
},
}
.parent {
width: 200px;
height: 200px;
background: #f66;
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px;
}
.child {
width: 100px;
height: 100px;
border: 1px solid #ccc;
}
在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在 v-on
或 @
监听按键事件时添加按键修饰符
。
<!-- 按键修饰符 -->
<!-- 原生 -->
<input type="text" @keyup="print1">
<!-- vue -->
<input type="text" @keyup.enter="print2">
methods: {
print1 (event) {
if (event.keyCode === 13) {
console.log('打印1')
}
},
print2 () {
console.log('打印2')
}
}
.enter
.tab
.delete (捕获“Delete”和“Backspace”两个按键)
.esc
.space
.up
.down
.left
.right
你可以使用以下系统按键修饰符
来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发
.ctrl
.alt
.shift
.meta
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
完整案例: 02_tempalte/09_event.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>09_绑定事件</title>
<style>
.parent {
width: 200px;
height: 200px;
background-color: #f66;
display: flex;
justify-content: center;
align-items: center;
}
.child {
width: 100px;
height: 100px;
background-color: #ccc;
}
</style>
</head>
<body>
<div id="app">
<!-- 内联事件处理器 -->
<button v-on:click="count++">加1</button> {{ count }}<br />
<!-- 方法事件处理器 -->
<button v-on:click="add">加1</button> {{ count }}<br />
<button @click="say('hello', $event)">say hello</button>
<button @click="say('bye', $event)">say bye</button>
<!-- 事件对象 - 阻止事件冒泡,阻止默认事件 -->
<form @submit="getUserInfo">
<input type="text" name="userName" placeholder="用户名" />
<input type="password" name="password" placeholder="密码" />
<input type="submit" value="提交">
</form>
<div class="parent" @click="print('parent', $event)">
<div class="child" @click="print('child', $event)"></div>
</div>
<!-- 事件修饰符 - 阻止事件冒泡,阻止默认事件 -->
<form @submit.prevent="getUserInfoVue">
<input type="text" name="userName" placeholder="用户名" />
<input type="password" name="password" placeholder="密码" />
<input type="submit" value="提交">
</form>
<div class="parent" @click="printVue('parent')">
<div class="child" @click.stop="printVue('child')"></div>
</div>
<!-- 事件对象 - 回车时打印数据 -->
<input type="text" @keyup="printData" />
<!-- 按键修饰符 - 回车时打印数据 -->
<input type="text" @keyup.enter="printDataVue" />
<!-- vue2中可以 根据 keyCode 作为修饰符, vue3中不支持 -->
<input type="text" @keyup.13="printDataVueCode" />
<!-- 使用系统修饰符可以自定义组合按键 -->
<!-- 用户点击 alt 加 enter时 清空输入框数据 -->
<!-- v-model 属于表单的输入绑定 -->
<input type="text" v-model="text" @keyup.alt.enter="clear"/> {{ text }}
<div @click.ctrl="doSomething">Do something</div>
</div>
</body>
<script src="lib/vue.global.js"></script>
<script>
const { createApp } = Vue
const app = createApp({
data () {
return {
count: 10,
name: 'Vue.js',
text: '1'
}
},
methods: { // 所有自定义的vue的事件都应该写在 methods 中
// 如果使用事件时,没有添加(),那么事件含有默认参数为 event
add (event) {
// this其实就是vue的实例
// this.count++
// this.count += 1
this.count = this.count + 1
console.log(this.name)
console.log(event) // PointerEvent 与原生js中的事件对象保持一致(react中使用的不是原生js的事件对象)
},
// 如果使用事件时添加(),并且还想用事件对象,那么传递事件对象的vue的专属参数 $event
say (msg, event) {
console.log(msg, event)
},
getUserInfo (event) {
event.preventDefault()
},
getUserInfoVue () {
},
print (msg, event) {
event.stopPropagation()
console.log(msg)
},
printVue (msg) {
console.log(msg)
},
printData (event) {
if (event.keyCode === 13) {
console.log('1')
}
},
printDataVue () {
console.log(2)
},
printDataVueCode () {
console.log(3)
},
clear () {
this.text = ''
},
doSomething () {
console.log('doSomething')
}
}
})
app.mount('#app')
</script>
</html>
2.5条件渲染
学习:v-if、v-else-if、v-else、v-show
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。
也可以使用 v-else
为 v-if
添加一个“else 区块”
一个 v-else
元素必须跟在一个 v-if
或者 v-else-if
元素后面,否则它将不会被识别。
<div v-if="grade >= 90">优</div>
<div v-else-if="grade >= 80">良</div>
<div v-else-if="grade >= 70">中</div>
<div v-else-if="grade >= 60">差</div>
<div v-else>不及格</div>
data () {
return {
grade: 66
}
}
v-else
和v-else-if
也可以在<template>
上使用。想要切换不止一个元素,在这种情况下我们可以在一个
<template>
元素上使用v-if
,这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个<template>
元素。---- tempalte是一个空标签
另一个可以用来按条件显示一个元素的指令是 v-show
。其用法基本一样:
不同之处在于 v-show
会在 DOM 渲染中保留该元素;v-show
仅切换了该元素上名为 display
的 CSS 属性。
v-show
不支持在<template>
元素上使用,也不能和v-else
搭配使用。
<!-- v-show -->
<div v-show="grade >= 90 && grade < 100">优</div>
<div v-show="grade >= 80 && grade < 90">良</div>
<div v-show="grade >= 70 && grade < 80">中</div>
<div v-show="grade >= 60 && grade < 70">差</div>
<div v-show="grade < 60">不及格</div>
完整案例:02_tempalte/10_condition.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>10_条件渲染</title>
</head>
<body>
<div id="app">
<button @click="grade++"> 加</button> {{ grade }}<button @click="grade--">减</button>
<!-- v-if v-else-if v-else -->
<div v-if="grade >=90">优</div>
<div v-else-if="grade >=80">良</div>
<div v-else-if="grade >=70">中</div>
<div v-else-if="grade >=60">差</div>
<div v-else>不及格</div>
<hr />
<!-- v-show -->
<div v-show="grade >= 90">优</div>
<div v-show="grade >= 80 && grade < 90">良</div>
<div v-show="grade >= 70 && grade < 80">中</div>
<div v-show="grade >= 60 && grade < 70">差</div>
<div v-show="grade < 60">不及格</div>
<!-- 假如不同条件下需要同时控制多个元素的显示和不显示 -->
<div v-if="flag">1</div>
<div v-if="flag">2</div>
<div v-if="flag">3</div>
<!-- template 属于vue框架中的空标签,审查元素时不会被渲染出来 -->
<template v-if="flag">
<div>4</div>
<div>5</div>
<div>6</div>
</template>
</div>
</body>
<script src="lib/vue.global.js"></script>
<script>
const { createApp } = Vue
const app = createApp({
data () {
return {
grade: 61,
flag: true
}
}
})
app.mount('#app')
</script>
</html>
v-if
vsv-show
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if
也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。相比之下,
v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有 CSSdisplay
属性会被切换。总的来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用v-show
较好;如果在运行时绑定条件很少改变,则v-if
会更合适。
2.6列表渲染
学习:v-for 以及key属性
我们可以使用 v-for
指令基于一个数组(对象、字符串)来渲染一个列表。v-for
指令的值需要使用 item in/of items
形式的特殊语法,其中 items
是源数据的数组,而 item
是迭代项的别名
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
<li v-for="item in items">
{{ item.message }}
</li>
在 v-for
块中可以完整地访问父作用域内的属性和变量。v-for
也支持使用可选的第二个参数表示当前项的位置索引。
data() {
return {
parentMessage: 'Parent',
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
列表渲染需要添加key值,对于多层嵌套的 v-for
,作用域的工作方式和函数的作用域很类似
完整案例: 02/template/11_list.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>
</head>
<body>
<div id="app">
<button @click="addAfter">后面追加数据</button>
<button @click="addBefore">前面追加数据</button>
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
<hr/>
<ul>
<li v-for="item in list" :key="item">{{ item }}</li>
</ul>
<hr/>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
<hr/>
<ul>
<li v-for="(item, index) in arr" :key="item.id">{{ index + 1 }} -{{ item.name }}</li>
</ul>
<hr/>
<ul>
<li v-for="value in obj" >{{ value }}</li>
</ul>
<hr/>
<ul>
<li v-for="(value, key) in obj" >{{key}}:{{ value }}</li>
</ul>
<hr/>
<ul>
<li v-for="(value, key, index) in obj" >{{ index + 1}} - {{key}}:{{ value }}</li>
</ul>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const { createApp } = Vue
const app = createApp({
data () {
return {
list: ['a', 'b', 'c', 'd'],
arr: [
{ name: '大勋', id: 1 },
{ name: '小吴', id: 2 },
{ name: '纪童伟', id: 3 },
{ name: '大勋', id: 4 }
],
obj: {
a: 11,
b: 22,
c: 33
}
}
},
methods: {
addBefore () {
this.list.unshift('f')
},
addAfter () {
this.list.push('e')
}
}
})
app.mount('#app')
</script>
</html>
v-if 与 v-for 同时存在于一个元素上,会发生什么?
02_template/12_vue3_if_for.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>vue3 v-for v-if优先级</title> </head> <body> <div id="app"> <!-- 审查元素查看优先级:vue3中,v-if的优先级高于v-for --> <ul> <li v-if="flag" v-for="item in list" :key="item">{{ item }}</li> </ul> </div> </body> <script src="../lib/vue.global.js"></script> <script> Vue.createApp({ data () { return { list: ['a', 'b', 'c', 'd'], flag: false } } }).mount('#app') </script> </html>
02_template/13_vue2_if_for.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>vue2 v-for v-if优先级</title> </head> <body> <div id="app"> <!-- 审查元素查看优先级:vue2中,v-for的优先级高于v-if --> <ul> <li v-if="flag" v-for="item in list" :key="item">{{ item }}</li> </ul> </div> </body> <script src="../lib/vue.js"></script> <script> new Vue({ data: { list: ['a', 'b', 'c', 'd'], flag: false } }).$mount('#app') </script> </html>
通过
审查元素
得知:vue3中,v-if的优先级高于v-for
vue2中,v-for的优先级高于v-if
2.7 表单输入绑定
学习:v-model
在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量
<input
:value="text"
@input="event => text = event.target.value">
v-model
指令帮我们简化了这一步骤:
<input v-model="text">
v-model
还可以用于各种不同类型的输入,<textarea>
、<select>
元素。它会根据所使用的元素自动使用对应的 DOM 属性和事件组合:
- 文本类型的
<input>
和<textarea>
元素会绑定value
property 并侦听input
事件;<input type="checkbox">
和<input type="radio">
会绑定checked
property 并侦听change
事件;<select>
会绑定value
property 并侦听change
事件
v-model
会忽略任何表单元素上初始的value
、checked
或selected
attribute。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。你应该在 JavaScript 中使用data
选项来声明该初始值。
完整案例: 02_template/14_model.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>
</head>
<body>
<div id="app">
<input type="text" :value="userName" @input="getUserName"/> {{ userName }} <br/>
<!-- lazy input ===> change -->
<!-- number 转换为数字,以数字开头-->
<!-- trim 去除两端空格-->
<input type="password" v-model.trim="password"/> {{ password }} <br/>
<input type="radio" name="sex" value="1" v-model="sex"> 男
<input type="radio" name="sex" value="0" v-model="sex"> 女 - {{ sex === '1' ? '男' : '女' }}<br/>
<input type="checkbox" name="hobby" v-model="hobby" value="🏀">🏀
<input type="checkbox" name="hobby" v-model="hobby" value="🏐️">🏐️
<input type="checkbox" name="hobby" v-model="hobby" value="🎾">🎾
<input type="checkbox" name="hobby" v-model="hobby" value="⚽️">⚽️ - {{ hobby }}<br/>
<select v-model="lesson">
<option disabled value="">请选择</option>
<option value="1">1阶段</option>
<option value="2">2阶段</option>
<option value="3">3阶段</option>
</select> - {{ lesson === '1' ? '1阶段': (lesson === '2' ? '2阶段' : (lesson === '3' ? '3阶段' : ''))}}<br/>
<textarea v-model="note"></textarea>{{ note }} <br/>
<input type="checkbox" v-model="flag"> 同意******协议 -- {{ flag }}
<button @click="getData">获取数据</button>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
// input (text password) textarea ----- value input
// select ---- value change
// radio checkbox ----- checked change
Vue.createApp({
data () {
return {
userName: '',
password: '',
sex: '1',
hobby: [],
lesson: '',
note: '',
flag: false
}
},
methods: {
getUserName (event) {
this.userName = event.target.value
},
getData () {
console.log(this.password)
}
}
}).mount('#app')
</script>
</html>
如果
v-model
表达式的初始值不匹配任何一个选择项,<select>
元素会渲染成一个“未选择”的状态。在 iOS 上,这将导致用户无法选择第一项,因为 iOS 在这种情况下不会触发一个 change 事件。因此,我们建议提供一个空值的禁用选项
修饰符
.lazy input — change
.number — 输出的为数字
.trim ---- 去除两端的空格
2.8 类与样式绑定
数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 class
和 style
都是 attribute,我们可以和其他 attribute 一样使用 v-bind
将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 class
和 style
的 v-bind
用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
不管是类class还是样式style,都有对象和数组的写法
完整案例: 02_template/15_style.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>
<style>
.box {
width: 200px;
height: 200px;
float: left;
margin-left: 5px;
}
.border {
border: 5px solid #000;
}
.box1 {
background-color: #f66;
}
.box2 {
background-color: #ccc;
}
.box3 {
background-color: #00f;
}
</style>
</head>
<body>
<div id="app">
<input type="color" v-model="color">
<h1>style</h1>
<div style="width: 100px; height: 100px; background-color:#f66;">1</div>
<div :style="`width: 100px; height: 100px; background-color:${color};`">2</div>
<div :style="{ width: '100px', height: '100px', backgroundColor: color }">3</div>
<div :style="[{ width: '100px', height: '100px'}, {backgroundColor: color }]">4</div>
<h1>class</h1>
<div class="box box1 border"></div>
<div class="box box2 border" ></div>
<div class="box box3 border"></div>
<div class="box" :class="{box1: flag, border: flag}"></div>
<div class="box" :class="{box2: flag, border: flag}"></div>
<div class="box" :class="{box3: flag, border: flag}"></div>
<div class="box" :class="[{box1: flag}, {border: flag}]"></div>
<div class="box" :class="[{box2: flag}, {border: flag}]"></div>
<div class="box" :class="[{box3: flag}, {border: flag}]"></div>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
Vue.createApp({
data () {
return {
color: '',
flag: true
}
}
}).mount('#app')
</script>
</html>
3.生命周期
学习:常见的8个生命周期钩子函数
3.1 vue2生命周期
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM,在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,目的是给予用户在一些特定的场景下添加他们自己代码的机会。
Vue生命周期的主要阶段:4个before, 4个ed,创建,装载,更新,销毁
-
挂载(初始化相关属性)
-
beforeCreate ---- 备孕
注意点:在此时不能获取data中的数据,也就是说 this.msg 得到的是
-
created ---- 怀上了
-
beforeMount ---- 怀胎十月
-
mounted【页面加载完毕的时候就是此时】 ---- 生下来了
注意点:默认情况下,在组件的生命周期中只会触发一次
-
-
更新(元素或组件的变更操作)
-
beforeUpdate
-
updated
注意点:可以重复触发的
-
-
销毁(销毁相关属性)
- beforeDestroy — game over前
- destroyed — game over
销毁(手动)使用 this.$destroy()
关于8个生命周期涉及到的方法,可以参考Vue官网API:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBTra27k-1673404830697)(assets/lifecycle.png)]
3.2 vue3生命周期
选项式API中将 beforeDestroy 以及 destroyed 修改为 beforeUnmount 和 unmounted,其余一致
https://cn.vuejs.org/guide/essentials/lifecycle.html#lifecycle-diagram
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5PopamTF-1673404830698)(assets/lifecycle.16e4c08e.png)]
如果是vue2的生命周期钩子函数
完整案例: 03_lifeCycle/16_lifeCycle_vue2.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>vue2 生命周期</title>
</head>
<body>
<div id="app">
{{ count }}
<button @click="add">加1</button>
</div>
</body>
<script src="../lib/vue.js"></script>
<script>
// new Vue({
// data: {
// count: 100
// }
// }).$mount('#app')
new Vue({
el: '#app',
data: {
count: 100
},
methods: {
add () {
this.count++
if (this.count === 110) {
this.$destroy()
}
}
},
beforeCreate () { // 备孕
// 在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用
console.log('beforeCreate')
},
created () { // 备孕怀上 意外怀孕 ---- 请求数据,修改状态 --- 但是不建议 - 不确定性
// 在实例创建完成后被立即同步调用。
// 在这一步中,实例已完成对选项的处理,
// 意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。
// 然而,挂载阶段还没开始,且 $el property 目前尚不可用。
console.log('created')
},
beforeMount () { // 怀胎十月
// 在挂载开始之前被调用:相关的 render 函数首次被调用。
console.log('beforeMount')
},
mounted () { // 出生了 --- 数据请求,修改状态,DOM操作,实例化操作,定时器,延时器
// 实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。
// 如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。
// 注意 mounted 不会保证所有的子组件也都被挂载完成。
// 如果你希望等到整个视图都渲染完毕再执行某些操作,
// 可以在 mounted 内部使用 vm.$nextTick:
console.log('mounted')
this.$nextTick(() => { // 保证了整个视图都渲染完毕
// 业务逻辑
})
},
beforeUpdate () {
// 在数据发生改变后,DOM 被更新之前被调用。
console.log('beforeUpdate')
},
updated () { // DOM操作,实例化操作
// 在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
// 不要修改状态,使用计算属性 和watcher代替
console.log('updated')
},
beforeDestroy () { // 一不小心触发条件 --- 取消定时器,延时器,销毁对象等
// 实例销毁之前调用。在这一步,实例仍然完全可用。
console.log('beforeDestroy')
},
destroyed () { // 没了
// 实例销毁后调用。
// 该钩子被调用后,对应 Vue 实例的所有指令都被解绑,
// 所有的事件监听器被移除,所有的子实例也都被销毁。
console.log('destroyed')
}
})
</script>
</html>
03_lifeCycle/17_lifeCycle_vue3.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>vue3 选项式API生命周期</title>
</head>
<body>
<div id="app">
{{ count }}
<button @click="add">加1</button>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const app = Vue.createApp({
data () {
console.log('data')
return {
count: 100
}
},
methods: {
add () {
this.count++
if (this.count === 110) {
app.unmount()
}
}
},
beforeCreate () { // 备孕
// 在组件实例初始化完成之后立即调用
// 会在实例初始化完成、props 解析之后、data() 和 computed 等选项处理之前立即调用
console.log('beforeCreate')
},
created () { // 备孕怀上 意外怀孕 ---- 请求数据,修改状态 --- 但是不建议 - 不确定性
// 在组件实例处理完所有与状态相关的选项后调用
// 当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。
// 然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。
console.log('created')
},
beforeMount () { // 怀胎十月
// 在组件被挂载之前调用。
// 当这个钩子被调用时,组件已经完成了其响应式状态的设置,
// 但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。
console.log('beforeMount')
},
mounted () { // 出生了 --- 数据请求,修改状态,DOM操作,实例化操作,定时器,延时器
// 在组件被挂载之后调用。
// 所有同步子组件都已经被挂载。
// 其自身的 DOM 树已经创建完成并插入了父容器中。
console.log('mounted')
this.$nextTick(() => { // 保证了整个视图都渲染完毕
// 业务逻辑
})
},
beforeUpdate () {
// 在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。
console.log('beforeUpdate')
},
updated () { // DOM操作,实例化操作
// 在组件因为一个响应式状态变更而更新其 DOM 树之后调用。
console.log('updated')
},
beforeUnmount () { // 一不小心触发条件 --- 取消定时器,延时器,销毁对象等
// 在一个组件实例被卸载之前调用。
console.log('beforeDestroy')
},
unmounted () { // 没了
// 在一个组件实例被卸载之后调用。
console.log('destroyed')
}
})
app.mount('#app')
</script>
</html>
4.响应式
4.1 响应式基础
学习:状态选项data,$data
选用选项式 API 时,会用 data
选项来声明组件的响应式状态。此选项的值应为返回一个对象的函数。Vue 将在创建新组件实例的时候调用此函数,并将函数返回的对象用响应式系统进行包装。此对象的所有顶层属性都会被代理到组件实例 (即方法和生命周期钩子中的 this
) 上。
Vue 在组件实例上暴露的内置 API 使用 $
作为前缀。它同时也为内部属性保留 _
前缀。因此,你应该避免在顶层 data
上使用任何以这些字符作前缀的属性。
从 data
选项函数中返回的对象,会被组件赋为响应式。组件实例将会代理对其数据对象的属性访问。
完整案例04_reactive/18_data.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>data</title>
</head>
<body>
<div id="app">
{{ count }}
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const app = Vue.createApp({
data () {
return {
count: 100
}
},
mounted () {
console.log(this)
console.log(this.count) // 100
this.count = 200
console.log(this.$data.count) // 200
this.$data.count = 300
console.log(this.count) // 300
console.log(this.$data.count) // 300
console.log(this._.data.count) // 300
this._.data.count = 400
console.log(this.count) // 400
console.log(this.$data.count) // 400
console.log(this._.data.count) // 400
console.log(this.$.data.count) // 400
}
})
app.mount('#app')
</script>
</html>
4.2 计算属性
学习computed
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护
推荐使用计算属性来描述依赖响应式状态的复杂逻辑
计算属性是基于它们的响应式依赖进行缓存的,计算属性比较适合对多个变量或者对象进行处理后返回一个结果值,也就是说多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化。
计算属性定义在Vue对象中,通过关键词 computed 属性对象中定义一个个函数,并返回一个值,使用计算属性时和 data 中的数据使用方式一致。
4.2.1 一般计算属性以及方法对比
完整案例:04_reactive/19_computed.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>
</head>
<body>
<div id="app">
<div>{{ msg }}</div>
<button @click="msg = 'hi computed'">改变msg</button>
<div>表达式:
{{ msg.split('').reverse().join('') }} -
{{ msg.split('').reverse().join('') }} -
{{ msg.split('').reverse().join('') }}
</div>
<div>计算属性:{{ reverseMsg }} - {{ reverseMsg }} - {{ reverseMsg }}</div>
<div>方法:{{ reverseMsgFn() }} - {{ reverseMsgFn() }} - {{ reverseMsgFn() }}</div>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
// 计算属性优于 表达式以及方法
const app = Vue.createApp({
data () {
return {
msg: 'hello computed'
}
},
methods: {
reverseMsgFn () {
console.log('22')
return this.msg.split('').reverse().join('')
}
},
computed: { // 所有计算属性
reverseMsg () { // 计算属性写为函数,必须含有返回值,使用和data一致
console.log('11')
return this.msg.split('').reverse().join('')
}
}
})
app.mount('#app')
</script>
</html>
计算属性具有依赖性,只有当依赖的值发生改变,才会重新计算
同等条件下,计算属性优于 方法 以及 js表达式。
4.2.2 可写计算属性
计算属性默认仅能通过计算函数得出结果。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建:
完整案例:04_reactive/20_computed.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>
</head>
<body>
<div id="app">
{{ reverseMsg }}
<button @click="updateComputed">修改计算属性</button>
<input type="text" v-model="firstName"> +
<input type="text" v-model="lastName"> = {{ fullName }}
<button @click="resetName">重置名字</button>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const app = Vue.createApp({
data () {
return {
msg: 'hello',
firstName: 'wu',
lastName: 'daxun'
}
},
computed: {
reverseMsg () {
return this.msg + '!!!'
},
// fullName () { // 默认是只读的
// return this.firstName + this.lastName
// }
// fullName: {
// get () {
// return this.firstName + this.lastName
// }
// }
fullName: {
get () { // 可读
return this.firstName + this.lastName
},
set (val) { // ************** 这里的setter 表示计算属性是可写的
this.firstName = val.split(' ')[0]
this.lastName = val.split(' ')[1]
}
}
},
methods: {
updateComputed () {
this.reverseMsg = 'hi!!!!!'
},
resetName () {
this.fullName = "吴 大勋"
}
}
})
app.mount('#app')
</script>
</html>
4.3 侦听器
学习:watch以及实例方法$watch
使用watch来侦听data中数据的变化,watch中的属性现阶段一定是data 中已经存在的数据。
watch 只能监听data中的数据变化吗?
watch可以监听路由中的数据的变化
**使用场景:**数据变化时执行异步或开销比较大的操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VkwuVHOM-1673404830698)(assets/20.png)]
完整案例:04_reactive/21_watch.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>
</head>
<body>
<div id="app">
<input type="text" v-model="firstName"> +
<input type="text" v-model="lastName"> = {{ fullName }}
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const app = Vue.createApp({
data () {
return {
firstName: '',
lastName: '',
fullName: ''
}
},
watch: {
firstName (newVal, oldVal) {
this.fullName = newVal + this.lastName
},
lastName (newVal, oldVal) {
this.fullName = this.firstName + newVal
}
}
})
app.mount('#app')
</script>
</html>
使用计算属性可以简化
完整案例:04_reactive/22_computed.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>
</head>
<body>
<div id="app">
<input type="text" v-model="firstName"> +
<input type="text" v-model="lastName"> = {{ fullName }}
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const app = Vue.createApp({
data () {
return {
firstName: '',
lastName: ''
}
},
computed: {
fullName () {
return this.firstName + this.lastName
}
}
})
app.mount('#app')
</script>
</html>
如何监听一个对象下的属性的变化?深度侦听
watch
默认是浅层的:被侦听的属性,仅在被赋新值时,才会触发回调函数——而嵌套属性的变化不会触发。如果想侦听所有嵌套的变更,你需要深层侦听器:如果一开始就需要监听数据,建议直接在options Api中添加 watch选项
如果在达到某一个条件下再开启监听,需要使用 this.$watch()手动添加侦听器
如果不使用深度侦听,如何监听对象下的属性的变化,可以通过 监听
对象.属性
的变化(vue2 + vue3),注意this指向完整案例:
04_reactive/23_deep_watch.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> </head> <body> <div id="app"> <input type="text" v-model="user.firstName"> + <input type="text" v-model="user.lastName"> = <div id="full">{{ user.fullName }} </div> <button @click="count++">加1</button>{{ count }} <button @click="startWatch">开始监听</button> <button @click="stopWatch">停止监听</button> </div> </body> <script src="../lib/vue.global.js"></script> <script> const app = Vue.createApp({ data () { return { user: { firstName: '1', lastName: '2', fullName: '12' }, count: 10 } }, methods: { // 手动开启监听器 startWatch () { this.unwatch = this.$watch('count', (newVal) => { console.log(newVal) }) }, stopWatch () { this.unwatch() } }, watch: { // count (newVal) { // console.log(newVal) // }, // 侦听 对象.属性, 变相的完成的 对象下数据的侦听,如果数据层级比较深呢? // 'user.firstName' (newVal, oldVal) { // this.user.fullName = newVal + this.user.lastName // }, // 'user.lastName' (newVal, oldVal) { // this.user.fullName = this.user.firstName + newVal // } user: { deep: true, // 开启深度侦听 immediate: true, // 自动执行一次监听数据,默认值为false handler (newVal, oldVal) { console.log(newVal) this.user.fullName = newVal.firstName + newVal.lastName }, // 默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。 // 这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。 // 在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: 'post' 选项 // 了解,一般使用在 组合式API flush: 'post' // vue3中新增的 } } }) app.mount('#app') </script> </html>
4.4 深入响应式系统
学习:renderTracked 以及 renderTraggered
https://cn.vuejs.org/guide/extras/reactivity-in-depth.html
Vue 最标志性的功能就是其低侵入性的响应式系统。组件状态都是由响应式的 JavaScript 对象组成的。当更改它们时,视图会随即自动更新。这让状态管理更加简单直观,但理解它是如何工作的也是很重要的,这可以帮助我们避免一些常见的陷阱。
4.4.1 什么是响应性
这个术语在今天的各种编程讨论中经常出现,但人们说它的时候究竟是想表达什么意思呢?本质上,响应性是一种可以使我们声明式地处理变化的编程范式。一个经常被拿来当作典型例子的用例即是 Excel 表格:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vv2T7251-1673404830699)(assets/image-20220915004948125.png)]
这里单元格 A2 中的值是通过公式 = A0 + A1
来定义的 (你可以在 A2 上点击来查看或编辑该公式),因此最终得到的值为 3,正如所料。但如果你试着更改 A0 或 A1,你会注意到 A2 也随即自动更新了。
而 JavaScript 默认并不是这样的。如果我们用 JavaScript 写类似的逻辑:
let A0 = 1
let A1 = 2
let A2 = A0 + A1
console.log(A2) // 3
A0 = 2
console.log(A2) // 仍然是 3
当我们更改 A0
后,A2
不会自动更新。
那么我们如何在 JavaScript 中做到这一点呢?首先,为了能重新运行计算的代码来更新 A2
,我们需要将其包装为一个函数:
let A0 = 1
let A1 = 2
let A2
function update() {
A2 = A0 + A1
}
update()
console.log(A2) // 3
A0 = 2
update()
console.log(A2) // 4
然后,我们需要定义几个术语:
- 这个
update()
函数会产生一个副作用,或者就简称为作用 (effect),因为它会更改程序里的状态。 A0
和A1
被视为这个作用的依赖 (dependency),因为它们的值被用来执行这个作用。因此这次作用也可以说是一个它依赖的订阅者 (subscriber)。
我们需要一个魔法函数,能够在 A0
或 A1
(这两个依赖) 变化时调用 update()
(产生作用)。
whenDepsChange(update)
这个 whenDepsChange()
函数有如下的任务:
- 当一个变量被读取时进行追踪。例如我们执行了表达式
A0 + A1
的计算,则A0
和A1
都被读取到了。 - 如果一个变量在当前运行的副作用中被读取了,就将该副作用设为此变量的一个订阅者。例如由于
A0
和A1
在update()
执行时被访问到了,则update()
需要在第一次调用之后成为A0
和A1
的订阅者。 - 探测一个变量的变化。例如当我们给
A0
赋了一个新的值后,应该通知其所有订阅了的副作用重新执行。
4.4.2 Vue 中的响应性是如何工作的
后续单独讲解,刨析vue2的响应式和vue3的响应式的区别以及实现原理
5.组件化
5.1 什么是组件化?理解组件化
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uAU2Ij3r-1673404830699)(assets/components.7fbb3771.png)]
这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。
5.2 如何封装一个vue组件
目标:理解一般思路即可
一个 Vue 组件在使用前需要先被“注册”,这样 Vue 才能在渲染模板时找到其对应的实现。
- 先构建组件的模板
- 定义组件
- 注册组件
- 使用组件
5.3 vue3组件注册和使用
学习:app.component()、components选项、template选项
组件注册有两种方式:全局注册和局部注册。
5.3.1 全局注册组件
我们可以使用 Vue 应用实例的 app.component()
方法,让组件在当前 Vue 应用中全局可用
import { createApp } from 'vue'
const app = createApp({})
app.component(
// 注册的名字
'MyComponent',
// 组件的实现
{
/* ... */
}
)
app.component()
方法可以被链式调用:
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
全局注册的组件可以在此应用的任意组件的模板中使用
<!-- 这在当前应用的任意组件中都可用 -->
<ComponentA/>
<ComponentB/>
<ComponentC/>
所有的子组件也可以使用全局注册的组件,这意味着这三个组件也都可以在彼此内部使用
完整案例:05_component/24_component_vue3.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>vue3全局注册组件</title>
</head>
<body>
<div id="app">
<!-- 使用组件
在html文件中 只能使用短横线方式调用组件,如果使用模版,则也可以使用 驼峰式
-->
<!-- <ComA ></ComA> 这里报警告 -->
<com-a></com-a>
<com-b></com-b>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
const app = Vue.createApp()
// 定义组件
const ComA = {
// 组件模版
template: `
<div>
{{ msg }}
</div>
`,
data () {
return {
msg: ' hello coma'
}
}
}
// 全局注册组件 app.component(组件名称, 定义好的组件的选项)
// 组件的名称可以采用 驼峰式命名 以及 短横线方式
app.component('ComA', ComA)
// app.component('com-a', ComA)
app.component('ComB', {
// 组件模版
template: `
<div>
{{ msg }}
</div>
`,
data () {
return {
msg: ' hello comb'
}
}
})
app.mount('#app')
</script>
</html>
vue2全局注册组件
完整案例:05_component/25_component_vue2.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>vue2全局注册组件</title>
</head>
<body>
<div id="app">
<!-- 使用组件
在html文件中 只能使用短横线方式调用组件,如果使用模版,则也可以使用 驼峰式
-->
<!-- <ComA ></ComA> 这里报警告 -->
<com-a></com-a>
<com-b></com-b>
</div>
</body>
<script src="../lib/vue.js"></script>
<script>
// 定义组件
const ComA = {
// 组件模版
template: `
<div>
{{ msg }}
</div>
`,
data () {
return {
msg: ' hello coma'
}
}
}
// 组件的名称可以采用 驼峰式命名 以及 短横线方式
Vue.component('ComA', ComA)
// app.component('com-a', ComA)
Vue.component('ComB', {
// 组件模版
template: `
<div>
{{ msg }}
</div>
`,
data () {
return {
msg: ' hello comb'
}
}
})
// Vue.component(组件名称, 组件选项)
// vue2全局注册组件必须放在 new Vue实例之前,
// 相当于当你new Vue 时,组件的已经挂载到了 Vue对象的原型上
const app = new Vue()
app.$mount('#app')
</script>
</html>
5.3.2 局部注册组件
全局注册虽然很方便,但有以下几个问题:
- 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
- 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
相比之下,局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。
局部注册需要使用 components
选项
对于每个 components
对象里的属性,它们的 key 名就是注册的组件名,而值就是相应组件的实现。
完整案例:05_component/26_components_vue3.html
vue3局部注册组件
<!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>vue3局部注册组件</title>
</head>
<body>
<div id="app">
<!-- 使用组件
在html文件中 只能使用短横线方式调用组件,如果使用模版,则也可以使用 驼峰式
-->
<!-- <ComA ></ComA> 这里报警告 -->
<com-a></com-a>
<com-b></com-b>
</div>
</body>
<script src="../lib/vue.global.js"></script>
<script>
// 定义组件
const ComA = {
// 组件模版
template: `
<div>
{{ msg }}
</div>
`,
data () {
return {
msg: ' hello coma'
}
}
}
const app = Vue.createApp({
components: { // 局部注册组件
ComA: ComA,
'com-b': {
// 组件模版
template: `
<div>
{{ msg }}
</div>
`,
data () {
return {
msg: ' hello comb'
}
}
}
}
})
app.mount('#app')
</script>
</html>
完整案例:05_component/27_components_vue2.html
vue2局部注册组件
<!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>vue2局部注册组件</title>
</head>
<body>
<div id="app">
<!-- 使用组件
在html文件中 只能使用短横线方式调用组件,如果使用模版,则也可以使用 驼峰式
-->
<!-- <ComA ></ComA> 这里报警告 -->
<com-a></com-a>
<com-b></com-b>
</div>
</body>
<script src="../lib/vue.js"></script>
<script>
// 定义组件
const ComA = {
// 组件模版
template: `
<div>
{{ msg }}
</div>
`,
data () {
return {
msg: ' hello coma'
}
}
}
const app = new Vue({
components: { // 局部注册组件
ComA: ComA,
'com-b': {
// 组件模版
template: `
<div>
{{ msg }}
</div>
`,
data () {
return {
msg: ' hello comb'
}
}
}
}
})
app.$mount('#app')
</script>
</html>
如何抽离组件的模版呢
局部注册的组件在后代组件中并*不*可用
5.3.3 组件使用注意事项
以上案例使用 PascalCase 作为组件名的注册格式,这是因为:
- PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 中导入和注册组件都很容易,同时 IDE 也能提供较好的自动补全。
<PascalCase />
在模板中更明显地表明了这是一个 Vue 组件,而不是原生 HTML 元素。同时也能够将 Vue 组件和自定义元素 (web components) 区分开来
在单文件组件和内联字符串模板中,我们都推荐这样做。但是,PascalCase 的标签名在 DOM 模板(html文件)中是不可用的
为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件。这意味着一个以 MyComponent
为名注册的组件,在模板中可以通过 <MyComponent>
或 <my-component>
引用。这让我们能够使用同样的 JavaScript 组件注册代码来配合不同来源的模板。