- 近期,整理下vue3的学习笔记,仅供参考!
vue3 + ts + vite 学习
vue2 和 vue3 的区别:
- 数据更新方式 v2 采用 Object.defineProperty, v3 使用 proxy
- tree-shaking
- fragments
- diff 算法不同
- 支持 ts
配置环境
npm init vite@latest 创建项目
在 vite-env.d.ts 中配置声明文件,ts 本身不能识别.vue 的文件,可能需要手动配置
vue3 基础
响应式数据:
ref
支持所有类型,此对象只有一个指向其内部值的属性 .value。
```js
ref() // 需要使用.value的形式
reactive()
shallowRef() // 浅层,.value外部状态管理,替换整个个对象
triggerRef() // 强制触发依赖于一个浅层 ref 的副作用,
// 防抖使用
customRef((track, trigger) => {
get() {
track()
},
set() {
trigger()
}
})
// 可以使用ref来绑定一个dom, 读取dom属性
<div ref="dom">this is a dom!!</div>
const dom = ref<HTMLDivElement>()
console.log(dom.value?.innerText)
reactive
只支持引用类型 Array Object Map Set 不需要使用.value 形式修改值, 直接修改
- reactive 中使用 proxy 不能直接赋值,否则会破坏响应式结构,解决方案: 数组可以使用 push + 解构; 对象中添加一个数组属性
let list1 = list.push(...res) // 法1 let list2 = reactive<{ arr: string[] }>({ arr: [] }) // 法2
readonly 只读属性
let obj = reactive({ name: 'wxt' })
const read = readonly(obj)
const add = () => {
read.name = '2323' // 此时两者都为wxt, 不可修改; 若obj.name修改,则两者都变为2323, reactive会影响readonly属性
console.log(obj, read)
}
shallowReactive 个浅层响应式对象里只有根级别的属性是响应式的, 会被 reactive 的属性影响,注意:若根元素发生 reactive 变化,此时深层次的也会发生变化
toRef toRefs, toRaw
toRef 基于响应式对象上的一个属性,创建一个对应的 ref。toRef(object, key), 使用 reactive + .value 使用
- 使用场景:
useSomeFeature(toRef(props, 'foo')) 作为函数的参数传入
toRefs 解构不失响应式属性, 功能:每个属性都是使用 toRef() 创建的响应式对象const {a, b} = toRefs(oo)
toRaw 返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。
vue3 响应式原理
// effect 函数---副作用函数
let activeEffect: Function // 闭包,保存副作用函数
export const effect = (fn: Function) => {
const _effect = function () {
activeEffect = _effect
fn()
}
_effect()
}
const targetMap = new WeakMap()
// track 追踪
export const track = (target: object, key: any) => {
// 第一层
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 第二层
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
// 第三层
deps.add(activeEffect)
}
// trigger 触发
export const trigger = (target: object, key: any) => {
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
deps.forEach((effect: Function) => effect())
}
// reactive函数
const isObject = (target: any) => target != null && typeof target == 'object'
export const reactive = <T extends object>(target: T): T => {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver) as object
track(target, key)
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
trigger(target, key)
return res
},
})
}
// 具体使用
const user = reactive({
name: 'zs',
age: 12,
fool: {
bar: {
text: 'ddfjdi ',
},
},
})
effect(() => {
document.querySelector(
'#app'
).innerText = `${user.name} -- ${user.age} ---${user.fool.bar.text}`
})
setTimeout(() => {
user.name = '大马纳'
setTimeout(() => {
user.fool.bar.text = 'you are'
}, 1000)
}, 2000)
computed 的使用
两种写法:
-
选项式写法(可读,可写)
const fullName = computed<string>({ get() { return firstName.value + '-' + lastName.value }, set(val) { ;[firstName.value, lastName.value] = val.split('-') }, }) -
组合函数写法(设置为只读属性,不可写)
const fullName = computed<string>(() => firstName.value + '-' + lastName.value)
watch 数据源可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
watch(x, (newVal, oldVal) => {})
watch(
() => x.d,
(newVal, oldVal) => {}
)
watch([x, y], ([newVal, newVal2]) => {})
watch(x, (val) => {}, { immediate: true, deep: true, flush: 'pre' })
// pre 组件更新之前调用 sync同步执行 post组件更新后执行
-- reactive 对象默认执行深度监听 ;ref 对象要设置 deep,但是 newVal, oldVal 相同,慎用
watchEffect 高级监听
- 副作用可以用来清理无效的副作用
- 返回值是一个用来停止该副作用的函数
- 不加参数,则默认监听立即执行,直接在回调函数中使用即可
const stop = watchEffect(
(onCleanup) => {
// let m:HTMLInputElement = document.querySelector('#btn') as HTMLInputElement
// console.log(m, 'm')
console.log(message1.value, message2.value)
onCleanup(() => console.log('before')) // 总是先执行
},
{
// flush: 'post',
}
)
生命周期
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
- 其中 setup 默认就包括 vue2 中的(beforeCreate, created)
- 父子组件的生命周期执行顺序: 父 onMounted 之前执行子组件的 onMounted
layout 布局,sass, scoped (element ui)
sass 常用语法
-
嵌套规则
.a { .b { } } -
变量使用
$main-color: 'red -
混入
@mixin xx{} ===> @include xx @mixin xx($a, $b) {} ====> @include xx(m, n) -
@at-root 独立出当前的父级元素,自己独立开始
-
插值语句
#{xx}$name: foo; $attr: border; p.#{name} { #{attr}-color: blue; }
父子组件传值
-
defineProps -- 使用默认值
const props = withDefaults(defineProps<{ txt: string }>(), { txt: '阳光开朗大的孩', }) -
defineEmits --- 子传父
const emit = defineEmits<{ (e: 'on-click', arr: number[], name: string): void (e: 'on-change', name: string): void }>() // 在子组件中发送 run() { emit('on-click', xx, xx) } // 在父组件中接收 <Son @on-click = "handle"/> const handle = (p1:xx, p2:xx) { console.log(p1, p2) } -
defineExpose 将子组件的属性暴露给父组件
-
当父组件通过模板引用的方式获取到当前组件的实例(ref)--> xxx.value 没有值
-
办法: 子组件暴露出来
const a = 1 const b = ref(2) defineExpose({ a, b, })
-
全局组件、局部组件、递归组件
- 全局组件: 页面中出现频率高的业务组件
- 局部组件: 页面中多模块,每个模块一个组件
- 递归组件: menu菜单
可选链操作符?和??在 vue 中的使用
a.children?.length // ?前面返回 undefined 隐式转换为 false
a.children?.length?.xxxx ?? [] // ?? 左侧是 undefined 或 null, 返回 ?? 右侧的值
动态组件
-
常用于 tab 栏的切换操作
用法
-
注册的组件名(shallowRef, 只监听到.value)
import { ref, reactive, shallowRef } from 'vue' const componentId = shallowRef(A) const activeIndex = ref(0) const list = reactive([ { name: 'comA', }, { name: 'comB', }, { name: 'comC', }, ]) const change = (item, index) => { componentId.value = item activeIndex.value = index } // 第二个ts import A from './components/A.vue' import B from './components/B.vue' import C from './components/C.vue' export default { components: { comA: A, comB: B, comC: C, }, }导入的组件对象 (使用 markRaw, shallowRef 避免组件的监听)
import A from './components/A.vue' import B from './components/B.vue' import C from './components/C.vue' import { ref, reactive, markRaw, shallowRef } from 'vue' const componentId = shallowRef(A) const activeIndex = ref(0) const list = reactive([ { name: 'comA', component: markRaw(A), }, { name: 'comB', component: markRaw(B), }, { name: 'comC', component: markRaw(C), }, ]) const change = (item, index) => { componentId.value = item.component cosnole.log(componentId.value, 'dddd') activeIndex.value = index }
-
插槽使用
- 使用场景: 要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。
- 默认插槽
#default - 具名插槽
#header - 作用域插槽
v-slot={a, b}-- 可以将子组件的数据传递给父组件 - 动态插槽
#[dynamicSlot]
<A>
<template #content>
<div>bjfidjfi</div>
</template>
</A>
异步组件 --- 提升性能(当页面出现白屏的时候,可以出现提示信息)
<Suspense>
<template #default>
<AsyncPage></AsyncPage> // 当数据成功加载的时候展示
</template>
<template #fallback> // loading时候
<Skeleon></Skeleon>
</template>
</Suspense>
// 引入异步租价你
const AsyncPage = defineAsyncComponent(() => import('./components/Async.vue'))
内置组件 Teleport
-- 它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去, 如果使用定位的时候可以考虑使用!
<Teleport to="body" :disabled="true">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
内置组件 keep-alive --- 用缓存提升用户体验
- 应用场景: 当切换不同的组件,之前表单的输入数据被清除(想要保存之前的状态)
// 属性有include, exclude, max
;<keep-alive exclude="Async">
<Async v-if="open" />
<Skeleon v-else />
</keep-alive>
// 在切换过程中缓存组件的数据
onActivated(() => {
console.log('a 从缓存中重新插入')
})
onDeactivated(() => {
console.log('a 移除并进入缓存')
})
动画组件 结合 animate.css 使用 , 结合 gsap 使用--- 具体看文档使用
-
name
-
自定义类名 + 默认类名
-
结合 animate.css 使用
-
xxx-enter-from 和 xx-leave-to 样式通常相同, xxx-leave-to 和 xxx-leave-from 通常为现在的样式
-
生命周期(8 个)
<Transition @before-enter="onBeforeEnter" @enter="onEnter" @after-enter="onAfterEnter" @enter-cancelled="onEnterCancelled" @before-leave="onBeforeLeave" @leave="onLeave" @after-leave="onAfterLeave" @leave-cancelled="onLeaveCancelled" > <p v-if="show"></p> // 外部通过控制show的值来控制显示 </Transition> -
transition-group 用来对一组数据进行操作
兄弟组件通信(A 和 B 组件通信)
- 通过父组件作为中转, A 触发操作修改值,通过属性传递给 B 组件, 数据定义在父组件中
- 结合 vue2 中使用的 eventBus,自己封装一个 bus
// 类似vue2中event bus 实现组件通信, emit触发,on监听
type BusClass<T> = {
emit: (name: T) => void
on: (name: T, callback: Function) => void
}
type BusParams = string | number | symbol
type List = {
[key: BusParams]: Array<Function>
}
class Bus<T extends BusParams> implements BusClass<T> {
list: List
constructor() {
this.list = {}
}
// 触发
emit(name: T, ...args: Array<any>) {
let events: Array<Function> = this.list[name]
events.forEach((event) => event.apply(this, args))
}
// 监听
on(name: T, callback: Function) {
let fn: Array<Function> = this.list[name] || []
fn.push(callback)
this.list[name] = fn
}
}
export default new Bus()
// 具体使用
xxx.$bus.emit('defineEventName', 待传递的值)
xxx.$bus.on('defineEventName', (val) => 执行的操作)
-
在 A 组件对应位置触发 emit 并传值, 在 B 组件中响应位置开启监听
- 可以使用全局变量注入,将 bus 应用到全局
app.config.globalProperties.$bus = bus // 在<script setup lang='ts'> </script>使用, 要使用proxy解构 import { getCurrentInstance, ref } from 'vue' const { proxy } = getCurrentInstance() console.log(proxy.$bus)
mitt 插件 --- 可以实现 bus 的功能
npm install mitt -S
// 全局挂载
import mitt from 'mitt'
const Mit = mitt()
//TypeScript注册
// 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module 'vue' {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}
const app = createApp(App)
//Vue3挂载全局API
app.config.globalProperties.$Bus = Mit
app.mount('#app')
// 常用属性
xxx.emit('xx', xx)
xxx.on('xx', () => {}) // 监听
xxx.off('xx', xx)) // 清除监听
xxx.$bus.all.clear() // 清除所有监听
tsx & vite 插件的使用
-- 便于组件灵活操作
npm install @vitejs/plugin-vue-jsx -D
-- defineComponent 类型推导的使用, 结合 tsx 语法使用
// 1. tsx语法
import { ref } from 'vue'
let v = ref<string>('')
const renderDom = (props: Props, content: any) => {
return (
<>
<input v-model={v.value} type="text" />
<div onClick={clickTap.bind(this, content)}>{v.value}</div>
// content.emit('xxx', ddfdf)\
</>
)
}
// 2. 函数语法
import { ref, defineComponent } from 'vue'
const renderDom = defineComponent({
props: {
xxx: String,
},
emits: ['on-click'],
setup(props, {emit}) {
let v = ref<string>('')
return (
<>
<input v-model={v.value} type="text" />
<div onClick={clickTap.bind(this, v.value)}>{v.value}</div>
//() => handle(item)
</>
)
},
})
import { defineComponent } from 'vue'
// 3.选项语法
const renderDom = defineComponent({
data() {
return {
test: 343,
}
},
render() {
return (
<>
<input v-model={this.test} type="text" />
<div>{this.test}</div>
</>
)
},
})
export default renderDom
```
tsx 中常见的语法
- v-bind 变为 {}
- ref 使用,不能直接获取值,需要 xx.value 才可以在页面中展示
- 支持 v-show, 不支持 v-if,可以 支持三元表达式
- 不支持 v-for, 用 map 来代替
- 绑定事件 以 on 开头,事件名称首字母大写
onClick= {handle}onClick={()=>{}} - 使用 prop 和 emits (setup 和 tsx 函数写法中,注意区别)
- slots 使用
const myslots = {
default: () => <div>默认值我是默认的</div>,
}
<A v-slots={myslots} test={'good'}></A>
// 或者 <A test={'good'}>{myslots}</A>
// 使用插槽
const A = (_, { slots }) => {
return (
<>
<div>{slots.default ? slots.default() : '默认值'}</div>
</>
)
}
vue3 自动引入安装插件
npm i -D unplugin-auto-import/vite 会自动引入 vue3 中需要用到 watch,computed ...
- 然后再 vite.config.ts 中使用插件
AutoImport({
imports: ['vue'],
dts: 'src/auto-import.d.ts',
}),
组件中 v-model 双向绑定,以及修饰符的使用
-- 结合 props 和 emits 操作, 可以绑定多个 v-modle
<B v-model="show" v-model:text.isBt="text"></B>
const props = defineProps<{
modelValue: boolean // 默认值
text: string
textModifiers?: { // 注意使用的固定格式xxModifiers: {}
isBt: boolean
}
}>()
const emits = defineEmits(['update:modelValue', 'update:text'])
const changeInput = (e: Event) => {
const t = e.target as HTMLInputElement
emits('update:text', props.textModifiers?.isBt ? t.value + '变态' : t.value)
}
自定义指令 directives
- 直接通过js操作dom
import { Directive, DirectiveBinding } from 'vue'
type Dir = {
background: string
}
// 自定义指令的钩子函数
const vColor: Directive = {
// mounted(...args: Array<any>) {},
created() {},
beforeMount() {},
// 通过指定泛型,可在代码中获取相关提示
mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
el.style.background = dir.value.background
},
beforeUpdate() {},
updated() { },
beforeUnmount() {},
unmount(){},
}
// 简写, 可以使用函数的形式, 常使用updated和mounted钩子函数
// 鉴权的使用
<button v-has-show="'shop-edit'">编辑</button>
<button v-has-show="'shop-delete'">删除</button>
const permissions = ['wx1213:shop-create', 'wx1213:shop-delete'] // 模拟后端返回的数据
// 自定义指令
const vHasShow: Directive<HTMLElement, DirectiveBinding> = (el, binding) => {
let isShow = permissions.includes(
localStorage.getItem('userId') + ':' + binding.value
)
if (!isShow) {
el.style.display = 'none'
}
}
- 简写模式:不关系其他钩子函数的时候,可以使用函数模式实现
-
常见的应用场景: 鉴权管理,判断用户是否具有相关权限(比如创建、编辑、删除权限)
-
实践 1: 利用指定实现拖拽的效果
// 注意盒子要设置为absolute模式, 具体使用直接加指令v-move即可,不需传入参数 import { Directive } from 'vue' const vMove: Directive<any, void> = (el: HTMLElement) => { let moveEl = el.firstElementChild as HTMLDivElement // 获取待移动元素 const mouseDown = (e: MouseEvent) => { let X = e.clientX - el.offsetLeft // 计算鼠标在外层元素的相对位置 let Y = e.clientY - el.offsetTop const move = (event: MouseEvent) => { el.style.left = event.clientX - X + 'px' // 减去相对位置,获得外层元素的位置 el.style.top = event.clientY - Y + 'px' } // 2. 抬起 3. 移动 document.addEventListener('mouseup', () => { document.addEventListener('mousemove', move) }) } // 在移动元素中监听鼠标操作 1. 按下 moveEl.addEventListener('mousedown', mouseDown) }-
实践 2: 图片懒加载 (当移动到可视区的时候,图片加载出)
-
利用 js 中 IntersectionObserver 接口
import type { Directive } from 'vue' // 静态加载所有图片模块 import.meta.glob 类似于之前的() => import xxxx const images: Record<string, { default: string }> = import.meta.globEager( './assets/imgs/*' ) let arr = Object.values(images).map((v) => v.default) const vLazy: Directive<HTMLImageElement, string> = async ( el, binding ) => { let url = await import('./assets/vue.jpeg') el.src = url.default // IntersectionObserver 异步观察目标元素与其祖先元素或顶级文档视口 let observer = new IntersectionObserver((entries) => { if (entries[0].intersectionRatio > 0 && entries[0].isIntersecting) { setTimeout(() => { el.src = binding.value }, 2000) observer.unobserve(el) } }) observer.observe(el) }
-
-
自定义 hooks
-
主要用来处理复用代码逻辑的封装, 类似 vue2 中 mixins
VueUse 用 hooks 写的开源库 vueuse.org/guide/
-
vue3 本身提供的 hooks,如 useAttrs, useSlots
-
hooks 的写法可以使用组合式函数来写,可以使用的相关的生命周期,以及 ref 和 reactive 等属性, 最后通过返回 promise 回调供外层调用。
import { useAttrs } from 'vue' console.log(useAttrs()) // 获取组件中所有动态属性对象 -
实践: 实现图片转 base64 编码
// 自定义hooks type Options = { el: string } export default (options: Options) => { return new Promise((resolve) => { // 通过promise 返回即可 onMounted(() => { let img: HTMLImageElement = document.querySelector( options.el ) as HTMLImageElement img.onload = () => { resolve(getBase64(img)) } }) const getBase64 = (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') } }) } // 调用 import useBase64 from './hooks/index' useBase64({ el: '#img' }).then((res) => console.log(res))-
hooks 和 自定义指令综合案例: 检测 dom 尺寸的变化大小 (npm 包的封装 ---- 小满大神实在是太强大了!!!)
export const useResize = (el: HTMLElement, callback: Function): void => { let resize = new ResizeObserver((entries) => { callback(entries[0].contentRect) // 将尺寸变化信息传出给外层的回调函数 }) resize.observe(el) } import type { App } from 'vue' // 使用自定义指令 const install = (app: App) => { app.directive('resize', { mounted(el, binding) { // binding.value中传入回调函数 useResize(el, binding.value) }, }) } useResize.install = install // 相当于实现了一个插件的封装
-
全局函数和变量
-
定义变量
app.config.globalProperties.$msg = 'this is pension 退休老实人' app.config.globalProperties.$filters = { format<T>(str: T) { return 'xdfd' + str }, } -
补充扩展声明
type Filter = { format<T>(str: T): string } declare module 'vue' { interface ComponentCustomProperties { $msg: string $filters: Filter } } -
使用
// <div>{{ $msg }}</div> 模板中直接用
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
console.log(app?.proxy?.$msg)
自定义 vue 插件
-
实际上就是通过全局变量,将响应的 vue 组件挂载
import { VNode, createVNode, render, App } from 'vue' import Loading from './index.vue' export default { install(app: App) { const vNode: VNode = createVNode(Loading) // 生成虚拟dom render(vNode, document.body) //render 把我们的Vnode 生成真实DOM 并且挂载到指定节点 app.config.globalProperties.$load = { show: () => vNode.component?.exposed?.show(), hide: () => vNode.component?.exposed?.hide(), } }, } // 使用方法: 引入xx.ts, 然后app.use(xx) import { getCurrentInstance } from 'vue' const instance = getCurrentInstance()?.proxy?$load
vue3 中常使用的 UI 框架
1. element plus 使用setup组合式 (常用)
2. ant design Vue 使用setup函数模式
3. iview 使用的options Api
4. Vant 移动端 -- hooks和业务组件
样式穿透
-
scoped 的渲染规则:
- 给 HTML 的 DOM 节点加一个不重复 data 属性(形如:data-v-123)来表示他的唯一性
- 在每句 css 选择器的末尾(编译后的生成的 css 语句)加一个当前组件的 data 属性选择器(如[data-v-123])来私有化样式
- 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的 data 属性
-
由于 style 中使用 scoped 属性,所以修改样式穿透要使用
:deep(.el-input__inner)
插槽选择器: 修改插槽内容的样式
:slotted(.a)
全局选择器:
:global(div)
动态 css
color: v-bind(ts中定义好的变量)可以通过 js 控制 css
module css
```vue
<style module> </style> // module='zz'
<div :class="$style.xx"></div> // class='zz.xx', 多种样式可以使用数组, 在style中定义相关的类样式
```
import {useCssModule} from 'vue' // 使用hooks const css = useCssModule('名称 zs') 打印或者使用 jsx 语法使用 css 中定义的类演示
tailwindcss
--- 类似 bootstrap 的 css 库
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest // 安装包
npx tailwindcss init -p // 配置文件
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
eventLoop 和 nextTick
eventloop
1.宏任务
script(整体代码)、setTimeout、setInterval、UI交互事件、postMessage、Ajax
2.微任务
Promise.then catch finally、MutaionObserver、process.nextTick(Node.js 环境)
运行机制:
所有的同步任务都是在主进程执行的形成一个执行栈,主线程之外,还存在一个"任务队列",异步任务执行队列中先执行宏任务,然后清空当次宏任务中的所有微任务,然后进行下一个tick如此形成循环。
nextTick
- vue 更新数据是同步的,更新 dom 是异步的。当我们操作 dom 的时候发现数据是上次的,就需要使用 nextTick
- 两种使用方式:
- 回调函数
nextTick(() => {}) - async + await, await 后面的代码都是异步的
await nextTick()
- 回调函数
Vue 开发移动端
- 主要是为了适配各种手机
npm install postcss-px-to-viewport -D-- 目前已经过期,使用 postcss8 版本的
移动端屏幕适配方案
1. `npm install postcss-px-to-viewport-8-plugin -D`
2. 新建postcss.config.js中,注册插件信息
```js
export default {
plugins: {
'postcss-px-to-viewport-8-plugin': {
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 750, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ['*', '!font-size'], // 能转化为vw的属性列表,!font-size表示font-size后面的单位不会被转换
viewportUnit: 'vw', // 希望使用的视口单位
fontViewportUnit: 'vw', // 字体使用的视口单位
// 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
// 下面配置表示类名中含有'keep-px'都不会被转换
selectorBlackList: ['keep-px'],
minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: [/node_modules/], // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
include: [/src/], // 如果设置了include,那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1338, // 横屏时使用的视口宽度
},
},
}
```
函数式编程 h 函数
- 直接跳过编译过程
```ts
const Btn = (props: Props, ctx: any) => {
return h(
'div',
{
onClick: () => {
ctx.emit('on-click', 123)
},
},
ctx.slots.header()
)
}
```
- h 函数接受三个参数:
1. type 元素的类型
2. propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
3. 子节点
- 使用场景: 展示成功和失败按钮,分别显示不同颜色
Vue 响应语法糖 (实践测试案例,暂时不适用生产环境)
import { $ref, $$, $ } from 'vue/macros ref定义变量可以直接修改 ref 对象的值,不需要.value,$$将对象返回 ref 类型的值, 解构保留 ref 属性
环境变量(配置开发和生产环境)
- dist 中 index.html 打开暂时没有反应,需要开启一个服务使用
`npm install http-server -g`
`http-server -p 9002`
新建两个环境配置文件, 配置使用到的常量
- .env.development
- .env.production
在 vue 组件中使用 `import.meta.env`
在 vite.config.ts 中使用通过 loadEnv 使用
```ts
import {loadEnv} from 'vue'
export default ({mode}:any) => {
console.log(loadEnv(mode, process.cwd())) // 具有两个参数
return ...
}
```
webpack 从 0 到 1 配置
- externals 配置 cdn 加载方式
- plugins 扩充 webapck 的功能,如 htmlPluginWebpack, 清除打包内容,解析 vue 模板
- rules 配置文件的解析方式,加载相关的 loader
vue3 的性能优化
谷歌浏览器自带的性能分析工具 lightHouse (灯塔)
vite 是使用 rollup 打包的,可使用相关的可视化工具
npm install rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer'
plugins: [
vue(),
vueJsx(),
visualizer({
open: true,
}),
]
// npm run build打包,可以看到每块打包后的体积
// vite 配置优化
build:{
chunkSizeWarningLimit:2000, // 体积限制警告
cssCodeSplit:true, //css 拆分
sourcemap:false, //不生成sourcemap
minify:false, //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。
assetsInlineLimit:5000 //小于该值 图片将打包成Base64
}
PWA 离线缓存技术
npm install vite-plugin-pwa -D
import { VitePWA } from 'vite-plugin-pwa'
plugins: [
VitePWA({
workbox: {
cacheId: 'XIaoman', //缓存名称
runtimeCaching: [
{
urlPattern: /.*\.js.*/, //缓存文件
handler: 'StaleWhileRevalidate', //重新验证时失效
options: {
cacheName: 'XiaoMan-js', //缓存js,名称
expiration: {
maxEntries: 30, //缓存文件数量 LRU算法
maxAgeSeconds: 30 * 24 * 60 * 60, //缓存有效期
},
},
},
],
},
}),
]
// npm run build 会生成sw.js, 开启http-server即可看到在application中有workServer相关信息
- 图片懒加载
npm install lazyPlugin -S - 虚拟列表
- 多线程,使用 new Worker 创建, worker 脚本与主进程的脚本必须遵守同源限制。他们所在的路径协议、域名、端口号三者需要相同
- vueUse 库已经集成了 webWorker, 防抖节流等功能
web components
- 就是提供自定义标签的能力,并且提供了标签内完整的生命周期 。(利用 h 函数和 shadow 影子 dom,支持模板模式)
- 在 vue 中使用的 defineCustomElement
跨域
- jsonP
- proxy (协议、域名、端口不同,注意是本地开发运行地址,和请求 api 的服务器地址) --- vite.config.js 中配置的是开发环境下的,生产环境要使用 nginx 中 proxy-pass 代理转发
pinia
npm install pinia -S
创建 store import { defineStore } from 'pinia' ,
defineStore(name, { state, getters, actions })
修改 state 不需要使用 vuex 中的 mutations actions 可以执行同步和异步操作 没有 modules,每个 store 都是独立的 具体使用:利用 hooks 调用使用
-
- 修改 state 的方法
- 组件中直接修改
xxx.current++ - 批量改
xxx.$patch({current: xx, xx:de}) - 常用函数写法
xxx.$patch((state) => {}) - 少用覆盖 state, 参数不能少
xx.$state = {} - action 修改
xx.setCurrent(param)
-
- 解构 store
- 普通解构后的 store 不具有响应式, 需要使用 storeToRefs 转换,其实转为 ref,要使用.value 修改值
-
- actions 和 getters
- actions 可以执行同步和异步,成员直接可以相互调用, 不需要返回
- getters 中具有缓存效果,成员可以相互调用, 类似 vue 中的 computed,必须 return 一个值---实际上是一个经过计算后的值
- actions 和 getters
-
API 使用
- $reset --- 重置 state
- $subscribe --- 监听 state 变化
- $onAction -- 监听 actions 变化
Test.$subscribe( (args, state) => { console.log(args, state) }, { detached: true, deep: true, flush: 'post', } ) Test.$onAction((args) => { args.after(() => { console.log('after') }) console.log('----eeee4e') }, true)
-
持久化插件 PiniaPluginContext
使用场景: state 值浏览器刷新数据会 reset,没有缓存
import { createPinia, PiniaPluginContext } from 'pinia' const setStorage = (key: string, value: any) => { localStorage.setItem(key, JSON.stringify(value)) } const getStorage = (key: string) => { return localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key) as string) : {} } // 持久化插件 type Options = { key: string } const __piniaKey__: string = 'ww' const piniaPlugin = (options: Options) => { return (context: PiniaPluginContext) => { const { store } = context const data = getStorage(`${options?.key ?? __piniaKey__}-${store.$id}`) // ?? 表示undefined就使用默认值 store.$subscribe(() => { setStorage( `${options?.key ?? __piniaKey__}-${store.$id}`, toRaw(store.$state) ) }) return { ...data, } } } const store = createPinia() app.use(store) store.use( piniaPlugin({ key: 'pinia', }) )
router
-
vue3 使用 vue-router@4, vue2 使用 vue-router@3
npm install vue-router -S -
router-view, router-link(to 属性)
-
两种路由模式分析
hash 使用#, 利用锚点在页面内进行导航,改变 url 中的 hash 部分不会引起页面刷新, 监听 hashchange 事件
history 不使用#, 提供 pushState 和 replaceState 方法(执行的时候不会触发 popstate 事件,通过 js 调用 history 的 back, go, forward 方法),改变 url 不会引起页面刷新,监听 popstate 事件
-
命名导航(name, path)和编程式导航 (使用 hooks --> useRouter)
-
历史记录
- router-link 中使用 replace 属性 ---浏览器没有历史记录,不可点击前进和后退
- 编程式导航使用
router.replace(xxx) - 自定义前进(几个)后退
router.go(1)router.back()
-
路由传参 (通过 useRoute 获得当前路由信息)
-
- query 和 path 结合使用,query 接收一个对象,参数在浏览器地址栏中可以直接看到;
-
- params 和 name 结合使用,此时页面刷新数据消失,可以使用地址动态传参
-
-
嵌套路由( 父子路由)
- 注意: 通过编程式导航或者的时候,要带上父路由的 path 才可以
-
命名视图
- 使用场景: 一个路由,多个视图,为 router-view 设置 components 属性,注意路由的 name 属性要去掉
-
重定向(别名)
- redirect 和 alias
- redirct 可以为字符串;接受一个 to 参数的回调函数,传递参数;返回相对位置的路径字符串
-
导航守卫
- 前置钩子
// 设置白名单 const whiteList = ['/'] router.beforeEach((to, from, next) => { if (whiteList.includes(to.path) || localStorage.getItem('token')) { next() } else { next('/') } })-
后置钩子 (afterEach)
-
实例: 加载进度条
// LoadingBar.vue wrapper fixed , 修改bar的width, 从0-100 const startLoading = () => { let dom = bar.value as HTMLElement speed.value = 1 // 递归调用,requestAnimationFrame优于settimeInterval timer.value = window.requestAnimationFrame(function fn() { if (speed.value < 90) { speed.value += 1 dom.style.width = speed.value + '%' timer.value = window.requestAnimationFrame(fn) } else { speed.value = 1 window.cancelAnimationFrame(timer.value) } }) } const endLoading = () => { let dom = bar.value as HTMLElement setTimeout(() => { window.requestAnimationFrame(() => { speed.value = 100 dom.style.width = speed.value + '%' }) }, 1000) } defineExpose({ startLoading, endLoading, }) // 在钩子函数中使用 , 需要挂载虚拟dom,render到body中,才可以使用 const vNode = createVnode(LoadingBar) render(vNode, document.body) router.beforeEach((to, from, next) => { vNode.component?.exposed?.startLoading() }) router.afterEach((to, from, next) => { vNode.component?.exposed?.endLoading() }) -
路由元信息: meta 中可以存放一些信息,如 tilte, hasAuth, icon, transition 等
- ts 需要对 meta 做个类型声明
declare module 'vue-router' { interface RouteMeta { title: string transition: string } } -
路由过渡动效
<router-view #default="{ route, Component }"> <transition :enter-active-class="`animate__animated ${route.meta.transition}`"> <component :is="Component"></component> </transition> </router-view> -
滚动行为
- 定义 router 的时候,有一个滚动行为和 history,routes 同级别,可以保存当前滚动的位置,切换路由的时候可以返回原来的滚动位置
const router = createRouter({ scrollBehavior: (to, from, savePosition) => { if (savePosition) { return savePosition } else { return { top: 0 } } }, }) -
动态路由: 常用于权限管理,如 vip 用户可以看到三个菜单,普通用户只能看到一个菜单。后台返回路由表,前端处理
- 方法: 使用
router.addRoute(),router.getRoute()
router.addRoute({ path: route.path, name: route.name, component: () => import(`../views/${route.component}`), // 拼接不可以使用@别名 }) // 注意: get请求需要通过{params:{ax: xx, b: xx}}, post可以通过{a:xx,b:xx} - 方法: 使用