Vue3简介
渐进式框架
- 无需构建步骤,渐进式增强静态的 HTML
- 在任何页面中作为 Web Components 嵌入
- 单页应用 (SPA)
- 全栈 / 服务端渲染 (SSR)
- Jamstack / 静态站点生成 (SSG)
- 开发桌面端、移动端、WebGL,甚至是命令行终端中的界面
API 风格
Vue 的组件可以按两种不同的风格书写:选项式 API 和组合式 API。
vue2中的风格为选项式api
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件监听器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
组合式api在vue3中应用更为广泛,需要注意的是在一个组件中可以有多个script标签,但是只能有一个标签有setup参数
<template>
<div>
<div>{{choose}}</div>
<button @click="changeFood">变换</button>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
let food = [{name:'自助餐',num:1},{name:'快餐',num:2},{name:'石锅饭',num:3},{name:'烤鸭',num:4},{name:'汉堡',num:5}]
let choose = ref<string>('')
let count = 0;
const changeFood = () => {
console.log(food)
food.forEach((foo) => {
count = Math.round(Math.random()*10)
if (count == foo.num) {
choose.value = foo.name
console.log(choose)
}
})
}
</script>
<style lang="less" scoped>
</style>
模板语法和插值语法
和vue2类似,可以查阅这个地址:vue2学习
虚拟dom
vue提供了一种虚拟dom的方法减少性能消耗,其本质通过js生成一个ast节点树,在vue进行更改时,通过比对前后vnode节点确定更改哪些节点,而不是全局节点刷新,提高了性能。
ref全家桶
ref
const message = ref<string>("jth")
let notRef:string = "message"
console.log(isRef(message))
console.log(isRef(notRef))
message.value = liu
ref
interface Ref<T> {
value: T
}
更改ref定义的值需要加上.value
定义ref后可以实现将定义的数据实现vue2中的响应式变化。
isRef:通过isRef判断是否为ref对象
shallowRef
创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
例子1:由于修改的属性是非响应式的,所以不会改变
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "jth"
})
const changeMsg = () => {
message.value.name = 'liu'
}
</script>
<style>
</style>
例子2:
动画
transation
单元素/组件的过渡
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
transition-group
- 在处理多个元素位置更新时,使用 组件,通过 FLIP 技术来提高性能。
<button @click="add">添加</button>
<button @click="pop">删除</button>
<transition-group
leave-active-class="animate__animated animate__backOutDown"
enter-active-class="animate__animated animate__bounceIn">
<div v-for="item in list" :key="item">{{item}} </div>
</transition-group>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import gsap from 'gsap'
import 'animate.css'
const list = reactive<number[]>([1,4,3,2,3,5,4,3])
const add = () => {
list.push(list.length+1);
}
const pop = () => {
list.pop();
}
</script>
通过animite.css和vue的过渡方法可以实现动态效果
provide/reject依赖注入
function provide<T>(key: InjectionKey<T> | string, value: T): void
provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
provide() 必须在组件的 setup() 阶段同步调用
<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 提供静态值
provide('foo', 'bar')
// 提供响应式的值
const count = ref(0)
provide('count', count)
// 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script>
inject()
// 没有默认值
function inject<T>(key: InjectionKey<T> | string): T | undefined
// 带有默认值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
// 使用工厂函数
function inject<T>(
key: InjectionKey<T> | string,
defaultValue: () => T,
treatDefaultAsFactory: true
): T
- 注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。
- 第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key
提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回undefined,除非提供了一个默认值。 - 第二个参数是可选的,即在没有匹配到 key
时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。 - 与注册生命周期钩子的 API 类似,inject() 必须在组件的 setup() 阶段同步调用。
- 当使用 TypeScript 时,key 可以是一个类型为 InjectionKey 的 symbol。InjectionKey 是一个
Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide() 和 inject() 之间值的类型。
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 注入值的默认方式
const foo = inject('foo')
// 注入响应式的值
const count = inject('count')
// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)
// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')
// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('foo', () => new Map())
// 注入时为了表明提供的默认值是个函数,需要传入第三个参数
const fn = inject('function', () => {}, false)
</script>
example
<template>
<div>
<h1>grandFather</h1>
<label>
<input type="radio" v-model="colorVal" value="red">
红色
</label>
<label>
<input type="radio" v-model="colorVal" value="pink">
粉色
</label>
<label>
<input type="radio" v-model="colorVal" value="yellow">
黄色
</label>
</div>
<div class="box">
</div>
<hr>
<ProvideA></ProvideA>
</template>
<script setup lang="ts">
import {ref, reactive, provide, readonly} from 'vue';
import ProvideA from "./provideA.vue";
let colorVal = ref<string>("red")
provide("color",readonly(colorVal))//使得子组件无法修改
</script>
<style scoped>
.box{
height: 50px;
width: 50px;
border: 1px solid #ccc;
background-color: v-bind(colorVal);
}
</style>
祖父组件,使用provide方法进行注入,将colorval传递
tips:v-bind在vue3中可以在css中使用。
<template>
<div>
<h1>father</h1>
<div class="box">
</div>
<hr>
<ProvideB></ProvideB>
</div>
</template>
<script setup lang="ts">
import { ref, reactive,inject } from 'vue';
import ProvideB from './provideB.vue'
import type {Ref} from "vue";//引入ref类型
let color = inject<Ref<string>>('color')//可以使用ref类型,inject可以接受泛型
</script>
<style scoped>
.box{
height: 50px;
width: 50px;
border: 1px solid #ccc;
background-color: v-bind(color);
}
</style>
子组件可以通过inject接受父组件传来的值,注意参数要和provide的第一个参数的名称相同。
<template>
<div>
<h1>son</h1>
<button @click="changeColor">改变为黄色</button>
<div class="box">
</div>
</div>
</template>
<script setup lang="ts">
import {ref, reactive, inject} from 'vue';
import type {Ref} from "vue";//引入ref类型
let color = inject<Ref<string>>('color')//默认值也可以(非空断言)
// let color = inject<Ref<string>>('color',ref('red'))//默认值也可以(非空断言)
const changeColor = () => {
// color?.value = yellow 可选链操作符无法赋值,可能为undefined
color!.value = "yellow"//非空断言
}
</script>
<style scoped>
.box {
height: 50px;
width: 50px;
border: 1px solid #ccc;
background-color: v-bind(color);
}
</style>
也可以通过子组件改变父组件的值,但是不符合设计模式中的开闭原则,我们可以加readonly将父组件的值包裹,当子组件试图改变父组件的值时,会进行进行警告
兄弟组件间传参
elder.vue
<template>
<div class="a">
<button @click="emity">派发一个事件</button>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
const emit = defineEmits(['toYounger'])
let flag:boolean = false
const emity = () => {
flag = !flag
emit('toYounger',flag)
}
</script>
app.vue
<template>
<div>
<Elder @toYounger="getFlag"></Elder>
<Younger :flag="Flag"></Younger>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import Elder from "./elder.vue"
import Younger from './younger.vue'
let Flag = ref(false)
const getFlag = (params:boolean) => {
Flag.value = params
}
</script>
<style scoped less>
</style>
younger.vue
<template>
<div class="b">
<h1>b</h1>
{{flag}}
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
type Props = {
flag : boolean
}
defineProps<Props>()
</script>
兄弟之间的传参可以借助自定义事件进行传递,并借助app.vue父组件进行传递。
bus全局事件总线
Bus.ts
type BusClass = {
emit:(name:string) => void
on:(name:string,calback:Function) => void
}
type PramsKey = string | number | symbol
type List = {
[key:PramsKey]:Array<Function>
}
class Bus implements BusClass{
list:List
constructor() {
this.list = {}
}
emit(name:string,...args:Array<any>){
let eventName:Array<Function> = this.list[name]
eventName.forEach(fn=>{
fn.apply(this,args)
})
}
on(name:string,callback:Function) {
let fn: Array<Function> = this.list[name] || [];
fn.push(callback)
this.list[name] = fn
}
}
export default new Bus()
使用bus.ts后,可以不再使用app.vue父亲组件通信
younger.vue
<template>
<div class="b">
<h1>b</h1>
{{Flag}}
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import Bus from '../../Bus'
// type Props = {
// flag : boolean
// }
//
// defineProps<Props>()
let Flag = ref(false)
Bus.on('toYounger',(flag:boolean)=>{
Flag.value = flag
})
</script>
elder
<template>
<div class="a">
<button @click="emity">派发一个事件</button>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
// const emit = defineEmits(['toYounger'])
import Bus from '../../Bus'
let flag:boolean = false
const emity = () => {
flag = !flag
// emit('toYounger',flag)
Bus.emit('toYounger',flag)
}
</script>
mitt
npm install mitt -s
自动引入(vue3小彩蛋)
npm i -D unplugin-auto-import
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'//vite版本
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),AutoImport({
imports:['vue'],//vue注入
dts:'src/auto-import.d.ts'//生成声明文件
})]
})
可以直接使用vue的相关api,无需使用import语句
v-mode详解(vue3版本)
v-mode在vue3中进行了较大更新
v-model本质为一个语法糖,通过props和emit组合完成的
- v-model 默认绑定的属性名为:modelValue
- v-model 默认绑定的事件名为:update:modelValue
- 支持自定义修饰符 Modifiers
- v-bind的sync修饰符和组件的model选项已经移除
son
<template>
<div>
<div class="son">
{{modelValue}}
<div>内容<input type="text" :value="textValue" @input="change"></div>
<button @click="toFather" >传给父亲</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
const props = defineProps<{
modelValue:string,
textValue:string,
//如果使用默认值的话为 modelModifiers
textValueModifiers?:{
jth:boolean
}
}>()
const emit = defineEmits(['update:modelValue','update:textValue'])
const toFather = () => {
emit('update:modelValue','fromSon')
}
const change = (e:Event) => {
const target = e.target as HTMLInputElement
emit('update:textValue',props?.textValueModifiers ? target.value + 'hantai' : target.value)
}
</script>
<style scoped>
</style>
father
<template>
<div>
<div class="module">
<div>父亲</div>
<h1>{{data}}</h1>
<h1>{{text}}</h1>
<div>===========================================</div>
<!-- 多个v-model-->
<Son v-model:textValue.jth="text" v-model="data"></Son>
</div>
</div>
</template>
<script setup lang="ts">
import Son from './son.vue'
let data = ref("fromFather")
let text = ref('fromFatherText')
</script>
<style scoped>
</style>
v-model:textValue.jth=“text” 在之前有.trim(去除空格).lazy懒加载,这些vue2的属性,但vue3允许我们自定义修饰符,接受数据的话可以看代码。
自定义指令(directive)
- created 元素初始化的时候
- beforeMount 指令绑定到元素后调用 只调用一次
- mounted 元素插入父级dom调用
- beforeUpdate 元素被更新之前调用
- update 这个周期方法被移除 改用updated
- beforeUnmount 在元素被移除前调用
- unmounted 指令被移除后调用 只调用一次
在vue2中为 bind inserted update componentUpdated unbind
<template>
<div>
<button>switch</button>
<div v-move:aaa="{jth:'red'}">hahah</div>
</div>
</template>
<script setup lang="ts">
import {Directive, DirectiveBinding, ref} from "vue";
let flag = ref<boolean>(true);
//定义一个泛型
type Dir= {
jth:string
}
const vMove:Directive = {
created () {
console.log('created')
},
beforeMount (...args:any) {
console.log(args)
console.log("before mounted")
},
mounted (el:HTMLElement,dir:DirectiveBinding<Dir>) {//<Dir>进行类型提示,因为如果没有提示的话取不到jth
el.style.background = dir.value.jth
console.log('mounted')
},
beforeUpdate (){
},
updated(){
},
beforeUnmount(){
},
unmounted(){
}
}
</script>
0:节点
1:instance:当前组件实例
value:传入的参数
arg:绑定的值
dir:生命周期函数
2:虚拟dom
3:上一个虚拟dom preVnode
函数简写
const vMo = (el:HTMLElement,binding:DirectiveBinding<Dir>) => {
}
自定义hooks
vue2中有mixin封装公用的数据和函数
但是mixin的生命周期比组件要快,导致组件的数据核函数可能会覆盖掉mixin的数据。变量来源也不确定,不好查阅
手写hooks
目的:生成图片的base64格式
import {onMounted} from "vue";
type Options = {
el:string
}
export default function (options:Options):Promise<{baseUrl:string}>{
return new Promise((resolve) => { //返回promise对象
onMounted(()=>{
let img : HTMLImageElement = document.querySelector(options.el) as HTMLImageElement
console.log(img,"===========")
img.onload = () => { //onload防止未加载完成报错
resolve({
baseUrl: base64(img)
})
}
})
const base64 = (el:HTMLImageElement) => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = el.width
canvas.height = el.height
ctx?.drawImage(el,0,0,canvas.width,canvas.height)
return canvas.toDataURL("img/jpg")
}
})
}
<template>
<div >
<div>jth</div>
<img id="img" height="722" width="1298" src="../../assets/img/15.jpg">
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import useBase64 from '../../hooks'
useBase64({el:'#img'}).then(res=>{
console.log(res)
})
</script>
vue3定义全局函数和变量
vue3中没有prototype属性,使用app.config.globalProperties代替
vue2
vue.prototype.$http = () => {}
vue3
const app = createApp(App)
app.config.globalProperties.$http = () => {}
vue3中移除了过滤器,我们可以自己写一个
const app = createApp(App)
app.config.globalProperties.$filters = {
format<T> (str:T){
return `jth-${str}`
}
}
type Filter = {
format<T>(str:T):string
}
// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型
declare module 'vue' {
export interface ComponentCustomProperties{
$Bus : typeof Mit
$filters:Filter
env:string
}
}
使用
第一种
<div>{{$filters?.format('的飞机')}}</div>
第二种
console.log(App?.proxy?.$env)
vue3编写插件
插件编写
<template>
<div>
<div v-show="isShow">
<div class="loading">loading</div>
</div>
</div>
</template>
<script setup lang="ts">
import {ref, reactive, defineExpose} from 'vue';
const isShow = ref<boolean>(false)
const show = () => {
isShow.value = true
}
const hide = () => {
isShow.value = false
}
//通过expose抛出相关函数和变量
defineExpose({
hide,
show,
isShow
})
</script>
- defineExpose是由于vue不允许操作setupState这个属性,但是可以使用expose获取
import type {App, VNode} from 'vue'
import {createVNode,render} from "vue";
import Loading from './index.vue'
export default {
//对象形式
install(app:App) {
const Vnode:VNode = createVNode(Loading)
//component 为 null ,使用render函数挂载
render(Vnode,document.body)
app.config.globalProperties.$loading = {
show:Vnode.component?.exposed?.show,
hide:Vnode.component?.exposed?.hide
}
// app.config.globalProperties.show()
console.log(app,Vnode.component?.exposed)//通过Vnode.component?.exposed获取相关函数变量
}
}
- 引入Vnode为了为了使节点变为虚拟dom方便操作它上面的属性。
//编写ts智能提示和报错
type Lod = {
show: () => void,
hide: () => void
}
declare module '@vue/runtime-core'{
export interface ComponentCustomProperties{
$loading:Lod
}
}
app.use(loading);
- 全局注册
const instance = getCurrentInstance()
instance?.proxy?.$loading.show()
setTimeout(()=>{
instance?.proxy?.$loading.hide()
},5000)
使用插件
use函数手写
MyUse.ts
import type{App} from "vue";
import {app} from "./main"
interface Use {
//类型约束,插件必须有install函数
install:(app:App,...options:any[]) =>void
}
const installList = new Set()
export function MyUse<T extends Use>(plugin:T,...options:any[]){
//缓存结构
if(installList.has(plugin)){
console.log("this plugin has registed",plugin)
}else{
plugin.install(app,...options)
installList.add(plugin)
}
}
//导出use使用
export const app = createApp(App)
MyUse(loading)
ui库
样式穿透:deep()
scoped的原理
vue中的scoped 通过在DOM结构以及css样式上加唯一不重复的标记:data-v-hash的方式,以保证唯一(而这个工作是由过PostCSS转译实现的),达到样式私有化模块化的目的。
总结一下scoped三条渲染规则:
- 给HTML的DOM节点加一个不重复data属性(形如:data-v-123)来表示他的唯一性
- 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-123])来私有化样式
- 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性
<style scoped lang = less>
.ipt{
:deep(sonComponent){
}
}
</style>
使用深度选择器可以让子组件使用该样式
新增css特性
插槽选择器
<div>
<slot></slot>
</div>
:slotted(.a){
color:red;
}
这样可以让样式在子组件中的插槽生效,写在子元素中
全局使用
:global(div){
}
动态css
/*对象写法*/
color: v-bind('style.color');
css module
<div :class="[$style.loading,$style.border]"></div>
<style module="jth">
这种形式可以自定义,不用写$style,只要jth.loading就可以了
vue3集成TailwindCss
下载tailwind
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
创建配置文件
npx tailwindcss init -p
修改配置文件 tailwind.config.js
2.6版本
module.exports = {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
3.0版本
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
event loop and nextTick
js执行机制
js为单线程,但是随着html5的到来js也支持了多线程webWorker,但是不允许操作dom
同步任务
代码从上到下一次执行
异步任务
- 宏任务
script整体代码
setTimeOut,
setInterVal
ajax - 微任务
promise.then .catch .finally 、
mutationObserver
所有的同步任务都是在主进程执行的形成一个执行栈,主线程之外,还存在一个"任务队列",异步任务执行队列中先执行宏任务,然后清空当次宏任务中的所有微任务,然后进行下一个tick如此形成循环。
nextTick 就是创建一个异步任务,那么它自然要等到同步任务执行完成后才执行。
一个小练习
setTimeout(()=>{
instance?.proxy?.$loading.hide()
},5000)
async function Prom(){
console.log("Y");
await Promise.resolve()
console.log("X")
}
setTimeout(() => {
console.log(1)
Promise.resolve().then(()=> {
console.log(2)
})
})
setTimeout(() => {
console.log(3)
Promise.resolve().then(()=> {
console.log(4)
})
})
Promise.resolve().then(()=> {
console.log(5)
})
Promise.resolve().then(()=> {
console.log(6)
})
Promise.resolve().then(()=> {
console.log(7)
})
Prom()
console.log(0);
nextTick用法
const message = ref<string>("jth")
const div = ref<HTMLElement>()
const change = async () => {
message.value = "jth is haitai"
await nextTick()
console.log(div.value?.innerText)
}