什么是Vue3
Vue.js 是一款流行的 JavaScript 前端框架,用于构建交互式的 Web 用户界面。Vue 3 是 Vue.js 的下一个主要版本,是对 Vue 2 的重大更新。Vue 3 在性能、开发体验和扩展性等方面都进行了改进和优化,其中一些重要的变化和功能包括:
更好的性能:Vue 3 在虚拟 DOM 的更新算法和静态树提升等方面进行了优化,提升了渲染性能。
Composition API:引入了 Composition API,它提供了一种新的组织组件代码的方式,使得组件逻辑更易于复用和组合,同时也更易于阅读和维护。
更小的包大小:Vue 3 改进了模块化的打包方式,可以减小最终构建产物的大小。
Teleport 和 Suspense:引入了 Teleport 和 Suspense 这两个新的内置组件,用于更好地处理组件之间的逻辑和异步加载。
更好的 TypeScript 支持:Vue 3 对 TypeScript 的支持更加完善,提供了更好的类型推导和类型定义。
更好的调试工具:Vue DevTools 工具得到了更新和改进,提供了更好的调试和性能分析功能。
更好的 TypeScript 支持:Vue 3 对 TypeScript 的支持更加完善,提供了更好的类型推导和类型定义。
更好的响应性系统:Vue 3 的响应性系统进行了重写,使得数据的更新和响应更加高效和可预测。
总的来说,Vue 3 在多个方面都进行了改进和优化,使得开发者能够更轻松地构建出性能优异、易维护和可扩展的 Web 应用程序。
初始化Vue3项目
- 快速上手
- 前提条件:已安装 18.0 或更高版本的 Node.js
npm create vue@latest
cd <your-project-name>
npm install
npm run dev
Vue3基础写法
组件基础
// vue2 options api 选项式 API
<script>
export default {
data() {
return {
age: 18
}
}
}
</script>
// vue3 composition api 组合式 API
<script>
export default {
setup() {
const age = 18
return {
age
}
}
}
</script>
基础(模板语法、指令、渲染)
<template>
<div class="about">
<!-- 基础 指令 -->
<h1>This is an about page</h1>
<div>{{ age }}</div>
<div>{{ ageRef }}</div>
<div v-once>{{ ageRef }}</div>
<div v-memo="[age10]">{{ ageRef }}</div>
<div v-text="text"></div>
<!-- click -->
<div @click="parentClick">
<button v-on:click="btn">点击</button>
<button @click="btn">点击 简写</button>
<button @[event].stop="btn">点击 方法变量</button>
</div>
<!-- bind -->
<div v-bind:id="id">v-bind{{ id }}</div>
<div :id="id">v-bind 简写 {{ id }}</div>
<!-- 样式 -->
<div>
<div class="boxBorder" :class="['boxColor', 'boxSize', isBlue ? 'blue' : null]">样式</div>
</div>
<!-- v-model -->
<div>
<input v-model="input" />
<input v-model="input2" />
<input v-model.lazy="input" />
<input v-model.trim="input" />
<input v-model.number="input" />
<input v-model.trim.number="input" />
{{ input }}
{{ input2 }}
</div>
<!-- 循环 -->
<div>
<div v-for="(item, index) in list" :key="index">{{ index }}-{{ item }}</div>
</div>
</div>
</template>
<!--
<script>
// vue2 options api 选项式 API
// export default {
// data() {
// return {
// age: 18
// }
// }
// }
// vue3 composition api 组合式 API
// export default {
// setup() {
// const age = 18
// return {
// age
// }
// }
// }
</script>
-->
<script setup lang="ts">
import { ref } from 'vue'
const age: number = 18
const ageRef = ref(18)
const age10 = ref(10)
setTimeout(() => {
ageRef.value = 19
}, 1000)
const text: string = '我今天很开心'
const btn = () => {
console.log('btn')
}
const event = 'click'
const parentClick = () => {
console.log('parentClick')
}
const id = '123'
const isBlue = true
// 非响应式
const input = 'input'
//响应式 ref reactive
const input2 = ref('input2')
const list = ['星期一', '星期二', '星期三']
</script>
<!-- npm install -D less -->
<style scoped lang="less">
.boxBorder {
border: 1px solid yellow;
}
.boxColor {
background-color: #eee;
}
.boxSize {
width: 200px;
height: 200px;
}
.blue {
color: blue;
}
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
Vue3响应式数据
tips: 设置-偏好设置-控制台-自定义格式设置工具 点击勾选,重启devtools, 控制台打印美化响应式数据
在Vue 3中,响应式数据的实现与Vue 2有一些不同。在Vue 2中,Vue通过Object.defineProperty()来实现数据的响应式,而在Vue 3中,Vue使用了ES6的Proxy来实现数据的响应式。
在Vue 3中,当你创建一个响应式的数据对象时,Vue会使用Proxy来监听这个数据对象的变化。当数据对象发生变化时,Vue会通知相关的视图进行更新。
即使改变嵌套对象或数组时,变化也会被检测到.。
ref、isRef、shallowRef、 triggerRef、 customRef
<template>
<div class="about">
<div>
{{ header }}
<button @click="change">change</button>
</div>
<div>
{{ footer }}
<button @click="changeFooter">footer change</button>
</div>
<div>
<div>customRef: {{ obj }}</div>
<button @click="changeCustomRef">customRef change</button>
</div>
<div ref="dom">我是dom</div>
</div>
</template>
<script setup lang="ts">
import { ref, isRef, shallowRef, triggerRef, customRef } from 'vue'
const header = ref({ title: 'test' })
const header2 = { title: 'test' }
const footer = shallowRef({ title: 'test' })
const dom = ref<HTMLDivElement>()
const change = () => {
header.value.title = 'test2' //触发渲染
// header.value = { title: 'test2' } // 触发渲染
console.log('header', isRef(header), isRef(header2))
console.log('header2', header)
// document.querySelector('')?.innerText
console.log('dom', dom.value?.innerText)
}
const changeFooter = () => {
footer.value.title = 'test2' // 不会触发渲染
// footer.value = { title: 'test2' } // 触发渲染
// header.value.title = 'test2' // 会带着footer.value.title 一起渲染 不要这样写
triggerRef(footer) // 强制更新,页面渲染
}
function MyRef<T>(value: T) {
return customRef((track, trigger) => {
let timer: any
return {
get() {
track()
return value
},
set(newValue) {
// 防抖 接口异步
clearTimeout(timer)
timer = setTimeout(() => {
console.log('set', newValue)
value = newValue
timer = null
trigger()
}, 3000)
}
}
})
}
const obj = MyRef<string>('CustomRef-test')
const changeCustomRef = () => {
obj.value = 'CustomRef-test2'
}
</script>
<style scoped lang="less">
</style>
reactive
reactive函数来创建一个响应式的数据对象。这个函数接收一个普通的JavaScript对象作为参数,并返回一个响应式代理对象(原始对象的 Proxy,它和原始对象是不相等的),用于监听这个数据对象的变化。
当使用reactive创建的响应式数据对象中的属性发生变化时,Vue会自动检测到这些变化并通知相关的视图进行更新,从而实现数据驱动视图的响应式更新。reactive函数是Vue 3中管理组件状态和数据的重要工具之一。
- 有限的值类型:它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。
- 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:
let state = reactive({ count: 0 }) // 上面的 ({ count: 0 }) 引用将不再被追踪 // (响应性连接已丢失!) state = reactive({ count: 1 })
- 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:
const state = reactive({ count: 0 }) // 当解构时,count 已经与 state.count 断开连接 let { count } = state // 不会影响原始的 state count++ // 该函数接收到的是一个普通的数字 // 并且无法追踪 state.count 的变化 // 我们必须传入整个对象以保持响应性 callSomeFunction(state.count)
ref 和 reactive 区别
ref 支持所有的类型 reactive 引用类型
ref 取值 赋值都要加.value reactive 不需要.value
reactive proxy 不能直接赋值 否则破坏响应式对象
ref 和 reactive 都是 Vue.js 中用于实现响应式数据绑定的方法,但它们之间存在一些重要的区别。
数据类型与包装方式:
ref:主要用于包装简单的响应式数据,如一个普通的 JavaScript 值。当你使用 ref 时,它会对这个值进行包装,使其变得响应式。
reactive:则用于将复杂的 JavaScript 对象(或数组)转换为响应式代理对象。这意味着你可以直接使用对象或数组的属性或元素,而无需通过额外的包装。访问与修改方式:
ref:当使用 ref 包装的响应式数据时,你需要通过 .value 属性来访问和修改该值。例如,如果你有一个通过 ref 创建的响应式变量 count,那么你会这样访问和修改它:count.value。
reactive:对于通过 reactive 创建的响应式对象或数组,你可以直接访问和修改其属性或元素,无需使用 .value。使用场景:
ref 在处理基本类型的数据(如字符串、数字等)时非常有用,尤其是当你需要将这些数据作为响应式引用传递时。
reactive 则更适用于处理复杂的对象或数组结构,尤其是当你需要对对象的属性或数组的元素进行响应式操作时。
reactive、readonly、shallowReactive
<template>
<div class="about">
<div>
{{ form }}
<div>
<input v-model="form.name" />
<input v-model="form.age" />
<button @click="btn">btn-reactive</button>
</div>
</div>
<div>
{{ list }}
<div v-for="(item, index) in list" :key="index">
<li>
{{ item }}
</li>
</div>
<button @click="addList">list change</button>
</div>
<div>
{{ objShallow }}
<button @click="changeObjShallow">shallow change</button>
</div>
</div>
</template>
<script setup lang="ts">
import {
reactive,
readonly,
shallowReactive
} from 'vue'
const form = reactive({
name: 'test',
age: 18
})
const btn = () => {
console.log('form', form)
}
// 数组可以使用push() 或使用对象 把数组作为对象的属性去修改
let list = reactive<number[]>([1, 2, 3])
const addList = () => {
list.push(4)
setTimeout(() => {
let newList = [5, 6, 7]
// list = newList
list.push(...newList)
}, 1000)
}
const read = readonly(form)
// read.name = 'xx' //无法为“name”赋值,因为它是只读属性
console.log('read: ', read)
const objShallow = shallowReactive({ header: { left: { num: 1 } } })
const changeObjShallow = () => {
// objShallow.header.left.num = 2 // 不会触发渲染
setTimeout(() => {
objShallow.header.left = { num: 3 } // 不会触发渲染
form.name = 'shallow' // reactive改变 shallowReactive一起渲染 不建议这样使用
// objShallow.header = { left: { num: 4 } } // 触发渲染
}, 1000)
}
vue文件中不同标签属性
script标签
setup
每个 *.vue 文件最多可以包含一个 <script setup>。(不包括一般的 <script>)
这个脚本块将被预处理为组件的 setup() 函数,这意味着它将为每一个组件实例都执行。<script setup> 中的顶层绑定都将自动暴露给模板。要了解更多细节,请看 <script setup> 的专门文档。
lang
代码块可以使用 lang 这个 attribute 来声明预处理器语言,最常见的用例就是在 <script> 中使用 TypeScript:
<script lang="ts">
// use TypeScript
</script>
style标签
scoped
当 <style> 标签带有 scoped attribute 的时候,它的 CSS 只会影响当前组件的元素,和 Shadow DOM 中的样式封装类似。使用时有一些注意事项,不过好处是不需要任何的 polyfill。它的实现方式是通过 PostCSS 将以下内容:
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
转换为:
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
子组件的根元素
使用 scoped 后,父组件的样式将不会渗透到子组件中。不过,子组件的根节点会同时被父组件的作用域样式和子组件的作用域样式影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。
深度选择器
处于 scoped 样式中的选择器如果想要做更“深度”的选择,也即:影响到子组件,可以使用 :deep() 这个伪类:
<style scoped>
.a :deep(.b) {
/* ... */
}
</style>
上面的代码会被编译成:
.a[data-v-f3f3eg9] .b {
/* ... */
}
:::tip 通过 v-html 创建的 DOM 内容不会被作用域样式影响,但你仍然可以使用深度选择器来设置其样式。 :::
插槽选择器
默认情况下,作用域样式不会影响到 <slot/> 渲染出来的内容,因为它们被认为是父组件所持有并传递进来的。使用 :slotted 伪类以明确地将插槽内容作为选择器的目标:
<style scoped>
:slotted(div) {
color: red;
}
</style>
全局选择器
如果想让其中一个样式规则应用到全局,比起另外创建一个 <style>,可以使用 :global 伪类来实现 (看下面的代码):
<style scoped>
:global(.red) {
color: red;
}
</style>
混合使用局部与全局样式
你也可以在同一个组件中同时包含作用域样式和非作用域样式:
<style>
/* 全局样式 */
</style>
<style scoped>
/* 局部样式 */
</style>
作用域样式须知
作用域样式并没有消除对 class 的需求。由于浏览器渲染各种各样 CSS 选择器的方式,p { color: red } 结合作用域样式使用时 (即当与 attribute 选择器组合的时候) 会慢很多倍。如果你使用 class 或者 id 来替代,例如 .example { color: red },那你几乎就可以避免性能的损失。
小心递归组件中的后代选择器!对于一个使用了 .a .b 选择器的样式规则来说,如果匹配到 .a 的元素包含了一个递归的子组件,那么所有的在那个子组件中的 .b 都会匹配到这条样式规则。
less
lang 在任意块上都能使用,比如我们可以在 <style> 标签中使用 Sass
<style lang="scss">
$primary-color: #333;
body {
color: $primary-color;
}
</style>
注意对不同预处理器的集成会根据你所使用的工具链而有所不同,具体细节请查看相应的工具链文档来确认