共计 6375 个字符,预计需要花费 16 分钟才能阅读完成。
vue3.x 和 vue2.x 版本对比
vue2.x 中绝大多数的 API 与特性,在 vue3.x 中同样支持。同时,vue3.x 中还新增了 3.x 所特有的功能、并废弃了某些 2.x 中的旧功能:
新增的功能有组合式API、多根节点组件、更好的 TypeScript 支持等
废弃的旧功能有过滤器、不再支持 $on、$off、$once 实例方法等
过滤器兼容性
过滤器仅在 vue2.x 和 1.x 中受支持,再 vue3.x 的版本中剔除了过滤器相关的功能
可以使用计算属性或方法代替被剔除的过滤器功能
单页面应用程序 SPA(Single Page Application)
优点
1. 良好的交互体验
- 单页应用的内容改变不需要重新加载整个页面
- 获取数据也是通过 Ajax 异步获取
- 没有页面之间的跳转,不会出现“白屏现象”
2. 良好的前后端工作分离模式
- 后端专注于提供 API 接口,更易实现 API 接口的复用
- 前端专注于页面的渲染,更利于前端工程化的发展
3. 减轻服务器的压力
- 服务器只提供数据,不负责页面的合成逻辑的处理,吞吐能力提高几倍
缺点
任何一种技术都有自己的局限性
1. 首屏加载慢
- 路由懒加载
- 代码压缩
- CDN 加速
- 网络传输压缩
2. 不利于 SEO
- SSR 服务器端渲染
快速创建 vue 的 SPA 项目
- 基于 vite 创建 SPA 项目
- 基于 vue-cli 创建 SPA 项目
vite 的基本使用
创建 vite 项目
按照顺序执行以下命令,即可基于 vite 创建 vue3.x 的工程化项目:
npm init vite-app 项目名称
cd 项目名称
npm install
npm run dev
梳理项目的结构
- node_modules 目录用来存放第三方依赖包
- public 是公共的静态资源目录
- src 是项目的源代码目录(写的所有代码都要放在此目录下)
- .gitignore 是 Git 的忽略文件
- index.html 是 SPA 单页面应用程序中唯一的 HTML 页面
- package.json 是项目的包管理配置文件
在 src 这个项目源代码目录下
- assets 目录用来存放项目中所有的静态资源文件(css、fonts等)
- components 目录用来存放项目中所有的自定义组件
- App.vue 是项目的根组件
- index.css 是项目的全局样式表文件
- main.js 是整个项目的打包入口文件
vite 项目的运行流程
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中
App.vue
用来编写待渲染的模板结构<index.html
中需要预留一个 **el 区域 **main.js
把 App.vue 渲染到了 index.html 所预留的区域中
在 App.vue 中编写模板结构
清空 App.vue 的默认内容,并书写以下的模板结构:
<temptale>
<h1>这是 App 根组件</h1>
</template>
在 index.html 中预留 el 区域
打开 index.html 页面,确认预留了 el 区域:
<body>
<!-- id 为 app 的 div 元素,就是将来 vue 要控制的区域 -->
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
在 main.js 中进行渲染
按照 vue3.x 的标准用法,把 App.vue 中模板内容渲染到 index.html 页面的 el 区域中:
// 1. 从 vue 中按需导入 createApp 函数,
// createApp 函数的作用:创建vue 的 “单页面应用程序实例”
import { creeateApp } from 'vue'
// 2. 导入待渲染的 App 组件
import App from './App.vue'
// 3. 调用 createApp() 函数,返回的是 “单页面应用程序的实例”,用常量 spa_app 进行接收
// 同时把 App 组件作为参数传给 createApp 函数,表示要把 App 渲染到 index.html 页面上
const spa_app = createApp(App)
// 4. 调用 spa_app 实例的 mount 方法,用来指定 vue 实际要控制的区域
spa_app.mount('#app')
在 template 中定义根节点
在 vue2.x 的版本中,<template>
节点内的 DOM 结构仅支持单个根节点但是,在 vue3.x 的版本中,<template>
中支持定义多个根节点
组件的注册
vue 中注册组件的方式分为 “全局注册” 和 “局部注册” 两种,其中:
- 被全局注册的组件,可以再全局任何一个组件内使用
// 在 main.js 中全局注册组件
import { createApp } from 'vue'
import App from './App.vue'
// 1. 导入 Swiper 和 Test 两个组件
import Swiper from './components/MySwiper.vue'
import Test from './components/Test.vue'
const app = createApp(App)
// 2. 调用 app 实例的 component() 方法,在全局注册 my-swiper 和 my-test 两个组件
app.component('my-swiper', Swiper)
app.component('my-test', Test)
app.mount('#app')
<!-- ------------------------------ -->
// 使用 app.component() 方法注册的全局组件,直接以标签的形式进行使用即可
// 在 main.js 中,注册了 my-swiper 和 my-test 两个全局组件
app.component('my-swiper', Swiper)
app.component('my-test', Test)
<!-- 在其他组件中,直接以标签的形式,使用刚才注册的全局组件即可 -->
<template>
<h1>App 根组件</h1>
<hr>
<my-swiper></my-swiper>
<my-test></my-test>
</template>
- 被局部注册的组件,只能在当前注册的范围内使用
<template>
<h1>App 根组件</h1>
<my-swiper></my-swiper>
<my-search></my-search>
</template>
<script>
import Search from './components/Search.vue'
export default {
// 通过components 节点,为当前组件注册私有组件
components: {
'my-search': Search
}
}
</script>
注册组件时名称的大小写
在进行组件的注册时,定义组件注册名称的方式有两种:
- 使用 kebab-case 命名法(俗称短横线命名法,例如 my-swiper 和 my-search)
- 使用 PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper 和 MySearch)
通过 name 属性注册组件
在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称:
样式穿透
/deep/
是 vue2.x 中实现样式穿透的方案
在 vue3.x 中推荐使用 :deep()
替代 /deep/
:deep(.title) {
color: pink;
}
动态样式
Class 与 Style 绑定
在实际开发中经常遇到动态操作元素样式的需求。因此,vue 运行开发者通过 v-bind
属性绑定指令,为元素动态绑定 class 属性的值和行内样式的 style 样式
动态绑定 HTML 的 class
可以通过三元表达式,动态的为元素绑定 class 的类名:
</h3 class="thin" :class="isItalic ? 'italic' : ''">MyDeep 组件</h3>
<button @click="isItalic = !isItalic">Toggle Italic</button>
data() {
retrun {
isItalic: true
}
}
.thin {
font-weight: 200;
}
.italic {
font-style: italic;
}
以数组语法绑定 HTML 的 calss
如果元素需要动态绑定多个 class 的类名,此时可以使用数组的语法格式:
</h3 class="thin" :class="[isItalic ? 'italic' : '', isDelete ? 'delete' : '']">
MyDeep 组件
</h3>
<button @click="isItalic = !isItalic">Toggle Italic</button>
<button @click="Delete = !Delete">Toggle Delete</button>
data() {
retrun {
isItalic: true,
isDelete: false
}
}
.thin {
font-weight: 200;
}
.italic {
font-style: italic;
}
.delete {
text-decoration: line-through;
}
以对象语法绑定 HTML 的 calss
使用数组语法动态绑定 class 会导致模板结构臃肿的问题,可以使用对象语法进行简化:
</h3 class="thin" :class="classObj">MyDeep 组件</h3>
<button @click="classObj.italic = !classObj.italic">Toggle Italic</button>
<button @click="classObj.delete = !classObj.delete">Toggle Delete</button>
data() {
retrun {
// 对象中,属性名是 class 类名,值是布尔值
classObj: {
italic: true,
delete: false
}
}
}
以对象语法绑定内联的 style
:style
的对象语法十分直观-看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式(camelCase)或短横线分隔(kebab-case,要用引号括起来)来命名:
<div :style="{ color: active, fontSize: fsize + 'px', 'backround-color': bgcolor }">
qiaofugui.cn
</div>
<button @click="fsize += 1">字号 +1</button>
<button @click="fsize -= 1">字号 -1</button>
data() {
return {
active: 'red',
fsize: 24,
bgcolor: 'pink'
}
}
props
自定义验证函数
在封装组件时,可以为 prop
属性指定自定义验证函数,从而对 props
属性的值进行更加精确的控制:
export default {
props: {
// 通过 “配置对象” 的形式,来定义 propD 属性的 “验证规则”
propD: {
type: {
// 通过 validator 函数,对 propD 属性的值进行校验,“属性的值” 可以通过形参 value 进行接收
validator(value) {
// propD 属性的值,必须匹配下列字符串中的一个
// validator 函数的返回值为 true 表示验证通过,false 表示验证失败
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
},
required: true,
default: 'success'
}
}
}
自定义事件
在封装组件时,为了让组件的使用者可以监听到组件内状态的变化,需要用到组件的自定义事件
自定义事件使用 3 个步骤
在封装组件时:
- 声明自定义事件
- 触发自定义事件
在使用组件时: - 监听自定义事件
声明自定义事件
开发者为自定义事件组件封装的自定义事件,必须先在 emits
节点中声明:
// 创建组件 MyCounter
<template>
<h3>Counter 组件</h3>
<button>+1</button>
</template>
<script>
export default {
// my-counter 组件的自定义事件,必须事先声明待 emits 节点中
emits: ['change']
}
</script>
触发自定义事件
在 emits
节点下声明的自定义事件,可以通过 this.$emit('自定义事件名称')
方法进行触发:
// 创建组件 MyCounter
<template>
<h3>Counter 组件</h3>
<button @click="onBtnClick">+1</button>
</template>
<script>
export default {
data() {
return {
count: 1
}
},
// my-counter 组件的自定义事件,必须事先声明待 emits 节点中
emits: ['change'],
methods: {
onBtnClick() {
// 当点击 +1 按钮时,调用 this.$emit() 方法,触发自定义的 change 事件
this.$emit('change')
}
}
}
</script>
监听自定义事件
在使用自定义的组件时,可以通过 v-on
的形式监听自定义事件:
// 使用组件 MyCounter
<!-- 使用 v-on 指令绑定事件监听 -->
<my-counter @change="getCount"></my-counter>
methods: {
getCount() {
console.log('监听到了 count 值的变化')
}
}
自定义事件传参
在调用 this.$emit()
方法触发自定义事件时,可以通过第 2 个参数为自定义事件传参:
// 创建组件 MyCounter
<template>
<h3>Counter 组件</h3>
<button @click="onBtnClick">+1</button>
</template>
<script>
export default {
data() {
return {
count: 1
}
},
emits: ['change'],
methods: {
onBtnClick() {
// 触发自定义事件时,通过第 2 个参数传参
this.$emit('change', this.count)
}
}
}
</script>
v-model:prpos名称、emits、$emit('update:props名称')
自定义指令
Vue3 自定义指令里面用 mounted(el) { }
全局自定义指令
const app = Vue.createApp({})
// 注册一个全局自定义指令 ‘v-focus’
app.directive('focus', {
// 当绑定的元素插入到 DOM 中时,自动触发 mounted 函数
mounted(el) {
// Focus the element
el.focus()
}
})
updated 函数
mounted 函数只在元素第一次插入 DOM 时被调用,当 DOM 更新时 mounted 函数不会被触发。updated 函数会在每次 DOM 更新完成后被调用
app.directive('focus', {
// 第一次插入 DOM 时触发这个函数
mounted(el) {
el.focus()
},
// 每次 DOM 更新时都会触发 updated 函数
updated(el) {
el.focus()
}
})
如果 mounted 和 updated 函数中的楼及完全相同,则可以简写成一下格式:
app.directive('focus', (el) => {
// 在 mounted 和 updated 时都会触发相同的业务处理
el.focus()
})
在绑定指令时,可以通过 “等号” 的形式为指令绑定具体的参数值(第二个形参,binding.)
在 vue2 的项目中使用自定义指令时,【mounted -> bind】【updated -> update】