enpitsulin

enpitsulin

这个人很懒,没有留下什么🤡
twitter
github
bilibili
nintendo switch
mastodon

Vue 3.3 の新しい特性の展望と簡単な評価

3.3 は現在ベータ段階にありますが、いくつかの特性は非常にエキサイティングです。ここでは新機能のプレビューを簡単に紹介し、今後のアップグレードに備えます😁

ジェネリックコンポーネントのサポート#

Vueはずっとジェネリックコンポーネントをうまく実現できませんでしたが、ついに 3.3 バージョンでこの機能が追加されました。

まず、TSX ユーザー向けにdefineComponentツール関数にジェネリックサポートが追加されました。パラメータとしてジェネリック関数を渡すと、型が正常に提示されます。たとえば、この特性を基にして tsx で簡単にテーブルコンポーネントを構築できます。

import { defineComponent } from 'vue';

type Props<T> = { data: T[]; columns: { label: string; field: keyof T }[] };

const Table = defineComponent(<T,>(props: Props<T>) => {
  return () => (
    <table>
      <thead>
        <tr>
          {props.columns?.map((item) => (
            <th>{item.label}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {props.data?.map((row) =>
          props.columns?.map((column) => <td>{row[column.field]}</td>)
        )}
      </tbody>
    </table>
  );
});

export default Object.assign(Table, {
  name: 'GenericsTableTsx',
  props: ['columns', 'data']
});

ただし、このコンポーネントにprops属性を渡す必要があることに注意してください。そうしないと、使用時にpropsであるべき属性が$attrsにマウントされてしまいます。この点は基本的にこのような使い方を排除していますので、単に型が正しいだけでは、実際の生産環境でこの方法でジェネリックコンポーネントを構築することはあまり推奨されません。

SFC ジェネリックコンポーネントのサポート#

実際、上記の機能はこのための前提です。SFC を使用して上記のコンポーネントを再現する方法を理解しましょう。

<template>
  <table>
    <thead>
      <tr>
        <th v-for="item in columns">
          <slot name="header-cell" v-bind="{ label: item.label }">
            {{ item.label }}
          </slot>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in data">
        <td v-for="column in columns">
          <slot name="cell" v-bind="{ data: row[column.field] }">
            {{ row[column.field] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>
<script setup lang="ts" generic="T">
  const { columns, data } = defineProps<{
    data: T[];
    columns: {
      label: string;
      field: keyof T;
    }[];
  }>();
</script>

[email protected]scriptgenericという属性を追加し、ジェネリックパラメータを作成できるようにしました。複数のパラメータは、もちろん ts のように,で区切ることができます。

評価:非常に強力な新機能で、Vue にようやくジェネリックコンポーネントが追加されたことは本当に喜ばしいことです。ただし、TSX のサポートにはまだprops属性を追加する必要があるため、少し面倒です。この問題はかなり前から存在しており、Vue チームが今後 TSX の開発体験を向上させることを期待しています。

defineProps マクロがサポートする型の導入#

この要求は 2 年が経過しましたが、大部分の開発者はこの使い方を達成するためにいくつかのコミュニティプラグインを使用していました。現在、公式に提供され、3.3 では外部からインポートした型を使用してPropsを簡単に作成できます。

<script setup lang="ts">
import type { SomeType } from 'some-where'

const props = defineProps<{ data: SomeType }>()
</script>

評価:期待通り、Vue プロジェクト内の型をより便利に管理できるようになり、SFC 内で長ったらしい型の体操を書く必要がなくなりました。

defineEmits マクロのより簡便な書き方#

3.2 では、defineEmitsはジェネリックに基づいて次のように使用する必要がありました。

defineEmits<{
  (e: 'foo', id: string): void
  (e: 'bar',...args: any[]): void
}>()

3.3 の書き方では、単一のパラメータに具名タプルの方法を使用して定義し、rest paramsのパラメータを使用する場合は直接T[]を使用して解決できます。

defineEmits<{
  foo: [id: string]
  bar: any[]
}>()

評価: DX を向上させる小さな機能で、関数のオーバーロード形式で多くの emits を書くのは確かに少し面倒です。

v-model に新しいツールを提供#

これは智子君新機能で、<script setup/>内でdefineModelを使用し、非<script setup/>内で使用するuseModelツールを使用できます。

// デフォルトのmodel (v-modelを通じて)
const modelValue = defineModel()
   // ^? Ref<any>
modelValue.value = 10

const modelValue = defineModel<string>() // 型を追加
   // ^? Ref<string | undefined>
modelValue.value = "hello"

// 設定されたデフォルトmodel、非undefinedを要求
const modelValue = defineModel<string>({ required: true })
   // ^? Ref<string>

// 特定名称のmodel (v-model:countを通じて)
const count = defineModel<number>('count')
count.value++

// デフォルト値を持つ特定名称のmodel
const count = defineModel<number>('count', { default: 0 })
   // ^? Ref<number>

// ローカルスコープで可変のmodel、名の通り
// 親コンポーネントからv-modelを渡す必要がありません
const count = defineModel<number>('count', { local: true, default: 0 })

また、useModelは非<script setup/>内で使用されるツールです。

import { useModel } from 'vue'

export default {
  props: {
    modelValue: { type: Number, default: 0 },
  },
  emits: ['update:modelValue'],
  setup(props) {
    const modelValue = useModel(props, 'modelValue')
       // ^? Ref<number>

    return { modelValue }
  }
}

評価:もう一つの DX 向上のツールで、v-modelの属性を定義するのは確かに煩雑で、sfc 内では実用性があまり高くありません。一般的にはvueuse/useVModelsと併用する必要があります。公式がこのマクロとツール関数を追加したのは本当に素晴らしいことです。

defineOptions#

また智子君の pr で、以前のRFCから来たもので、この内容は多くの人がVue Macroで使用していたでしょう。

本来、Vue では<script setup>内でinheritAttrs/nameなどの元々のOption Apiの属性を定義する必要がある場合、これらの属性を単独でエクスポートする<script>を作成する必要がありましたが、defineOptionsがあればこの手順を省略できます。

<script setup>
// 一部のコード
</script>
<script>
export default {
  name: "ComponentName"
}
</script> 
<script setup>
defineOptions({
  name: "ComponentName"
})
// 一部のコード
</script> 

評価:この特性はVue Macroで使用でき、先行体験として非常に快適です。

defineSlots マクロおよび slots 属性#

これも智子君からのもので、TQL です。

slotsの具体的な型を定義することを許可し、まずSlotsTypeが新たに追加され、slots属性はoptions apiで使用できます。

import { SlotsType } from 'vue'

export default defineComponent({
  slots: Object as SlotsType<{
    default: { foo: string; bar: number }
    item: { data: number }
  }>,
  setup(props, { slots }) {
    expectType<undefined | ((scope: { foo: string; bar: number }) => any)>(
      slots.default
    )
    expectType<undefined | ((scope: { data: number }) => any)>(slots.item)
  }
})

この定義されたコンポーネントSlotComponentを使用する場合は、次のようになります。

<template>
  <SlotComponent>
    <template #default="{ foo, bar }">
      {{ foo }} は文字列、{{ bar }} は数値です
    </template>
    <template #item="{ data }">
      {{ data }} は数値です
    </template>
  </SlotComponent>
</template>

defineSlotsslots属性に似ていますが、関数構文を提供します。

// オブジェクト構文と同じ動作をします。ESがdefaultを属性キーワードとして扱わなかったことに感謝します。喜ばしいことです😆
const slots = defineSlots<{
  default(props: { foo: string; bar: number }): any // または VNode[]
}>()

評価:スロットに正しい型があることは、コンポーネントライブラリにとっては非常に良いニュースです。結局、scopeSlotの導入から現在まで、ユーザーは自分がどのように特定のscopeSlotを使用すべきかをうまく理解できていません。

テンプレート内での console.log の使用#

突然のデバッグで使用する可能性のあるconsole.logですが、テンプレート内では使いにくいです。現在 3.3 では追加のサポートが加わり、テンプレートスコープに関数を追加して印刷する必要がなくなりました。

評価:大したことではありませんが、DX を向上させ、時折使用することで非常に快適に感じます。

あまり重要でない特性#

Suspense の改善#

個人的には、Vue の<Suspense>は一時的に注目する必要はないと思います。実験的な特性が長い間続いています。PR はこちらです。

廃止および変更された特性#

v-is ディレクティブの廃止#

v-isが廃止され、すべて:isディレクティブに変更されました(本当にこのディレクティブを使っている人がいるのか興味があります)。

app.config.unwrapInjectedRef#

app.config.unwrapInjectedRefという属性がなくなり、3.3 ではOption apiを使用するinject属性に対してデフォルトで ref がアンラップされます。

vnode hook の廃止#

vnode hook はあまり使用されていないと思われ、@vnode-*ディレクティブが@vue:*に変更されました。この特性はあまり使用されていないようで、オンラインでもあまり紹介されていないようです。おそらく、Vnode のライフサイクルにコンポーネントライフサイクルに似た機能を提供するためのものですが、この特性が何をするのかを知っている方がいれば教えてください。

エコシステム開発者への改善#

app.runWithContext()#

App にrunWithContext()が追加され、アプリケーションレベルのグローバル変数の存在を保証するために使用できます。たとえば、provideの各値を使用して、Vue エコシステムのさまざまなパッケージ、pinia/vue-routerなどを改善できます。

const app = createApp(App)

app.provide('foo', 1)

app.runWithContext(() => inject('foo')) // 1を返すべきです

hasInjectionContext#

hasInjectionContextは、Vue ベースのライブラリの作者向けに、inject()を使用できるかどうかを確認するためのツールです。現在の環境で使用できる場合は true を返し、使用できない環境は setup の外です。この関数を使用することで、ライブラリの作者は現在の環境の検出に余分な手間を省くことができます。

評価:これらはエコシステム開発者にとって非常に便利な機能であり、ライブラリを作成している方は注意してください。

注意が必要な点#

TSX ユーザーにとって、vue3.3 ではデフォルトでグローバル JSX 名前空間が登録されていないため、手動で tsconfig.json のjsxImportSourceを変更するか、魔法のコメント/* @jsxImportSource vue */を使用する必要があります。これはグローバル jsx 型の衝突を避けるためです。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。