最近开源了一套后台管理模板Wocwin-Admin,是基于 Vue3.2、TypeScript、Vite4、Pinia、Element-Plus、Qiankun(微前端) 技术栈,借此归纳一下Vue3.2的新语法。
import { createApp } from "vue";
import App from "./App.vue";
// 所有业务api接口
import api from "@/api";
const instance = createApp(App);
// 注册全局api方法
instance.config.globalProperties.$api = api;
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
const { appContext } = getCurrentInstance()
const global = appContext.config.globalProperties
// 获取所有业务api接口
console.log(global.$api)
</script>
输出结果如下:
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
// proxy 就是当前组件实例,相当于Vue2单文件的this——没有全局的、路由、状态管理之类的
const { proxy, appContext } = getCurrentInstance()
// 这个 global 就是全局实例(可以获取挂载全局的方法)
const global = appContext.config.globalProperties
</script>
其实本质上
Vue3
每个组件还是一个根节点,因为DOM
树只能是树状结构的,只是Vue3
在编译阶段新增了判断
,如果当前组件不只一个根元素,就添加一个fragment
组件把这个多根组件的给包起来,相当于这个组件还是只有一个根节点,并且fragment
组件是一个不会被渲染出来的内置组件
<template>
<div>1div>
<div>2div>
<div>3div>
template>
<template>
<el-button ref="buttonRef">el-button>
template>
<script setup lang="ts">
import { ref} from "vue";
// 注意:这个变量名和 DOM 上的 ref 属性必须同名,会自动形成绑定
const buttonRef= ref(null)
// 获取到 DOM
console.log(buttonRef.value)
script>
<script setup lang="ts">
import { onBeforeUnmount, onDeactivated } from 'vue'
// 组件卸载前
onBeforeUnmount(() => {
// 清除定时器
clearTimeout(timer)
// 清除监听事件
window.removeAddEventListener('所监听的事件')
})
// 退出缓存组件
onDeactivated(() => {
// 清除定时器
clearTimeout(timer)
// 清除监听事件
window.removeAddEventListener('所监听的事件')
})
</script>
通常情况:
ref
用于创建一个响应式的基本数据类型(如字符串、数字或布尔值)
。它返回一个包含响应式值的对象,在script
可以通过 .value 属性访问该值,template
中直接使用。
通常情况:reactive
用于创建一个响应式的对象。它返回一个新的响应式对象,该对象与原始对象具有相同的属性和值。
<script setup lang="ts">
import { ref, reactive } from "vue"
const count = ref(1)
// 输出结果:1 注意:ref 返回的属性在 template 中直接使用,不需要再.value
console.log(count.value)
const state= reactive({
name: "wocwin",
address: "广州市",
...
})
console.log(state.name) // wocwin
</script>
解决直接解构会丢失响应式
:这两共同点就是用来创建响应式的引用的
,主要用来取出响应式对象里的属性,或者解构响应式对象,解构出来的属性值依然是响应式属性
。
<script setup lang="ts">
import { reactive, toRef, toRefs } from "vue"
const state= reactive({
name: "wocwin",
address: "广州市",
})
// 这样能拿到 name / address,但是会变成普通变量,没有响应式效果
const { name, address} = state
// 取出来一个响应式属性
const name = toRef(state, 'name')
// 这样解构出来的所有属性都是有响应式的
const { name, address} = toRefs(state)
</script>
watch
是一个函数,能接收三个参数,参数一是监听的属性,参数二是接收新值和老值的回调函数,参数三是配置项
watch
是对传入的一个或多个值进行监听,触发时会返回新值和老值,且默认第一次不会执行
watchEffect
是传入一个立即执行函数,所以默认第一次就会执行,且不需要传入监听内容,会自动收集函数内的数据源作为依赖,当依赖发生变化时会重新执行函数,不会返回新值和老值。
<script setup lang="ts">
import { watch, ref, reactive,watchEffect } from "vue"
const name = ref("wocwin")
const state = reactive({
address: "广州市",
age: 22,
children: []
})
// 监听 ref 属性
watch(name, (newName, oldName) => { ... })
// 监听单个属性
watch(
() => state.address,
(newAddress, oldAddress) => { ... }
)
// 监听多个属性,数组放多个值,返回的新值和老值也是数组形式
watch([data.age, data.address], ([newAge, newAddress], [oldAge, oldAddress]) => {
...
})
// 第三个参数是一个对象,为可配置项
watch(data.children, (newList, oldList) => { ... }, {
// 这两个和 Vue2 一样
immediate: true,
deep: true,
})
// 在 watch 回调函数中能接收第三个参数 onInvalidate,为清除副作用的函数,首次触发监听的回调函数(handler)不会执行 onInvalidate,之后每次触发默认会先执行 onInvalidate
watch(name, (newName, oldName,onInvalidate) => {
console.log("wocwin")
onInvalidate(() => {
console.log(2222)
})
})
// onInvalidate 的使用场景就是:比如监听的回调函数(handler)里有一些异步操作,当再次触发 watch 的时候可以用它来对前面未完成的异步任务执行取消/重置/初始化等操作。
/**
*watchEffect
*/
watchEffect(() => {
// 会自动收集这个函数使用到的属性作为依赖,进行监听
// 监听的是 state.name 属性,不会监听 state
console.log(state.name)
})
</script>
<script setup lang="ts">
import { computed } from "vue"
const props = defineProps({
modelValue: {
type: [String, Number, Array]
},
})
const emits = defineEmits(['update:modelValue'])
// 函数写法
const isFirst = computed(() => {
...
})
// 对象写法
// vue3 v-model简写
let childSelectedValue: any = computed({
get() {
return props.modelValue
},
set(val) {
emits('update:modelValue', val)
}
})
</script>
<script setup lang="ts">
import { nextTick} from 'vue'
// 方式 一
const handleClick = async () => {
await nextTick()
console.log("wocwin")
}
// 方式二
nextTick(() => {
console.log("wocwin")
})
// 方式三
nextTick().then(() => {
console.log("wocwin")
})
</script>
Vue2 中逻辑的抽离复用一般用 mixins:但是其没有独立命名空间,mixins 会和组件内部产生命名冲突
Vue3 中逻辑抽离复用的 hooks 语法,其实就是一个函数,可以传参,拿返回值来用。
import { storeToRefs } from "pinia";
import { Theme } from "./interface";
import { ElMessage } from "element-plus";
import { DEFAULT_PRIMARY } from "@/config";
import { useGlobalStore } from "@/store/modules/global";
import { getLightColor, getDarkColor } from "@/utils/color";
import { asideTheme, AsideThemeType } from "@/styles/theme/aside";
/**
* @description 全局主题 hooks
* */
export const useTheme = () => {
const globalStore = useGlobalStore();
const { primary, isDark, isGrey, isWeak, asideInverted, layout } = storeToRefs(globalStore);
// 切换暗黑模式 ==> 并带修改主题颜色、侧边栏颜色
const switchDark = () => {
const body = document.documentElement as HTMLElement;
if (isDark.value) body.setAttribute("class", "dark");
else body.setAttribute("class", "");
changePrimary(primary.value);
setAsideTheme();
};
// 修改主题颜色
const changePrimary = (val: string | null) => {
if (!val) {
val = DEFAULT_PRIMARY;
ElMessage({ type: "success", message: `主题颜色已重置为 ${DEFAULT_PRIMARY}` });
}
// 计算主题颜色变化
document.documentElement.style.setProperty("--el-color-primary", val);
document.documentElement.style.setProperty(
"--el-color-primary-dark-2",
isDark.value ? `${getLightColor(val, 0.2)}` : `${getDarkColor(val, 0.3)}`
);
for (let i = 1; i <= 9; i++) {
const primaryColor = isDark.value ? `${getDarkColor(val, i / 10)}` : `${getLightColor(val, i / 10)}`;
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, primaryColor);
}
globalStore.setGlobalState("primary", val);
};
// 灰色和弱色切换
const changeGreyOrWeak = (type: Theme.GreyOrWeakType, value: boolean) => {
const body = document.body as HTMLElement;
if (!value) return body.removeAttribute("style");
const styles: Record<Theme.GreyOrWeakType, string> = {
grey: "filter: grayscale(1)",
weak: "filter: invert(80%)"
};
body.setAttribute("style", styles[type]);
const propName = type === "grey" ? "isWeak" : "isGrey";
globalStore.setGlobalState(propName, false);
};
// 设置侧边栏样式 ==> light、inverted、dark
const setAsideTheme = () => {
// 默认所有侧边栏为 light 模式
let type: AsideThemeType = "light";
// transverse 布局下菜单栏为 inverted 模式
if (layout.value == "transverse") type = "inverted";
// 侧边栏反转色目前只支持在 vertical 布局模式下生效
if (layout.value == "vertical" && asideInverted.value) type = "inverted";
// 侧边栏 dark 模式
if (isDark.value) type = "dark";
const theme = asideTheme[type!];
for (const [key, value] of Object.entries(theme)) {
document.documentElement.style.setProperty(key, value);
}
};
// init theme
const initTheme = () => {
switchDark();
if (isGrey.value) changeGreyOrWeak("grey", true);
if (isWeak.value) changeGreyOrWeak("weak", true);
};
return {
initTheme,
switchDark,
changePrimary,
changeGreyOrWeak,
setAsideTheme
};
};
<template>
<div class="theme-item">
<span>主题颜色span>
<el-color-picker v-model="primary" :predefine="colorList" @change="changePrimary" />
div>
template>
<script setup lang="ts">
import { useTheme } from "@/hooks/useTheme";
const { changePrimary } = useTheme();
script>
// 父组件写法
<template>
<child v-model:name="name" v-model:age="age" />
template>
<script setup lang="ts">
import { ref } from "vue"
const name = ref('wocwin')
const age = ref(22)
script>
// 子组件
<script setup lang="ts">
const emit = defineEmits(['update:name', 'update:age'])
const handleClick = () => {
console.log('点击了')
emit('update:name', '这是新的名字')
}
script>
<style lang="scss" scoped>
:deep(.el-form) {
.el-form-item {
...
}
style>
<template>
<div class="name">wocwindiv>
template>
<script setup>
import { ref } from "vue"
const str = ref('#f00')
script>
<style scoped lang="scss">
.name {
color: v-bind(str); // JS 中的色值变量 #f00 就赋值到这来了
}
style>
wocwin-admin地址
wocwin-admin在线预览地址
基于ElementUi或AntdUI再次封装基础组件文档
基于Element-plus再次封装基础组件文档(vue3+ts)