ホームページ  >  記事  >  ウェブフロントエンド  >  Vue3 スロット Slot の実装原理は何ですか?

Vue3 スロット Slot の実装原理は何ですか?

王林
王林転載
2023-05-24 09:28:481718ブラウズ

Vue のスロットの公式定義

Vue は、一連のコンテンツ配布 API を実装しています。この API の設計は、Web コンポーネント仕様草案からインスピレーションを受けており、<slot></slot> 要素は機能します。分散コンテンツをホストするためのアウトレットとして。

スロットとは

それでは、スロットとは一体何なのでしょうか?スロットは実際には、親コンポーネントによって渡されたスロットのコンテンツを受け取り、VNode を生成してそれを返す関数です。

通常は <slot></slot> を使用します。このタグのペアは、親コンポーネントによって渡されたコンテンツを受け取ります。このタグのペアの最終的なコンパイルは、次の関数です。 VNode を作成します。これをスロット VNode を作成する関数と呼ぶことができます。

// <slot></slot>标签被vue3编译之后的内容
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return _renderSlot(_ctx.$slots, "default")
}

<slot></slot> タグは、Vue3 によってコンパイルされた後、_renderSlot という関数になることが明確にわかります。

スロットの使用方法

スロットを使用するには、親子コンポーネントが存在する必要があります。

親コンポーネントに次のコンテンツがあるとします。

<todo-button>
  Add todo
</todo-button>

親コンポーネントで todo-button 子コンポーネントを使用し、 Add todo## を渡します。 # のスロットの内容。

todo-button サブコンポーネント テンプレートの内容

<button class="btn-primary">
  <slot></slot>
</button>

コンポーネントがレンダリングされると、

<slot></slot> は「Add todo」に置き換えられます。 。

コンポーネント レンダリングの原理を確認する

それでは、基礎となる原理は何でしょうか?スロットの基本原理を理解する前に、Vue3 コンポーネントの動作原理も確認する必要があります。

コンポーネントの核心は、多数の VNode を生成できることです。 Vue の場合、コンポーネントのコアはそのレンダリング関数です。コンポーネントのマウントの本質は、レンダリング関数を実行し、レンダリングされる VNode を取得することです。data/props/computed に関しては、これらはすべて、 VNode を生成するレンダリング関数 ソース サービスで最も重要なことは、コンポーネントによって最終的に生成される VNode です。これは、レンダリングする必要があるコンテンツだからです。

スロットの初期化原理

Vue3 は、VNode タイプのコンポーネントを検出すると、コンポーネントのレンダリング プロセスに入ります。コンポーネント レンダリングのプロセスでは、まずコンポーネント インスタンスを作成し、次にコンポーネント インスタンスを初期化します。コンポーネント インスタンスの初期化時に、スロット関連のコンテンツが処理されます。

ソース コードの runtime-core\src\component.ts で

Vue3 スロット Slot の実装原理は何ですか?

#関数 initSlots でコンポーネント スロットの関連コンテンツを初期化します

それでは、initSlots 関数はどのようなもので、何を行うのでしょうか?

runtime-core\src\componentSlots.ts

Vue3 スロット Slot の実装原理は何ですか?

まず、コンポーネントがスロット コンポーネントであるかどうかを判断する必要があります。コンポーネントがスロットコンポーネントかどうか?まず戻って、上記の親コンポーネントのコンパイルされたコードを確認する必要があります。

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_todo_button = _resolveComponent("todo-button")
  return (_openBlock(), _createBlock(_component_todo_button, null, {
    default: _withCtx(() => [
      _createTextVNode(" Add todo ")
    ], undefined, true),
    _: 1 /* STABLE */
  }))
}

Slot コンポーネントの子のコンテンツがオブジェクト型であることがわかります。これは次のコードです。

{
    default: _withCtx(() => [
      _createTextVNode(" Add todo ")
    ], undefined, true),
    _: 1 /* STABLE */
}

