现阶段开发 vue3
我的项目应用 vite
+ ts
应该已成为规范范式了吧,新的语法体验 vue composition api
再配合 script setup
谁用谁晓得啊,开发和构建方面,vite
作为下一代构建工具,想必大家也有所理解,应用 ES6 module
+ ESbuild
反对本地开发,速度和效率腾飞啊,就一个字——爽,对于 TypeScript
,感觉都没必要说了,当初还没上车的连忙的了~
<!-- more -->
前言
vite
作为一个构建工具,咱们目前理解如何应用即可,该工具足够优良,默认对很多性能都做到了反对(css module
、less
、scss
),且其作为 vue
之父尤大大的作品,对于 vue
也有着较好的反对,目前使用率也曾经很高了,nuxt
等多个大我的项目都已反对,联合文档和社区,目前应用足够了,也无需放心疑难杂症~,快上车~
vue3
,感觉变动最大的就是全面拥抱了函数式编程,联合 composition api
当初真的能够做到对于简单业务代码的轻松治理,摈弃以前的 this this 一大堆,不敌对的mixin
,应用当初的 hooks
解决,逻辑复用、功能模块拆分几乎太不便了,语法、api应用上也优雅不便了很多,很值得一试
另一大亮点就是 vue3
对 TS
的良好反对,当初我的项目能够全面拥抱 TS写法了,而后与 setup
再联合我接下来举荐的几款工具,组合起来应用,几乎不要太爽
对于 TS
,首先要做到对类型的定义,这是和传统写JS有所不同的中央,然而这一步却是很必要也很值得的,这对你接下来的工作或者说对于这个我的项目的当前都是有很大的益处
如这样一个场景,和后端接口对接:
在后期,咱们拿到接口文档,依照格局和类型定义好对应 TS类型,并联合 Mock写好接口和业务逻辑,应用时通过TS能够高效的实现代码开发,同时能够极大的防止犯错,对于前期保护迭代有很大的保障
import.meta
应用 vite做为构建工具,能够通过 import.meta
获取对应办法不便疾速的解决业务
环境变量获取
import.meta.env
// console.log(import.meta.env){ "BASE_URL": "/", "MODE": "development", "DEV": false, "PROD": true, "SSR": false}
留神:
配合.env
/.env.development
/.env.production
等文件设置环境变量应用时,变量Key
应该以VITE_
为前缀
{ "script":{ "dev": "vite --mode development" }}
为了避免意外透露 env 变量给客户端,只有以 VITE_为前缀的变量才会裸露给 Vite 解决的代码。只会向您的客户端源代码VITE_SOME_KEY公开import.meta.env.VITE_SOME_KEY,但DB_PASSWORD不会。
批量解决文件
import.meta.globEager
// 读取当前目录下的所有 .ts文件const modules = import.meta.globEager('./**/*.ts')
ref和reactive
都能够用来定义响应式数据
ref
次要用于定义根本类型,应用时须要通过 .value
读取或批改
根本类型:除去Object,包含:String
、Number
、boolean
、null
、undefined
控制台打印数据结构为 RefImpl
// refconst count = ref(0)count.value++console.log(count.value)
在定义根本类型时,响应式原理是与 vue2.x相似的 Object.defineProperty()
,通过 get
、 set
读取、批改数据
不过,ref也能够定义援用类型的数据,留神,当定义援用类型时,其外部实现则是借助 reactive
能够通过打印数据在控制台查看构造,别离为:RefImpl
、Proxy
reactive
只能定义援用类型,即Object,包含:Object
、Array
、Date
、function
,定义根本类型时会正告
应用时,间接通过属性读写
// reactiveconst state = reactive({count:0})state.count++console.log(state.value)
reactive默认对对象内的所有属性都进行响应式解决,并能够实现深层监听
该响应式能力是通过 ES6 Proxy
实现的,其能够做到对属性的新增、删除监听,解决了 defineProperty
的缺点,并且对嵌套属性有着良好的反对,能够轻松实现 a.b.c.d=xx
的响应式更新
Proxy
与Reflect
均为ES6语法,个别他俩是一起配合应用,能够很好的做到平安优雅的对属性做出劫持更新
小结
模板 tempalte 会主动解包,在模板中应用时,不须要 .value
对于援用类型来说,简略了解即:ref实质也是reactive,ref(obj)等价于reactive({value: obj}),ref
的底层实现就是 reactive
能够发现,所谓响应式其实就是对属性的劫持
ref
和reactive
定义的数据,每一层都是响应式的
watch、watchEffect
监听响应式数据的变动
watch
根本语法和 vue2相似,不过在这里有一些不同的应用形式
监听 ref定义的响应式数据(根本类型)
- 函数式写法须要
.value
,监听的是一个值的变动
const count = ref(0);const str = ref('abc');// 1. 一般写法// watch能够省略 .valuewatch(count, (val, old) => console.log({ val, old }));// 2. 函数写法watch( () => count.value, (val, old) => console.log({ val, old }),);// 3. 数组写法watch( () => [count.value, str.value], (val, old) => console.log({ val, old }),);
监听 ref定义的响应式数据(援用类型)
- 需明确的是,
ref
定义援用类型,外部是应用reactive
实现的,因而,须要通过.value
拿到响应式对象,再进行属性监听
const refState = ref({ count: 0, str: 'abc',});// 1. 一般写法,有效// => refState.value 无效watch(refState, (val, old) => console.log({ val, old }));// 2. 函数写法watch( () => refState.value.count, (val, old) => console.log({ val, old }),);
监听 reactive定义的响应式数据
- 须要针对属性监听
state.count
const state = reactive({ count: 0, str: 'abc', a: { b: { c: 'a-b-c', }, },});// 1. 一般写法// 后果:val, old 新旧值雷同,// watch(state, (val, old) => console.log({ val, old }));// 2. 函数写法// 后果:指定属性变动才会触发watch( () => state.value.a.b.c, // 只监听指定的 属性 (val, old) => console.log({ val, old }),);
watchEffect
接管一个函数,不须要设置监听对象,该办法会主动接管函数外部应用到的依赖,当依赖产生更新时,触发该函数执行
该函数会初始化默认执行一次
watchEffect(()=>{ if(state.count>1){ // 只有 count变动,这个 watchEffect函数就会执行一次 // 当 count > 1时,做对应行为 }})
watch、watchEffect 小结
应用watch
时,须要思考的状况比拟多
watch
更强调后果,watchEffect
强调过程
就用法方面来说 watchEffect
仿佛更简略易用~
shallowRef和shallowReactive
- 递归监听和非递归监听
ref
和reactive
都属于递归监听,也就是数据的每一层都是响应式的,如果数据量比拟大,十分耗费性能,非递归监听只会监听数据的第一层。
script setup 写法的 props 、context解决形式
在以 <script setup lang="ts">
模式应用 setup时,默认整个 script都是setup的函数作用域,咱们不用再一一 return
定义的每个变量和办法,能够间接应用
但,对于 props
、emit
的定义,以及 ctx
属性的获取问题
vue也针对此,为咱们提供了3个新API
defineProps
defineEmit
useContext
// 这3个api和setup 的属性一一对应setup(props, { emit, ctx}){}
如果想通过父组件获取子组件的属性,须要在子组件中通过 defineExpose
定义须要裸露的属性
// 子组件 Childconst count = ref(0)defineExpose({ count,});// 父组件// <Child ref="ChildRef" />const ChildRef = ref<RefType<{ count: number }>>(0);const count = ChildRef.value.count
更多API见官网文档,说的很具体,这里就不再赘述了
props Type 类型定义问题
抛开 vue默认的几种根本类型,在一些非凡场景须要定义比较复杂的类型,须要通过 PropType
配合应用
如定义菜单路由类型
props: { menuData: { type: Array as PropType<MenuDataItem[]>, default: () => [], },}
这里如果依照惯例类型 Array
很难满足咱们的需要(只晓得是个数据,然而数据形态并不分明),原始类型写法很难准确推导各属性的类型定义
prop、ref、emit 数据通信
prop
强调单项数据流(父=>子),相似react,次要用来传参给子组件
ref
两种用法:
- 以援用的形式把子组件的实例指给
ref
,使得能够在父组件中获取到子组件中的所有属性和办法,能够通过配合defineExpose
API来实现 - 用于获取DOM元素
// 如:在应用echarts绑定DOM节点时// <div class="chart-box" ref="chartRef"></div>const chartRef = ref<HTMLDivElement | null>(null);echarts.init(unref(chartRef))
emit
次要用于子组件向父组件传递参数和通信emit
(子=>父),父组件通过事件办法@event
接管
<!-- 父组件 -->emit('getMessage', '我是父组件!')<!-- 子组件 --><child @event="handleMethod">
jsx 语法
在应用的过程中发现,jsx配合模板语法有着极大的灵活性,对于jsx语法,有 react开发教训的应该会感到很相熟,开发体验上很类似
然而,对于vue来说,有着得天独厚的的劣势,它自身作为模板语法,通过对实例办法的注入,指令的应用等,能够疾速而高效的进行开发,在一些场景下, jsx语法 + vue模板语法有着齐全不同的体验~
如:在模板中能够 <div @click="$router.push('xx')" v-auth="create"></div>
Table组件
以比拟常见的table组件为例,咱们封装好可复用的分页逻辑后,再把 columns
单出拆分进去用,应用jsx语法封装,依据不同组件应用做不同的配置,这样也更不便保护
export function columnsConfig(refresh: () => void) { // ... 其余业务逻辑 const columns: ColumnProps[] = [ { title: 'IP地址和端口', dataIndex: 'ip', width: 150, customRender: ({ record }) => `${record.ip}:${record.port}`, }, { title: '操作', key: 'action', width: 200, fixed: 'right', customRender: ({ record }) => <Space> <Button type="primary" onClick={() => router.push(`/app/product/detail/${record.id}`)}>详情</Button> <Divider type="vertical" /> { record.isSelf && <Popconfirm title="你确定要退出网络吗?" onConfirm={async () => { const res = await fetchApi.delete(record.id); if (res) { message.success(`已申请退出网络`); // 触发列表更新 refresh?.(); } }} > <Button>删除</Button> </Popconfirm> } </Space> }, ]; return columns;}
当 action操作列 业务比较复杂时,须要频繁的和其余数据通信,咱们也能够把action操作列剥离出,在 vue外部解决,再配合 Table组件的再封装解决
Table组件封装
<template> <a-table :columns="columns"> <!-- 函数式写法自定义 操作列 --> <template #action="{ record }"> <template v-for="(action, index) in getActions" :key="`${index}-${action.label}`"> <!-- 气泡确认框 --> <a-popconfirm v-if="action.enable" :title="action?.title" @confirm="action?.onConfirm(record)" @cancel="action?.onCancel(record)" > <a @click.prevent="() => {}" :type="action.type">{{ action.label }}</a> </a-popconfirm> <!-- 按钮 --> <a v-else @click="action?.onClick(record)" :type="action.type">{{ action.label }}</a> <!-- 分割线 --> <a-divider type="vertical" v-if="index < getActions.length - 1" /> </template> </template> </a-table></template><script lang="ts">// action 操作列const getActions = computed(() => { return (toRaw(props.actions) || []) .filter((action) => hasPermission(action.auth)) .map((action) => { const { popConfirm } = action; return { type: 'link', ...action, ...(popConfirm || {}), enable: !!popConfirm, }; });});</script>
应用
// <Table :columns="columns" :actions="tableActions"/>export const columns = [ // ... { title: '操作', key: 'action', width: 120, slots: { customRender: 'action' }, },]const tableActions = ref([ { label: '编辑', auth: AuthEnum.user_update, // 配置按钮权限 onClick: async (row) => { modalState.visible = true; const res = await store.fetchDetail(row.id); if (res) formModel.value = res; }, } // ...]
这是我在上个我的项目中实战应用的一点心得,对于开发效率晋升还是很显著的,保护起来也是很不便,更多用法也欢送大家一起交流学习,就目前体验来说vue3很棒~
event bus
vue3中移除了实例中挂载 $emit
的行为,如果想持续应用能够独自下载对应的 npm包,如:mitt,该包很轻量,仅 200byte
api与用法相似,只是扭转为了函数式创立,须要确保单个操作的 emitter创立惟一
import mitt from 'mitt'const emitter = mitt()export emitter
结语
这篇文章其实相当于本人的学习笔记,也是为了加深印象,在应用的过程中记录了遇到的一些问题,心愿能够给本人和大家带来一些帮忙。就内容而言属于入门应用程度,目前暂未波及到深水区,本文会依据应用状况继续更新