Vue3
组合式API
1.钩子函数steup
函数的普通用法
< script>
export default {
setup ( ) {
return {
}
}
}
< / script>
< template>
< / template>
简写使用setup
< script setup>
< / script>
< template>
< / template>
2.响应式API
ref函数
< script setup>
import {
ref } from 'vue'
const state = ref ( 0 )
function increment ( ) {
state. value++
}
< / script>
< template>
< button @click= "increment" >
{
{
state } }
< / button>
< / template>
reactive函数
< script setup>
import {
reactive } from 'vue'
const state = reactive ( {
count : 0 } )
function increment ( ) {
state. count++
}
< / script>
< template>
< button @click= "increment" >
{
{
state. count } }
< / button>
< / template>
3.计算属性API
单向响应
< script setup>
import {
computed, reactive } from 'vue'
const Person= reactive ( {
X : '张' , M : '三' } )
Person. XM = computed ( ( ) => {
return Person. X + '-' + Person. M
} )
< / script>
< template>
姓 : < input v- model= "Person.X" > < br>
名 : < input v- model= "Person.M" > < br>
单向响应 : < input v- model= "Person.XM" >
< / template>
双向响应
< script setup>
import {
computed, reactive } from 'vue'
const Person= reactive ( {
X : '张' , M : '三' } )
Person. AXM = computed ( {
get ( ) {
return Person. X + '-' + Person. M
} ,
set ( value) {
const arr= value. split ( '-' )
Person. X = arr[ 0 ]
Person. M = arr[ 1 ]
}
} )
< / script>
< template>
姓 : < input v- model= "Person.X" > < br>
名 : < input v- model= "Person.M" > < br>
双向响应 : < input v- model= "Person.AXM" >
< / template>
4.监听属性API
监听整个对象
< ! --
< script setup>
import {
reactive, watch} from 'vue'
const Person= reactive ( {
name : '张三' , age : 18 , job : {
salary : 20 } } )
watch ( Person, ( newVal, oldVal ) => {
console. log ( '用户信息发生了变化' , newVal, oldVal) ;
} )
< / script>
< template>
< h2> 年龄:{
{
Person. age} } < / h2>
< button @click= "Person.age++" > + 1 < / button>
< / template>
监听对象中单个属性
< ! -- 监听对象中单个属性, 监听单个属性可以检测到新旧值 -- >
< script setup>
import {
reactive, watch} from 'vue'
const Person= reactive ( {
name : '张三' , age : 18 , job : {
salary : 20 } } )
watch ( ( ) => Person. age, ( newVal, oldVal ) => {
console. log ( '用户年龄发生了变化' , newVal, oldVal) ;
} )
< / script>
< template>
< h2> 年龄:{
{
Person. age} } < / h2>
< button @click= "Person.age++" > + 1 < / button>
< / template>
监听多个对象
< ! -- 监听对象中多个个属性, 监听单个属性可以检测到新旧值 -- >
< script setup>
import {
reactive, watch} from 'vue'
const Person= reactive ( {
name : '张三' , age : 18 , job : {
salary : 20 } } )
watch ( [ ( ) => Person. name, ( ) => Person. age] , ( newValue, oldValue ) => {
console. log ( 'person.name或者person.age的值变化了' , newValue, oldValue) ;
} )
< / script>
< template>
< h2> 姓名:{
{
Person. name} } < / h2>
< button @click= "Person.name+='~'" > 修改< / button>
< h2> 年龄:{
{
Person. age} } < / h2>
< button @click= "Person.age++" > + 1 < / button>
< / template>
监听对象中对象(深度监听)
< ! -- 监听对象中对象, 必须开启深度监听, 一般情况不监听对象 -- >
< script setup>
import {
reactive, watch} from 'vue'
const Person= reactive ( {
name : '张三' , age : 18 , job : {
salary : 20 } } )
watch ( ( ) => Person. job, ( newValue, oldValue ) => {
console. log ( 'person.job的值变化了' , newValue, oldValue) ;
} , {
deep : true
} )
< / script>
< template>
< h2> 薪资:{
{
Person. job. salary} } K < / h2>
< button @click= "Person.job.salary++" > + 1 < / button>
< / template>
5.高级监听API
基本使用(默认执行一次)
< ! -- watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 -- >
< script setup>
import {
reactive, watchEffect} from 'vue'
const Person= reactive ( {
name : '张三'
} )
watchEffect ( ( ) => {
Person. name
console. log ( '姓名发送了变化' ) ;
} )
< / script>
< template>
< h2> 姓名:{
{
Person. name} } < / h2>
< button @click= "Person.name+='~'" > 修改< / button>
< / template>
监听御前处理oninvalidate参数
< script setup lang= "ts" >
import {
reactive, watchEffect } from "vue" ;
const Person = reactive ( {
name : "张三" ,
} ) ;
watchEffect ( ( oninvalidate ) => {
oninvalidate ( ( ) => {
console. log ( "before" ) ;
} ) ;
Person. name;
console. log ( "姓名发送了变化" ) ;
} ) ;
< / script>
< template>
< h2> 姓名:{
{
Person. name } } < / h2>
< button @click= "Person.name += '~'" > 修改< / button>
< / template>
停止监听
< script setup lang= "ts" >
import {
reactive, watchEffect } from "vue" ;
const Person = reactive ( {
name : "张三" ,
} ) ;
const stop = watchEffect ( ( oninvalidate ) => {
oninvalidate ( ( ) => {
console. log ( "before" ) ;
} ) ;
Person. name;
console. log ( "姓名发送了变化" ) ;
< / script>
< template>
< h2> 姓名:{
{
Person. name } } < / h2>
< button @click= "Person.name += '~'" > 修改< / button>
< button @click= "stop" > 停止< / button>
< / template>
6.响应式对象解构API
toRef函数
< script setup>
import {
reactive, toRef} from 'vue'
const person= reactive ( {
A : 1 , B : 2 } )
const A = toRef ( person, 'A' )
< / script>
< template>
< h2> 姓名:{
{
A } } < / h2>
< button @click= "person.A+='~'" > 修改< / button>
< / template>
toRefs
< script setup lang= "ts" >
import {
reactive, toRefs} from 'vue'
const person= reactive ( {
A : 1 , B : 2 } )
const {
A , B } = toRefs ( person)
< / script>
< template>
< h2> 姓名:{
{
A } } < / h2>
< button @click= "A+=1" > 修改< / button>
< / template>
7.生命周期API
< script setup>
import {
onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref} from "vue" ;
onBeforeMount ( ( ) => {
console. log ( '---挂载之前---' ) ;
} )
onMounted ( ( ) => {
console. log ( '---挂载---' ) ;
} )
onBeforeUpdate ( ( ) => {
console. log ( '---更新之前---' ) ;
} )
onUpdated ( ( ) => {
console. log ( '---更新---' ) ;
} )
onBeforeUnmount ( ( ) => {
console. log ( '---卸载之前---' ) ;
} )
onUnmounted ( ( ) => {
console. log ( '---卸载---' ) ;
} )
< / script>
8.ref获取dom
< template>
< div>
< div ref= "box" > 我是div< / div>
< / div>
< / template>
< script>
import {
ref, onMounted } from "vue" ;
export default {
setup ( ) {
let box = ref ( null ) ;
onMounted ( ( ) => {
console. log ( box. value) ;
} )
console. log ( box. value) ;
return {
box } ;
} ,
} ;
< / script>
9.Hooks
(1)官方hooks
useAttrs()
< ! -- 父组件 -- >
< template>
< Acom a= "456" title= "789" / >
< / template>
< ! -- 子组件 -- >
< ! -- 获取父组件传过来的全部参数 -- >
< script setup lang= "ts" >
import {
useAttrs } from 'vue'
let attr = useAttrs ( )
console. log ( attr)
< / script>
(2)自定hooks
自定义hooks转换图片
import {
onMounted } from 'vue'
type Options = {
el : string
}
export default function ( options : Options) : Promise< {
baseUrl : string } > {
return new Promise ( resolve => {
onMounted ( ( ) => {
const img : HTMLImageElement = document. querySelector (
options. el
) as HTMLImageElement
img. 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 ( 'image/jpg' )
}
} )
}
使用hooks
< script setup lang= "ts" >
import BASE64 from './hooks'
BASE64 ( {
el : '#img' } ) . then ( resolve => {
console. log ( resolve. baseUrl)
} )
< / script>
(3)第三方hooks
安装依赖yarn add @vueuse/core
简单使用
< script setup lang= "ts" >
import {
ref } from 'vue'
import {
useDraggable } from '@vueuse/core'
const el = ref< HTMLElement | null > ( null )
const {
x, y, style } = useDraggable ( el, {
initialValue : {
x : 40 , y : 40 }
} )
< / script>
< template>
< div ref= "el" : style= "style" style= "position: fixed" >
Drag me! I am at {
{
x } } , {
{
y } }
< / div>
< / template>
组件间通讯
1.props父传子
父组件
< script setup >
import HelloWorld from './components/HelloWorld.vue'
< / script>
< template>
< HelloWorld msg= "1" / >
< / template>
子组件
< script setup>
const props= defineProps ( {
msg : String} )
console. log ( props. msg)
< / script>
2.emit子传父
父组件
< script setup >
import HelloWorld from './components/HelloWorld.vue'
const getuser = ( a ) => {
console. log ( a)
}
< / script>
< template>
< HelloWorld @getuser= "getuser" / >
< / template>
子组件
< script setup lang= "ts" >
const emit = defineEmits ( [ 'getuser' ] )
function buttonClick ( ) {
emit ( 'getuser' , 1 )
}
< / script>
< template>
< button @click= "buttonClick" > 传输< / button>
< / template>
自定义事件事件校检
< script setup>
const emit = defineEmits ( {
click : null ,
submit : ( {
email, password } ) => {
if ( email && password) {
return true
} else {
console. warn ( 'Invalid submit event payload!' )
return false
}
}
} )
function submitForm ( email, password ) {
emit ( 'submit' , {
email, password } )
}
< / script>
3.插槽通讯
(1)匿名插槽
子组件
< template>
< ! -- slot插槽占位 -- >
< slot> < / slot>
< / template>
父组件
< script setup lang= "ts" >
import HelloWorld from "./components/HelloWorld.vue" ;
< / script>
< template>
< HelloWorld>
插槽传递
< / HelloWorld>
< / template>
(2)具名插槽
父组件
< script setup lang= "ts" >
import HelloWorld from "./components/HelloWorld.vue" ;
< / script>
< template>
< HelloWorld>
< ! -- v- slot: 简写# -- >
< template v- slot: btn>
< button> 具名插槽< / button>
< / template>
< / HelloWorld>
< / template>
子组件
< template>
< ! -- slot插槽占位 -- >
< slot name= "btn" > < / slot>
< / template>
(3)作用域插槽
理解:数据在子组件的自身,但根据数据生成的结构需要父组件决定。
父组件
< script setup lang= "ts" >
import HelloWorld from "./components/HelloWorld.vue" ;
const person= [ {
name : '小明' , age : 18 } , {
name : '小红' , age : 20 } ]
< / script>
< template>
< HelloWorld : person= "person" >
< template #tab= "scope" >
< tr v- for = "(item,index) in scope.person" : key= "index" >
< th> {
{
item. name} } < / th>
< th> {
{
item. age} } < / th>
< th> < button > 编辑< / button> < / th>
< / tr>
< / template>
< / HelloWorld>
< / template>
子组件
< script setup lang= "ts" >
const props= defineProps< {
person : {
name : string, age : number} [ ] } > ( )
< / script>
< template>
< table border= "1" >
< tr>
< th> 姓名< / th>
< th> 年龄< / th>
< th> 操作< / th>
< / tr>
< ! -- 作用域插槽命名 -- >
< slot name= "tab" : person= "props.person" > < / slot>
< / table>
< / template>
4.依赖注入
父组件(祖先组件)
< ! -- 依赖注入传的参可以在子组件中改变 -- >
< 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 } from 'vue'
const flag = inject ( 'flag' , ref ( 1 ) )
< / script>
5.兄弟传参
(1)父组件当成一个桥梁
(2)发布订阅模式
Bus传递
type BusClass = {
emit : ( name : string) => void
on : ( name : string, callback : 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> ) {
const evnentName : Array< Function> = this . list[ name]
evnentName. forEach ( fn => {
fn . apply ( this , args)
} )
}
on ( name : string, callback : Function) {
const fn : Array< Function> = this . list[ name] || [ ]
fn. push ( callback)
this . list[ name] = fn
}
}
export default new Bus ( )
A组件传递数值
< script setup lang= "ts" >
import {
ref } from 'vue'
import Bus from '../utils/Bus'
const flag = ref ( 1 )
const Pass = ( ) => {
Bus. emit ( 'pass' , flag)
}
< / script>
< template>
< div>
我是A
< div> {
{
flag } } < / div>
< button @click= "Pass" > Pass< / button>
< / div>
< / template>
< style scoped lang= "less" > < / style>
B组件接收数值
< script setup lang= "ts" >
import Bus from '../utils/Bus'
import {
ref, type Ref } from 'vue'
const flag = ref ( 0 )
Bus. on ( 'pass' , ( Flag : Ref< number> ) => {
console. log ( Flag)
flag. value = Flag. value
} )
< / script>
< template>
< div>
我是B
< div> {
{
flag } } < / div>
< button @click= "flag++" > + < / button>
< / div>
< / template>
< style scoped lang= "less" > < / style>
(3)第三方库mitt
安装yarn add mitt
全局挂载mit
import {
createApp } from 'vue'
import {
createPinia } from 'pinia'
import App from './App.vue'
import './assets/main.css'
import mitt from 'mitt'
const Mit = mitt ( )
const app = createApp ( App)
declare module 'vue' {
export interface ComponentCustomProperties {
$Bus : typeof Mit
}
}
app. use ( createPinia ( ) )
app. config. globalProperties. $Bus = Mit
app. mount ( '#app' )
A组件传递数值
< script setup lang= "ts" >
import {
getCurrentInstance, ref } from 'vue'
const instance = getCurrentInstance ( )
const flag = ref ( 1 )
const Pass = ( ) => {
instance?. proxy?. $Bus. emit ( 'pass' , flag)
}
< / script>
< template>
< div>
我是A
< div> {
{
flag } } < / div>
< button @click= "Pass" > Pass< / button>
< / div>
< / template>
< style scoped lang= "less" > < / style>
B组件接收数值
< script setup lang= "ts" >
import {
getCurrentInstance, ref, type Ref } from 'vue'
const instance = getCurrentInstance ( )
const flag = ref ( 0 )
instance?. proxy?. $Bus. on ( 'pass' , Flag => {
flag. value = ( Flag as Ref< number> ) . value
} )
< / script>
< template>
< div>
我是B
< div> {
{
flag } } < / div>
< button @click= "flag++" > + < / button>
< / div>
< / template>
< style scoped lang= "less" > < / style>
*监听事件
< script setup lang= "ts" >
import {
getCurrentInstance, ref, type Ref } from 'vue'
const instance = getCurrentInstance ( )
const flag = ref ( 0 )
instance?. proxy?. $Bus. on ( '*' , ( type, Flag ) => {
flag. value = ( Flag as Ref< number> ) . value
} )
< / script>
取消监听事件
< script setup lang= "ts" >
import {
getCurrentInstance, ref, type Ref } from 'vue'
const instance = getCurrentInstance ( )
const flag = ref ( 0 )
instance?. proxy?. $Bus. off ( 'pass' , Flag => {
flag. value = ( Flag as Ref< number> ) . value
} )
< / script>
取消全部监听事件
< script setup lang= "ts" >
import {
getCurrentInstance, ref, } from 'vue'
const instance = getCurrentInstance ( )
instance?. proxy?. $Bus. all. clear ( )
< / script>
Typescript的支持
1.全局接口的抽取
src下定义types文件夹命名xx.d.ts
建立Person接口person.d.ts
interface personInterface {
name : string
age : number
}
组件中直接使用
< script setup lang= "ts" >
const props= defineProps< {
person : personInterface[ ] } > ( )
< / script>
如果不是在src下或src文件下的xx.d.ts文件则需要在tsconfig.json中配置
{
{
...
} ,
"include" : [ "src/**/*.ts" , "src/**/*.d.ts" , "src/**/*.tsx" , "src/**/*.vue" ] ,
"references" : [ {
"path" : "./tsconfig.node.json" } ]
}
2.类型增强
使用环境:全局定义的数据,函数在vue组件中直接访问报错
index.html中定义数据
<! DOCTYPE html >
< html lang = " en" >
< head>
...
</ head>
< script>
const global= 1
</ script>
< body>
...
</ body>
</ html>
定义类型增强
declare const global : string;
组件中直接读取
< script setup lang= "ts" >
console. log ( global)
< / script>
3.第三方库类型声明
安装一个库
安装库的ts类型声明@types/xxxx
4.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>
5.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 } > ( )
function buttonClick ( ) {
emit ( 'getuser' , 1 )
}
< / script>
< template>
< button @click= "buttonClick" > 传输< / button>
< / template>
< style scoped>
< / style>
6.依赖注入类型推断
父组件(祖先组件)
< 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>
7.定义全局函数和全局函数的类型支持
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.基本使用
创建vue3的项目yarn create vite || npm init vite@latest
安装插件Volar
2.配置项目路径
tsconfig.json中添加
{
"compilerOptions" : {
...
"baseUrl" : "./" ,
"paths" : {
"@/*" : [
"src/*"
]
}
} ,
...
}
vite.config.ts中添加
export default defineConfig ( {
plugins : [ vue ( ) ] ,
resolve : {
alias : {
"@" : join ( __dirname, 'src' )
}
}
} )
3.eslint和prettierrc的配置
.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
}
.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'
}
}
4.vite环境变量的配置
vite的环境在import中
< script setup lang= "ts" >
console. log ( import . meta. env)
< / script>
创建.env.development .env.production
package.json中配置运行生产环境,会自动注入
{
...
"scripts" : {
"dev" : "vite --mode development" ,
...
} ,
}
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'
export default ( {
mode } : any) => {
console. log ( loadEnv ( mode, process. cwd ( ) ) )
return defineConfig ( {
plugins : [ vue ( ) ] ,
resolve : {
alias : {
'@' : fileURLToPath ( new URL ( './src' , import . meta. url) )
}
}
} )
}
找不到模块“./App.vue”或其相应的类型声明
declare module '*.vue' {
import type {
DefineComponent } from 'vue'
const component : DefineComponent< {
} , {
} , any>
export default component
}
类型“ImportMeta”上不存在属性“env”
{
...
"compilerOptions" : {
...
"types" : [ "vite/client" ] ,
} ,
...
}
指令的重构
1.v-model指令
(1)v-model实现组件间数据双向绑定
父组件
< script setup lang= "ts" >
import HelloWorld from "./components/HelloWorld.vue" ;
import {
ref } from "vue" ;
const num= ref ( 1 )
< / script>
< template>
< HelloWorld v- model= "num" / >
< / template>
子组件
< script setup lang= "ts" >
import {
computed } from 'vue' ;
const props= defineProps< {
modelValue : number} > ( )
const emit = defineEmits< {
( e: 'update:modelValue' , id : number) : void } > ( )
const value= computed ( {
get ( ) {
return + props. modelValue
} ,
set ( value) {
emit ( 'update:modelValue' , + value)
}
} )
< / script>
< template>
< input type= "text" v- model= "value" >
< / template>
v-model的原理
< template>
< ! -- < HelloWorld v- model= "num" / > -- >
< HelloWorld : modelValue= "num" @update: modelValue= "num = $event" / >
< / template>
(2)v-model传递特定的名称
父组件
< script setup lang= "ts" >
import {
ref } from "vue" ;
import HelloWorld from "./components/HelloWorld.vue" ;
const num= ref ( 1 )
< / script>
< template>
< ! -- < HelloWorld : num= "num @update:=" num = $event"" / > -- >
< HelloWorld v- model: num= "num" / >
< / template>
子组件
< script setup lang= "ts" >
import {
computed } from 'vue' ;
const props= defineProps< {
num : number} > ( )
const emit = defineEmits< {
( e: 'update:num' , id : number) : void } > ( )
const value= computed ( {
get ( ) {
return + props. num
} ,
set ( value) {
emit ( 'update:num' , + value)
}
} )
< / script>
< template>
< input type= "text" v- model= "value" >
< / template>
2.自定义指令
(1)自定义指令的简单使用
全局自定义指令
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>
const vFocus = {
mounted : ( el ) => el. focus ( )
}
< / script>
< template>
< input v- focus / >
< / template>
(2)自定义指令详解
自定义指令的生命周期
< script setup lang= "ts" >
import type {
Directive, DirectiveBinding } from 'vue'
type Dir = {
background : string }
const vMove : Directive = {
created ( ) {
} ,
beforeMount ( ) {
} ,
mounted ( el : HTMLElement, dir : DirectiveBinding< Dir> ) {
console. log ( dir. value. background)
el. style. background = dir. value. background
} ,
beforeUpdate ( ) {
} ,
updated ( ) {
} ,
beforeUnmount ( ) {
} ,
unmounted ( ) {
}
}
< / script>
< template>
< ! -- 自定义指令,参数,修饰符 -- >
< div v- move: a. x= "{ background: 'red' }" > 自定义指令< / div>
< / template>
< style scoped lang= "less" > < / style>
生命周期的简写
< script setup lang= "ts" >
import type {
Directive, DirectiveBinding } from 'vue'
type Dir = {
background : string }
const vMove : Directive = ( el : HTMLElement, dir : DirectiveBinding< Dir> ) => {
el. style. background = dir. value. background
}
< / script>
< template>
< ! -- 自定义指令,参数,修饰符 -- >
< div v- move: a. x= "{ background: 'red' }" > 自定义指令< / div>
< / template>
< style scoped lang= "less" > < / style>
自定义拖拽指令
< 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>
响应式原理
1.了解Proxy
Proxy代理的get方法
< script>
let obj= {
name : 'Vue' ,
age : 8
}
let obj2= new Proxy ( obj, {
get ( target, property) {
console. log ( '执行了get' ) ;
return target[ property]
}
} )
console. log ( obj2. age)
</ script>
Proxy代理的set方法
< script>
let obj= {
name : 'Vue' ,
age : 8
}
let obj2= new Proxy ( obj, {
set ( target, property, newValue) {
console. log ( '执行了set' )
target[ property] = newValue
}
} )
obj2. age= 7
console. log ( obj2. age)
</ script>
2.了解Object.defineProperty
Object.defineProperty(对象.定义属性,用来为一个对象添加新属性)
< script>
let person = {
name : '张三' ,
sex : '男' ,
}
Object. defineProperty ( person, 'age' , {
value= 18
} )
console. log ( person)
</ script>
Object.defineProperty属性的可枚举可修改的实现
< script>
let person = {
name : '张三' ,
sex : '男' ,
}
Object. defineProperty ( person, 'age' , {
enumerable= true
writable= true
configurable : true
value= 18
} )
console. log ( person)
</ script>
Object.defineProperty() 的get()方法
< script>
let person = {
name : '张三' ,
sex : '男' ,
}
function Observer ( obj ) {
const keys = Object. keys ( obj)
keys. forEach ( ( key ) => {
Object. defineProperty ( this , key, {
get ( ) {
return obj[ key]
}
} )
} )
}
const obs = new Observer ( person)
console. log ( obs. sex) ;
</ script>
Object.defineProperty() 的set()方法
< script>
let person = {
name : '张三' ,
sex : '男' ,
}
function Observer ( obj ) {
const keys = Object. keys ( obj)
keys. forEach ( ( key ) => {
Object. defineProperty ( this , key, {
set ( val) {
console. log ( 'set方法调用了' )
obj[ key] = val
}
} )
} )
}
const obs = new Observer ( person)
obs. name= 15
</ script>
3.Vue双向绑定的实现的对比
Vue3的Proxy实现
< body>
< input type = " text" id = " ipt" >
< p id = ' op' > </ p>
< script>
function reactive ( obj ) {
return new Proxy ( obj, {
get ( target, property) {
return target[ property]
} ,
set ( target, property, newVal) {
target[ property] = newVal
}
} )
}
let newObj = reactive ( [ 1 , 2 ] )
console. log ( newObj[ 1 ] )
const ipt = document. querySelector ( '#ipt' )
ipt. value = newObj[ 1 ]
document. querySelector ( '#op' ) . innerHTML = newObj[ 1 ]
ipt. addEventListener ( 'input' , function ( e ) {
newObj[ 1 ] = e. target. value
document. querySelector ( '#op' ) . innerHTML = newObj[ 1 ]
} )
</ script>
</ body>
Vue2的Object.defineProperty实现
< body>
< input type = " text" id = " ipt" >
< p id = ' op' > </ p>
< script>
function Observer ( obj ) {
const keys = Object. keys ( obj)
keys. forEach ( ( key ) => {
Object. defineProperty ( this , key, {
get ( ) {
console. log ( 'get方法被调用了' ) ;
return obj[ key]
} ,
set ( val) {
console. log ( 'set方法调用了' )
obj[ key] = val
}
} )
} )
}
const obs = new Observer ( [ 1 , 2 , 3 ] )
const ipt = document. querySelector ( '#ipt' )
ipt. value = obs[ 1 ]
document. querySelector ( '#op' ) . innerHTML = obs[ 1 ]
ipt. addEventListener ( 'input' , function ( e ) {
obs[ 1 ] = e. target. value
document. querySelector ( '#op' ) . innerHTML = obs[ 1 ]
} )
</ script>
</ body>
上面的测试,Object.property是可以检测到通过索引改变数组的操作的,而Vue没有实现,Object.defineProperty表示这个锅我不背
内置组件
1.内置组件
(1)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>
(2)Transition组件
非命名动画
< 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>
命名动画
< 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>
过度动画
< Transition mode= "out-in" >
...
< / Transition>
结合第三方库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>
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>
生命周期结合第三方库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>
初始化动画
< 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>
初始化动画结合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>
(3)transition-group过度列表
Transition组件无法对v-for的列表进行渲染
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>
添加列表时的动画效果
< 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>
平移动画move-class
< script setup lang= "ts" >
import {
ref } from 'vue'
import _ from 'lodash'
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>
状态过度(数字过度颜色过度)
< 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>
(4)keep-alive组件
开启keep-alive 生命周期的变化
初次进入时: onMounted-> onActivated
退出后触发: deactivated
缓存数据
< 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>
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>
2.普通组件
(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' )
使用组件
< template>
< div>
< Acom> < / Acom>
< / div>
< / template>
(2)异步组件
子组件中发送了请求变成异步
< 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>
父组件异步调用组件
< script setup lang= "ts" >
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>
语法糖组件命名问题
安装依赖yarn add vite-plugin-vue-setup-extend
直接命名
< script lang= "ts" setup name= "xxx" >
< / script>
常用的CSS的功能
样式穿透
< style scoped lang= "less" >
: deep ( input ) {
color : red;
}
< / style>
插槽选择器
< template>
< div>
< slot name= "nums" : nums= "['1', '2', '3']" > < / slot>
< / div>
< / template>
< style scoped lang= "less" >
: slotted ( . li) {
color : red;
}
< / style>
全局选择器
< 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>
动态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.CSS原子化
安装unocssyarn add unocss
vite的配置文件中配置
import {
fileURLToPath, URL } from 'node:url'
import pxtoViewPort from 'postcss-px-to-viewport'
import {
defineConfig } from 'vite'
import unocss from 'unocss/vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig ( {
plugins : [
vue ( ) ,
unocss ( {
rules : [
[ 'flex' , {
display : 'flex' } ] ,
[ 'red' , {
color : 'red' } ] ,
[ / ^m-(\d+)$ / , ( [ , d] ) => ( {
margin : ` ${
Number ( d) * 10 } px ` } ) ]
]
} )
] ,
resolve : {
alias : {
'@' : fileURLToPath ( new URL ( './src' , import . meta. url) )
}
}
} )
main.ts中引入import 'uno.css'
其他预设配置中引入
import {
fileURLToPath, URL } from 'node:url'
import {
defineConfig } from 'vite'
import unocss from 'unocss/vite'
import vue from '@vitejs/plugin-vue'
import {
presetIcons, presetAttributify, presetUno } from 'unocss'
export default defineConfig ( {
plugins : [
vue ( ) ,
unocss ( {
presets : [ presetIcons ( ) , presetAttributify ( ) , presetUno ( ) ] ,
rules : [
[ 'flex' , {
display : 'flex' } ] ,
[ 'red' , {
color : 'red' } ] ,
[ / ^m-(\d+)$ / , ( [ , d] ) => ( {
margin : ` ${
Number ( d) * 10 } px ` } ) ]
]
} )
] ,
resolve : {
alias : {
'@' : fileURLToPath ( new URL ( './src' , import . meta. url) )
}
}
} )
第一预设图标库
npm i - D @iconify- json/ ic
第二预设属性语义化 无须class
< div color= "red" > left< / div>
第三预设
默认的 @unocss/preset-uno 预设(实验阶段)是一系列流行的原子化框架的 通用超集,
包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。
例如,ml-3(Tailwind),ms-2(Bootstrap),ma4(Tachyons),mt-10px(Windi CSS)均会生效。
5.Vue3集成Tailwind CSS
安装依赖yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
安装插件tailwind css inteliSence
生成配置文件npx tailwindcss init -p
tailwind.config.js配置文件中添加
module. exports = {
content : [ './index.html' , './src/**/*.{vue,js,ts,jsx,tsx}' ] ,
theme : {
extend : {
}
} ,
plugins : [ ]
}
创建index.css文件并且在mian.ts中引入
@tailwind base;
@tailwind components;
@tailwind utilities;
使用tailwindcss的样式
< script setup lang= "ts" > < / script>
< template>
< div
class = "w-screen h-screen bg-red-600 flex justify-center items-center text-8xl text-teal-50"
>
hello tailwind
< / div>
< / template>
< style scoped lang= "less" > < / style>
面试常用源码
1.app.use()的源码实现
实现myuse
import type {
App } from 'vue'
import {
app } from '../main'
interface Use {
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 ( '插件件已经注册' )
return
}
plugin. install ( app, ... options)
installList. add ( plugin)
}
使用myuse调用插件
import {
createApp } from 'vue'
import {
createPinia } from 'pinia'
import App from './App.vue'
import './assets/main.css'
import Loading from './components/Loading'
import {
MyUse } from './utils/myuse'
export const app = createApp ( App)
MyUse ( Loading)
app. use ( createPinia ( ) )
app. mount ( '#app' )
type Lod = {
show : ( ) => void
hide : ( ) => void
}
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$loading : Lod
}
}
移动端适配
1.第一种适配方案
安装依赖yarn add amfe-flexible postcss [email protected]
main.ts引入amfe-flexibleimport "amfe-flexible"
根目录下创建postcss.config.js文件并配置
module. exports = {
plugins : {
'postcss-pxtorem' : {
rootValue : 37.5 ,
propList : [ '*' ]
}
}
}
2.第二种适配方案
安装依赖yarn add postcss-px-to-viewport -D
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'
export default defineConfig ( {
plugins : [ vue ( ) ] ,
css : {
postcss : {
plugins : [
pxtoViewPort ( {
unitToConvert : 'px' ,
viewportWidth : 750 ,
unitPrecision : 6 ,
propList : [ '*' ] ,
viewportUnit : 'vw' ,
fontViewportUnit : 'vw' ,
selectorBlackList : [ 'ignore-' ] ,
minPixelValue : 1 ,
mediaQuery : true ,
replace : true ,
landscape : false
} )
]
}
} ,
resolve : {
alias : {
'@' : fileURLToPath ( new URL ( './src' , import . meta. url) )
}
}
} )
创建postcss-px-to-viewport.d.ts的声明文件
declare module 'postcss-px-to-viewport' {
type Options = {
unitToConvert : 'px' | 'rem' | 'cm' | 'em'
viewportWidth : number
viewportHeight : number
unitPrecision : number
viewportUnit : string
fontViewportUnit : string
selectorBlackList : string[ ]
propList : string[ ]
minPixelValue : number
mediaQuery : boolean
replace : boolean
landscape : boolean
landscapeUnit : string
landscapeWidth : number
}
export default function ( options : Partial< Options> ) : any
}
在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"
}
]
}
注意:如果外面用到了postcss.config.js
,在postcss.config.js
中添加配置文件
module. exports = {
plugins : {
tailwindcss : {
} ,
autoprefixer : {
} ,
'postcss-px-to-viewport' : {
unitToConvert : 'px' ,
viewportWidth : 320
}
}
}
的其他知识点
1.全局函数和全局变量
全局函数
import {
createApp } from 'vue'
import {
createPinia } from 'pinia'
import App from './App.vue'
import './assets/main.css'
const app = createApp ( App)
type Fileter = {
format : < T > ( str : T ) => string
}
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$filters : Fileter
}
}
app. config. globalProperties. $filters = {
format< T > ( str: T ) : string {
return ` 真 ${
str} `
}
}
app. use ( createPinia ( ) )
app. mount ( '#app' )
全局变量
import {
createApp } from 'vue'
import {
createPinia } from 'pinia'
import App from './App.vue'
import './assets/main.css'
const app = createApp ( App)
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$env : string
}
}
app. config. globalProperties. $env = '全局变量'
app. use ( createPinia ( ) )
app. mount ( '#app' )
2.自定义插件
封装插件的样式,抛出插件的显示隐藏方法
< script setup lang= "ts" >
import {
ref } from 'vue'
const isShow = ref ( false )
const show = ( ) => {
console. log ( 111 )
isShow. value = true
}
const hide = ( ) => {
isShow. value = false
}
defineExpose ( {
show,
hide
} )
< / script>
< template>
< div v- if = "isShow" class = "loading" > loading... . < / div>
< / template>
< style scoped lang= "less" > < / style>
创建接收调用插件的方法
import {
render, type App, type VNode } from 'vue'
import Loading from './index.vue'
import {
createVNode } from 'vue'
export default {
install ( app : App) {
const Vnode : VNode = createVNode ( Loading)
render ( Vnode, document. body)
app. config. globalProperties. $loading = {
show : Vnode. component?. exposed?. show,
hide : Vnode. component?. exposed?. hide
}
}
}
main.ts中挂载上面的方方法
import {
createApp } from 'vue'
import {
createPinia } from 'pinia'
import App from './App.vue'
import './assets/main.css'
import Loading from './components/Loading'
const app = createApp ( App)
app. use ( Loading)
app. use ( createPinia ( ) )
app. mount ( '#app' )
对插件的方法进行声明
type Lod = {
show : ( ) => void
hide : ( ) => void
}
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$loading : Lod
}
}
使用插件
< script setup lang= "ts" >
import {
getCurrentInstance } from 'vue'
const instance = getCurrentInstance ( )
instance?. proxy?. $loading. show ( )
setTimeout ( ( ) => {
instance?. proxy?. $loading. hide ( )
} , 5000 )
< / script>
< template>
< div> < / div>
< / template>
< style scoped lang= "less" > < / style>
3.函数式编程
h函数
h 接收三个参数
1.type 元素的类型
2.propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
3.children 子节点
h函数的多种组合
h ( 'div' )
h ( 'div' , {
id : 'foo' } )
h ( 'div' , {
class : 'bar' , innerHTML : 'hello' } )
h ( 'div' , {
'.name' : 'some-name' , '^width' : '100' } )
h ( 'div' , {
class : [ foo, {
bar } ] , style : {
color : 'red' } } )
h ( 'div' , {
onClick : ( ) => {
} } )
h ( 'div' , {
id : 'foo' } , 'hello' )
h ( 'div' , 'hello' )
h ( 'div' , [ h ( 'span' , 'hello' ) ] )
h ( 'div' , [ 'hello' , h ( 'span' , 'hello' ) ] )
使用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>
接收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>
定义插槽
< 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>
4.vue性能优化
(1)跑分和打包体积
跑分vue开发工具Lighthouse
从Performance页的表现结果来看,得分37分,并提供了很多的时间信息,我们来解释下这些选项代表的意思:
FCP (First Contentful Paint):首次内容绘制的时间,浏览器第一次绘制DOM相关的内容,也是用户第一次看到页面内容的时间。
Speed Index: 页面各个可见部分的显示平均时间,当我们的页面上存在轮播图或者需要从后端获取内容加载时,这个数据会被影响到。
LCP (Largest Contentful Paint):最大内容绘制时间,页面最大的元素绘制完成的时间。
TTI(Time to Interactive):从页面开始渲染到用户可以与页面进行交互的时间,内容必须渲染完毕,交互元素绑定的事件已经注册完成。
TBT(Total Blocking Time):记录了首次内容绘制到用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。
CLS(Cumulative Layout Shift):计算布局偏移值得分,会比较两次渲染帧的内容偏移情况,可能导致用户想点击A按钮,但下一帧中,A按钮被挤到旁边,导致用户实际点击了B按钮。
打包后rollup的插件yarn add rollup-plugin-visualizer
import {
fileURLToPath, URL } from 'node:url'
import {
defineConfig, loadEnv } from 'vite'
import unocss from 'unocss/vite'
import vue from '@vitejs/plugin-vue'
import {
visualizer } from 'rollup-plugin-visualizer'
export default ( {
mode } : any) => {
console. log ( loadEnv ( mode, process. cwd ( ) ) )
return defineConfig ( {
plugins : [ vue ( ) ,
visualizer ( {
open : true } ) ] ,
resolve : {
alias : {
'@' : fileURLToPath ( new URL ( './src' , import . meta. url) )
}
}
} )
}
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" ;
export default defineConfig ( {
...
build : {
chunkSizeWarningLimit : 2000 ,
cssCodeSplit : true ,
sourcemap : false ,
minify : 'terser' ,
assetsInlineLimit : 5000
}
} )
(2)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" ;
export default defineConfig ( {
plugins : [
vue ( ) ,
vueJsx ( ) ,
VitePWA ( {
workbox : {
cacheId : "key" ,
runtimeCaching : [
{
urlPattern : / .*\.js.* / ,
handler : "StaleWhileRevalidate" ,
options : {
cacheName : "XiaoMan-js" ,
expiration : {
maxEntries : 30 ,
maxAgeSeconds : 30 * 24 * 60 * 60 ,
} ,
} ,
} ,
] ,
} ,
} ) ,
] ,
... .
} ) ;
(3)其他性能优化
图片懒加载
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" >
虚拟列表实现
后台返回多数据
展示可视区的dom
多线程 使用 new Worker 创建
const myWorker1 = new Worker ( "./calcBox.js" ) ;
worker. postMessage ( arrayBuffer, [ arrayBuffer] ) ;
self. onmessage = function ( e ) {
} ;
关闭
worker. terminate ( ) ;
防抖节流