すると、このコンポーネントのVNodeが作成されると、その子がObject型かどうかが判定され、Object型であれば、そのVNodeのshapeFlagにSlotコンポーネントのタグが付けられます。コンポーネント。

テンプレートを通じてコン​​パイルされている場合、これは標準スロットの子であり、

__ 属性を持ち、コンポーネント インスタンスに直接配置できます。 。 ユーザー自身が作成したスロット オブジェクトの場合は、

# 属性がないため、正規化する必要があり、

normalizeObjectSlots に進みます。 ユーザーの動作が仕様に従っていない場合は、normalizeVNodeSlots

プロセスに従います。

スロットの内容を分析する

まずサブコンポーネントのコンパイルされたコードを見てみましょう:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("button", { class: "btn-primary" }, [
    _renderSlot(_ctx.$slots, "default")
  ]))
}

これについては上でも説明しました

<slot></slot>

タグは vue3 によってコンパイルされると、

_renderSlot という関数になります。

Vue3 スロット Slot の実装原理は何ですか?renderSlot

この関数は 5 つのパラメータを受け取ります。1 つ目はインスタンス上のスロット関数オブジェクト

slots で、2 つ目はスロットの名前、つまりスロットのコンテンツを指定された場所にレンダリングします。3 番目はスロット スコープによって受信された props、4 番目はスロットのデフォルトのコンテンツ レンダリング関数、5 番目は Notそれが何を意味するかはまだわかりません。 スコープ スロットの原則

スコープ スロットは、子コンポーネントが親コンポーネントにパラメータを渡す方法であり、スロットのコンテンツが子コンポーネント内のデータにのみアクセスできるようにします。

サブコンポーネント テンプレート

<slot username="coboy"></slot>

コンパイルされたコード

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return _renderSlot(_ctx.$slots, "default", { username: "coboy" })
}

親コンポーネント テンプレート

<todo-button>
    <template v-slot:default="slotProps">
        {{ slotProps.username }}
    </template>
</todo-button>

コンパイルされたコード

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_todo_button = _resolveComponent("todo-button")
  return (_openBlock(), _createBlock(_component_todo_button, null, {
    default: _withCtx((slotProps) => [
      _createTextVNode(_toDisplayString(slotProps.username), 1 /* TEXT */)
    ]),
    _: 1 /* STABLE */
  }))
}

上記の通り renderSlot 関数を通じてこれは次のコードに簡単に要約できます。

export function renderSlots(slots, name, props) {
  const slot = slots[name]
  if (slot) {
    if (typeof slot === &#39;function&#39;) {
      return createVNode(Fragment, {}, slot(props))
    }
  }
}

slots

はコンポーネント インスタンスによってアップロードされたスロット コンテンツであり、実際にはこのコンテンツです。

{
    default: _withCtx((slotProps) => [
      _createTextVNode(_toDisplayString(slotProps.username), 1 /* TEXT */)
    ]),
    _: 1 /* STABLE */
}
name はデフォルトです。次に、slots[name] が取得するのは次の関数です
_withCtx((slotProps) => [
      _createTextVNode(_toDisplayString(slotProps.username), 1 /* TEXT */)
])

slot(props) は明らかにslot({ username: "coboy" }) であり、子コンポーネントのデータを親コンポーネントに転送します。スロットが入っています。

具名插槽原理

有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout></base-layout> 组件:

<div class="container">
  <header>
    <!-- 我们希望把页头放这里 -->
  </header>
  <main>
    <!-- 我们希望把主要内容放这里 -->
  </main>
  <footer>
    <!-- 我们希望把页脚放这里 -->
  </footer>
</div>

对于这样的情况,<slot></slot> 元素有一个特殊的 attribute:name。通过它可以为不同的插槽分配独立的 ID,也就能够以此来决定内容应该渲染到什么地方:

<!--子组件-->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 name 的 <slot></slot> 出口会带有隐含的名字“default”。

