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()#

アプリに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 型の衝突を避けるためです。

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