各位掘金的小伙伴,大家好!沉寂了一段时间,小卷又和大伙儿见面了。这次我们一起来卷Vue3自研组件库实战。本套专栏小卷会采用Vue3开发的最新的技术栈,从手把手环境搭建到各个组件开发的设计思想、代码实现、重构到组件发布等一系列流程都会涵盖到,小伙伴们赶快关注起来吧!
基本工程环境
node环境
友情提醒:为了避免软件以及依赖版本不一致造成的学习阻碍问题,跟随一起学习的小伙伴请务必保证自己使用的工具和依赖版本和小卷教程中的保持一致哦~ node采用较新的稳定版本:
18.18.2
,这数字还挺吉利的~随之一起安装的npm
版本为9.8.1
。node软件的下载与安装这里就略过了,顺便提下本地缓存的npm目录的位置可以用下面的命令来自行设定:
shell
# 设置全局模块安装目录
npm config set prefix /path/to/global/modules
# 设置npm缓存目录
npm config set cache /path/to/npm/cache
设置完成后,不要忘了将路径/path/to/global/modules
配置到Path
环境变量中,以便可以在命令行直接访问全局安装的npm工具命令哦~ 为方便管理npm源,咱们全局安装一个好用的工具nrm
:
shell
npm i -g nrm
该工具的使用很简单:
shell
# 查看npm源列表
nrm ls
# 切换npm源
nrm use taobao
vite工程初始化
vite工程的初始化可以交给vite命令行工具,全局安装下该工具:
shell
npm i -g vite
然后就可以在命令行中进行vite工程的初始化了,使用交互式命令,设置工程创建选项即可:
shell
npm init vite
这里我们先提供一个最精简的vite版的vue3工程。里面的依赖项仅包含了:
json
{
...
"dependencies": {
"vue": "^3.4.21"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.2.0"
}
}
最基本的vue
的3
版本的依赖,以及vite
和它与vue
相关的插件。后续的其他插件和工具的集成,咱们将在这个最简化的版本上一点点升级。 作为vue
工程,需要告诉vite
去配置vue
插件。我们看到在项目根路径下vite.config.js
就是用来对vite
进行各种插件集成以满足各类型项目构建需求的。很显然,这里我们仅使用了vue
插件:
js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
})
Vue3工程代码结构
这里我们只关注vite构建单页面的Vue3应用,页面的入口自然是最外层的index.html
,这里会通过module
的形式来导入入口的main.js
:
html
...
<html lang="en">
<head>
...
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
在入口的src/main.js
中将完成根组件App
的创建与挂载,并导入全局的样式文件:
js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
我们先不对页面样式做过多的修饰,简单设置一个柔和的背景色: src/style.css
css
body {
background: #fcfdf5;
}
再来看根组件App
,这里我们采用Vue3
提供的setup
新语法来完成"只声明,免注册"的舒适编程风格,来注册并使用HelloWorld
组件: src/App.vue
html
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<div>
<h3>一个最简单的Vue3应用</h3>
</div>
<HelloWorld msg="hello Vue3" />
</template>
<style scoped>
</style>
注意,在
Vue3
中组件模板不再要求只能有一个根元素了哦~
最后是组件HelloWorld
,一起来看下,这里咱们通过隐式属性定义函数defineProps
声明了一个可以从外部传入消息的msg
属性。通过ref
函数对一个计数器变量count
提升为响应式变量,以实现页面点击按钮时能看到计数器值的实时变化: src/components/HelloWorld.vue
html
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div>
<button type="button" @click="count++">count is {{ count }}</button>
</div>
</template>
<style scoped>
</style>
安装和启动应用
在项目根路径下执行:
shell
npm i
启动服务
shell
npm run dev
页面效果:
集成jsx
jsx的语法可以很容易的实现在js脚本中进行组件html内容的动态渲染,这种形式比起传统的html标签中应用vue指令的形式,具有更好的语义和可读性;同时也让html模板的渲染变得更灵活。
安装插件
shell
npm i -D @vitejs/plugin-vue-jsx@3.1.0
温馨提醒:这里为了和教程中用的版本保持一致,特意把安装的版本进行了指定哦~
配置插件
vite.config.js
js
...
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [..., vueJsx()],
})
现在,咱们对之前开发的App
和HelloWorld
两个组件用jsx
语法进行改写吧。 关于vue3
中具体的jsx
语法,大伙儿可以参考下官方技术文档《渲染函数 & JSX》,这里咱们只用它来完成vue
单文件组件的改写。
温馨提示
小卷在采用智能编辑器webstorm编辑
.jsx
后缀的文件时,代码的缩进并不是自己期望的,折腾了下,发现在工程根目录下加一个.editorconfig
文件就解决问题啦~
iniroot = true [*] indent_style = tab indent_size = 2
src/App.jsx
jsx
import { defineComponent } from 'vue'
import HelloWorld from './components/HelloWorld'
export default defineComponent({
setup() {
return () => {
return (
<div>
<div>
<h3>一个最简单的Vue3应用</h3>
</div>
<HelloWorld msg="hello Vue3"/>
</div>
)
}
}
})
这里我们通过接收一个vue组件的配置对象并调用defineComponent
函数,来导出一个vue组件。要返回的模板内容写在setup
钩子的回调中,这里的内容很简单,都是静态的,但要注意jsx返回的html内容必须只能有一个根元素哦 。 App
组件在main.js
中导入时,可以省略后缀的.jsx
:
js
import App from './App'
src/components/HelloWorld.jsx
该组件模板中会渲染动态的内容,并多了按钮的交互。这里绑定表达式、变量的输出值以及绑定事件处理函数的调用逻辑都是写在{ ... }
中,注意要和传统vue
单文件中模板用{{ ... }}
小胡子语法区别开哦。特别要注意的是,响应式变量在jsx中输出时不能省略.value
的引用哦~
jsx
import { defineComponent, ref } from 'vue'
export default defineComponent({
props: {
msg: String
},
setup(props) {
const { msg } = props
const count = ref(0)
return () => {
return (
<div>
<h1>{ msg }</h1>
<div>
<button type='button' onClick={ () => count.value++ }>count is { count.value }</button>
</div>
</div>
)
}
}
})
另外,注意下组件属性的定义和使用方式。
集成Typescript
为了进一步让前端的代码有更高的可读性和可维护性,是时候引入typescript
了。依赖安装:
shell
npm i -D typescript@5.4.5 vue-tsc@2.0.14
这里除了安装基本的ts
库,还要安装vue
组件对于ts
语法的编译器工具vue-tsc
。 还需要在工程根目录下准备两个ts
的配置文件:tsconfig.json
和tsconfig.node.json
,这两个文件可以在使用vite
命令行工具生成项目指定使用typescript
时,自动生成,这里就不贴出配置内容了,也可以在小卷的github代码库中获得参考。
接下来我们的重心是对工程中所有.js
和.jsx
后缀的文件进行相应的改写,改写为.ts
和.tsx
文件,只要修改文件名,内容不变动。涉及修改的文件包括:
- vite.config.ts
- src/main.ts
- src/App.tsx
- src/components/HelloWorld.tsx 不要忘了在
index.html
中引入main.ts
也要调整下哦。 为了在启动dev
服务时能对ts
进行编译,咱们对package.json
中的dev
命令脚本进行调整:
json
{
...
"scripts": {
"dev": "vue-tsc && vite",
...
}
...
}
执行npm run dev
,报如下的错误:
解决办法: 在tsconfig.json
的compilerOptions
配置选项下加一个配置项"jsxImportSource": "vue"
,告诉tsc
编译器在编译jsx
的模板中的内容时以vue
作为导入源。
现在我们用ts
来封装HelloWorld
组件的属性类型,以提供更好的类型检查。为此,我们对HelloWorld
组件string
类型的msg
属性做一下调整,封装一个IMsg
类型。新建ts
文件: src/components/types.ts
ts
import { ExtractPropTypes, PropType } from 'vue'
// 导出vue的属性定义
export const props = {
msg: {
// 类型为自定义的属性类型,这里对Object的属性类型采用了泛型的形式以精确到具体的ts类型
type: Object as PropType<IMsg>,
required: true
}
} as const // 将其作为一个常量导出,对外部是只读的
// 对Vue的属性定义对象进行ts类型的提取,以方便外部对属性的ts类型进行识别
export type Props = ExtractPropTypes<typeof props>
// 自定义消息类型
export interface IMsg {
info: string
}
src/components/HelloWorld.tsx
tsx
...
import { props, Props } from './types'
export default defineComponent({
props, // 使用导入的属性定义
// 对实际的属性配置声明包装类型
setup(props: Props) {
...
return () => {
return (
<div>
<h1>{ msg.info }</h1>
...
</div>
)
}
}
})
这里咱们引入定义的属性配置及其类型。 App
组件在使用HelloWorld
组件进行属性定义的地方相应的做一下调整:
src/App.tsx
tsx
...
export default defineComponent({
setup() {
return () => {
return (
<div>
...
<HelloWorld msg={ { info: 'hello tsx!!!' } }/>
</div>
)
}
}
})
显然这里我们要传入满足类型的参数,否则对于集成了ts
检查的编辑器以及运行vue-tsc
命令时都会得到类型检查错误信息:
ok!小伙伴们,学到这里咱们对采用tsx
来开发Vue3
组件进行了一个快速的尝鲜,想必大家尝到一点前端组件规范化开发的甜头,这还不够,下一小节,咱们还将进一步集成eslint
和prettier
以及结合编辑器插件的自动修复不规范的代码来进一步优化我们的前端开发环境,等这些都做到位了,我们将一起来写一个高性能的Tree组件来打开咱们组件化实践的序幕,大家加油!