来让我们看看进阶知识吧,大致包含以下内容,内容并不完成,但是想在这里先分享一下。
目录
环境变量
基本使用
import.meta.env // 获取当前环境变量信息
自定义环境变量
src/.env.development
VITE_HTTP = 开发环境接口地址
src/.env.production
VITE_HTTP = 生产环境接口地址
生效 package.json
"scripts": {
"dev": "vite --mode development"
}
跨域
后端cors
"Access-Control-Allow-Origin": "地址"
前端Proxy
vite.config.ts
server: {
proxy: {
'/api': {
target: "地址",
rewrite: (path) => path.replace(正则) // 重写地址,可选
}
}
}
API
defineProps
vue3.3之后支持
- 泛型支持
<script generic="T">
defineProps<{
name: T[]
}>
</script>
defineEmits
- ts写法
// 子组件
const emit = defineEmits<{
(event: string, msg1: string, msg2: string): void
}>()
const fun = () => {
// (事件, 参数)
emit('send', 'hello', 'heihei')
}
// 父组件
<Child @send="getMsg"></Child>
const getMsg = (msg1: string, msg2: string) => {
console.log(msg1, msg2)
}
- 简化
const emit = defineEmits<{
'send': [msg1: string, msg2: string]
}>
defineOptions
- 常用:定义组件名
defineOptions({
name: "组件名"
})
defineSlots
- 常用于约束插槽
// 子组件
const title = "子组件插槽"
<div>
<slot :title="title"></slot>
</div>
defineSlots<{
default(props: {title: string}):void
}>()
// 父组件
<Child>
<template #default="{title}">
{{title}}
</template>
</Child>
Hooks
useAttrs
获取组件上传输的属性
// 父组件
<template>
<ChildComponent title="传递title属性" id="传递id属性"/>
</template>
// 子组件
<script setup lang="ts">
import {useAttrs} from "vue"
const attrs = useAttrs()
attrs.title // 传递title属性
attrs.id // 传递id属性
</script>
自定义hook
需求:自定义指令和hook监听dom的宽高变化
function useResize(el: HTMLElement, callback: Function) {
// callback(entities: 被观察元素的数组, observer: 当前实例)
let resizeObserver = new ResizeObserver((entries) => {
// 获取第一个元素的contentRect,里面包含宽高属性
callback(entries[0].contentRect)
})
resizeObserver.observe(el)
}
/**
* 自定义指令
*/
const install = (app: App) => {
app.directive('resize', {
// el: 绑定的元素 binding.value: 指令的值
mounted(el, binding) {
useResize(el, binding.value)
}
})
}
useResize.install = install
export default useResize
// hooks使用
onMounted(() => {
useResize(document.querySelector("#resize") as HTMLElement, (dom: any) => {
console.log(dom)
})
})
// 自定义指令
createApp(App).use(useResize)
<div v-resize="fun">
</div>
const fun = (dom: any) => {
console.log(dom)
}
插件
自定义插件
需求:自定义全局加载消息插件
// 组件
defineExpose({
show
hide
})
// 插件
import type {App, VNode} from "vue";
import Loading from "./index.vue"
import {createVNode, render} from "vue";
export default {
install(app: App) {
// 将组件转为VNode,便可以访问组件上的dom内容
const LoadingVnode: VNode = createVNode(Loading)
// LoadingVnode上是component是空的,需要挂载一下(这里是全局的,挂载到body上)
render(LoadingVnode, document.body)
// 获取到组件上的方法(组件上的方法记得defineExpose暴露出来)
app.config.globalProperties.$myLoading = {
// LoadingVnode.component?.exposed可以获取到组件上暴露的方法
show: LoadingVnode.component?.exposed?.show,
hide: LoadingVnode.component?.exposed?.hide
}
}
}
Web Components
原生写法
这是什么东西呢,就是原生写组件,非常好玩,我们可以尝试写一个原生按钮组件,有一个很重要的点,那就是shadow DOM
index.html
<script src="./btn.js"></script>
<body>
可以复用, 试试看
<my-btn></my-btn>
<my-btn></my-btn>
<my-btn></my-btn>
</body>
btn.js(通过js操作dom)
class Btn extends HTMLElement {
constructor() {
super()
const shadowDom = this.attachShadow({ mode: 'open' })
this.p = this.h('p')
this.p.innerText = "我的按钮"
this.p.setAttribute('style', 'width:120px;height:40px;border:1px solid black;border-radius: 5px;')
shadowDom.appendChild(this.p)
}
// 创建元素的h函数
h (el) {
return document.createElement(el)
}
}
window.customElements.define('my-btn', Btn)
btn.js(通过模板操作)
这样用的好处是什么呢,样式是隔离的,不会影响其他元素,是不是很不错,有没有想封装一个自己的UI库呢
class Btn extends HTMLElement {
constructor() {
super()
const shadowDom = this.attachShadow({ mode: 'open' })
this.template = this.h('template')
this.template.innerHTML = `
<style>
p{
width:120px;
height:40px;
border:1px solid black;
border-radius: 5px;
}
</style>
<p class="p">
我是按钮
</p>
`
shadowDom.appendChild(this.template.content.cloneNode(true))
}
// 创建元素的h函数
h (el) {
return document.createElement(el)
}
}
window.customElements.define('my-btn', Btn)
vue + 写法
配置vite.config.ts
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.includes("hang-")
}
}
})
]
封装组件
组件名需以.ce.vue结尾
组件导入
import {defineCustomElement} from 'vue'
const Btn = defineCustomElement(封装的vue组件)
window.customElements.define('hang-btn', Btn)
nextTick
原理
解决的问题:vue中dom的更新是异步,数据的更新是同步的,有时数据更新,dom却没有更新。
使用
需求:有一个聊天对话框,当输入消息,点击发送,消息对话框增加一条消息,当增加到一定量,有滚动条(记得设置属性)时,我们继续添加消息,则应滚动到最下方增加用户体验。
// 1. 获取dom
const box = ref<HTMLDivElement>("box")
// 2. 设置距离(此时,会有一个错位)
box.value!.scrollTop = 99999
// 3. 方案一 nextTick + callback
nextTick(() => {
box.value!.scrollTop = 99999
})
// 4. 方案二 nextTick + async await
async 操作dom的函数
await nextTick()
下面代码全为异步
box.value!.scrollTop = 99999
函数
h函数
优势:直接跳过模板编译
场景:封装一个组件
const Btn = () => {
// h(节点,属性,内容)
return h('button', {
style: {},
class: "",
onClick: () => {console.log('click')}
}, '按钮')
}
全局
全局函数
main.ts
// 定义$filter函数
app.config.globalProperties.$filter = {
format<T>(str: T) {
return `hang-${str}`
}
}
// 使用
<template>
<div>
{{ $filter.format("yyds") }} // hang-yyds
</div>
</template>
全局变量
main.ts
// 定义$env变量
app.config.globalProperties.$env = 'dev'
// 使用
<template>
<div>
{{ $env }} // dev
</div>
</template>
报错处理
type Filter = {
format<T>(str: T): string
}
declare module 'vue/runtime-core' {
export interface ComponentCustomProperties {
$filter: Filter,
$env: string
}
}
script获取值
import {getCurrentInstance} from "vue" // 获取当前组件实例
const instance = getCurrentInstance()
instance.proxy.$env
instance.proxy.$filter.format
样式
scoped
会给dom节点添加一个不重复属性来确定其唯一性
deep
定制化UI组件库样式
:deep(类名){
样式
}
全局选择器
:global(类) {
样式
}
动态样式
<template>
<div class="box1">
我是一个盒子
<div :class="[hang.box2]">
我是第二个盒子
</div>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue"
const style = ref({
color: "red",
fontSize: "14px"
})
</script>
<style scoped module="hang">
.box1 {
color: v-bind(style.color);
font-size: v-bind(style.fontSize);
}
.box2 {
border: 1px solid #ccc;
}
</style>
Suspense组件
骨架屏
用于加载异步组件,搭配骨架屏组件使用
<template>
<Suspense>
<template #default>需要显示的组件,由于需要发起请求,可能会耗时</template>
<template #fallback>骨架屏组件,当请求还未完成时显示</template>
</Suspense>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
// 异步组件
const AsyncComp = defineAsyncComponent(() => import('@/components/AsyncComp.vue'));
</script>
keep-alive组件
缓存
用于缓存,可以用于缓存信息,比如说我们可以包裹一个form表单
<template>
<KeepAlive>
<form>
休需要缓存的表单, 如用户输入的信息进行缓存,切换页面返回信息依然存在
</form>
</KeepAlive>
</template>
部分缓存
如果范围太大,我们只想缓存一部分呢
<KeepAlive :include="["组件"]">
<KeepAlive :exclude="["组件"]">
限制数量
如果想要限制缓存数量
<KeepAlive :max="10">
生命周期
使用之后新增了什么东西呢(两个生命周期)
<script setup lang="ts">
import { onMounted, onUnmounted, onActivated, onDeactivated } from 'vue';
onMounted(() => {
console.log("我只发生一次, 可以把初始化请求给我");
}),
onActivated(() => {
console.log("每次切换包裹的组件,我就执行一次");
})
onDeactivated(() => {
console.log("keep-alive卸载");
})
onUnmounted(() => {
console.log("我不再执行了");
}),
</script>
性能优化
分析打包后的体积
由于vue3项目默认打包基于rollup
npm install rollup-plugin-visualizer
引入插件vite.config.ts
import {visualizer} from "rollup-plugin-visualizer"
plugins: [visualizer({open: true})]
打包
npm run build
通过配置
vite.config.ts
build: {
cssCodeSplit: true, // css分片
sourcemap: false, // 不生成sourcemap
minify: "", // esbuild打包速度最快 terser打包体积最小
assetsInlineLimit: 4000,
}
PWA离线存储技术
让web网页无限接近于Native应用,也就是原生应用
启动打包后的项目服务
npm install http-server -g
dist目录下
http-server -p 端口号