Vue3.3 的新功能的一些體驗(yàn)

Vue3 在大版本 3.3 里面推出來(lái)了一些新功能(主要是語(yǔ)法糖),網(wǎng)上有各種文章,但是看起來(lái)似乎是一樣的。
我覺(jué)得吧,有新特性了,不能光看,還要?jiǎng)邮謬L試一下。

DefineOptions 宏定義

先來(lái)一個(gè)簡(jiǎn)單的,以前我們有時(shí)候想設(shè)個(gè)name,有時(shí)候不想讓組件自動(dòng)繼承屬性,這時(shí)候需要單獨(dú)設(shè)置一個(gè)script進(jìn)行設(shè)置,現(xiàn)在簡(jiǎn)化了操作,直接使用 defineOptions 即可。

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

defineModel

defineModel 這是一個(gè)語(yǔ)法糖,目前需要手動(dòng)開(kāi)啟,否則無(wú)法識(shí)別。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue({
    script: {
      defineModel: true,
      propsDestructure: true // 解構(gòu) props
    }
  })],
})

有人嫌棄 組件內(nèi)部 v-model 的實(shí)現(xiàn)方式有點(diǎn)繁瑣,所以就做了這個(gè)語(yǔ)法糖給大家減少代碼量,我們也來(lái)體驗(yàn)一下。

const modelValue = defineModel()
console.log(modelValue)

我們看看 的結(jié)構(gòu)

{__v_isRef: true}
    value: (...)
    __v_isRef: true
    get value: ? value()
    set value: ? value(value)

只是一個(gè)普通的對(duì)象看不出來(lái)有什么名堂,我們來(lái)看一下內(nèi)部的實(shí)現(xiàn)方式:

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], // 監(jiān)聽(tīng)外部組件的值的變化
      (v) => proxy.value = v // 賦值給內(nèi)部屬性
    );
    watch(proxy, (value) => { // 監(jiān)聽(tīng)內(nèi)部屬性的變化
      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); // 內(nèi)部組件賦值,提交給外部組件
      }
    };
  }
}

前面各種判斷,然后option模式下返回一個(gè) ref;setup 模式下返回一個(gè)對(duì)象。取值的時(shí)候,返回 props[name]

Props 的響應(yīng)式解構(gòu)

我個(gè)人是不喜歡解構(gòu)的,直接使用不香嗎?其實(shí)vue表面上不讓我們用,其實(shí)內(nèi)部悄悄的在用,比如上面那個(gè)useModel 不就是嘛。

這個(gè)也是實(shí)驗(yàn)性的,想要體驗(yàn)需要手動(dòng)設(shè)置,設(shè)置方法在上面。

const { name } = defineProps<{ name: string }>()
watchEffect(() => {
  console.log(`name is: ${name}`)
})

const aa = computed(() => { return name + '響應(yīng)'})

看打印效果,只是普通的string,那么是如何實(shí)現(xiàn)響應(yīng)的呢?還得看看“編譯后”的代碼是什么樣子的。

  setup(__props, { expose: __expose }) {
    __expose();
    watchEffect(() => {
      console.log(`name is: ${__props.name}`);
    });
    const aa = computed(() => {
      return __props.name + "\u54CD\u5E94";
    });

編譯后會(huì)生成一個(gè) setup 函數(shù),props 通過(guò) 參數(shù) __props 傳入,需要監(jiān)聽(tīng)的地方,會(huì)把 name 變成 __props.name,這樣就實(shí)現(xiàn)響應(yīng)性了。也就是說(shuō),還是一個(gè)語(yǔ)法糖。

從外部文件引入 props 的定義( 單文件組件類(lèi)型導(dǎo)入)

從外部引入 props 的定義,這個(gè)功能非常實(shí)用,以前封裝UI庫(kù),想實(shí)現(xiàn)共享屬性定義的時(shí)候卡了好久,使用OptionAPI,還是使用CompositionAPI,都各有優(yōu)缺點(diǎn),最后只好折中一下。

現(xiàn)在支持外部導(dǎo)入那就方便多了。

比如我們先在一個(gè)ts文件里面定義一個(gè)接口:

export interface IFromItemProps {
  /**
   * 表單的 model
   */
  model: {[key: string]: any},
  /**
   * 對(duì)應(yīng)的字段名稱(chēng)
   */
  colName: string,
  /**
   * 控件的備選項(xiàng),單選、多選、等控件需要
   */
  optionList?: Array<{
    label: string,
    value: string | number | boolean,
    disabled: boolean
  }>,
  /**
   * 是否顯示可清空的按鈕,默認(rèn)顯示
   */
  clearable?: boolean,
  /**
   * 浮動(dòng)的提示信息,部分控件支持
   */
  title?: string,
  /**
   * 組件尺寸
   */
  size?: string
}

text 組件

然后我們可以 基于 el-input 做一個(gè)自己的 nf-text ,然后引入接口定義,還可以在 nf-list 等里面引入,這比以前使用的方式正規(guī)多了,也能更好的支持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">
  // 引入 類(lèi)型定義
  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 組件

你可能會(huì)覺(jué)得,這封裝的有意義嗎?只看一個(gè)確實(shí)沒(méi)啥意思,不過(guò)表單里面不是只有文本框這一種,還需要其他類(lèi)型,定義接口就是為了統(tǒng)一風(fēng)格。

我們?cè)俜庋b一個(gè)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>

這里使用 v-for 創(chuàng)建 el-option,外部調(diào)用的時(shí)候就方便多了。

<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>
  ...

封裝之后,我們不用關(guān)心組件是否需要子組件(比如el-select需要設(shè)置 el-option),都是
<nf-text v-bind="myText"></nf-text>
這種簡(jiǎn)單粗暴的方式,而組件需要的屬性,我們可以做成json的形式,這樣更方便。

另外大家不要忘記 vue 提供的動(dòng)態(tài)組件(component :is="xxx"),這樣我們用 v-for 就可以把一個(gè)表單里的所有子組件都給遍歷出來(lái),不用一個(gè)一個(gè)的寫(xiě)了。

小結(jié)

目前只對(duì)這幾個(gè)新特性感興趣體驗(yàn)了一下,其他的還沒(méi)來(lái)得及。還有一個(gè) props 設(shè)置默認(rèn)值的問(wèn)題,可以使用 withDefaults:

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

只是好像 默認(rèn)值的部分需要直接寫(xiě)進(jìn)去。這個(gè),等待以后版本的更新吧,估計(jì)以后都會(huì)支持外部導(dǎo)入的方式吧。

參考文檔

  • 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 發(fā)布:十分鐘速遞

官方幫助文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容