在向具名插槽提供内容的时候,我们可以在一个 <template></template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<!--父组件-->
<base-layout>
  <template v-slot:header>
    <h2>header</h2>
  </template>
  <template v-slot:default>
    <p>default</p>
  </template>
  <template v-slot:footer>
    <p>footer</p>
  </template>
</base-layout>

父组件编译之后的内容:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_base_layout = _resolveComponent("base-layout")
  return (_openBlock(), _createBlock(_component_base_layout, null, {
    header: _withCtx(() => [
      _createElementVNode("h2", null, "header")
    ]),
    default: _withCtx(() => [
      _createElementVNode("p", null, "default")
    ]),
    footer: _withCtx(() => [
      _createElementVNode("p", null, "footer")
    ]),
    _: 1 /* STABLE */
  }))
}

子组件编译之后的内容:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", { class: "container" }, [
    _createElementVNode("header", null, [
      _renderSlot(_ctx.$slots, "header")
    ]),
    _createElementVNode("main", null, [
      _renderSlot(_ctx.$slots, "default")
    ]),
    _createElementVNode("footer", null, [
      _renderSlot(_ctx.$slots, "footer")
    ])
  ]))
}

通过子组件编译之后的内容我们可以看到这三个Slot渲染函数

_renderSlot(_ctx.$slots, "header")

_renderSlot(_ctx.$slots, "default")

_renderSlot(_ctx.$slots, "footer")

然后我们再回顾一下renderSlot渲染函数

// renderSlots的简化
export function renderSlots(slots, name, props) {
  const slot = slots[name]
  if (slot) {
    if (typeof slot === &#39;function&#39;) {
      return createVNode(Fragment, {}, slot(props))
    }
  }
}

这个时候我们就可以很清楚的知道所谓具名函数是通过renderSlots渲染函数的第二参数去定位要渲染的父组件提供的插槽内容。父组件的插槽内容编译之后变成了一个Object的数据类型。

{
    header: _withCtx(() => [
      _createElementVNode("h2", null, "header")
    ]),
    default: _withCtx(() => [
      _createElementVNode("p", null, "default")
    ]),
    footer: _withCtx(() => [
      _createElementVNode("p", null, "footer")
    ]),
    _: 1 /* STABLE */
}

默认内容插槽的原理

我们可能希望这个 <button></button> 内绝大多数情况下都渲染“Submit”文本。为了将“Submit”作为备用内容,我们可以将它放在 <slot></slot> 标签内

<button type="submit">
  <slot>Submit</slot>
</button>

现在当我们在一个父级组件中使用 &lt;submit-button&gt;&lt;/submit-button&gt; 并且不提供任何插槽内容时:

&lt;submit-button&gt;&lt;/submit-button&gt;

备用内容“Submit”将会被渲染:

<button type="submit">
  Submit
</button>

但是如果我们提供内容:

<submit-button>
  Save
</submit-button>

则这个提供的内容将会被渲染从而取代备用内容:

<button type="submit">
  Save
</button>

这其中的原理是什么呢?我们先来看看上面默认内容插槽编译之后的代码

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("button", { type: "submit" }, [
    _renderSlot(_ctx.$slots, "default", {}, () => [
      _createTextVNode("Submit")
    ])
  ]))
}

我们可以看到插槽函数的内容是这样的

_renderSlot(_ctx.$slots, "default", {}, () => [
    _createTextVNode("Submit")
])

我们再回顾看一下renderSlot函数

renderSlot函数接受五个参数,第四个是插槽的默认内容渲染函数。

Vue3 スロット Slot の実装原理は何ですか?

再通过renderSlot函数的源码我们可以看到,

第一步,先获取父组件提供的内容插槽的内容,

在第二个步骤中,若父组件已提供插槽内容,则使用该插槽内容,否则执行默认的内容渲染函数以获取默认内容。

以上がVue3 スロット Slot の実装原理は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。