Vue3 在大版本 3.3 里面推出来了一些新功能(主要是语法糖),网上有各种文章,但是看起来似乎是一样的。
我觉得吧,有新特性了,不能光看,还要动手尝试一下。
先来一个简单的,以前我们有时候想设个name,有时候不想让组件自动继承属性,这时候需要单独设置一个script进行设置,现在简化了操作,直接使用 defineOptions 即可。
<script setup lang="ts"> defineOptions({ name: 'Foo', inheritAttrs: false, // ... 更多自定义属性 }) </script>
defineModel 这是一个语法糖,目前需要手动开启,否则无法识别。
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue({ script: { defineModel: true, propsDestructure: true // 解构 props } })], })
有人嫌弃 组件内部 v-model 的实现方式有点繁琐,所以就做了这个语法糖给大家减少代码量,我们也来体验一下。
const modelValue = defineModel() console.log(modelValue)
我们看看 的结构
{__v_isRef: true} value: (...) __v_isRef: true get value: ƒ value() set value: ƒ value(value)
只是一个普通的对象看不出来有什么名堂,我们来看一下内部的实现方式:
function useModel(props, name, options) { const i = getCurrentInstance(); if (process.env.NODE_ENV !== "production" && !i) { warn(`useModel() called without active instance.`); return ref(); } if (process.env.NODE_ENV !== "production" && !i.propsOptions[0][name]) { warn(`useModel() called with prop "${name}" which is not declared.`); return ref(); } if (options && options.local) { const proxy = ref(props[name]); watch( () => props[name], // 监听外部组件的值的变化 (v) => proxy.value = v // 赋值给内部属性 ); watch(proxy, (value) => { // 监听内部属性的变化 if (value !== props[name]) { i.emit(`update:${name}`, value); // 提交给外部组件 } }); return proxy; } else { return { __v_isRef: true, get value() { return props[name]; // 返回外部组件的值 }, set value(value) { i.emit(`update:${name}`, value); // 内部组件赋值,提交给外部组件 } }; } }
前面各种判断,然后option模式下返回一个 ref,setup 模式下返回一个对象。取值的时候,返回 props[name]
我个人是不喜欢解构的,直接使用不香吗?其实vue表面上不让我们用,其实内部悄悄的在用,比如上面那个useModel 不就是嘛。
这个也是实验性的,想要体验需要手动设置,设置方法在上面。
const { name } = defineProps<{ name: string }>() watchEffect(() => { console.log(`name is: ${name}`) }) const aa = computed(() => { return name + '响应'})
看打印效果,只是普通的string,那么是如何实现响应的呢?还得看看“编译后”的代码是什么样子的。
setup(__props, { expose: __expose }) { __expose(); watchEffect(() => { console.log(`name is: ${__props.name}`); }); const aa = computed(() => { return __props.name + "\u54CD\u5E94"; });
编译后会生成一个 setup 函数,props 通过 参数 __props
传入,需要监听的地方,会把 name 变成 __props.name
,这样就实现响应性了。也就是说,还是一个语法糖。
从外部引入 props 的定义,这个功能非常实用,以前封装UI库,想实现共享属性定义的时候卡了好久,使用OptionAPI,还是使用CompositionAPI,都各有优缺点,最后只好折中一下。
现在支持外部导入那就方便多了。
export interface IFromItemProps { /** * 表单的 model */ model: {[key: string]: any}, /** * 对应的字段名称 */ colName: string, /** * 控件的备选项,单选、多选、等控件需要 */ optionList?: Array<{ label: string, value: string | number | boolean, disabled: boolean }>, /** * 是否显示可清空的按钮,默认显示 */ clearable?: boolean, /** * 浮动的提示信息,部分控件支持 */ title?: string, /** * 组件尺寸 */ size?: string }
然后我们可以 基于 el-input 做一个自己的 nf-text ,然后引入接口定义,还可以在 nf-list 等里面引入,这比以前使用的方式正规多了,也能更好的支持TS。
<template> <el-input v-model="model[colName]" v-bind="$attrs" :id="'c' + colName" :name="'c' + colName" :size="size" :clearable="clearable" > </el-input> </template>
<script setup lang="ts"> // 引入 类型定义 import type { IFromItemProps } from './base' // 定义 props const props = defineProps<IFromItemProps>() console.log('props - text', props) </script>
看看效果
Proxy {model: Proxy, colName: 'name', title: '姓名', size: 'small', clearable: true, …} [[Handler]]: Object [[Target]]: Proxy [[Handler]]: Object [[Target]]: Object clearable: true colName: "name" model: Proxy {name: 'jyk', city: Array(0), time: ''} optionList: undefined size: "small" title: "姓名" [[Prototype]]: Object [[IsRevoked]]: false [[IsRevoked]]: false
你可能会觉得,这封装的有意义吗?只看一个确实没啥意思,不过表单里面不是只有文本框这一种,还需要其他类型,定义接口就是为了统一风格。
我们再封装一个select看看:
<template> <el-select v-model="model[colName]" v-bind="$attrs" :id="'c' + colName" :name="'c' + colName" :size="size" :clearable="clearable" :multiple="multiple" > <el-option v-for="item in optionList" :key="'select' + item.value" :label="item.label" :value="item.value" :disabled="item.disabled" > </el-option> </el-select> </template>
这里处理了一下 el-option ,使用 v-for 创建 el-option。
<script setup lang="ts"> import type { IFromItemProps } from './base' const props = defineProps<IFromItemProps & {multiple?: boolean}>() console.log('props - list', props) </script>
import nfText from './form/text.vue' import nfList from './form/list.vue' import nfDatetime from './form/datetime.vue' const model = reactive({ name: 'jyk', city: '', time: '' }) const myText = { colName: 'name' } const myList = { colName: 'city', multiple: true, optionList: [ { label: '北京', value: 1 }, { label: '上海', value: 2 } ] }
<nf-text :model="model" v-bind="myText"></nf-text> <nf-list :model="model" v-bind="myList"></nf-list> ...
封装之后,我们不用关心组件是否需要子组件(比如el-select需要设置 el-option),都是<nf-text v-bind="myText"></nf-text>
这种简单粗暴的方式,而组件需要的属性,我们可以做成json的形式,这样更方便。
另外大家不要忘记 vue 提供的动态组件(component :is="xxx"),这样我们用 v-for 就可以把一个表单里的所有子组件都给遍历出来,不用一个一个的写了。
目前只对这几个新特性感兴趣体验了一下,其他的还没来得及。还有一个 props 设置默认值的问题,可以使用 withDefaults:
const props = withDefaults(defineProps< IFromItemProps >(), { clearable: true })
只是好像 默认值的部分需要直接写进去。这个,等待以后更新吧,估计以后都会支持外部导入的方式吧。
[1] Generic component enhancements - Discussion #436: https://github.com/vuejs/rfcs/discussions/436
[2] unplugin-vue-define-options - npm: https://www.npmjs.com/package/unplugin-vue-define-options
[3] Announcing Vue 3.3 | The Vue Point: https://blog.vuejs.org/posts/vue-3-3
[4] Vue 3.3 主要新特性详解 - 三咲智子 Kevin Deng: https://xlog.sxzz.moe/vue-3-3
Vue3.3 发布:十分钟速递
Vue3.3 正式发布!
官方帮助文档