vue
MVVM(Model-View-ViewModel)架构
『View』:视图层(UI 用户界面)
『ViewModel』:业务逻辑层(一切 js 可视为业务逻辑)
『Model』:数据层(存储数据及对数据的处理如增删改查)发现传统的vue2 逻辑比较分散 可读性差 可维护性差
对比vue3 逻辑分明 可维护性 高
重写双向绑定
vue2 基于Object.defineProperty()实现 vue3 基于Proxy proxy与Object.defineProperty(obj, prop, desc)方式相比有以下优势: //丢掉麻烦的备份数据 //省去for in 循环 //可以监听数组变化 //代码更简化 //可以监听动态新增的属性; //可以监听删除的属性 ; //可以监听数组的索引和 length 属性; let proxyObj = new Proxy(obj,{ get : function (target,prop) { return prop in target ? target[prop] : 0 }, set : function (target,prop,value) { target[prop] = 888; } })
Vue3 优化Vdom
在Vue2中,每次更新diff,都是全量对比,Vue3则只对比带有标记的,这样大大减少了非动态内容的对比消耗
patch flag 优化静态树
配置环境构建项目
vite 的优势
冷服务 默认的构建目标浏览器是能 在 script 标签上支持原生 ESM 和 原生 ESM 动态导入
HMR 速度快到惊人的 模块热更新(HMR)
Rollup打包 它使用 Rollup 打包你的代码,并且它是预配置的 并且支持大部分rollup插件
使用vite初始化一个项目
npm init vite@latest vue create <project>
public 下面的不会被编译 可以存放静态资源
assets 下面可以存放可编译的静态资源
components 下面用来存放我们的组件
App.vue 是全局组件
main ts 全局的ts文件
index.html 非常重要的入口文件 (webpack,rollup 他们的入口文件都是enrty input 是一个js文件 而Vite 的入口文件是一个html文件,他刚开始不会编译这些js文件 只有当你用到的时候 如script src="xxxxx.js" 会发起一个请求被vite拦截这时候才会解析js文件)
vite config ts 这是vite的配置文件具体配置项 后面会详解
VsCode Vue3 插件推荐 Vue Language Features (Volar)
v- 开头都是vue 的指令
v-text 用来显示文本
v-html 用来展示富文本
v-if 用来控制元素的显示隐藏(切换真假DOM)
v-else-if 表示 v-if 的“else if 块”。可以链式调用
v-else v-if条件收尾语句
v-show 用来控制元素的显示隐藏(display none block Css切换)
v-on 简写@ 用来给元素添加事件
v-bind 简写: 用来绑定元素的属性Attr
v-model 双向绑定
v-for 用来遍历元素
v-on修饰符 冒泡案例
v-once 性能优化只渲染一次
v-memo 性能优化会有缓存
ref
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个
.value
property,指向该内部值注意被ref包装之后需要.value 来进行赋值
// Ref TS对应的接口、 interface Ref<T> { value: T }
isRef
判断是不是一个ref对象
import { ref, Ref,isRef } from 'vue' let message: Ref<string | number> = ref("我是message") let notRef:number = 123 const changeMsg = () => { message.value = "change msg" console.log(isRef(message)); //true console.log(isRef(notRef)); //false }
shallowRef
创建一个跟踪自身
.value
变化的 ref,但不会使其值也变成响应式的 浅层次响应import { Ref, shallowRef } from 'vue' type Obj = { name: string } let message: Ref<Obj> = shallowRef({ name: "小满" }) // 这样改变值页面不会响应 const changeMsg = () => { message.value.name = '大满' }
shallowRef和ref不能一块使用会被影响 也就是不能在一个方法里同时改变ref和shallowRef的值
triggerRef
强制更新页面DOM
let message: Ref<Obj> = shallowRef({ name: "小满" }) const changeMsg = () => { message.value.name = '大满' triggerRef(message) }
reactive
用来绑定复杂的数据类型 例如 对象 数组 他是不可以绑定普通的数据类型
这样是不允许 会给我们报错import { reactive } from 'vue' let person = reactive({ name:"小满" }) person.name = "大满"
数组异步赋值问题
这样赋值页面是不会变化的因为会脱离响应式
let person = reactive<number[]>([]) setTimeout(() => { person = [1, 2, 3] console.log(person); },1000) // 解决方法 //使用push import { reactive } from 'vue' let person = reactive<number[]>([]) setTimeout(() => { const arr = [1, 2, 3] person.push(...arr) console.log(person); },1000) //包裹一层对象 type Person = { list?:Array<number> } let person = reactive<Person>({ list:[] }) setTimeout(() => { const arr = [1, 2, 3] person.list = arr; console.log(person); },1000)
readonly
拷贝一份proxy对象将其设置为只读
shallowReactive
只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图
markRaw
会给值加一个__skip__的属性reactive碰到这个属性会跳过不会做proxy代理
toRef
只能适用于响应式的值 非响应式的化毫无变化
一般用于提取出某一项的值
import { reactive, toRef } from 'vue' const obj = { foo: 1, bar: 1 } const state = toRef(obj, 'bar') // bar 转化为响应式对象 const change = () => { state.value++ console.log(obj, state); }
toRefs
可以帮我们批量创建ref对象主要是方便我们解构使用
import { reactive, toRefs } from 'vue' const obj = reactive({ foo: 1, bar: 1 }) let { foo, bar } = toRefs(obj) foo.value++ console.log(foo, bar);
toRaw
将响应式对象转化为普通对象
import { reactive, toRaw } from 'vue' const obj = reactive({ foo: 1, bar: 1 }) const state = toRaw(obj) // 响应式对象转化为普通对象 const change = () => { console.log(obj, state); }
computed用法
计算属性就是当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值。
1.函数形式
import { computed, reactive, ref } from 'vue' let price = ref(0)//$0 let m = computed<string>(()=>{ return `$` + price.value }) price.value = 500
2.对象形式
<script setup lang="ts"> import { computed, ref } from 'vue' let price = ref<number | string>(1)//$0 let mul = computed({ get: () => { return price.value }, set: (value) => { price.value = 'set' + value } }) </script> <style> </style>
watch用法
watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用
watch第一个参数监听源
watch第二个参数回调函数cb(newVal,oldVal)
watch第三个参数一个options配置项是一个对象{
immediate:true //是否立即调用一次deep:true //是否开启深度监听
}
监听Ref 案例
import { ref, watch } from 'vue' let message = ref({ nav:{ bar:{ name:"" } } }) watch(message, (newVal, oldVal) => { console.log('新的值----', newVal); console.log('旧的值----', oldVal); },{ immediate:true, deep:true })
监听多个ref 注意变成数组啦
import { ref, watch ,reactive} from 'vue' let message = ref('') let message2 = ref('') watch([message,message2], (newVal, oldVal) => { console.log('新的值----', newVal); console.log('旧的值----', oldVal); })
监听Reactive
使用reactive监听深层对象开启和不开启deep 效果一样
import { ref, watch ,reactive} from 'vue' let message = reactive({ nav:{ bar:{ name:"" } } }) watch(message, (newVal, oldVal) => { console.log('新的值----', newVal); console.log('旧的值----', oldVal); })
监听reactive 单一值
import { ref, watch ,reactive} from 'vue' let message = reactive({ name:"", name2:"" }) watch(()=>message.name, (newVal, oldVal) => { console.log('新的值----', newVal); console.log('旧的值----', oldVal); })
watchEffect高级侦听器
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次
let message = ref<string>('') let message2 = ref<string>('') watchEffect(() => { //console.log('message', message.value); console.log('message2', message2.value); })
清除副作用
就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖
import { watchEffect, ref } from 'vue' let message = ref<string>('') let message2 = ref<string>('') watchEffect((oninvalidate) => { //console.log('message', message.value); oninvalidate(()=>{ }) console.log('message2', message2.value); })
停止跟踪 watchEffect 返回一个函数 调用之后将停止更新
onTrigger 可以帮助我们调试 watchEffect
const stop = watchEffect((oninvalidate) => { //console.log('message', message.value); oninvalidate(()=>{ }) console.log('message2', message2.value); },{ flush:"post", //onTrigger 可以帮助我们调试 watchEffect onTrigger () { } }) stop()
更多的配置项
副作用刷新时机 flush 一般使用post
比如监听dom元素时使用post 否则dom元素刚开始获取会是undefined
vue3生命周期
简单来说就是一个组件从创建 到 销毁的 过程 成为生命周期
在我们使用Vue3 组合式API 是没有
beforeCreate 和 created 这两个生命周期的
onBeforeMount()
在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。
onMounted()
在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问
onBeforeUpdate()
数据更新时调用,发生在虚拟 DOM 打补丁之前。
onUpdated()
DOM更新后,updated的方法即会调用。
onBeforeUnmount()
在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
onUnmounted()
卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
父子组件传参
<template> <div class="layout"> <Menu v-bind:data="data" title="我是标题"></Menu> <div class="layout-right"> <Header></Header> <Content></Content> </div> </div> </template> <script setup lang="ts"> import Menu from './Menu/index.vue' import Header from './Header/index.vue' import Content from './Content/index.vue' import { reactive } from 'vue'; const data = reactive<number[]>([1, 2, 3]) </script>
子组件接受值
通过defineProps 来接受 defineProps是无须引入的直接使用即可
如果我们使用的TypeScript
可以使用传递字面量类型的纯类型语法做为参数
如 这是TS特有的
<template> <div class="menu"> 菜单区域 {{ title }} <div>{{ data }}</div> </div> </template> <script setup lang="ts"> defineProps<{ title:string, data:number[] }>() </script>
TS 特有的默认值方式
withDefaults是个函数也是无须引入开箱即用接受一个props函数第二个参数是一个对象设置默认值
type Props = { title?: string, data?: number[] } withDefaults(defineProps<Props>(), { title: "张三", data: () => [1, 2, 3] })
子组件给父组件传参
是通过defineEmits派发一个事件
<script setup lang="ts"> import { reactive } from 'vue' const list = reactive<number[]>([4, 5, 6]) const emit = defineEmits(['on-click']) //如果用了ts可以这样两种方式 // const emit = defineEmits<{ // (e: "on-click", name: string): void // }>() const clickTap = () => { emit('on-click', list) } </script>
我们在子组件绑定了一个click 事件 然后通过defineEmits 注册了一个自定义事件
点击click 触发 emit 去调用我们注册的事件 然后传递参数
父组件接受子组件的事件
我们从Menu 组件接受子组件派发的事件on-click 后面是我们自己定义的函数名称getList
会把参数返回过来
<template> <div class="layout"> <Menu @on-click="getList"></Menu> <div class="layout-right"> <Header></Header> <Content></Content> </div> </div> </template> <script setup lang="ts"> import Menu from './Menu/index.vue' import Header from './Header/index.vue' import Content from './Content/index.vue' import { reactive } from 'vue'; const data = reactive<number[]>([1, 2, 3]) const getList = (list: number[]) => { console.log(list,'父组件接受子组件'); } </script>
子组件暴露给父组件内部属性
通过defineExpose
我们从父组件获取子组件实例通过ref
<Menu ref="refMenu"></Menu> //这样获取是有代码提示的 <script setup lang="ts"> import MenuCom from '../xxxxxxx.vue' //注意这儿的typeof里面放的是组件名字(MenuCom)不是ref的名字 ref的名字对应开头的变量名(refMenu) const refMenu = ref<InstanceType<typeof MenuCom>>() </script>
这时候父组件想要读到子组件的属性可以通过 defineExpose暴露
const list = reactive<number[]>([4, 5, 6]) defineExpose({ list })
配置全局组件
例如组件使用频率非常高(table,Input,button,等)这些组件 几乎每个页面都在使用便可以封装成全局组件
在main.ts 引入我们的组件跟随在createApp(App) 后面 切记不能放到mount 后面这是一个链式调用用
其次调用 component 第一个参数组件名称 第二个参数组件实例
import { createApp } from 'vue' import App from './App.vue' import './assets/css/reset/index.less' import Card from './components/Card/index.vue' createApp(App).component('Card',Card).mount('#app')
配置局部组件
就是在一个组件内(A) 通过import 去引入别的组件(B) 称之为局部组件
应为B组件只能在A组件内使用 所以是局部组件
如果C组件想用B组件 就需要C组件也手动import 引入 B 组件
配置递归组件
原理跟我们写js递归是一样的 自己调用自己 通过一个条件来结束递归 否则导致内存泄漏
<div style="margin-left:10px;" class="tree"> <div :key="index" v-for="(item,index) in data"> <div @click='clickItem(item)'>{{item.name}} </div> <TreeItem @on-click='clickItem' v-if='item?.children?.length' :data="item.children"></TreeItem> </div> </div>
动态组件用法
什么是动态组件 就是:让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
在挂载点使用component标签,然后使用v-bind:is=”组件”
import A from './A.vue' import B from './B.vue' <component :is="A"></component>
通过is 切换 A B 组件
使用场景
tab切换 居多
注意事项
1.在Vue2 的时候is 是通过组件名称切换的 在Vue3 setup 是通过组件实例切换的
2.如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with `markRaw` or using `shallowRef` instead of `ref`.
Component that was made reactive:这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处 节省性能开销 推荐我们使用shallowRef 或者 markRaw 跳过proxy 代理
修改如下
const tab = reactive<Com[]>([{ name: "A组件", comName: markRaw(A) }, { name: "B组件", comName: markRaw(B) }])
插槽
插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。
匿名插槽
// 子组件内部 <template> <div> <slot></slot> </div> </template> <Dialog> // 父组件使用 <template v-slot> <div>2132</div> </template> </Dialog>
具名插槽
具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中
// 子组件内部 <div> <slot name="header"></slot> <slot></slot> <slot name="footer"></slot> </div> // 父组件使用 <Dialog> <template v-slot:header> <div>1</div> </template> <template v-slot> <div>2</div> </template> <template v-slot:footer> <div>3</div> </template> </Dialog>
插槽简写
<Dialog> <template #header> <div>1</div> </template> <template #default> <div>2</div> </template> <template #footer> <div>3</div> </template> </Dialog>
作用域插槽
<Dialog> <template #header> <div>1</div> </template> <template #default="{ data }"> <div>{{ data }}</div> </template> <template #footer> <div>3</div> </template> </Dialog> <div> <slot name="header"></slot> <div> <div v-for="item in 100"> <slot :data="item"></slot> </div> </div> <slot name="footer"></slot> </div>
异步组件
父组件引用子组件 通过defineAsyncComponent加载异步配合import 函数模式便可以分包
<script setup lang="ts"> import { reactive, ref, markRaw, toRaw, defineAsyncComponent } from 'vue' const Dialog = defineAsyncComponent(() => import('../../components/Dialog/index.vue')) //完整写法 const AsyncComp = defineAsyncComponent({ // 加载函数 loader: () => import('./Foo.vue'), // 加载异步组件时使用的组件 loadingComponent: LoadingComponent, // 展示加载组件前的延迟时间,默认为 200ms delay: 200, // 加载失败后展示的组件 errorComponent: ErrorComponent, // 如果提供了一个 timeout 时间限制,并超时了 // 也会显示这里配置的报错组件,默认值是:Infinity timeout: 3000 })
suspense
<suspense>
组件有两个插槽。它们都只接收一个直接子节点。default
插槽里的节点会尽可能展示出来。如果不能,则展示fallback
插槽里的节点。<Suspense> <template #default> <Dialog> <template #default> <div>我在哪儿</div> </template> </Dialog> </template> <template #fallback> <div>loading...</div> </template> </Suspense>
Teleport传送组件
Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。
主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响
使用方法
通过to 属性 插入指定元素位置 to="body" 便可以将
Teleport
内容传送到指定位置<Teleport to="body"> <Loading></Loading> </Teleport>
多个使用场景
<Teleport to=".modal1"> <Loading></Loading> </Teleport> <Teleport to=".modal2"> <Loading></Loading> </Teleport>
动态控制teleport
使用disabled 设置为 true则 to属性不生效 false 则生效
<teleport :disabled="true" to='body'> <A></A> </teleport>
内置组件keep-alive
有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。
开启keep-alive 生命周期的变化
初次进入时: onMounted> onActivated
退出后触发 deactivated
再次进入:
只会触发 onActivated
事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivated中
<!-- 基本 --> <keep-alive> <A></A> </keep-alive> <!-- 多个条件判断的子组件 --> <keep-alive> <comp-a v-if="a > 1"></comp-a> <comp-b v-else></comp-b> </keep-alive>
include
和exclude
<keep-alive :include="" :exclude="" :max=""></keep-alive>
Provide / Inject
provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。
// 爷爷组件 <script setup lang='ts'> import { provide, ref } from 'vue' import A from './components/A.vue' let flag = ref<number>(1) provide('flag', flag) </script> // 子组件 <script setup lang='ts'> import { inject, Ref, ref } from 'vue' const flag = inject<Ref<number>>('flag', ref(1)) const change = () => { flag.value = 2 } </script>
兄弟组件传参
1.一种是通过父组件中转
A 组件派发事件通过App.vue 接受A组件派发的事件然后在Props 传给B组件 也是可以实现的
2.通过自定义的EventBus实现
3.下载现成的插件Mitt
v-model
在Vue3 v-model 是破坏性更新的
v-model在组件里面也是很重要的
v-model 其实是一个语法糖 通过props 和 emit组合而成的
1.默认值的改变
prop:value -> modelValue;
事件:input -> update:modelValue;
v-bind 的 .sync 修饰符和组件的 model 选项已移除
新增 支持多个v-model
新增 支持自定义 修饰符 Modifiers
// 子组件 <template> <div v-if='propData.modelValue ' class="dialog"> <div class="dialog-header"> <div>标题</div><div @click="close">x</div> </div> <div class="dialog-content"> 内容 </div> </div> </template> <script setup lang='ts'> type Props = { modelValue:boolean } const propData = defineProps<Props>() const emit = defineEmits(['update:modelValue']) const close = () => { emit('update:modelValue',false) } </script>
// 父组件 <template> <button @click="show = !show">开关{{show}}</button> <Dialog v-model:title='title' v-model="show"></Dialog> </template> <script setup lang='ts'> import Dialog from "./components/Dialog/index.vue"; import {ref} from 'vue' const show = ref(false) </script> <style> </style>
自定义修饰符
添加到组件
v-model
的修饰符将通过modelModifiers
prop 提供给组件。在下面的示例中,我们创建了一个组件,其中包含默认为空对象的modelModifiers
prop<script setup lang='ts'> type Props = { modelValue: boolean, title?: string, modelModifiers?: { default: () => {} } titleModifiers?: { isBt:boolean default: () => {} } } const propData = defineProps<Props>() const emit = defineEmits(['update:modelValue', 'update:title']) const close = () => { console.log(propData.modelModifiers); emit('update:modelValue', false) emit('update:title', propData?.titleModifuers?.isBt ? '变态' : propData.title.value) }
自定义指令
Vue3指令的钩子函数
created 元素初始化的时候
beforeMount 指令绑定到元素后调用 只调用一次
mounted 元素插入父级dom调用
beforeUpdate 元素被更新之前调用
update 这个周期方法被移除 改用updated
beforeUnmount 在元素被移除前调用
unmounted 指令被移除后调用 只调用一次
Vue2 指令 bind inserted update componentUpdated unbind在setup内定义局部指令
但这里有一个需要注意的限制:必须以
vNameOfDirective
的形式来命名本地自定义指令,以使得它们可以直接在模板中使用<template> <button @click="show = !show">开关{{show}} ----- {{title}}</button> <Dialog v-move-directive="{background:'green',flag:show}"></Dialog> </template> <script setup> type Value = { background:string } const vMoveDirective: Directive = { created: () => { console.log("初始化====>"); }, beforeMount(...args: Array<any>) { // 在元素上做些操作 console.log("初始化一次=======>"); }, mounted(el: any, dir: DirectiveBinding<Value>) { el.style.background = dir.value.background; console.log("初始化========>"); }, beforeUpdate() { console.log("更新之前"); }, updated() { console.log("更新结束"); }, beforeUnmount(...args: Array<any>) { console.log(args); console.log("======>卸载之前"); }, unmounted(...args: Array<any>) { console.log(args); console.log("======>卸载完成"); }, }; </script>
.生命周期钩子参数详解
第一个 el 当前绑定的DOM 元素第二个 binding
instance:使用指令的组件实例。
value:传递给指令的值。例如,在 v-my-directive="1 + 1" 中,该值为 2。
oldValue:先前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否有更改都可用。
arg:传递给指令的参数(如果有的话)。例如在 v-my-directive:foo 中,arg 为 "foo"。
modifiers:包含修饰符(如果有的话) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}。
dir:一个对象,在注册指令时作为参数传递。例如,在以下指令中第三个 当前元素的虚拟DOM 也就是Vnode
第四个 prevNode 上一个虚拟节点,仅在
beforeUpdate
和updated
钩子中可用函数简写
你可能想在
mounted
和updated
时触发相同行为,而不关心其他的钩子函数。那么你可以通过将这个函数模式实现<template> <div> <input v-model="value" type="text" /> <A v-move="{ background: value }"></A> </div> </template> <script setup lang='ts'> import A from './components/A.vue' import { ref, Directive, DirectiveBinding } from 'vue' let value = ref<string>('') type Dir = { background: string } const vMove: Directive = (el, binding: DirectiveBinding<Dir>) => { el.style.background = binding.value.background } </script>
案例权限按钮
<template> <div class="btns"> <button v-has-show="'shop:create'">创建</button> <button v-has-show="'shop:edit'">编辑</button> <button v-has-show="'shop:delete'">删除</button> </div> </template> <script setup lang='ts'> import { ref, reactive, } from 'vue' import type {Directive} from 'vue' //permission localStorage.setItem('userId','xiaoman-zs') //mock后台返回的数据 const permission = [ 'xiaoman-zs:shop:edit', 'xiaoman-zs:shop:create', 'xiaoman-zs:shop:delete' ] const userId = localStorage.getItem('userId') as string const vHasShow:Directive<HTMLElement,string> = (el,bingding) => { if(!permission.includes(userId+':'+ bingding.value)){ el.style.display = 'none' } } </script> <style scoped lang='less'> .btns{ button{ margin: 10px; } } </style>
自定义hooks
hooks库 vueUse
案例转换图片为base64
import { onMounted } from 'vue' type options = { el: string } export default function (options: options): Promise<{ baseUrl: string }> { return new Promise(resolve => { onMounted(() => { let img: HTMLImageElement = document.querySelector(options.el) as HTMLImageElement img.onload = () => { resolve({ baseUrl: baseUrl(img) }) } }) const baseUrl = (el: HTMLImageElement): string => { const canvas: HTMLCanvasElement = 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/png') } }) }
全局函数和变量
globalProperties
由于Vue3 没有Prototype 属性 使用 app.config.globalProperties 代替 然后去定义变量和函数
// 之前 (Vue 2.x) Vue.prototype.$http = () => {} // 之后 (Vue 3.x) const app = createApp({}) app.config.globalProperties.$http = () => {}
过滤器
在Vue3 移除了
我们正好可以使用全局函数代替Filters
案例
app.config.globalProperties.$filters = { format<T>(str: T): string { return `${str}` } }
声明文件 不然TS无法正确类型 推导
type Filter = { format<T>(str:T):string } // 声明要扩充@vue/runtime-core包的声明. 如果vue不好使则用@vue/runtime-core // 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型. declare module 'vue' { export interface ComponentCustomProperties { $filters: Filter } }
setup获取方法和值
import {ref,reactive,getCurrentInstance} from 'vue' const app = getCurrentInstance() console.log(app?.proxy?.$filters.format('js'))
自定义插件
插件
插件是自包含的代码,通常向 Vue 添加全局级功能。你如果是一个对象需要有install方法Vue会帮你自动注入到install 方法 你如果是function 就直接当install 方法去使用使用插件
在使用 createApp() 初始化 Vue 应用程序后,你可以通过调用 use() 方法将插件添加到你的应用程序中。实现一个Loading
import type { App, VNode } from 'vue' import loading from './index.vue' import { createVNode, render } from 'vue' export default { install(app: App) { // 转成vnode 然后挂载在全局 const vNode: VNode = createVNode(loading) render(vNode, document.body) app.config.globalProperties.$loading = { show: vNode.component?.exposed?.show, hide: vNode.component?.exposed?.hide } // app.config.globalProperties.$loading.show() console.log(vNode, 'qweqwe') } }
import Loading from './components/loading' let app = createApp(App) app.use(Loading) type Lod = { show: () => void, hide: () => void } //编写ts loading 声明文件放置报错 和 智能提示 declare module '@vue/runtime-core' { export interface ComponentCustomProperties { $loading: Lod } } app.mount('#app')
使用
<template> <div></div> </template> <script setup lang='ts'> import { ref,reactive,getCurrentInstance} from 'vue' const instance = getCurrentInstance() instance?.proxy?.$Loading.show() setTimeout(()=>{ instance?.proxy?.$Loading.hide() },5000) // console.log(instance) </script> <style> *{ padding: 0; margin: 0; } </style>
nextTick