Vueレンダリング関数&JSX

13198 ワード

きそ
Vueは、ほとんどの場合templateを使用してHTMLを作成することをお勧めします.しかし、いくつかのシーンでは、JavaScriptの完全なプログラミング能力が本当に必要です.これはrender関数で、templateよりもコンパイラに近いです.

Hello world!


HTMLレイヤでは、コンポーネントインタフェースを定義することにしました.
Hello world!

level propによってheadingラベルを動的に生成するコンポーネントを書き始めると、すぐにこのような実装を考えられるかもしれません.

  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
  <h2 v-else-if="level === 2">
    <slot></slot>
  </h2>
  <h3 v-else-if="level === 3">
    <slot></slot>
  </h3>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>

Vue.component('anchored-heading', {
  template: '#anchored-heading-template',
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

このシーンでtemplateを使用するのは最善の選択ではありません.まずコードが冗長で、異なるレベルのタイトルにアンカー要素を挿入するために、繰り返し使用する必要があります.
テンプレートはほとんどのコンポーネントで非常に使いやすいが、ここでは簡潔ではない.では、render関数を使用して上記の例を書き直してみましょう.
Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // tag name     
      this.$slots.default //        
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

簡単ではっきりしています!簡単に言えば、このようなコードは多く簡略化されていますが、Vueのインスタンス属性を非常に熟知する必要があります.この例では、anchored-headingのHello worldのようなslotプロパティを使用してコンポーネントにコンテンツを渡さない場合を知る必要があります.これらのサブ要素は、コンポーネントインスタンスに格納$slots.defaultで.
ノード、ツリー、および仮想DOM
関数を深くレンダリングする前に、ブラウザの動作原理を理解することが重要です.
次のHTMLを例にとります.

My title

Some text content

ブラウザがこれらのコードを読むと、家族の発展を追跡するためにファミリーツリーを描くように、「DOMノード」ツリーが作成されます.
各要素はノードです.各テキストもノードです.コメントもノードです.ノードはページの一部です.ファミリーツリーのように、各ノードには子供ノード(すなわち、各部分に他の部分を含めることができる)があります.
これらのノードを効率的に更新するのは難しいですが、手動でこの作業を完了する必要はありません.VueにページのHTMLが何であるかを伝えるだけです.これはテンプレートにあります.

{{ blogTitle }}


または、レンダリング関数で次の操作を行います.
render: function (createElement) {
  return createElement('h1', this.blogTitle)
}

どちらの場合も、blogTitleが変更されても、Vueはページの更新を自動的に維持します.
バーチャルDOM
Vueは、仮想DOMを確立することによって、実際のDOMで発生した変化を追跡する.
この行のコードを近くで見てください.
return createElement('h1', this.blogTitle)

createElementはいったい何を返しますか?実は実際のDOM要素ではありません.
より正確な名前はcreateNodeDescriptionかもしれません.Vueページでどのようなノードとそのサブノードをレンダリングする必要があるかを示す情報が含まれているからです.このようなノードを「仮想ノード(Virtual Node)」と記述し、「VNode」と略記することも多い.
「仮想DOM」は、Vueコンポーネントツリーによって構築されたVNodeツリー全体の呼び方です.
义齿
次に、createElement関数でテンプレートを生成する方法を熟知する必要があります.ここではcreateElementが受け入れるパラメータです.
// @returns {VNode}
createElement(
  // {String | Object | Function}
  //    HTML      ,      ,       
  //     String/Object    ,    
  'div',

  // {Object}
  //                
  //   ,     template        。    。
  {
    // (      )
  },

  // {String | Array}
  //     (VNodes),  `createElement()`     ,
  //          “    ”。    。
  [
    '      ',
    createElement('h1', '    '),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

深いdataオブジェクト
テンプレート構文でv-bind:classとv-bind:styleが特別に扱われるように、VNodeデータオブジェクトでは、以下のプロパティ名が最もレベルの高いフィールドであることに注意してください.このオブジェクトでは、DOMプロパティのようにinnerHTML(v-htmlコマンドの代わりになります)のように、通常のHTMLプロパティをバインドすることもできます.
{
  //  `v-bind:class`    API
  'class': {
    foo: true,
    bar: false
  },
  //  `v-bind:style`    API
  style: {
    color: 'red',
    fontSize: '14px'
  },
  //     HTML   
  attrs: {
    id: 'foo'
  },
  //    props
  props: {
    myProp: 'bar'
  },
  // DOM   
  domProps: {
    innerHTML: 'baz'
  },
  //         `on`
  //         `v-on:keyup.enter`    
  //        keyCode。
  on: {
    click: this.clickHandler
  },
  //      ,        ,         
  // `vm.$emit`      。
  nativeOn: {
    click: this.nativeClickHandler
  },
  //      。  ,     `binding`    `oldValue`
  //   ,   Vue            。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // Scoped slots in the form of
  // { name: props => VNode | Array }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  //              ,        
  slot: 'name-of-slot',
  //         
  key: 'myKey',
  ref: 'myRef'
}

拘束
VNodesは一意でなければなりません.
コンポーネントツリー内のすべてのVNodesは一意でなければなりません.これは、次のrender functionが無効であることを意味します.
render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div', [
    //   -    VNodes
    myParagraphVNode, myParagraphVNode
  ])
}

本当に何度も要素/コンポーネントを繰り返す必要がある場合は、ファクトリ関数を使用して実装できます.
たとえば、次の例のrender関数は、20個の重複する段落を完全に効果的にレンダリングします.
render: function (createElement) {
  return createElement('div',
    Array.apply(null, { length: 20 }).map(function () {
      return createElement('p', 'hi')
    })
  )
}

テンプレートの代わりにJavaScriptを使用
v-ifとv-for
オリジナルのJavaScriptを使用して何かを実現するのは簡単なので、Vueのrender関数は専用のAPIを提供していません.たとえば、templateのv-ifとv-for:
  • {{ item.name }}

No items found.


これらはrender関数でJavaScriptのif/elseとmapによって書き換えられます.
props: ['items'],
render: function (createElement) {
  if (this.items.length) {
    return createElement('ul', this.items.map(function (item) {
      return createElement('li', item.name)
    }))
  } else {
    return createElement('p', 'No items found.')
  }
}

v-model
render関数にはv-modelに対応するapiがありません.自分で論理を実現する必要があります.
props: ['value'],
render: function (createElement) {
  var self = this
  return createElement('input', {
    domProps: {
      value: self.value
    },
    on: {
      input: function (event) {
        self.$emit('input', event.target.value)
      }
    }
  })
}

これは底に深く入り込むために払うもので、面倒ですが、v-modelに比べて、より柔軟にコントロールすることができます.
はい.passive、.capture和.onceイベント修飾子、Vueは対応する接頭辞を提供してonに使用することができます:
Modifier(s)
Prefix
.passive
&
.capture
!
.once
~
.capture.once or .once.capture
~!
例:
on: {
  '!click': this.doThisInCapturingMode,
  '~keyup': this.doThisOnce,
  '~!mouseover': this.doThisOnceInCapturingMode
}

他の修飾子では、イベント処理関数でイベントメソッドを使用できるため、接頭辞は重要ではありません.
ここでは、すべての修飾子を使用する例を示します.
on: {
  keyup: function (event) {
    //                   
    //    
    if (event.target !== event.currentTarget) return
    //          enter    
    //        shift  
    //    
    if (!event.shiftKey || event.keyCode !== 13) return
    //        
    event.stopPropagation()
    //          keyup   
    event.preventDefault()
    // ...
  }
}

スロット
これからはslotsは、VNodesリストの静的コンテンツを取得します.
render: function (createElement) {
  // `
` return createElement('div', this.$slots.default) }

これからもscopedSlotsでは、VNodesを返す関数として使用できる役割ドメインスロットを取得します.
props: ['message'],
render: function (createElement) {
  // `
` return createElement('div', [ this.$scopedSlots.default({ text: this.message }) ]) }

レンダー関数を使用してサブアセンブリに役割ドメインスロットを渡す場合は、VNodeデータのscopedSlotsドメインを使用します.
render: function (createElement) {
  return createElement('div', [
    createElement('child', {
      // pass `scopedSlots` in the data object
      // in the form of { name: props => VNode | Array }
      scopedSlots: {
        default: function (props) {
          return createElement('span', props.text)
        }
      }
    })
  ])
}

JSX
render関数をたくさん書いたら、つらいかもしれません.
createElement(
  'anchored-heading', {
    props: {
      level: 1
    }
  }, [
    createElement('span', 'Hello'),
    ' world!'
  ]
)

特にテンプレートが簡単な場合:

  Hello world!


これは、VueでJSX構文を使用するためのBabelプラグインがある理由です.テンプレートに近い構文に戻ることができます.
mport AnchoredHeading from './AnchoredHeading.vue'

new Vue({
  el: '#demo',
  render: function (h) {
    return (
      
        Hello world!
      
    )
  }
})

hをcreateElementの別名とすることはVue生態系における一般的な慣例であり,実際にはJSXが要求するものであり,役割ドメインでhが機能を失うとアプリケーションでエラーがトリガーされる.
関数コンポーネント
以前に作成したアンカータイトルコンポーネントは、彼に渡されたステータスを管理したり傍受したりせず、ライフサイクルメソッドもありません.パラメータを受信する関数にすぎません.
この例では、コンポーネントをfunctionalとマークします.これは、ステータスなし(dataなし)、インスタンスなし(thisコンテキストなし)であることを意味します.
関数コンポーネントは次のようになります.
Vue.component('my-component', {
  functional: true,
  //          
  //             
  render: function (createElement, context) {
    // ...
  },
  // Props   
  props: {
    // ...
  }
})

注:2.3.0以前のバージョンでは、関数コンポーネントがpropsを受け入れる場合は、propsオプションが必要です.2.3.0以降のバージョンでは、propsオプションを省略することができ、すべてのコンポーネントのプロパティが自動的にpropsとして解析されます.
2.5.0以降では、単一ファイルコンポーネントを使用している場合は、テンプレートベースの関数コンポーネントを次のように宣言できます.


コンポーネントに必要なすべては、次のようなコンテキストで伝達されます.
  • props:propsを提供するオブジェクト
  • children:VNodeサブノードの配列
  • slots:slotsオブジェクト
  • data:コンポーネントに渡されるdataオブジェクト
  • parent:親コンポーネントへの参照
  • listeners:(2.3.0+)コンポーネントに登録されているv-onリスナーを含むオブジェクト.これはdataを指すだけです.onの別名.
  • injections:(2.3.0+)injectオプションが使用されている場合、オブジェクトには注入すべき属性が含まれます.

  • functional:trueを追加した後、アンカータイトルコンポーネントのrender関数間でcontextパラメータを簡単に更新します.this.$slots.defaultがcontextに更新されました.後でレベルがcontextに更新されました.props.level.
    関数コンポーネントは1つの関数であるため、レンダリングコストも大幅に低くなります.しかしながら、永続化インスタンスの欠如は、関数コンポーネントがVue devtoolsのコンポーネントツリーに表示されないことを意味する.
    パッケージコンポーネントとしても役立ちます.たとえば、これらを行う必要がある場合:
  • プログラム的に複数のコンポーネントの中から1つの
  • を選択する.
  • は、children、props、dataをサブコンポーネントに渡す前にそれらを操作する.

  • 次に、propsに入力された値に依存するsmart-listコンポーネントの例を示します.これは、より具体的なコンポーネントを表すことができます.
    var EmptyList = { /* ... */ }
    var TableList = { /* ... */ }
    var OrderedList = { /* ... */ }
    var UnorderedList = { /* ... */ }
    
    Vue.component('smart-list', {
      functional: true,
      render: function (createElement, context) {
        function appropriateListComponent () {
          var items = context.props.items
    
          if (items.length === 0)           return EmptyList
          if (typeof items[0] === 'object') return TableList
          if (context.props.isOrdered)      return OrderedList
    
          return UnorderedList
        }
    
        return createElement(
          appropriateListComponent(),
          context.data,
          context.children
        )
      },
      props: {
        items: {
          type: Array,
          required: true
        },
        isOrdered: Boolean
      }
    })
    

    サブエレメントまたはサブアセンブリにプロパティとイベントを渡す
    通常のコンポーネントでは、propとして定義されていないプロパティがコンポーネントのルート要素に自動的に追加され、既存の同名プロパティが置き換えられたり、スマートにマージされたりします.
    しかし、関数コンポーネントでは、この動作を定義する必要があります.
    Vue.component('my-functional-button', {
      functional: true,
      render: function (createElement, context) {
        //            、     、    。
        return createElement('button', context.data, context.children)
      }
    })
    

    createElementにcontextを転送する.Dataを2番目のパラメータとしてmy-functional-button上のすべての特性とイベントリスナーを渡しました.実際にはこれは非常に透明で、それらの事件は要求されていません.native修飾子.
    テンプレートベースの関数コンポーネントを使用する場合は、プロパティとリスナーを手動で追加する必要があります.独立したコンテキストコンテンツにアクセスできるのでdataを使用できます.attrsはHTMLプロパティを渡したり、listeners(data.onの別名)を使用してイベントリスナーを渡したりすることができます.
    
    

    slots()とchildrenの比較
    なぜslots()とchildrenが同時に必要なのか知りたいかもしれません.slots().defaultはchildrenと似ているのではないでしょうか.いくつかのシーンでは、そうですが、関数コンポーネントと次のようなchildrenであれば?
    
      

    first

    second


    このコンポーネントに対してchildrenは2つの段落ラベルを与えますが、slots()です.defaultは2番目の匿名段落ラベル、slots()のみを渡します.fooは、最初の名前付き段落ラベルを渡します.childrenとslots()を同時に持っているので、コンポーネントをslot()システムで配布するか、childrenで簡単に受信して他のコンポーネントに処理させるかを選択できます.