一、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 [email protected] -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
}
}