require不存在
一切准备就绪后,按下了项目启动按钮,很快啊,651ms项目就启动了,不愧是vite速度就是快,嘴角疯狂上扬。
image-20220804230003937
浏览器加载完项目后,我傻眼了,我的登陆界面呢🌚?顺势打开控制台,发现报错require is not defined
。
image-20220804230914786
解决方案
打开Login.vue
文件后,发现我用require
导入了一些图片文件,在VueCLI环境下的require会交给webpack处理。在vite中是不存在的,那么我们就需要查看vite是怎么处理静态文件了。
翻了下文档后,在静态资源处理章节发现他有两种处理方法:
- 通过
import
语句直接导入图片 - 通过new URL来导入图片
我打算将所有组件都重构为setup
形式,因此直接使用import
方式来导入图片可以保持组件的一致性,可以大大提升可读性。
我们写个简单的demo来尝试下,如下所示:
<template> <img :src="loginUndo" alt="" /> </template> <script lang="ts" setup> import loginUndo from "@/assets/img/login/LoginWindow_BigDefaultHeadImage@2x.png"; </script> <style scoped></style>
已经可以正确解析出图片的路径了。
image-20220804234223781
注意:本文不会过多讲解setup的语法,对此不了解的开发者请移步:单文件组件 - script setup
new URL方式可以用来引入一个动态资源,例如:你有一份json配置文件,里面描述了图片的文件名,这些图片是放在项目中的,他们的访问前缀都一样,此时你就可以通过遍历json文件通过此方式来引入这些图片。
vue相关模块不存在
我试图从vue的包中导入shallowRef
时,编辑器报错: TS2305: Module 'xxx' has no exported member 'shallowRef'.
。
image-20220806102302026
解决方案
经过一番排查后,是因为项目typescript版本是3.x,跟3.2版本的vue不兼容,需要将其升级至4.x版本。
打开package.json
文件,作出如下所示的修改,重新执行yarn install
命令即可。
{ "devDependencies": { - "typescript": "~3.9.3", + "typescript": "~4.7.4", } }
setup中的变量警告未被使用
当我在setup中声明了一个函数或者导入了一个文件,在template中已经使用了,但是他却报错ESLint: 'xx' is assigned a value but never used.(@typescript-eslint/no-unused-vars)
image-20220806231446097
解决方案
在 eslint-plugin-vue 插件的Issues
中看到有人遇到了跟我同样的问题,在v9.0.0: regression in unused variables in script setup中我找到了解决方案。
我们需要升级下@vue/eslint-config-typescript
和eslint-plugin-vue
的版本号,如下所示:
{ "devDependencies": { "@vue/eslint-config-typescript": "^11.0.0", "eslint-plugin-vue": "^9.0.0" } }
随后在eslint的配置文件中,添加parser
属性,重新执行yarn install
命令即可。
module.exports = { + parser: 'vue-eslint-parser' }
模块隔离
Vite 使用 esbuild 来转译 TypeScript,并受限于单文件转译的限制,因此需要在ts的配置文件中将isolatedModules
属性设置为true。
{ "compilerOptions": { "isolatedModules": true } }
process不存在
在路由配置文件中,我们需要从process
中获取BASE_URL
,此时编辑器报错: TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try
npm i --save-dev @types/node and then add 'node' to the types field in your tsconfig.
image-20220806105226383
解决方案
由于vite中已经没有process了,需要用import.meta
来代替,那么上述的路由配置文件就应该改为:
const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), // 地址栏不带# routes });
无法导入json文件
在表情面板模块,我将每个表情都放入了json文件中。在vite中引入文件需要使用import,改了写法后,发现它报错:Cannot find module 'xx.json'. Consider using '--resolveJsonModule' to import module with '.json' extension.
image-20220806111708308
解决方案
我们需要在ts的配置文件中添加resolveJsonModule
属性,如下所示:
{ "compilerOptions": { + "resolveJsonModule": true } }
使用vite提供的对象
当我想使用vite所提供的glob属性时,发现编辑器报错: TS2339: Property 'glob' does not exist on type 'ImportMeta'.
解决方案也很简单,我们只需要在ts的配置文件中添加vite/client
即可,如下所示:
{ "compilerOptions": { "types": [ + "vite/client" ] } }
获取全局属性
当我们使用一些第三方库的时候它会在globalProperties
挂载一些方法,当在ts+setup环境下使用时,会出现类型无法推导问题,如下所示:
- 第三方库提供了一个
$connect
方法 - 我们通过proxy来访问
<script lang="ts" setup> import { getCurrentInstance, onMounted, ComponentInternalInstance } from "vue"; onMounted(() => { const { proxy } = getCurrentInstance() as ComponentInternalInstance; proxy.$connect(); }) </script>
他会出现报错: TS2339: Property 'xx' does not exist on type 'ComponentPublicInstance{}, {}, {}, {}, {}, {}, {}, {}, false, ComponentOptionsBase >'.
image-20220809103616969
解决方案
我们可以在type
目录下新建一个global
文件夹,在这里存放一些我们扩展出来的全局方法。
如下所示,我们:
- 创建了一个useCurrentInstance方法
- 将
globalProperties
属性暴露出去
import { ComponentInternalInstance, getCurrentInstance } from "vue"; export default function useCurrentInstance() { const { appContext } = getCurrentInstance() as ComponentInternalInstance; const proxy = appContext.config.globalProperties; return { proxy }; }
我们在组件中使用暴露出来的proxy即可,如下所示:
<script lang="ts" setup> import useCurrentInstance from "@/type/global/UseCurrentInstance"; onMounted(() => { const { proxy } = useCurrentInstance(); proxy.$connect(); }) </script>
无法识别NodeJS类型
我们在给setinterval
和setTimeout
指定类型时,会用到NodeJS
模块,会出现报错:ESLint: 'NodeJS' is not defined.(no-undef)
。
这个问题的解决方案是:打开eslint的配置文件在globals
对象中添加NodeJS
选项,如下所示:
{ globals: { NodeJS: true } }
除了将类型声明为NodeJS.Timeout外,我们还可以将其声明为
number
类型,但是需要携带window前缀(window.setinterval/window.setTimeout)
管理静态资源
当我们在组件中使用import
导入很多静态资源时,组件看起来会很杂乱。此时我们可以将其按照功能类型进行拆分。我的做法如下:
- 在src下创建
resource
文件夹 - 根据功能类型创建
ts
文件,将其导出
import defaultAvatar from "@/assets/img/login/LoginWindow_BigDefaultHeadImage@2x.png"; import defaultLoginBtnIcon from "@/assets/img/login/icon-enter-undo@2x.png"; import loginUndo from "@/assets/img/login/icon-enter-undo@2x.png"; import loginBtnHover from "@/assets/img/login/icon-enter-hover@2x.png"; import loginBtnDown from "@/assets/img/login/icon-enter-down@2x.png"; export { defaultAvatar, defaultLoginBtnIcon, loginUndo, loginBtnHover, loginBtnDown };
image-20220808212416992
分离模版与逻辑代码
我的项目中有一个很复杂的组件,有上千行代码,去年我用CompositionAPI
优化了一版,将组件中所有的方法都拆分成了一个个独立的ts
文件,做到了逻辑代码与模版代码分离,模版需要什么方法我就通过import导入进来,最后return给模版。
在拆分出来的文件中,是没有办法访问vue提供的一些内置属性的,比如:
defineProps、defineEmits、getCurrentInstance
。因此我想了一个奇妙的方法:将这些无法访问的属性都存起来。具体的做法请移步我另一篇文章:使用Vue3的CompositionAPI来优化代码量-创建InitData.ts文件
适配方案
vue3.2的setup语法糖支持import进来的方法都能在模版中直接使用,那我们的组件又可以精简下了,我花了亿点点时间对其进行了适配🤒
之前我们想获取组件的emit需要从context中拿,props声明并从setup函数的参数中获取,如下所示:
<script> export default defineComponent({ name: "message-display", props: { listId: String, // 消息id messageStatus: Number, // 消息类型 buddyId: String, // 好友id buddyName: String, // 好友昵称 serverTime: String // 服务器时间 }, setup(props, context) { // 访问emit context.emit } }) </script>
现在我们就不用这么麻烦了,直接通过defineProps、defineEmits获取即可,如下所示:
<script lang="ts" setup> // 获取父组件传递值 const props = defineProps<{ listId: string; // 消息id messageStatus: number; // 消息类型 buddyId: string; // 好友id buddyName: string; // 好友昵称 serverTime: string; // 服务器时间 }>(); const emit = defineEmits<{ ( e: "update-last-message", msgObj: { text: string; id: string; time: string; } ): void; }>(); // 事件监听函数,传入props和emit将其存储到initData中 const { userID, onlineUsers } = eventMonitoring(props, emit) as { userID: ComputedRef<string>; onlineUsers: ComputedRef<number>; }; </script>
此组件重构后的完整代码请移步:
- message-display.vue
- EventMonitoring.ts
项目地址
至此,项目的重构工作就结束了。本文重构好的项目代码地址:
- chat-system
写在最后
至此,文章就分享完毕了。
我是神奇的程序员,一位前端开发工程师。
如果你对我感兴趣,请移步我的个人网站,进一步了解。
- 公众号无法外链,如果文中有链接,可点击下方阅读原文查看😊