1、ts 报错 Could not find a declaration file for module
ts 提示这个错误通常由以下几种情况:
- 引入的 npm 包不是用 typescript 编写的;
- 没有找到正确的类型定义文件;
针对第一种,我们可以尝试安装 @types/包名
来安装这个包对应的类型文件,如:
npm i @types/lodash -D
如果没有找到,我们也可以手动为它声明一下,此时可以直接到 Vite 项目自动生成的 env.d.ts
中添加声明,如果没有这个文件,可以在根目录下新建 shims-vue.d.ts
,(只要以 .d.ts 结尾就行,但是这个文件中不能包含 import 语句,不然 declare module 失效,如果你要 import,建议再建一个文件),然后输入以下内容,注意最后要重新启动服务才生效。
● Typescript 书写声明文件(可能是最全的)
● TypeScript 声明文件全解析
● declaration 声明文件
// declare 声明一个ambient module(即:没有内部实现的 module声明)
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
declare module 'XX' // 当前文件中不能包含 import 语句,不然 declare module 失效
// xx即你包不能找到声明的包名,如 declare module 'lodash'
2、vue3+vite中 process.env 的配置&使用方法
vite 中的环境变量通过 import.meta.env
来暴露出来,但是不是所有文件中都可以获取到,如果我们想通过 process.env
的方式来读取,则需要像下面这样配置一下。
a. 在 vite.config.js 中,添加以下部分代码
import { defineConfig, loadEnv } from 'vite'
export default ({ mode }) =>
defineConfig({
define: {
'process.env': loadEnv(mode, process.cwd())
},
...
}
b. 在文件中使用变量值
// 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API, // api 的 base_url
timeout: 5000 // 请求超时时间
})
3、vue + ts 配置路径别名后 ts 报错
有 2 个位置需要配置一下,一个 vite.config.js
,一个是 tsconfig.json
(baseUrl 和 paths)
export default ({ mode }) =>
defineConfig({
base: './',
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}
{
"baseUrl": "./",
"compilerOptions": {
//...
"paths": {
"@/*": [
"./src/*"
]
},
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"**/*.d.ts",
"src/**/*.vue"
]
}
4、commitlint 无效,commit-msg 不执行
可能是配置不正确或者 commitlint
本身的缓存问题导致的,建议先删除 .husky
目录下的 commit-msg
文件,然后使用下面的命令重新生成 commit-msg
文件即可,注意内容格式不要错 。
npx husky add .husky/commit-msg "npx --no-install commitlint --edit "$1""
5、commitlint 校验不通过,本地修改全部丢失,如何找回?
由于 commit
有规范校验,当我们提交的格式不正确时候,它会把内容弹出去,不会 commit
上去,同时默认会把本地的修改全部重做。此时,大家不要慌,commitlint
其实会自动帮我们在 stash
里做一个备份,我们可以通过 git stash list
查看一下,发现有这样的一条或多条记录。
此时,我们就可以使用 git stash pop
或 git stash apply
弹出草稿箱里的数据来恢复。类似的丢失,我们也可以先用 git reflog
先查看日志,然后配合 git reset --hard
版本号 进行恢复。
6、ElementPlus 按需引入后,单独调用 ElMessage、ElLoading 或其它组件时,出现样式丢失问题。
就是由于按需引入导致的,当前单独使用某个组件时(比如在 axios 中),Vite 并不知道你要用到这个组件,所以并没有去加载它对应的样式,此时我们只需要补上相关组件的样式即可,当然你全量引入也许。
方法一:在 main.ts 中单独引入那些组件的样式
// 引入Elmessage和Elloading的css样式文件
import 'element-plus/theme-chalk/el-loading.css'
import 'element-plus/theme-chalk/el-message.css'
方法二(推荐):通过 vite-plugin-style-import
和 consola
来实现自动引入
a. 首先安装一下,
yarn add vite-plugin-style-import consola -D
b. 在 vite.config.ts
中添加如下配置
// vite.config.ts
import {
createStyleImportPlugin,
ElementPlusResolve,
} from 'vite-plugin-style-import'
export default {
plugins: [
// ...
createStyleImportPlugin({
resolves: [ElementPlusResolve()],
libs: [
{
libraryName: 'element-plus',
esModule: true,
resolveStyle: (name: string) => {
return `element-plus/theme-chalk/${name}.css`
},
},
]
}),
],
}
7、Non-relative paths are not allowed when ‘baseUrl’ is not set. Did you forget a leading ‘./’
出现此问题,需要你在 tsconfig.json
中配置 baseUrl
为 “.” 或 “./”
8、vite.config.ts 中注入的 scss/less 变量无效
我们通常会使用 preprocessorOptions
字段来注入 scss/less
变量和样式,以便在组件中直接使用,但有时,我们配置后发现并不生效,在 vue 组件中读取不到注入的变量(组件库按需引入场景),此时可以按如下步骤排查问题:
- 检查
vite.config.ts
中是否正确配置additionalData
字段; - vue 文件中的
style
标签是否配置了lang="scss"
或lang="less"
。
有时,控制台还会提示 @forward rules must be written before any other
这个错误,意思是某些样式必须要在其它样式之前引入,这里通常是指我们自定义过的 ElementUI 样式,我们稍微修改下配置,像下面这样既可以完美解决,这样 main.ts
中就不需要再去引入 elementui 的样式了。参考:element-plus 更换主题色报错
css: {
preprocessorOptions: {
scss: {
prependData: '@use "@/assets/style/element/index.scss" as *;', // elementui,如果你想导入所有样式 @use "element-plus/theme-chalk/src/index.scss" as *;
additionalData: `@import "@/assets/style/vars.scss";` // 注入scss变量
}
}
}
你也可以把 vars.scss 放到 index.scss 中,然后只引入一个文件
// element/index.scss
/* 覆盖elementui的变量值 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #3ca483
)
)
);
@import '../vars.scss';
@import '../mixin.scss';
// vite.config.ts
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "@/assets/style/element/index.scss" as *;', // 引入elementui变量和自定义scss变量
}
}
}
最后,如果打包的时候报错,提示样式重复引入了(见下图 this module is already being loaded)。你可能需要如下的配置(根据自己实际情况修改):
css: {
preprocessorOptions: {
scss: {
additionalData: (content, loaderContext) => {
if (
loaderContext.endsWith('assets/style/element/index.scss') ||
loaderContext.endsWith('assets/style/mixin.scss') ||
loaderContext.endsWith('assets/style/vars.scss')
) {
return content
}
return `@use "@/assets/style/element/index.scss" as *; ${content}`
} // 引入elementui变量和自定义scss变量
}
}
}
扩展知识:
@use
和@import
的区别在于:
- 不管使用了多少次样式表,@use都只会引入和执行一次。
- 与全局使用相反,@use是有命名空间的,而且只在当前样式表中生效。
- 以 —或者 _开头的命名空间被认为是私有的,不会被引入其他样式表。
element-plus 自动引入修改主题色
unipapp 解决无法编译sass
9、推荐使用 VSCode 和 Vue 官方拓展 Volar,它为 Vue 3 提供了全面的 IDE 支持
你需要在 VSCode 中禁用(工作区) Vetur,避免两者冲突。
10、Uncaught TypeError: Cannot read properties of undefined (reading ‘config’)
使用 app.config.globalProperties
取代 Vue.prototype
11、ElementPlus 组件默认展示的文字是英文
因为 Element-Plus 框架默认显示的是英文版,需要修改语言配置,有两种方法来设置。
a. 如果你是全量引入 ElementPlus,可以在 main.ts
中添加如下代码:
import { createApp } from "vue";
import App from "./App.vue";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import 'dayjs/locale/zh-cn';
import locale from "element-plus/lib/locale/lang/zh-cn";
createApp(App).use(ElementPlus, { locale }).mount("#app");
b. 如果你是按需引入 ElementPlus,main.ts 中可能无法直接配置,此时可以利用 ElementPlus 提供的内置组件 ConfigProvider 来实现。我们可以在 App.vue 文件中像如下这样配置:
<template>
<el-config-provider :locale="locale">
<Layout />
</el-config-provider>
</template>
<script setup lang="ts">
import Layout from '@/layout/index.vue'
import { ElConfigProvider } from 'element-plus'
import zhCN from 'element-plus/lib/locale/lang/zh-cn'
const locale = zhCN
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
12、vite 项目修改 node_modules 中的模块后,引入的文件依然是旧的
为了提高运行速度,vite 在首次运行时,对 node_modules
中的包进行了 esmodule
化,存储在 node_modules/.vite
目录下,这样下次就可以直接使用浏览器读取,提高加载速度。当我们修改了某一个 node_modules
后,vite 并不知道,也不会去更新 .vite
目录,所有我们只需要先删除 .vite
目录,然后重新运行项目即可。
13、setTimeout 和 setInterval 的类型定义
当我们再 ts 中使用 setTimeout
和 setInterval
时,发现并没有 Timer 这个基本类型,于是我们直接使用 let timer:null | number = null 这种方式来定义,但是当我们给 timer 再次赋值的时候,发现还是会报错,提示 setTimeout 返回的不是 number 类型,此时只需要在 setTimeout 或 setInterval 前面加上 window
来调用即可。
let timer:null | number = null;
timer = window.setTimeout(() => {}, 1000)
14、TS 中如何定义 defineProps 和 defineEmits 的类型
interface Props {
mode?: string
type?: string
data?: any
}
interface Emit {
(e: 'approve' | 'cancel' | 'remove'): void
(e: 'revise-record' | 'update-progress', id: number): void
(e: 'save', form: Forms): void
}
// withDefaults 用来指定props的默认值,如果不需要可以直接写成
// const props = defineProps<Props>()
const props = withDefaults(defineProps<Props>(), {
mode: 'add',
type: 'form',
data: {}
})
const emit = defineEmits<Emit>()
// 按上面的写法,当 Emit 中只有一项时,Eslint 可能会提示 prefer-function-type,意思是建议你使用函数类型声明
// 所以我们把 interface 修改为 type 即可解决
type Emit = (e: 'select' | 'remove', data: Person, index: number) => void
const emit = defineEmits<Emit>()
// 当然你也可以在 eslintrc.js 中禁用该规则
// .eslintrc.json
{
"rules": {
"@typescript-eslint/prefer-function-type": "off"
}
}
15、Vue3 setup 语法为组件添加 name 属性
Vue3 默认会根据文件名来推断组件的名称,如果组件对应的文件名为 index.vue
,那么在 vue-dev-tools
中看到的组件就都是 Index
,不太好区分,而且在使用 keep-alive
的 include
和 exclude
功能时,必须显示的声明 name
才能正常执行逻辑。
在以往的 Vue 版本中,我们可以直接在 script
中通过 name
属性为组件指定名字,但是如果你使用 vue3
的 setup
语法,是不支持直接定义 name
的,你需要通过 vite-plugin-vue-setup-extend-plus 插件来实现。
注意:
vite-plugin-vue-setup-extend
这个老版本插件会影响debug
功能,导致断点位置不准确,所以一定要安装vite-plugin-vue-setup-extend-plus
。
a、安装
yarn add vite-plugin-vue-setup-extend-plus -D
b、配置 vite.config.ts
import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend-plus'
export default defineConfig({
plugins: [ VueSetupExtend() ]
})
c、使用
<script lang="ts" setup name="Demo"></script>
16、watch 监听多个数据
const country = ref()
const province = ref()
// 监听单个数据
watch(
() => country.value,
newCountry => {
console.log('--country---', newCountry)
}
)
// 监听多个数据
// 第一个参数 () => [country.value, province.value] 传入要监听的数据
// 第二个参数是回调函数,参数分别代表更改后与更改前的值,([newCountry, newProvince], [oldCountry, oldProvince])
watch(
() => [country.value, province.value],
([newCountry, newProvince], [oldCountry, oldProvince]) => {
console.log(oldCountry, '--country---', newCountry)
console.log(oldProvince, '--province---', newProvince)
}
)
17、Vue3 + TS 中使用 svg 图标
直接参考使用 vite-plugin-svg-icons
来支持,具体请查看它的 文档,这里简单附上我项目中的配置:
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
// 获取svg路径
const getSvgPath = (folder: string) => {
return path.resolve(process.cwd(), 'src/assets/svg/' + folder)
}
export default ({ mode }) => {
return defineConfig({
plugins: [
vue(),
// ...
createSvgIconsPlugin({
iconDirs: [getSvgPath('portal'), getSvgPath('menu')], // 指定需要缓存的图标文件夹(支持多个)
symbolId: 'icon-[name]' // 指定symbolId格式
})
]
})
18、获取当前组件实例
const { appContext } = getCurrentInstance()!
19、使用最新版 vue-router 配置 404 页面时提示如下错误
Catch all routes (“*“) must now be defined using a param with a custom regexp
原因:Vue Router
不再使用path-to-regexp
,而是实现了自己的解析系统,该系统允许路由排名并启用动态路由。由于我们通常会在每个项目中添加一条单独的包罗万象的路线,因此支持的特殊语法没有太大的好处。参数的编码是跨路线编码,无一例外使事情更容易预测。
[{
path: '/404',
name: '404',
component: () => import('@/views/404.vue')
},
{
path: '/:pathMatch(.*)',
redirect: '/404'
}]
20、Vue3 v-for 循环中如何赋值和获取 ref
由于数组中每一个元素 key
都是唯一的,可以通过 key 值给对应内容赋值
<div v-for=(item, index) in arrList>
<div :ref="el => myRefs[index]=el"></div>
</div>
<script setup lang="ts">
const myRefs = ref<Ref<string[]>>([])
return {
myRefs
}
</script>
21、Element UI 中 ElMessageBox.prompt 中 inputValidator 的用法
当我们在使用 ElMessageBox.prompt
时,如果要对其中的 input 内容进行校验,需要用到 inputValidator
这个字段,配置自定义的验证规则,它是一个函数,返回 true 和 false,如果返回的是字符串,将作为展示的错误文案。代码如下:
ElMessageBox.prompt('量化单位:', '', {
customClass: 'prompt-custom-unit',
showClose: false,
closeOnClickModal: false,
closeOnPressEscape: false,
dangerouslyUseHTMLString: true,
// inputErrorMessage: '量化单位不能为空',
inputValidator: (value: string) => {
if (!value) return '量化单位不能为空'
if (value.length > 10) return '量化单位最多10个字符'
return true
},
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
const value = instance.inputValue
row.unit = value
} else {
row.unit = ''
}
done()
}
})
22、Vue3 Extraneous non-props attributes (id) were passed to component but could not be automatically
原因:
- 一个组件可能有多个根节点,请确保组件在单一根节点下
- 外部组件不要直接放在 template 下,最外层加div包裹,如下图:
23、对 el-form 中的表单进行验证时,提示的错误文案是英文的
当我们给
el-form-item
添加了required
属性之后,它内容的表单就需要进行验证,但是验证时,发现提示的错误文案是英文的,并不是我们手动配置的message
,这是由于 ElementUI 的国际化没有处理完善导致的,我们可以通过一下方法解决:
去掉 el-form-item
上的 required
属性,在 rules 中配置 required: true
,如果有其他判断条件,可以通过 validator
配置单独的验证方法(注意:当表单元素不存在时,el-form 的校验是不生效的)。
<el-form-item class="form-item-extra-name" prop="name">
<el-checkbox v-model="saveAsGroup">保存为分组</el-checkbox>
<el-input
v-model="ruleForm.name"
clearable
:maxlength="20"
placeholder="请输入名称"
/>
</el-form-item>
const validateName = (rule, value, callback) => {
if (!saveAsGroup.value) return callback()
if (!value) return callback(rule.message)
}
const rules = {
name: [
{
required: true,
message: '请输入分组名称',
validator: validateName,
trigger: 'change'
}
]
}
24、vue3 + vite2 引入 assets 目录下的图片时路径不对
<!-- vite 下无效 -->
<img :src="require('@/assets/img/happy.png')" />
在
vue + webpack
的架构下,当我们想引入src/assets/img
下的图片时,通过会使用require
方式来引入,但vite
架构下已经行不通了,因为require
是webpack
架构下的方法,我们可以通过以下方法解决:
1. 在资源 url 前添加 @
或者 src
<!-- 路径前直接添加 @ 前缀,需要在 vite.config.ts 中配置资源别名 -->
<img src="@/assets/img/happy.png" />
<!-- 路径前直接添加 src 前缀 -->
<img :src="`src/assets/img/${item.icon}.png`" />
但是经过尝试,发现打包后,仍然无法访问图片资源,看打包后的代码,发现图片已经被转换为
base64
,且做了路径的映射,那应该还是路径不对导致的。继续查询 vite 文档,于是有了第 2 种方法。
2. 通过 vite 文档中提到的 new URL + import.meta.url 方式来解决,我们可以封装一个方法,在其它地方直接用,如下所示:
import.meta.url
是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL。
/*
* @name: getAssets 获取assets目录的文件路径
* @name: 文件名,需要包括扩展名
* @folder: 需要读取的目录,默认为img目录
*/
export const getAssets = (name: string, folder = 'img') => {
// 注意路径一定要以../assets开头,开发环境下,vite 会自动拼上 src
return new URL(`../assets/${folder}/${name}`, import.meta.url).href
}
在 vue 文件中使用
<template>
<img :src="imgUrl" />
</template>
<script setup lang="ts">
import { getAssets } from "./utils/index";
const imgUrl = getAssets('happy.png')
</script>
scss
中引入背景图
.el-slider {
&::before {
content: ' ';
width: 20px;
height: 20px;
display: inline-block;
background: url('@/assets/img/happy.png');
background-size: 100%;
position: absolute;
margin-left: -35px;
top: -9px;
}
}
相关文章
● vue3+vite assets动态引入图片的几种方式,解决打包后图片路径错误不显示的问题
● vite+vue3打包后图片404问题:已解决
25、el-autocomplete 组件下拉框重复弹出的问题
在使用 ElementPlus 中的
el-autocomplete
组件时,发现了一个问题,就是当我搜索一个字符串,匹配到数据后,当我点击了下拉框中的数据后,下拉框消失后会再次弹出,影响我的其它操作。
经过查阅资料,发现这是个由来已久的 issue,官方 demo 也有这个问题。
我们可以用如下方法自行解决。
a、首先给 el-autocomplete
组件绑定一个 ref
const elAutoComplete = ref<any>()
b、然后在选择数据的方法里调用组件内容的方法,手动取消组件的激活状态
elAutoComplete.value.close()
elAutoComplete.value.inputRef.blur()
完整代码如下:
<template>
<el-autocomplete
ref="elAutoComplete"
v-model="searchStr"
popper-class="autocomplete-search-person"
style="width: 100%"
clearable
value-key="nick_name"
:maxlength="32"
placeholder="请输入姓名"
:debounce="500"
:fetch-suggestions="querySearch"
@select="choosePerson"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
<template #default="{ item }">
<div
:class="[
'search-item',
{ disabled: selected.includes(item.passport) }
]"
>
<Avatar
:size="30"
:name="item.nick_name"
:src="item.avatar"
randomColor
/>
<p :title="item.nick_name">{{ item.nick_name }}</p>
</div>
</template>
</el-autocomplete>
</template>
<script setup lang="ts" name="ObjectiveList">
const elAutoComplete = ref<any>()
const choosePerson = (data: userInfoRes | {}) => {
if (!data) return
const result = data as userInfoRes
if (personList.value.length === limit) {
return ElMessage.warning(`一次最多查看${limit}人!`)
}
if (
personList.value
.map((item: userInfoRes) => item.passport)
.includes(result.passport)
) {
return ElMessage.warning('请不要重复添加!')
}
curPerson.value = result
personList.value.push(result)
// 手动关闭下拉框,避免出现两次(就是这两句)
elAutoComplete.value.close()
elAutoComplete.value.inputRef.blur()
savePersonList()
getObjectiveList(result.passport)
}
</script>
26、el-popover 根据内容和滚动容器动态调整展示位置
在使用
el-popover
的过程中,发现了一个问题,当el-popover
中包含动态内容的时候,el-popover
组件展示的位置会出现异常,表现出来的现象就是,内容更新后,popover
没有重新更新位置,导致展示异常或者内容被遮挡,具体请看下图:
a、初始情况,popover
内部的内容没有展开,是正常的。
b、当我把内部的内容展开后,就出现了内容被切掉,被浏览器顶部遮挡的情况,并且滚动页面时并不会重新计算 popover
的位置。
而正常应该是如下情况才对,并且需要在页面滚动时,动态计算 popover
的位置:
通过查阅文档和百度,发现了一个属性 popper-options
可以用来控制 popover
的行为,el-popover
内部其实是通过 popper.js
实现的,所以依然支持传入 popper.js
的配置。但是很遗憾,ElementPlus
官方文档并没有给出相关示例,且网上大部分都是针对 Vue2 + ElementUI 的配置(如:element-ui 组件 Popover 弹出框,el-popover 样式、定位以及二次确认弹出框自动关闭问题),在 Vue3 + ElementPlus 的场景下没效果,还报错,提示配置项不存在,应该是相关版本都更新了用法和配置的写法 。
后面经过查阅 ElementPlus 的相关 issue 以及 popper.js 的文档,才最终得到了正确的配置方式,更多配置可以参考 popper.js 文档中的 modifiers
配置。
● 相关 issue:(Feature Request) popover auto placement
● popper.js modifiers 配置
自测有效的 popper-options
的配置如下:
:popper-options="{
placement: 'top', // 默认的 placement
modifiers: [
{
name: 'flip', // 只是一个名字
options: {
boundariesElement: 'viewport', // 边界元素,默认是 body
fallbackPlacements: ['bottom'], // 供组件动态选择的 placement
removeOnDestroy: true // 是否在组件销毁时移除 DOM
}
}
]
}"
请参考如下完整代码:
<template>
<el-popover
ref="popoverAlignTopRef"
popper-class="popover-hover-aligned"
:width="360"
:show-after="600"
:hide-after="600"
trigger="hover"
:popper-options="{
placement: 'top',
modifiers: [
{
name: 'flip',
options: {
boundariesElement: 'viewport',
fallbackPlacements: ['bottom'],
removeOnDestroy: true
}
}
]
}"
>
<template #reference>
<Avatar :size="32" :src="item.avatar" />
</template>
<div class="popover-wrapper">
<span :class="['expander',{ expanded: item.expanded }]" @click="togglePopoverExpand(item, 'top')">
<el-icon v-if="item.kt_list.length > 0"><CaretRight/></el-icon>
任务({{ item.kt_list.length }})
</span>
</div>
</el-popover>
</template>
<script setup lang="ts">
const popoverAlignTopRef = ref()
// 展开/收起的交互
const togglePopoverExpand = async (row, type: 'top' | 'bottom') => {
row.expanded = !row.expanded
// 执行相关组件实例中的 update 方法,重新计算 popover 的位置,记得加上 nextTick
await nextTick()
const currentPopover =
type === 'top' ? popoverAlignTopRef : popoverAlignBottomRef
currentPopover.value[0].popperRef.popperInstanceRef.update()
}
</script>
27、Vite 打包后报错 globalThis is not defined
使用 vite2 打包后,在低版本浏览器中访问,发现控制台报错:Uncaught (in promise) ReferenceError: globalThis is not defined。
通过查阅资料,发现 globalThis 在 chrome 71 以上才支持,老版本浏览器没有这个属性,因此才出现这个错误提示。而 vite
默认并不会处理这些兼容问题,需要我们手动引入 @vitejs/plugin-legacy
这个插件才解决。
// vite.config.ts
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
legacy({
targets: ['Chrome 63'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
modernPolyfills: true
})
],
build:{
target:'es2015'
}
})
28、WARN [@vue/compiler-sfc] ::v-deep usage as a combinator has been deprecated.
在我们运行项目的时候,控制台可能会有很多这种黄色警告信息,提示
::v-deep
的用法已经过时了,主要是我们的样式文件中或者组件库的样式中有使用::v-deep
这种已过时的样式穿透符导致的,在 vue3 开发环境下,我们只需要把它替换成 :deep() 即可,示例如下:
// 错误代码:
::v-deep .ant-select-selector {
height: 30px;
}
// 更改后
:deep(.ant-select-selector) {
height: 30px;
}
29、This expression is not callable. Not all constituents of type ‘string | ((searchTerm: string) => Promise) | []’ are callable
当我们在使用
hooks
来抽离公用代码和逻辑的时候,可能会遇到这个错误提示(该表达式不能被调用),比如像下面这个hooks
,看起来都是正常定义没什么问题,但是控制台就会给我们抛出这类 TS 异常信息。
// 定义 hooks
import store from '@/store';
export const useViewType = () => {
const setViewType = (value: any): void => {
store.dispatch('configs/setViewType', value)
}
const viewType = computed({
get: () => (store as any).state.configs.viewType,
set: setViewType
})
return [viewType, setViewType];
}
// 使用 hooks
import { useViewType } from '@/hooks/config'
const [viewType, setViewType] = useViewType()
搜索良久,在 stackoverflow
找到了解释(见源问答),意思就是说 return
语句后面的写法无法保证 hooks
解构后原有元素的顺序,可能导致异常,并提供了两种解决方法,见下图:
此时,我们简单调整一下写法即可规避这个报错:
export const useViewType = () => {
const setViewType = (value: any): void => {
store.dispatch('configs/setViewType', value)
}
const viewType = computed({
get: () => (store as any).state.configs.viewType,
set: setViewType
})
return [viewType, setViewType] as const; // 注意这里多了 as const
}
30、TS 中无法直接在 window 上挂载变量
在 TypeScript 中,当我们需要给window对象添加全局变量(如 testName),或者需要使用 window 下自定义创建的变量(以 testName 为例)。会出现以下 ts 报错:类型 Window & typeof globalThis 上不存在属性 testName。产生类型报错的原因是因为 window 数据类型定义如下:
declare var window: Window & typeof globalThis
我们可以通过如下几种方式来处理:
a、增加自定义属性声明,在类型声明文件 typings.d.ts
中,增加如下声明
interface Window {
testName: string
}
b、将 window
类型强制转换为 any
(window as any).testName = 'Eric'
// 或
const win:any = window
win.testName = 'Eric'
c、使用 方括号
window['testName'] = 'Eric'
31、WangEditor 默认值不为空,导致表单验证不正常
在项目中不小心用到了 WangEditor 这个富文本编辑器,正常用没什么问题,可是当我在表单验证的场景下使用的时候,发现它的值默认不是空字符串,而是一段
html
片段<p><br/></p>
,看起来像是为了某种目的做的占位符。这个会导致绑定的表单值在初始状态下值不为空,进而造成校验不正常。
为了解决这个问题,看了一下官方文档和这个工具的 issue,发现确实存在这个问题,见 issue4619。
最后,经过尝试,直接使用实例上的 isEmpty() 方法就可以判断富文本是否为空。如果为空,手动返回空字符串即可,实际业务逻辑里大概是向下面这样:
const handleBlur = editor => {
showEditor.value = false
emit(
'change',
editor.isEmpty() ? '' : editor.getHtml(),
editor.getText(),
true
)
}
32、vue3 setup 语法中使用 jsx 以及 render 函数的使用
最近在项目中对
ElementPlus
的el-table
进行二次封装的时候,发现 vue 文件中不能直接写jsx
代码,render
函数也和vue2.x
里的用法也不一样了。
为了解决这个问题,我查阅了相关资料,发现需要做如下操作才可以支持:
- a、安装
@vitejs/plugin-vue-jsx
这个插件,并在vite.config.ts
中引入。
// vite.config.ts
import vueJsx from "@vitejs/plugin-vue-jsx";
export default {
plugins: [vueJsx()]
}
- b、在
ts.config.json
中添加"jsx": "preserve"
这个属性。
// tsconfig.ts
{
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "fragment"
}
- c、在 vue 文件中的
script
标签上添加lang="tsx"
或者lang="jsx"
,项目用ts
编写就用tsx
,这样就可以支持jsx
了。
<script lang="tsx">
import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
const msg = ref("tsx component");
return () => {
return <div>{msg.value}</div>;
};
}
});
</script>
注意:
setup
语法糖中是不可以使用render
函数的,必须要用传统的setup
选项,在末尾return
才可以。
可以在 setup 选项里直接return ()=> h()
或者多个return ()=> [h(), h()]
,不用再写render
了,如果return
了渲染函数,那么就不能再返回其它属性了,不过可以通过expose
暴露出去。
可以参考如下写法:
<script lang="tsx">
import { h,ref,renderSlot,reactive} from 'vue'
import Hello from './hello.vue'
interface Data {
[key: string]: unknown
}
interface SetupContext {
expose: (exposed?: Record<string, any>) => void
}
setup(props:Data, { expose }: SetupContext) {
function sayhello() {
console.log("Hello world!")
}
expose({
sayhello
})
return () => h(Hello)
}
</script>
参考文章:
33、手动触发 el-popover ,点击组件外区域自动关闭
业务中经常会用到
el-popover
这个组件来封装一些筛选组件,大部分情况下组件提供的属性都够我们用了,但是有些交互情况下,默认的属性并不太好实现。
比如如下图的一个人员筛选,需要在点击表头筛选图标后展示这个筛选组件(基于 el-popover
),并且当我点击组件之外的区域时自动关闭组件,这个需求默认的属性是可以实现的,只需要配置 trigger
为 click
即可。
但是这个组件内部还有一个搜索框,我用的 el-autocomplete
这个组件,我发现在搜索框搜索的时候,会触发 el-popover
的失焦导致整个组件被关闭,这就很苦恼。
经过再次查看文档,发现可以把 trigger
改为 "manual"
来自己控制 el-popover
的打开和关闭。这个时候,我再次搜索人员,发现已经不会导致组件自动关闭了,但是新的问题出现了,当我想点击组件外部区域关闭组件时,没效果(文档里也说了,manual
模式下这个行为不会触发关闭)。
看来得自己实现一个 clickoutside 了,不过我觉得很麻烦,这时我想到了 vue-use 这个 hooks
库里面有现成的,直接用吧,下面是官方的使用方法,只需要绑定一个 ref
就完事儿了,这样就解决了无法关闭的问题。
<template>
<div ref="target">
Hello world
</div>
<div>
Outside element
</div>
</template>
<script>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
export default {
setup() {
const target = ref(null)
onClickOutside(target, (event) => console.log(event))
return { target }
}
}
</script>
那么在我的组件中,把 ref
加到组件的包裹元素上即可(我这里还加了一个变量 isPicking
来标记是否正在搜索人员,避免选人过程中触发了关闭),附上这个组件的源码。
<template>
<el-popover
v-model:visible="visible"
popper-class="user-filter-popper"
:placement="placement"
:title="title"
:width="width"
:show-arrow="false"
:trigger="trigger"
>
<template #reference>
<el-icon class="user-filter-trigger" @click="open">
<Filter />
</el-icon>
</template>
<div ref="refPopover" class="filter-wrapper">
<div class="filter-body">
<el-autocomplete
ref="refAutoComplete"
v-model="searchStr"
popper-class="autocomplete-search-person"
style="width: 100%"
clearable
value-key="nick_name"
:maxlength="20"
placeholder="请输入员工姓名"
:debounce="500"
:fetch-suggestions="querySearch"
@select="choosePerson"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
<template #default="{ item }">
<div
:class="[
'search-item',
{ disabled: selected?.includes(item.passport) }
]"
>
<Avatar
:size="30"
:name="item.nick_name"
:src="item.avatar"
randomColor
/>
<p :title="item.nick_name">{{ item.nick_name }}</p>
</div>
</template>
</el-autocomplete>
<el-checkbox
v-if="resultList.length"
v-model="checkAll"
class="checkbox-all"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
>全选</el-checkbox
>
<el-empty v-else :image-size="60" description="无数据" />
<el-checkbox-group v-model="checkedList" @change="handleCheckedChange">
<el-checkbox
v-for="item in resultList"
:key="item.passport"
:label="item.passport"
>{{ item.nick_name }}</el-checkbox
>
</el-checkbox-group>
</div>
<div class="filter-footer">
<el-button size="small" @click="reset(false)">重置</el-button>
<el-button type="primary" size="small" @click="confirm">确定</el-button>
</div>
</div>
</el-popover>
</template>
<script setup lang="ts" name="UserFilter">
import { onClickOutside } from '@vueuse/core'
import { searchEmployee } from '@/api/statistic'
import { Filter, Search } from '@element-plus/icons-vue'
const props = withDefaults(
defineProps<{
value?: any
placement?: string
title?: string
width?: string | number
trigger?: string
}>(),
{
value: '',
placement: 'bottom',
title: '',
width: 200,
trigger: 'manual'
}
)
const emits = defineEmits<{
(e: 'change', data: any)
}>()
const visible = ref(false)
const refPopover = ref<any>(null)
const refAutoComplete = ref<any>(null)
const searchStr = ref<string>('')
const curPerson = ref<any>()
const resultList = ref<Array<any>>([])
const checkedList = ref<Array<any>>([])
const isPicking = ref(false) // 是否正在选人
const checkAll = ref(false)
const isIndeterminate = ref(true)
const handleCheckAllChange = (val: boolean) => {
checkedList.value = val ? resultList.value.map(item => item.passport) : []
isIndeterminate.value = false
}
const handleCheckedChange = (value: string[]) => {
const checkedCount = value.length
checkAll.value = checkedCount === resultList.value.length
isIndeterminate.value =
checkedCount > 0 && checkedCount < checkedList.value.length
}
const querySearch = (str: string, cb: Function) => {
if (!searchStr.value) return cb([])
searchEmployee({
cycle_id: 0,
department_list: [],
role_list: [],
keyword: searchStr.value
}).then(res => {
let result = res.data || ([] as any)
cb(result)
isPicking.value = true
})
}
const choosePerson = (data: any) => {
if (!data) return
const result = data
if (resultList.value.find(item => item.passport === result.passport))
return ElMessage.warning('请不要重复添加!')
// 手动关闭下拉框,避免出现两次
refAutoComplete.value.close()
refAutoComplete.value.inputRef.blur()
curPerson.value = result
resultList.value.push(result)
searchStr.value = ''
isPicking.value = false
}
const handleChange = (field, value) => {
emits('change', checkedList.value)
}
const open = () => {
visible.value = true
}
const close = () => {
visible.value = false
}
const confirm = () => {
emits('change', checkedList.value)
close()
}
const reset = (emmitChange = false) => {
resultList.value = []
checkedList.value = []
emmitChange && emits('change', checkedList.value)
}
// trigger为manual时需要单独处理点击组件外部自动关闭
onClickOutside(refPopover, event => {
!isPicking.value && close()
})
defineExpose({
reset
})
</script>
<style lang="scss">
.user-filter-trigger {
position: relative;
top: 2px;
margin-left: 6px;
cursor: pointer;
width: 1em;
color: $color_grey;
&[aria-describedby],
&:hover {
color: $color_theme;
}
}
</style>
<style lang="scss">
.user-filter-popper {
padding: 16px 16px 0 !important;
.filter-wrapper {
}
.filter-body {
margin-bottom: 12px;
.el-autocomplete {
margin-bottom: 10px;
}
.checkbox-all {
display: flex;
padding: 10px 0;
}
.el-checkbox-group {
.el-checkbox {
display: flex;
width: 100%;
height: 28px;
margin-right: 0;
.el-checkbox__label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.el-empty {
padding: 20px 0;
}
}
.filter-footer {
padding: 8px 0;
display: flex;
justify-content: flex-end;
border-top: 1px solid $border_color1;
}
}
</style>
34、动态设置 el-datepicker 的 disabled-date 属性无效
在使用
el-datepicker
的过程中,需求往往会需要限制它的可选择范围,比如只能选择今天及之后的时间,或者根据开始时间和结束时间来限定,ElementPlus 的el-datepicker
为我们提供了一个disabled-date
属性,我们可以使用它来支持这种特性(通过传入一个返回布尔值的函数来实现)。
问题是,在 vue3 中,当我想动态的设置 disabled-date
的值时(函数内部的相关值都是响应式的 ref
数据),发现传入的配置并不会生效(其实 disabled-date
已经是最新的了),达不到限制选择范围的作用,但是我尝试直接写死 disabled-date
函数是可以生效的。
经过测试,发现是由于 disabled-date
更新后,el-datepicker
组件没有重新渲染导致的。于是,我修改了一下写法,最开始给 disabled-date
赋值为 undefined
。在 template
中给 el-datepicker
组件添加一个 v-if = "disabled-date"
,这样,只有当 disabled-date
有值之后才会去渲染,果然解决了这个问题(当然,你也可以通过添加动态 key
的方式来触发组件重新渲染)。相关代码参考如下:
<template>
<div :class="['select-date', { disabled }]">
<label>时间:</label>
<el-date-picker
v-if="disabledDate"
v-model="params.date"
:default-value="params.date"
:disabled="disabled"
class="select-item-date"
type="daterange"
:disabled-date="disabledDate"
:editable="false"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="x"
/>
</div>
</template>
<script setup lang="ts" name="SelectDate">
import { UnwrapNestedRefs } from 'vue'
import { formatStampTime } from '@/utils/common'
import storeStorage from '@/utils/storage'
interface SelectRolePropsInterface {
disabled?: boolean
}
const props = withDefaults(defineProps<SelectRolePropsInterface>(), {
disabled: false
})
const emit = defineEmits<{ (e: 'change', value: any) }>()
const cycleInfo = ref<any>()
const params: UnwrapNestedRefs<{
date: undefined | [Date, Date]
}> = reactive({
date: undefined
})
const disabledDate = ref<undefined | Function>(undefined) // 日期选择器可选范围
onMounted(() => init())
// 初始化
const init = () => {
getCycleFromStorage()
// 设置日期可选范围
const { start, end } = cycleInfo.value
const startVal = start * 1000
const endVal = end * 1000
params.date = [new Date(startVal), new Date(endVal)]
disabledDate.value = (time: Date) =>
time.getTime() < startVal || time.getTime() > endVal
}
// 从缓存中获取周期数据
const getCycleFromStorage = () => {
const cacheData = storeStorage.get('cycleId')
if (cacheData.value) {
cycleInfo.value = JSON.parse(cacheData.value)
}
}
</script>
35、自定义 el-datepicker 的快捷选项
在使用
el-datepicker
的过程中,往往会用到快捷选项这个功能,就是下图所示的这种 本周、本月 等快捷菜单,实现一键快速选择指定日期范围,ElementPlus 的文档里也简单说明了配置方式,主要是通过shortcuts
这个属性进行配置的,但是并没有详细的文档。
此时,我的需求是点击菜单,并高亮该菜单,然后自动选择该范围的日期,并关闭选择框。但是官方文档里并没有看到相关的配置属性,于是自己实现一下。
a、配置快捷方式
shortcuts
中单个菜单的配置由 text
、value
、onClick
三个属性组成,value
可以为 Date对象
或者 Function
,onClick
代表点击后的回调函数,用来实现一些自定义的功能(注意:当 value
有值的时候,onClick
将被忽略),源码见 useShortcut 。
const shortcuts = [
{
text: '全周期',
value: [startVal, endVal]
},
{
text: '上周',
value: () => {
const start = getLastWeekFirstDay() // 上周第一天
const end = getLastWeekLastDay() // 上周最后一天
return [start, end]
}
},
{
text: '本周',
value: () => {
const start = getCurrentWeekFirstDay() // 本周第一天
const end = getCurrentWeekLastDay() // 本周最后一天
return [start, end]
}
},
{
text: '本月',
onClick: picker => {
// 这里的日期需要用 dayjs 包一层,emit 才能生效(调试源码知道的)
const start = dayjs(getCurrentMonthFirstDay()) // 本月第一天
const end = dayjs(getCurrentMonthLastDay()) // 本月最后一天
picker.emit('pick', [start, end])
// 这里可以做一些自定义操作 ...
}
}
]
b、快捷方式高亮控制
<el-date-picker
v-if="disabledDate"
v-model="params.date"
:default-value="params.date"
:disabled="disabled"
popper-class="popper-select-date"
class="select-item-date"
type="datetimerange"
:shortcuts="shortcuts"
:disabled-date="disabledDate"
:editable="false"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD"
value-format="x"
@change="handleChange('date', $event)"
@visible-change="handleVisibleChange"
/>
// 控制快捷选项的高亮状态
const handleVisibleChange = (isReset = false) => {
const btns = Array.from(
document.querySelectorAll('.popper-select-date .el-picker-panel__shortcut')
)
for (let i = 0; i < btns.length; i++) {
const el = btns[i]
if (isReset) {
el.classList.remove('active')
return
}
el.addEventListener('click', e => {
btns.forEach(el => {
el.classList.remove('active')
})
e.target.classList.add('active')
})
}
}
36、配置 keep-alive 之后,对 DOM 的操作不会实时生效
有时候,我们需要通过
keep-alive
实现页面或组件的缓存,以此提升用户体验。但是,一旦使用了keep-alive
,它可能会导致一些潜在的问题,比如,对DOM
的操作不会及时刷新。
场景:有一个带筛选的列表页面,它属于一个三级路由,通过里面的条目进入对应的详情页面,展示具体的数据,详情页也有一个筛选条(和列表页是同一个组件),此时我们只对列表页面添加了 keep-alive
。
但当我从列表页进入详情页后,对详情页中的一个 DOM
进行操作之后,页面上对应的元素并没有变化,需要我刷新页面才会改变(也就是没有重新渲染)。期初我以为直接给筛选组件加个 key
就可以修复这个问题的,但尝试无果,偶然间找到一篇文章说是要给 keep-alive
添加一个 key
,试了一下,是可以的,只需要修改所有 keep-alive
相关的位置即可(我们可以使用当前完整路径 fullPath
来作为唯一 ‘key’),如下示例:
<keep-alive>
<router-view v-if="$route.meta.keepAlive" :key="$route.fullPath">
<!--要缓存的视图组件 -->
</keep-alive>
<router-view v-else :key="$route.fullPath">
<!-- 不缓存的视图组件 -->
</router-view>
<!--也可以这样写 -->
<router-view v-slot="{ Component }" :key="$route.fullPath">
<keep-alive>
<component :is="Component" v-if="$route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!$route.meta.keepAlive" />
</router-view>
注意:
🈸 上面这种写法可能导致一些奇怪的问题(比如页面 DOM 渲染问题,重复请求等等)。经过多次尝试,建议使用官方提供的 include/exclude
来进行配置,基本不会再出现这些问题,同时避免了代码冗余。
如下代码:
<template>
<router-view v-slot="{ Component }">
<keep-alive :include="KEEP_ALIVE_PAGES">
<component :is="Component" />
</keep-alive>
</router-view>
</template>
<script>
import { KEEP_ALIVE_PAGES } from '@/enum/index'
</script>
// @/enum/index
export const KEEP_ALIVE_PAGES = ['AimMake'] // 需要缓存的页面的 name 集合
由于我的项目中有 3 级路由,而 vue 的
keep-alive
不支持超过两级路由的缓存,所以我需要给每一级的router-view
包一层keep-alive
来实现第三级路由的缓存功能(但是这样写的话,我这边出现了组件重复渲染导致重复请求的问题,还有DOM
未更新的情况,最后我尝试去掉第一层的keep-alive
后竟然好了,真坑!!),以下是相关代码:
<!--Layout 页面(一级路由)-->
<template>
<div id="main-container">
<div id="aside-container">
<AsidePortal />
<router-view name="aside" />
</div>
<div id="content-container">
<router-view name="topbar" />
<router-view v-slot="{ Component }">
<!-- holy:多层路由最外层不套keep-alive竟然解决了内层缓存冗余问题,2里面就不行 -->
<component :is="Component" />
</router-view>
</div>
</div>
</template>
<!--Content 页面(二级路由)-->
<template>
<div id="content-area">
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-if="route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!route.meta.keepAlive" />
</router-view>
</div>
</template>
<!--业务页面(三级路由)-->
<template>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-if="route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!route.meta.keepAlive" />
</router-view>
</template>
可以看到我只在最后两级路由上添加了 keep-alive
,具体原因未知,大家遇到这种问题还是多加尝试吧!。
参考:
- vue中使用keepAlive及使用后生命周期的改变
- keep-alive处理嵌套深层级的路由方案
- vue三级及三级以上路由 keep-alive 开启缓存失效问题处理
- keep-alive不能缓存多层级路由(vue-router)菜单问题解决
- 关于keep-alive路由多级嵌套不生效的解决方案
- Vue3-KeepAlive,多个页面使用keepalive方式
- vue-router多层嵌套+keep-alive
37、setup 语法糖中使用 beforeRouteEnter
众所周知,在使用了
keep-alive
之后,我们需要对它进行控制,比如从列表进详情,详情返回的时候使用缓存,从其它页面进列表页的时候不走缓存,这就需要用到beforeRouteEnter
这个钩子。
然而在 vue3 的setup
语法中并不能使用这个钩子,因为它晚于beforeRouteEnter
这个钩子,setup
内部只能用beforeRouteUpdate
和beforeRouteLeave
,这样的话,我们就需要写 2 个script
来支持,(还面临一个问题:一个script
标签中无法直接引用另一个script
标签内的方法 ),就像下面这样:
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
beforeRouteEnter(to, from, next) {
// getData() // 报错xxx
next()
}
})
</script>
<script lang="ts" setup>
const getData = () => {}
</script>
经过大量搜索资料,发现了如下一些解决方案(按实现成本从低到高排序):
1、通过 defineExpose
把指定方法暴露出去,通过组件实例来引用
<script lang="ts">
import { defineComponent, ComponentPublicInstance } from 'vue'
interface IInstance extends ComponentPublicInstance {
getData(from: string): void
}
export default defineComponent({
beforeRouteEnter(to, from, next) {
next((vm) => {
const instance = vm as IInstance
instance.getData(from.path)
})
}
})
</script>
<script lang="ts" setup>
const getData = (from: string) => {}
defineExpose({ getData })
</script>
2、通过引入 unplugin-vue-define-options
实现在 setup 内部定义组件的 options
a、安装 unplugin-vue-define-options
插件
yarn add unplugin-vue-define-options -D
b、vite.config.ts
中配置插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import DefineOptions from 'unplugin-vue-define-options/vite'
export default defineConfig({
plugins: [vue(), DefineOptions()]
})
c、在组件中使用 defineOptions
来定义配置
<script lang="ts" setup>
defineOptions({
name: '组件名',
beforeRouteEnter(to, from, next) {
next((vm) => {
const instance = vm as any
instance.getData(from.path)
})
}
})
const getData = (from: string) => {}
</script>
3、把 setup
的写法改为传统的 defineComponent
写法
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
const getData = () => {}
return { getData }
},
beforeRouteEnter(to, from, next) {
next(instance => {
instance.getData(from.path)
})
}
})
</script>
😁 提示(一劳永逸型):如果你的逻辑很复杂,可以结合方法 1 和方法 2 ,最后只用写 1 个 script
标签即可,如下代码:
<script lang="ts" setup>
import { ComponentPublicInstance } from 'vue'
interface IInstance extends ComponentPublicInstance {
getData(from: string): void
}
defineOptions({
name: '组件名',
beforeRouteEnter(to, from, next) {
next((vm) => {
const instance = vm as IInstance
instance.getData(from.path)
})
}
})
const getData = (from: string) => {}
defineExpose({ getData })
</script>
参考:
- Usage of beforeRouteEnter with script setup #302
- Vue3 如何在 setup 语法糖中使用 beforeRouteEnter
- 聊聊vue3中的name属性,看看怎么使用!
- keep-alive不能缓存多层级路由(vue-router)菜单问题解决
评论区