二十六,hooks使用
hooks,复用代码进行封装,钩子函数。和vue2中的mixins相似,将共同部分抽离出来。也有开源的库,vueUse,包含各种hooks,可以在 官网 查看。
我们自己怎么编写呢,要知道hook底层就是个函数,返回promise方便我们后续then处理,函数里面就是具体功能的实现了,同时也有onMounted等生命周期处理不同钩子下的内容。
// 自定义hooks
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:base64(img)
})
}
})
const base64 = (el:HTMLImageElement)=>{
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = el.width
canvas.height = el.height
//参数:使用的图像,剪切x,剪切y,图像宽,图像高,画布x,画布y,使用图像的宽,使用图像的高
ctx?.drawImage(el,0,0,canvas.width,canvas.height)
return canvas.toDataURL('image/jpeg')
}
})
}
我们编写一个图片转 base64 的 hook 叫 useBase64 ,然后的是一个promise函数,里面已经在onMounted中获取dom元素,然后canvas转换,返回结果;那在使用的时候就简单了。
import useBase64 from './index'
useBase64({
el:'#img'
}).then((res)=>{
console.log(res.baseurl);
})
二十七,全局变量和方法
全局的变量和方法,那就是在入口文件,main.ts中进行定义了,可以挂载到app(createApp)上,这样全局都可以访问到,之前全局定义的组件也是在main.ts中。
vue2中是,Vue.prototype.$xxx=xxx,
在vue3中,对象有些不同,是在app.config.globalProperties上
// 定义全局变量和方法
app.config.globalProperties.$env = 'dev'
app.config.globalProperties.$filter = {
format<T>(str:T){
return 'myfilter-'+str
}
}
type Filter = {
format<T>(str:T):string
}
// 对应是声明文件
declare module 'vue'{
export interface ComponentCustomProperties{
$filter:Filter,
$env:string
}
}
在使用中,template中可以直接进行使用,在script中需要通过实例获取得到。
// js
var app = getCurrentInstance()
console.log(app?.proxy?.$filter.format('好好'));
// html
<div>{{ $env }}</div>
<div>{{ $filter.format('111') }}</div>
二十八,样式框架使用 elplus
vue3 支持新版样式框架,支持ts,这里介绍的是 element-plus。
安装,引入,use注册,然后使用,不多介绍;写法和按需引入官网都有。
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
app.use(ElementPlus);
这里对scoped进行一个解释,html单页面,css需要进行模块化,避免样式错乱。
规则:
1,给html的dom节点加一个不重复的data属性,(data-v-123)来表示唯一性。
2,在每句css选择器末尾(生成之后的)加一个当前组件的data属性,来私有化。
3,如果组件内部包括其他组件,只会给其他组件的最外层加上当前的data属性。
有时候 2,3 会冲突,因为data属性是加在末尾的,组件里面的层次没有data属性,无法命中,所以提供了一个样式穿透,vue2--> /deep/ vue3--> :deep(选择器)。
源码里,scoped是通过postCss中的一个插件来处理的,接收一个css文件和一个api来分析,修改它的规则(css抽象树的方式),跟babel类似。
还有 :slotted() 插槽,:global() 全局 , v-bind() 动态修改css 的用法。
1,:slotted(选择器),处理插槽中的样式。
// html
<slot></slot>
// css
:slotted(.a) {
color: red;
}
2,:global(选择器) ,处理全局的样式
:global(div) {
background-color: antiquewhite;
}
3,v-bind(),参数如果是值,直接使用;如果是对象,需要引号包裹
// js
let style1 = ref('blue')
let style = ref({
color: "pink",
});
// css
.dym {
background-color : v-bind(style1); /* 如果是值,直接使用 */
color: v-bind("style.color"); /* 如果是对象,使用引号包裹 */
}
也可以使用 module 模块化的形式,类似于react。给style定义module名称,然后对象形式访问。
css module 使用,默认叫$style ,可以自定义名字,使用名字.
// html
<div :class="[mName.dym2, mName.border]">
css module 使用,默认叫$style ,可以自定义名字,使用名字
</div>
<style module="mName">
.dym2 {
color: blueviolet;
}
.border {
border: 1px solid #2cc;
}
</style>
这里介绍一个库 tailwindcss,js编写css, postcss解析,更加的快捷,不过打包编译时间也会增加。
二十九,编写全局插件,app.use() 注册
定义一个loading组件,通过封装为一个插件,可以在全局注册使用。要知道,能够被app.use()注册的应该具有什么,首先导出一个对象,也可以是个函数,必须具有install()的方法,因为app.use()就是会调用函数或者对象里的install来实现注册。
需要将loading组件引入,然后使用 createVNode(loading)来生成vNode类型,抽象元素,然后render(VNode,document.body)挂载在body下,最后,app.config.globalProperties 上挂载,vNode.component?.exposed?.xxx 可以得到exposed暴露出来的属性方法。install会返回app参数,main.ts中的app。
// 可以导出一个对象,也可以是函数
// 通过在main.ts中使用 app.use()进行注册
import type { App, VNode } from "vue";
import loading from "./index.vue";
import { createVNode, render } from "vue";
// 1,对象里必须有install,会进行调用
export default {
// install 会回传一个app函数,(main.ts中的全局app)
install(app: App) {
// 需要将 组件 挂载到全局,vNode类型
const vNode: VNode = createVNode(loading);
// 挂载 render 函数 (vnode,挂载点)
render(vNode, document.body);
// 可以获取到 component 的方法
// vNode.component.setupState 可以获得到所有的函数,最好使用 exposed 访问暴露出的方法
// 可以全局挂载
app.config.globalProperties._loading = {
show: vNode.component?.exposed?.show,
hide: vNode.component?.exposed?.hide,
};
console.log(vNode, vNode.component?.exposed);
},
};
然后 main.ts 中引入loading,app.use(loading)注册。用以下方法就可以使用了。
var instance = getCurrentInstance()
instance?.proxy?._loading.show()
setTimeout(() => {
instance?.proxy?._loading.hide()
}, 3000);
三十,nextTick的使用
知识准备:
event loop,js是单线程,不然同时操作dom会出现问题。h5中的web workder支持多线程,也不能操作dom,一般是将费时的任务分出去,优化体验。
js 因为单线程,会出现很多排队,会等待很久,所以出现了异步。
同步:代码从上到下按顺序执行。
异步:分为宏任务和微任务。
宏任务:script,setTimeout,setInterval,ui交互,postMessage,Ajax
微任务:promise then catch finally,mutationObserver(监听dom变化),process.nextTick(nodejs环境),await。
同步任务在主进程形成一个执行栈,除此之外,还有一个任务队列。异步任务先执行宏任务,然后清空当次宏任务的所有微任务。
nextTick 就是创建下一个异步任务,等到同步任务执行完毕后执行。
我们先来看个例子,看和输出结果一样吗:
async function Prom() {
console.log("X");
await Promise.resolve(); // 微任务
console.log("Y");
}
setTimeout(() => {
console.log(1);
Promise.resolve().then(() => {
console.log(2);
});
}, 0);
setTimeout(() => {
console.log(3);
Promise.resolve().then(() => {
console.log(4);
});
}, 0);
Promise.resolve().then(() => {
console.log(5);
});
Promise.resolve().then(() => {
console.log(6);
});
Promise.resolve().then(() => {
console.log(7);
});
Promise.resolve().then(() => {
console.log(8);
});
Prom();
console.log(0);
// 执行结果: X 0 5 6 7 8 Y 1 2 3 4
nextTick() 的执行原理就是 flushJobs() 函数。
执行watch的对应队列,flush:pre的,顺序队列,父级在子级前创建,然后执行flush:post的回调。
永远返回promise,微任务,所以会先走上面的任务,然后走微任务,nextTick。
const change = async () => {
message.value = "小小";
console.log(div.value?.innerText, "同步情况下打印的值并没有改变"); // 原始
await nextTick();
console.log(div.value?.innerText, "异步情况下打印的值改变了"); // 小小
for (let i = 0; i < 5; i++) {
num.value = i;
}
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
// 5 5 5 5 5
};
三十一,unocss 原子化css ☆
unocss,目的是实现原子化css,可以在html中直接写css内容,动态根据你的规则生成对应的css,可以简化css的编写,也可以在熟练后更清晰的查看html。我形容的是我的理解,更深入的了解请到官网。
安装unocss,然后在 vite.config.ts 中引入注册。这里可以配置一些预设,也可以根据规则自定义
- presetIcons图标库,需要安装库@iconify-json (icones官网 https://icones.js.org/ 找一套)
- presetAttributify这个预设,就可以直接在标签上使用 属性 方式。
- presetUno 工具类,可以直接使用,比如tailwind里的属性。
import unoCss from 'unocss/vite'
plugins: [
unoCss({
// 预设
/* presetIcons图标库,需要安装库@iconify-json (icones官网 https://icones.js.org/ 找一套)
presetAttributify这个预设,就可以直接在标签上使用 属性 方式。
presetUno 工具类,可以直接使用,比如tailwind里的属性。
*/
presets:[presetIcons(),presetAttributify(),presetUno()],
rules:[
['flex',{display:'flex'}],
['red',{color:'red'}],
[/^m-(\d+)$/,([,d])=>({margin:`${Number(d)*10}px`})]
],
shortcuts:{
cike:['flex','red','m-3']
}
})
],
然后使用的方法,可以是class,也可以是属性形式,还可以使用一些内设好的样式。
<div class="flex red m-1">刺客</div>
<div class="cike">刺客2</div>
<div m="2" red>刺客3</div>
<div class="bg-red-100">tailwind</div>
图标使用:
<div class="i-ic-baseline-add-shopping-cart"></div>
现阶段还是试验阶段,是否可以使用到团队里,进行规则的规范也很重要,可以多了解一些tailwind和unocss的关系。
三十二,h函数
h函数,是vue中插件虚拟节点的辅助函数。接收三个参数,标签名,属性对象,子节点。
这里和 createVNode() 方法一样的,render进行渲染。
<btn @on-click="getBtn">
<template #default>232</template>
</btn>
// h 函数
type Props = {
text?: string;
};
const btn = (props: Props, ctx: any) => {
return h(
"div",
{
class: ["rounded", "bg-green-400", "text-gray-50", "text-center", "p-2"],
onClick: () => {
ctx.emit("on-click", "按钮click");
},
},
// props.text
ctx.slots.default()
);
};
var getBtn = (str: string) => {
console.log(str);
};
三十三,了解use函数基础的实现
之前的例子里,写了一个loading插件,通过app.use()来注册的,那我们来了解一下use的基础实现
要知道,之前强调一定要有install函数,接收的参数是 插件和options配置。
import type {App} from 'vue'
// 引入 app
import {app} from './main'
// 对 泛型T 做个约束,必须有install函数
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.error('已经注册',plugin);
}else{
plugin.install(app,...options)
installList.add(plugin)
}
// 这里可以进行链式调用。
return app
}
通过一个set来管理插件,避免重复,如果重复,给出提示;如果没有重复,执行插件的install方法,然后加入到set里,最后,返回app,可以支持链式调用。类似于app.use(A).use(B)的用法也是很常见的。
import { MyUse } from "./myUse";
MyUse(Loading)
这样测试也是可以实现loading的使用的。