• Vue3.3 的新功能的一些体验


    Vue3 在大版本 3.3 里面推出来了一些新功能(主要是语法糖),网上有各种文章,但是看起来似乎是一样的。
    我觉得吧,有新特性了,不能光看,还要动手尝试一下。

    DefineOptions 宏定义

    先来一个简单的,以前我们有时候想设个name,有时候不想让组件自动继承属性,这时候需要单独设置一个script进行设置,现在简化了操作,直接使用 defineOptions 即可。

    <script setup lang="ts">
    defineOptions({
      name: 'Foo',
      inheritAttrs: false,
      // ... 更多自定义属性
    })
    script>
    

    defineModel

    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]

    Props 的响应式解构

    我个人是不喜欢解构的,直接使用不香吗?其实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 的定义( 单文件组件类型导入)

    从外部引入 props 的定义,这个功能非常实用,以前封装UI库,想实现共享属性定义的时候卡了好久,使用OptionAPI,还是使用CompositionAPI,都各有优缺点,最后只好折中一下。

    现在支持外部导入那就方便多了。

    比如我们先在一个ts文件里面定义一个接口:

    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
    }
    

    text

    然后我们可以 基于 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
    

    list

    你可能会觉得,这封装的有意义吗?只看一个确实没啥意思,不过表单里面不是只有文本框这一种,还需要其他类型,定义接口就是为了统一风格。

    我们再封装一个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),都是这种简单粗暴的方式,而组件需要的属性,我们可以做成json的形式,这样更方便。

    另外大家不要忘记 vue 提供的动态组件(component :is="xxx"),这样我们用 v-for 就可以把一个表单里的所有子组件都给遍历出来,不用一个一个的写了。

    小结

    目前只对这几个新特性感兴趣体验了一下,其他的还没来得及。还有一个 props 设置默认值的问题,可以使用 withDefaults:

      const props = withDefaults(defineProps< IFromItemProps >(), {
        clearable: true
      })
    

    只是好像 默认值的部分需要直接写进去。这个,等待以后更新吧,估计以后都会支持外部导入的方式吧。

    参考文档

    • Announcing Vue 3.3 | The Vue Point
    • Vue 3.3 主要新特性详解 - 三咲智子 Kevin Deng

    参考资料

    [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 正式发布!

    官方帮助文档

  • 相关阅读:
    每日leetcode[删除排序链表中的重复元素]
    常见的几种排序方式
    【Flutter】使用Android Studio 创建第一个flutter应用。
    计算机丢失MSVCP140.dll的解决方法分享
    Linux文件、目录操作命令
    pyModbusTCP 读取零点 CN- 8031 /CT-121F DI 数字输入
    基于Python的飞机票销售系统的设计和实现
    Linux安装jrockit-jdk1.6.0_29-R28.2.0-4.1.0-linux-x64
    2022亚太数学杯数学建模竞赛B题(思路、程序......)
    驱动人生国庆宅家指南,这些游戏值得一刷!
  • 原文地址:https://www.cnblogs.com/jyk/p/17406348.html