Typescript的支持
全局接口的抽取
1.src下定义types文件夹命名xx.d.ts
2.建立Person接口person.d.ts
interface personInterface{
name:string
age:number
}
3.组件中直接使用
<script setup lang="ts">
const props=defineProps<{person:personInterface[]}>()
</script>
4.如果不是在src下或src文件下的xx.d.ts文件则需要在tsconfig.json中配置
{
{
...
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], //配置全局目录
"references": [{ "path": "./tsconfig.node.json" }]
}
类型增强
1.使用环境:全局定义的数据,函数在vue组件中直接访问报错
2.index.html中定义数据
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<script>
const global=1
</script>
<body>
...
</body>
</html>
3.定义类型增强
// common.d.ts
declare const global:string;
4.组件中直接读取
<script setup lang="ts">
console.log(global)
</script>
第三方库类型声明
1.安装一个库
2.安装库的ts类型声明@types/xxxx
props组件通讯TS
父组件
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld msg="1"/>
</template>
子组件
<script setup lang="ts">
interface msgIterface{
msg:string
}
const props = withDefaults(defineProps<msgIterface>(),{
msg:'默认值'
})
console.log(props.msg)
</script>
emit组件通讯TS
父组件
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
const getuser=(a:number)=>{
console.log(a)
}
</script>
<template>
<HelloWorld @getuser="getuser"/>
</template>
<style scoped>
</style>
子组件
<script setup lang="ts">
const emit = defineEmits<{(e: 'getuser', id: number): void}>()
// (e: 事件名, 键名:类型): void
function buttonClick() {
emit('getuser',1)
}
</script>
<template>
<button @click="buttonClick">传输</button>
</template>
<style scoped>
</style>
依赖注入类型推断
父组件(祖先组件)
<template>
<div class="App">
<button>我是App</button>
<A></A>
</div>
</template>
<script setup lang="ts">
import { provide, ref } from 'vue'
import A from './components/Acom.vue'
let flag = ref<number>(1)
provide('flag', flag)
</script>
子组件(后代组件)
<template>
<div>
我是B
<div>{{ flag }}</div>
<button @click="flag++">+1</button>
</div>
</template>
<script setup lang="ts">
import { inject, ref , type Ref} from 'vue'
// 注入值,默认值(让其可以进行类型推断)
const flag<Ref<number>> = inject('flag', ref(1))
</script>
定义全局函数和全局函数的类型支持
import { createApp } from 'vue'
...
const app = createApp(App)
type Fileter = {
format: <T>(str: T) => string
}
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$filters: Fileter
$env: string
}
}
// 全局函数
app.config.globalProperties.$filters = {
format<T>(str: T): string {
return `真${str}`
}
}
// 全局变量
app.config.globalProperties.$env = '全局变量'
...
脚手架Vite
1.基本使用
1.创建vue3的项目yarn create vite || npm init vite@latest
2.安装插件Volar
配置项目路径
1.tsconfig.json中添加
// 让ts可以识别这个路径
{
"compilerOptions": {
...
"baseUrl": "./",
"paths": {
"@/*":[
"src/*"
]
}
},
...
}
2.vite.config.ts中添加
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve:{
alias:{
"@":join(__dirname,'src')
}
}
})
eslint和prettierrc的配置
1..prettierrc.json
{
"arrowParens": "always",
"bracketSameLine": true,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 120,
"proseWrap": "never",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"singleAttributePerLine": false
}
2..eslintrc.cjs
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier'
],
rules: {
'vue/multi-word-component-names': 'off', // 关闭命名
semi: 0 // 结尾无分号
},
parserOptions: {
ecmaVersion: 'latest'
}
}
vite环境变量的配置
1.vite的环境在import中
<script setup lang="ts">
console.log(import.meta.env)
</script>
2.创建.env.development .env.production
3.package.json中配置运行生产环境,会自动注入
{
...
"scripts": {
"dev": "vite --mode development",
...
},
}
4.vite.config.ts中读取环境变量
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import unocss from 'unocss/vite'
import vue from '@vitejs/plugin-vue'
import { presetIcons, presetAttributify, presetUno } from 'unocss'
// https://vitejs.dev/config/
export default ({ mode }: any) => {
// 读取环境变量
console.log(loadEnv(mode, process.cwd()))
return defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
}
5.找不到模块“./App.vue”或其相应的类型声明
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
6.类型“ImportMeta”上不存在属性“env”
// tsconfig.json
{
...
"compilerOptions": {
...
"types": [ "vite/client" ],
},
...
}
自定义指令
全局自定义指令
// mian.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
const app=createApp(App)
app.directive('focus',{
mounted(el){
el.focus()
}
})
app.mount('#app')
使用自定义指令
<template>
<input type="text" v-model="value" v-focus>
</template>
局部自定义指令
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
自定义拖拽指令
<script setup lang="ts">
import type { Directive } from 'vue'
const vMove: Directive = (el: HTMLElement) => {
const move = (e: MouseEvent) => {
console.log(e)
el.style.left = e.clientX + 'px'
el.style.top = e.clientY + 'px'
}
// 鼠标按下
el.addEventListener('mousedown', () => {
// 鼠标按下拖拽
document.addEventListener('mousemove', move)
// 鼠标松开
document.addEventListener('mouseup', () => {
// 清除事件
document.removeEventListener('mousemove', move)
})
})
}
</script>
<template>
<!-- 自定义指令,参数,修饰符 -->
<div
v-move
style="
background-color: red;
width: 200px;
height: 200px;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
"
>
<div style="background-color: black; width: 200px; color: white">
自定义指令
</div>
</div>
</template>
内置组件
Teleport组件
可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去
父组件:
<!-- 遮罩层组件传送到body下 -->
<script setup lang="ts">
import Acom from './components/Acom.vue'
</script>
<template>
<div class="app"></div>
<Acom/>
</template>
<style scoped >
.app{
width: 200px;
height: 200px;
background-color: pink;
}
</style>
子组件
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<button @click="open=true">显示遮罩层</button>
<!-- 传送到body -->
<Teleport to="body">
<div class="cover" v-show="open">
<span @click="open=false"> X</span>
</div>
</Teleport>
</template>
<style scoped>
.cover {
position: absolute;
z-index:2;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0,0,0,0.5);
}
</style>
Transition组件
1.非命名动画
<script setup lang="ts">
import { ref } from 'vue';
const show=ref(true)
</script>
<template>
<button @click="show=!show">显示/隐藏</button>
<Transition>
<div class="div" v-if="show"></div>
</Transition>
</template>
<style scoped>
.div{
background-color: pink;
width: 200px;
height: 200px;
margin: auto;
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
2.命名动画
<script setup lang="ts">
import { ref } from 'vue';
const show=ref(true)
</script>
<template>
<button @click="show=!show">显示/隐藏</button>
<Transition name="fade">
<div class="div" v-if="show"></div>
</Transition>
</template>
<style scoped>
.div{
background-color: pink;
width: 200px;
height: 200px;
margin: auto;
}
.fade-enter-active {
transition: all 0.3s ease-out;
}
.fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.fade-enter-from,
.fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>
3.过度动画
<Transition mode="out-in">
...
</Transition>
4.结合第三方库Animate.css
<!-- yarn add animate.css -->
<script setup lang="ts">
import { ref } from 'vue'
import 'animate.css'
import Acom from './components/Acom.vue'
const show = ref(true)
</script>
<template>
<transition
leave-active-class="animate__animated animate__fadeOut"
enter-active-class="animate__animated animate__fadeIn"
>
<Acom v-if="show"></Acom>
</transition>
<button @click="show = !show">显示/隐藏</button>
</template>
<style scoped lang="less"></style>
4.transition 生命周期
<script setup lang="ts">
import { ref } from 'vue'
import 'animate.css'
import Acom from './components/Acom.vue'
const show = ref(true)
const beforeEnter = () => {
console.log('进入之前')
}
const enter = (_, done: Function) => {
console.log('过度曲线')
setTimeout(() => {
done()
}, 3000)
}
const afterEnter = () => {
console.log('过度完成')
}
const enterCancelled = () => {
console.log('进入效果被打断')
}
const beforeLeave = () => {
console.log('离开之前')
}
const leave = (_, done: Function) => {
setTimeout(() => {
done()
}, 3000)
console.log('过度曲线')
}
const afterLeave = () => {
console.log('离开之后')
}
const leaveCancelled = () => {
console.log('离开效果被打断')
}
</script>
<template>
<transition
leave-active-class="animate__animated animate__fadeOut"
enter-active-class="animate__animated animate__fadeIn"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
<Acom v-if="show"></Acom>
</transition>
<button @click="show = !show">显示/隐藏</button>
</template>
5.生命周期结合第三方库gsap.js
<!-- yarn add gsap -->
<script setup lang="ts">
import { ref } from 'vue'
import Acom from './components/Acom.vue'
import gsap from 'gsap'
const show = ref(true)
// 进入之前
const beforeEnter = (el: Element) => {
gsap.set(el, {
width: 0,
height: 0
})
}
// 进入过度动画
const enter = (el: Element, done: gsap.Callback) => {
gsap.to(el, {
width: 200,
height: 200,
onComplete: done
})
}
// 离开之前
const beforeLeave = (el: Element) => {
gsap.set(el, {
width: 200,
height: 200
})
}
// 进入过度动画
const leave = (el: Element, done: gsap.Callback) => {
gsap.to(el, {
width: 0,
height: 0,
onComplete: done
})
}
</script>
<template>
<transition
@before-enter="beforeEnter"
@enter="enter"
@before-leave="beforeLeave"
@leave="leave"
>
<Acom v-if="show"></Acom>
</transition>
<button @click="show = !show">显示/隐藏</button>
</template>
6.初始化动画
<script setup lang="ts">
import { ref } from 'vue'
import Acom from './components/Acom.vue'
const show = ref(true)
</script>
<template>
<transition
appear-from-class="from"
appear-active-class="active"
appear-to-class="to"
appear
>
<Acom v-if="show"></Acom>
</transition>
<button @click="show = !show">显示/隐藏</button>
</template>
<style scoped>
.from {
/* 初始化之前 */
width: 0;
height: 0;
}
.active {
/* 过度动画 */
transition: all 2s ease;
}
.to {
/* 初始化完成 */
width: 200px;
height: 200px;
}
</style>
7.初始化动画结合Animate.css
<script setup lang="ts">
import { ref } from 'vue'
import Acom from './components/Acom.vue'
import 'animate.css'
const show = ref(true)
</script>
<template>
<transition appear-active-class="animate__animated animate__heartBeat" appear>
<Acom v-if="show"></Acom>
</transition>
<button @click="show = !show">显示/隐藏</button>
</template>
<style scoped></style>
transition-group过度列表
1.Transition组件无法对v-for的列表进行渲染
2.transition-group的tag属性
<!-- tag属性可以让transition-group多加一层节点元素 -->
<template>
<div class="wraps">
<transition-group tag="session">
<!-- 使用transition-group渲染的组件要有key-->
<div class="item" v-for="item in 5" :key="item">{{ item }}</div>
</transition-group>
</div>
</template>
3.添加列表时的动画效果
<script setup lang="ts">
import { ref } from 'vue'
import 'animate.css'
const num = ref(5)
</script>
<template>
<div class="wraps">
<transition-group
leave-active-class="animate__animated animate__fadeOut"
enter-active-class="animate__animated animate__fadeIn"
>
<!-- 使用transition-group渲染的组件要有key-->
<div class="item" v-for="item in num" :key="item">{{ item }}</div>
</transition-group>
</div>
<button @click="num++">添加</button>
<button @click="num--">删除</button>
</template>
<style scoped lang="less">
.wraps {
display: flex;
flex-wrap: wrap;
word-break: break-all;
border: 1px solid #ccc;
.item {
margin: 10px;
}
}
</style>
4.平移动画move-class
<script setup lang="ts">
import { ref } from 'vue'
import _ from 'lodash'
// 建立9x9数组
let list = ref(
Array.apply(null, { length: 81 } as number[]).map((_, index) => {
return {
id: index,
number: (index % 9) + 1
}
})
)
// 打乱数组
const random = () => {
list.value = _.shuffle(list.value)
}
console.log(list)
</script>
<template>
<div>
<button @click="random">打乱</button>
<transition-group tag="div" class="wraps" move-class="move">
<div v-for="item in list" :key="item.id" class="item">
{{ item.number }}
</div>
</transition-group>
</div>
</template>
<style scoped lang="less">
.wraps {
display: flex;
flex-wrap: wrap; // 换行
width: calc(25px * 10 + 9px);
.item {
width: 25px;
height: 25px;
border: 1px solid #ccc;
text-align: center;
}
}
.move {
transition: all 1s;
}
</style>
5.状态过度(数字过度颜色过度)
<script setup lang="ts">
import { reactive, watch } from 'vue'
import gsap from 'gsap'
const num = reactive({
current: 0,
tweenedNumber: 0
})
watch(
() => num.current,
newVal => {
gsap.to(num, {
duration: 1, // 过度时间
tweenedNumber: newVal
})
}
)
</script>
<template>
<div>
<input type="text" v-model="num.current" step="20" />
<div>
<!-- 去掉小数点 -->
{{ num.tweenedNumber.toFixed(0) }}
</div>
</div>
</template>
<style scoped lang="less"></style>
keep-alive组件
1.开启keep-alive 生命周期的变化
初次进入时: onMounted-> onActivated
退出后触发: deactivated
2.缓存数据
<script setup lang="ts">
import { ref } from 'vue'
import Acom from './components/Acom.vue'
const show = ref(true)
</script>
<template>
<keep-alive>
<Acom v-if="show"></Acom>
</keep-alive>
<button @click="show = !show">显示/隐藏</button>
</template>
3.include属性和exclude属性
<!-- 注意组件一定要命名才可以使用include -->
<script setup lang="ts">
import { ref } from 'vue'
import Acom from './components/Acom.vue'
import Bcom from './components/Bcom.vue'
const show = ref(true)
</script>
<template>
<keep-alive :include="['Acom']" :exclude="['Bcom']">
<Acom v-if="show"></Acom>
<Bcom v-else></Bcom>
</keep-alive>
<button @click="show = !show">显示/隐藏</button>
</template>
<style scoped lang="less"></style>
普通组件
1.配置全局组件
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import Acom from './components/Acom.vue'
import './assets/main.css'
const app = createApp(App)
app.use(createPinia())
app.component('Acom', Acom)
app.mount('#app')
2.使用组件
<template>
<div>
<Acom></Acom>
</div>
</template>
异步组件
1.子组件中发送了请求变成异步
<script setup lang="ts">
interface ResItf {
code: number
data: { a: number; b: number }[]
message: string
}
let p: Promise<ResItf> = new Promise(resolve => {
setTimeout(() => {}, 3000)
resolve({
code: 0,
data: [
{ a: 1, b: 2 },
{ a: 11, b: 22 }
],
message: ''
})
})
const a = await p
console.log(a)
</script>
<template>
<div>异步组件</div>
<div>异步组件</div>
<div>异步组件</div>
</template>
2.父组件异步调用组件
<script setup lang="ts">
// 异步组件不能这样引入
// import Acom from './components/Acom.vue'
import { defineAsyncComponent } from 'vue'
const Acom = defineAsyncComponent(() => import('./components/Acom.vue'))
</script>
<template>
<div>
<Suspense>
<template #default>
<Acom></Acom>
</template>
<template #fallback> 加载中。。。 </template>
</Suspense>
</div>
</template>
<style scoped lang="less"></style>
常用的CSS的功能
1.样式穿透
<style scoped lang="less">
:deep(input) {
color: red;
}
</style>
2.插槽选择器
<template>
<div>
<slot name="nums" :nums="['1', '2', '3']"> </slot>
</div>
</template>
<style scoped lang="less">
:slotted(.li) {
color: red;
}
</style>
3.全局选择器
<script setup lang="ts"></script>
<template>
<div>
<slot name="nums" :nums="['1', '2', '3']"> </slot>
</div>
</template>
<style scoped lang="less">
:global(.li) {
color: red;
}
</style>
4.动态CSS
<script setup lang="ts">
import { reactive } from 'vue'
const style = reactive({
color: 'red'
})
setTimeout(() => {
style.color = 'blue'
}, 3000)
</script>
<template>
<div class="div">动态css</div>
</template>
<style scoped lang="less">
.div {
color: v-bind('style.color');
}
</style>
移动端适配
第一种适配方案
1.安装依赖yarn add amfe-flexible postcss postcss-pxtorem@5.1.1
2.main.ts引入amfe-flexibleimport "amfe-flexible"
3.根目录下创建postcss.config.js文件并配置
module.exports = {
plugins: {
'postcss-pxtorem': {
// 能够把所有元素的px单位转成Rem
// rootValue: 转换px的基准值。
// 编码时, 一个元素宽是75px,则换成rem之后就是2rem
rootValue: 37.5,
propList: ['*']
}
}
}
第二种适配方案
1.安装依赖yarn add postcss-px-to-viewport -D
2.vite.config.ts内置postcss.config.js中修改配置
import { fileURLToPath, URL } from 'node:url'
import pxtoViewPort from 'postcss-px-to-viewport'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
css: {
postcss: {
plugins: [
// postcss-px-to-viewport的配置
pxtoViewPort({
unitToConvert: 'px', // 要转化的单位
viewportWidth: 750, // UI设计稿的宽度
unitPrecision: 6, // 转换后的精度,即小数点位数
propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
selectorBlackList: ['ignore-'], // 指定不转换为视窗单位的类名,
minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
replace: true, // 是否转换后直接更换属性值
landscape: false // 是否处理横屏情况
})
]
}
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
3.创建postcss-px-to-viewport.d.ts的声明文件
declare module 'postcss-px-to-viewport' {
type Options = {
unitToConvert: 'px' | 'rem' | 'cm' | 'em'
viewportWidth: number
viewportHeight: number // not now used; TODO: need for different units and math for different properties
unitPrecision: number
viewportUnit: string
fontViewportUnit: string // vmin is more suitable.
selectorBlackList: string[]
propList: string[]
minPixelValue: number
mediaQuery: boolean
replace: boolean
landscape: boolean
landscapeUnit: string
landscapeWidth: number
}
export default function (options: Partial<Options>): any
}
4.在tsconfig.json中引入声明文件
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "postcss-px-to-viewport.d.ts"],
"compilerOptions": {
"baseUrl": ".",
"types": ["element-plus/global"],
"paths": {
"@/*": ["./src/*"]
}
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
}
5.注意:如果外面用到了postcss.config.js,在postcss.config.js中添加配置文件
// 要禁用vite.config.ts内置postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-px-to-viewport': {
unitToConvert: 'px', // 要转化的单位
viewportWidth: 320 // UI设计稿的宽度
// unitPrecision: 6, // 转换后的精度,即小数点位数
// propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
// viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
// fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
// selectorBlackList: ['wrap'], // 指定不转换为视窗单位的类名,
// minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
// mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
// replace: true, // 是否转换后直接更换属性值
// exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
// landscape: false // 是否处理横屏情况
}
}
}
函数式编程
1.h函数
h 接收三个参数
1.type 元素的类型
2.propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
3.children 子节点
2.h函数的多种组合
// 除类型之外的所有参数都是可选的
h('div')
h('div', { id: 'foo' })
//属性和属性都可以在道具中使用
//Vue会自动选择正确的分配方式
h('div', { class: 'bar', innerHTML: 'hello' })
// props modifiers such as .prop and .attr can be added
// with '.' and `^' prefixes respectively
h('div', { '.name': 'some-name', '^width': '100' })
// class 和 style 可以是对象或者数组
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// 定义事件需要加on 如 onXxx
h('div', { onClick: () => {} })
// 子集可以字符串
h('div', { id: 'foo' }, 'hello')
//如果没有props是可以省略props 的
h('div', 'hello')
h('div', [h('span', 'hello')])
// 子数组可以包含混合的VNode和字符串
h('div', ['hello', h('span', 'hello')])
3.使用props传递参数
<template>
<Btn text="按钮"></Btn>
</template>
<script setup lang='ts'>
import { h, } from 'vue';
type Props = {
text: string
}
const Btn = (props: Props, ctx: any) => {
return h('div', {
class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
}, props.text)
}
</script>
4.接收emit
<template>
<Btn @on-click="getNum" text="按钮"></Btn>
</template>
<script setup lang='ts'>
import { h, } from 'vue';
type Props = {
text: string
}
const Btn = (props: Props, ctx: any) => {
return h('div', {
class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
onClick: () => {
ctx.emit('on-click', 123)
}
}, props.text)
}
const getNum = (num: number) => {
console.log(num);
}
</script>
5.定义插槽
<template>
<Btn @on-click="getNum">
<template #default>
按钮slots
</template>
</Btn>
</template>
<script setup lang='ts'>
import { h, } from 'vue';
type Props = {
text?: string
}
const Btn = (props: Props, ctx: any) => {
return h('div', {
class: 'p-2.5 text-white bg-green-500 rounded shadow-lg w-20 text-center inline m-1',
onClick: () => {
ctx.emit('on-click', 123)
}
}, ctx.slots.default())
}
const getNum = (num: number) => {
console.log(num);
}
</script>
vue性能优化
vite配置文件中vite的优化
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
// https://vitejs.dev/config/
export default defineConfig({
...
build: {
chunkSizeWarningLimit: 2000,
cssCodeSplit: true, //css 拆分
sourcemap: false, //不生成sourcemap
minify: 'terser', //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。
assetsInlineLimit: 5000 //小于该值 图片将打包成Base64
}
})
PWA离线存储技术
安装依赖yarn add vite-plugin-pwa -D
配置
import { fileURLToPath, URL } from "node:url";
import { VitePWA } from "vite-plugin-pwa";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
VitePWA({
workbox: {
cacheId: "key", //缓存名称
runtimeCaching: [
{
urlPattern: /.*\.js.*/, //缓存文件
handler: "StaleWhileRevalidate", //重新验证时失效
options: {
cacheName: "XiaoMan-js", //缓存js,名称
expiration: {
maxEntries: 30, //缓存文件数量 LRU算法
maxAgeSeconds: 30 * 24 * 60 * 60, //缓存有效期
},
},
},
],
},
}),
],
....
});
图片懒加载
import { createApp } from 'vue'
import App from './app'
import lazyPlugin from 'vue3-lazy'
const app = createApp(App)
app.use(lazyPlugin, {
loading: 'loading.png',
error: 'error.png'
})
app.mount('#app')
<img v-lazy="user.avatar" >