vue-cliでvueのプロジェクトを作成し、main.jsを見た時にrender関数って何?って思った人も多いかと思います。本文書ではVueのバージョン2を利用している中で初めてrender関数を見た時に調べたことをまとめたものでVueにおけるrender関数の使用方法の基礎について説明を行っています。class, style属性やイベントなどVue.jsで頻繁に利用する機能を使って動作確認を行っているので本文書を読むことでrender関数でどのようなことができるのかはイメージがつくと思います。

Vue 2だけではなくVue 3でのrender関数の使い方を確認しています。

render関数は何をしてくれるのか

Vue.jsのコンポーネントは通常templateタグの中にhtml文とv-ifなどのvueが持っているv-ディレクティブを利用することでブラウザ上にコンテンツを表示させることができます。render関数はtemplateタグを利用することなくブラウザ上にコンテンツを表示させることができます。 Vue.jsではtemplataタグをコンパイルしてrender関数に変換しています。

templateタグはrender関数に変換されるのでtemplateタグで実行できることはrender関数を使うことで実行することができます。render関数を使いこなすことができればtemplateタグを利用した場合に複雑になる処理もrender関数を利用することで効率的に記述することができます。

render関数でできることをなぜわざわざtemplateタグを利用するのかという疑問も浮かぶかと思います。一つはtemplateタグを利用してもらうとわかるように通常のHTMLと同じように記述できるためコードを記述することも読むこともrender関数に比べて簡単です。実際に本文書でもrender関数を直接利用しますがtemplateタグよりも読みにくいさを感じると思います。

vue-cliで見つけたrender関数

Vue CLIを使用してvue.jsのバージョン2のプロジェクトを作成後main.jsを確認するとrender関数が利用されています。


new Vue({
  render: h => h(App),
}).$mount('#app')

hはHyperScriptの略でrender h => h(App)は下記を略した記述方法です。HyperScriptはVue.jsに限定された機能ではありません。


render: function(createElement){
  return createElement(App)
}

HyperScriptをcreateElementの引数にタグや文字列を入れることでブラウザ上に記述した内容を表示させる機能があることがわかったので試しながら理解を深めていきます。

はじめてのrender関数

Hello Worldを表示(Vue 2)

テキストHello Worldをrender関数を利用してブラウザ上に表示させるためには以下のような記述が必要となります。createElementでは第一引数に要素名、第二引数に要素の中に入る文字列を記述しています。


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Render Function</title>
</head>
<body>
  <div id="app">
  </div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>

var app = new Vue({
  el: '#app',
  render: function(createElement){
    return createElement('p','Hello World')
  }
})

</script> 
</body>
</html>

ブラウザで見るとHello Worldが表示されます。

render関数でHello World
render関数でHello World

render関数は下記のように記述することができます。


var app = new Vue({
  el: '#app',
  render(createElement) {
    return createElement('p', 'Hello World');
  },
});

Hello Worldを表示(Vue 3)

Vue 3で動作確認を行う場合はcdnのURLも異なります。Vue 3の場合は下記のURLを利用してください。またVue 2とVue 3ではインスタンスの作成方法も異なるので注意してください。


<script src="https://unpkg.com/vue@next"></script>

インスタンスを作成する場合にcreateApp関数を利用します。createElement関数ではなくh関数を利用して記述することができます。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Render Function</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const app = Vue.createApp({
        render() {
          return Vue.h('p', 'Hello World');
        },
      }).mount('#app');
    </script>
  </body>
</html>

ブラウザには”Hello World”が表示されます。

HyperScript単独での利用方法

HyperScriptを理解するためにHyperScriptのみを利用した場合にはどのように設定することでコンテンツを表示することができるのか確認しておきます。任意の場所に任意の名前のフォルダを作成してください。ここではhyperscript_tryという名前にしています。

hyperscript_tryフォルダを作成後そのフォルダに移動してnpm init -yコマンドでpackage.jsonファイルを作成します。


 % mkdir hyperscript_try
 % cd hyperscript_try
 % npm init -y

hyperscriptとwebpackのインストールを行います。


 % npm install hyperscript 
 % npm install --save-dev webpack webpack-cli

インストールした後のpackage.jsonファイルは以下の通りです。webpackをnpm run devコマンドで実行できるようにscriptプロパティを更新しています。


{
  "name": "hyperscript_try",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack --mode development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "hyperscript": "^2.0.2"
  },
  "devDependencies": {
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1"
  }
}

hyperscript.tryフォルダの下にindex.htmlファイルを作成します。hyperscriptで作成する要素を追加する場所のdivにはidのappを設定しています。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>HyperScript</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./dist/main.js"></script>
  </body>
</html>

srcフォルダを作成してindex.jsファイルを作成します。document.getElementByIdの引数にappを設定してindex.htmlファイルのdiv要素を取得しています。取得したdiv要素にh(hyperscript)関数で作成した要素を追加しています。


var h = require('hyperscript');

const app = document.getElementById('app');

app.appendChild(h('p', 'Hello World'));

最後にnpm run devコマンドでwepackを利用するとdistフォルダが作成されその下にmain.jsファイルが作成されます。index.htmlファイルではこのファイルを指定しています。

ブラウザでindex.htmlファイルにアクセスすると”Hello World”が表示されます。

さらにHyperScriptのGithubページに掲載されているExampleのコードを利用して動作確認を行っておきます。


var h = require('hyperscript');

const app = document.getElementById('app');

app.appendChild(
  h(
    'div#page',
    h(
      'div#header',
      h('h1.classy', 'h', { style: { 'background-color': '#22f' } })
    ),
    h(
      'div#menu',
      { style: { 'background-color': '#2f2' } },
      h('ul', h('li', 'one'), h('li', 'two'), h('li', 'three'))
    ),
    h('h2', 'content title', { style: { 'background-color': '#f22' } }),
    h(
      'p',
      "so it's just like a templating engine,\n",
      'but easy to use inline with javascript\n'
    ),
    h(
      'p',
      'the intention is for this to be used to create\n',
      'reusable, interactive html widgets. '
    )
  )
);
hyperscriptの例
hyperscriptの例

このようにHyperScriptのみを利用してブラウザ上にコンテンツを表示させることができます。

データプロパティの利用方法

Vueインスタンスの中で設定したデータプロパティをrender関数の中で利用することができます。

Vue3の場合

データプロパティのmessageを定義して値を”Hello World”に設定しています。ブラウザ上には”Hello World”が表示されます。


const app = Vue.createApp({
  data() {
    return {
      message: 'Hello World',
    };
  },
  render() {
    return Vue.h('p', this.message);
  },
}).mount('#app');

Vue2の場合

データプロパティのmessageを定義して値を”Hello World”に設定しています。ブラウザ上には”Hello World”が表示されます。


var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello World'
  },
  render: function(createElement){
    return createElement('h1',this.message)
  }
})

属性の設定(class, style)

classや属性の設定も行うことができます。属性を使用する場合は第二引数を使用して、第三引数が文字列になります。

Vue3の場合

render関数の第二引数にオブジェクトを利用してclassでhelloを設定しています。設定したhelloクラスを適用するためにstyleタグを追加し、helloクラスで文字の大きさと太さを設定しています。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Render Function</title>
  </head>
  <style>
    .hello {
      font-weight: 900;
      font-size: 1.5em;
    }
  </style>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            message: 'Hello World',
          };
        },
        render() {
          return Vue.h('p', { class: 'hello' }, this.message);
        },
      }).mount('#app');
    </script>
  </body>
</html>

ブラウザで確認するとhelloクラスが適用されていることがわかります。

render関数にclassを使ってスタイルを利用
render関数にclassを使ってスタイルを利用

ここで一度render関数を利用しない場合の記述方法を確認しておきます。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Render Function</title>
  </head>
  <style>>
    .hello {
      font-weight: 900;
      font-size: 1.5em;
    }
  </style>
  <body>
    <div id="app">
      <p> class="hello">{{ message }}</p>
    </div>
    <script> src="https://unpkg.com/vue@next"></script>
    <script>>
      const app = Vue.createApp({
        data() {
          return {
            message: 'Hello World',
          };
        },
      }).mount('#app');
    </script>
  </body>
</html>

render関数を利用した場合と全く同じ内容がブラウザ上に表示されます。render関数を利用した記述と利用しない記述を比較した際にシンプルなコンテンツであれば圧倒的に利用しない方がこれまでのHTMLと同じように記述するために簡単であることがわかります。

Vueではclass属性にv-bindディレクティブを利用することでclassの適用を動的に制御することができます。データプロパティのisHelloを設定してtrue, falseによってhelloクラスを適用するか設定したい場合には下記のように記述することができます。ここではv-bindディレクティブや短縮形の:(コロン)は利用することはありません。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Render Function</title>
  </head>
  <style>
    .hello {
      font-weight: 900;
      font-size: 1.5em;
    }
  </style>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            message: 'Hello World',
            isHello: true,
          };
        },
        render() {
          return Vue.h('p', { class: { hello: this.isHello } }, this.message);
        },
      }).mount('#app');
    </script>
  </body>
</html>

isHelloの値をtrueにした場合はhelloクラスが適用されますがfalseにした場合にはhelloクラスが適用されることはありません。

style属性もrender関数を利用して設定することができます。render関数におけるclass属性の設定が理解できた人なら設定方法は想像できるかと思います。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Render Function</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            message: 'Hello World',
          };
        },
        render() {
          return Vue.h(
            'p',
            { style: 'font-size:1.5rem;font-weight:900;' },
            this.message
          );
        },
      }).mount('#app');
    </script>
  </body>
</html>

style属性とclass属性を両方設定する場合はどうするのかという声が聞こえてきそうなので確認します。第二引数にはオブジェクトで設定できることがポイントです。classプロパティとstyleプロパティを設定してそれぞれに値を設定します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Render Function</title>
  </head>
  <style>
    .hello {
      font-weight: 900;
      font-size: 1.5em;
    }
  </style>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            message: 'Hello World',
          };
        },
        render() {
          return Vue.h(
            'p',
            { style: 'background-color:red;color:white;', class: 'hello' },
            this.message
          );
        },
      }).mount('#app');
    </script>
  </body>
</html>

classとstyleが設定されます。

render関数でclassとstyleを同時に適用
render関数でclassとstyleを同時に適用

Vue2の場合

Vue2を利用した場合のclass, styleの適用方法は下記の通りとなります。設定する方法についてはVue3と同じです。


render: function(createElement){
  return createElement('h1',{ class: 'hello' },this.message)
}

style属性も同様に設定可能です。


render: function(createElement){
  return createElement('h1',{ style: 'background-color:red;color:white;' },this.message)
}

データプロパティを利用して動的にclass属性の設定も行うことができます。下記ではclassのFooのみclassが適用されます。


var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello World',
    isActive: true,
    isFalse: false
  },
render: function(createElement){
  return createElement('h1',{ class: {
    foo: this.isActive,
    bar: this.isFalse
  } },this.message)
}
})

イベントの設定

templateタグでイベントを設定する場合にはv-onディレクティブまたは@を利用します。render関数での設定方法について確認を行います。

Vue 3の場合

第二引数のオブジェクトにonClickプロパティを設定して関数を設定します。ブラウザ上にclickボタンが表示されるのでボタンをクリックするとコンソールに’Hello World”が表示されます。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Render Function</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue@next"></script>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            message: 'Hello World',
          };
        },
        render() {
          return Vue.h(
            'button',
            { onClick: () => console.log(this.message) },
            'click'
          );
        },
      }).mount('#app');
    </script>
  </body>
</html>

render関数の中に関数を直接記述していますがVueインスタンスのmethodsに関数を追加することで実行することもできます。


const app = Vue.createApp({
  data() {
    return {
      message: 'Hello World',
    };
  },
  methods: {
    alert() {
      alert(this.message);
    },
  },
  render() {
    return Vue.h('button', { onClick: this.alert }, 'click');
  },
}).mount('#app');

ボタンをクリックするとalertが表示されます。

methodsによりalertのポップアップ表示
methodsによりalertのポップアップ表示

関数を設定する場合にthis.alertに()をつけるとページを開いた瞬間にalert関数が実行されます。それを回避するためには先ほどの動作確認時のように()をつけないか()=>this.alert()と記述してください。

Vue2の場合

Vue2でも同様にclickイベントも設定を行うことができます。Vue3では設定方法が異なります。h1のHello Worldをクリックするとalertがポップアップします。


var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello World',
  },
  methods:{
    alert: function(){
      alert('click')
    }
  },
  render: function(createElement){
    return createElement('h1',{ on: {click: this.alert}},this.message)
  }
})

子要素の追加

これまではh1, p, buttonタグなど1つの要素しかありませんでした。templateタグの中で複数の要素が記述する方法をrender関数でもできるように設定方法を確認します。

render関数では1つの要素だけではなくその下に子要素も追加することができます。

Vue3の場合

ulタグの中にliタグがあるような親子関係のある要素で確認します。子要素を配列を利用して記述することができます。


const app = Vue.createApp({
  render() {
    return Vue.h('ul', [
      Vue.h('li', 'Hello Vue'),
      Vue.h('li', 'Hello Nuxt.js'),
    ]);
  },
}).mount('#app');

render関数でul, liを表示
render関数でul, liを表示

配列ではなくデータプロパティに定義した配列をmap関数で展開して表示させることもできます。templateタグに慣れている人にとっては少しわかりずらいかもしれません。


const app = Vue.createApp({
  data() {
    return { lists: ['Hello Vue', 'Hello Nuxt.js'] };
  },
  render() {
    return Vue.h(
      'ul',
      this.lists.map((list) => {
        return Vue.h('li', list);
      })
    );
  },
}).mount('#app');

divという要素の下にただの文字列とpタグが複数入っている場合の設定方法を確認します。配列で複数の子要素を設定することができます。文字列を入れたい場合は配列の要素として文字列を入れるだけです。


const app = Vue.createApp({
  render() {
    return Vue.h('div', [
      'Hello World',
      Vue.h('p', 'Hello Vue'),
      Vue.h('p', 'Hello Nuext.js'),
    ]);
  },
}).mount('#app');

デベロッパーツールを利用して要素を一緒に確認しておきましょう。Hello Worldは文字列とその下にpタグが続いていることが確認できます。

子要素に文字列と要素の組み合わせ
子要素に文字列と要素の組み合わせ

さらにその下の要素を作成していく場合は下記のように記述することができます。


const app = Vue.createApp({
  render() {
    return Vue.h('div', Vue.h('div', Vue.h('p', 'Hello')));
  },
}).mount('#app');

div要素の下にdiv要素、さらにp要素が作成できるていることが確認できます。

要素の階層を深くした場合
要素の階層を深くした場合

Vue2の場合

Vue3と同様に配列を利用してul, liを表示することができます。


var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello World',
  },
  render: function(createElement){
    return createElement('ul',{ class: 'hello'},
      [
        createElement('li',this.message),
        createElement('li','Hello Vue')
      ]
    )
  }
})

コンポーネント

コンポーネントを作成し、作成したコンポーネントをrender関数で利用する方法を確認します。

Vue 3の場合

app.component関数でHelloWorldコンポーネントを作成し、作成したHelloWorldコンポーネントをresolveComponent関数を利用してVue.h関数に設定しています。Hello Worldコンポーネントに記述している”Hello World”が画面上に表示されます。


const app = Vue.createApp({
  render() {
    return Vue.h(Vue.resolveComponent('HelloWorld'));
  },
});

app.component('HelloWorld', {
  render() {
    return Vue.h('p', 'Hello World');
  },
});

app.mount('#app');

propsを渡したい場合

propsを渡したい場合はh関数の第二引数を利用することができます。子コンポーネント側でのpropsの取得方法についてはこれまでの方法と特に変更はありません。


const app = Vue.createApp({
  render() {
    return Vue.h(Vue.resolveComponent('HelloWorld'), {
      message: 'Propsを渡す',
    });
  },
});

app.component('HelloWorld', {
  render() {
    return Vue.h('p', `Hello World ${this.message}`);
  },
  props: ['message'],
});

app.mount('#app');

コンポーネントでrender関数を使う(Vue2)

ここで非常にシンプルな子コンポーネントを使ってrender関数の使い方を確認していきましょう。

コンポーネントHeader.vueファイルを作成します。親のコンポーネントからpropsのlevelを受け取り、render関数と受け取ったlevelを使ってh1タグの文字列をブラウザに表示させます。this.$slots.defaultは親コンポーネントから挿入されるコンテンツが入っています。


<script>
export default {
  name: 'Header',
  props: {
    level: {
      type: Number,
      required: true
    }   
  },
  render: function (createElement) {
    return createElement(
      'h' + this.level,
      this.$slots.default
    )
  },
}
</script>

親側のコンポーネントを作成します。


<template>
  <div>
  <Header :level="1">
    Render関数を理解する
  </Header>
</div>
</template>
<script>
import Header from './components/Header.vue'
export default {
  name: 'app',
  components: {
    Header,
  },
}
</script>

子コンポーネントの$this.slots.$defaultの中には、Headerタグの間に入ったテキストが入ります。子コンポーネントでもrender関数を利用できることが確認できます。

コンポーネントにrender関数を利用
コンポーネントにrender関数を利用

render関数を利用せずtemplateタグを利用した場合は下記のような記述になります。


<template>
  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
</template>
<script>
export default {
  name: 'Header',
  props: {
    level: {
      type: Number,
      required: true
    }   
  },
}
</script>

コンポーネントは再利用が可能なので、h2タグに変更したい場合はどのような変更が必要かrender関数を利用する場合としない場合で比較してみましょう。

render関数を利用する場合はlevelの値を2に変更するだけであとの変更は必要ありません。


<Header :level="2">
  Render関数を理解する
</Header>

render関数を利用しない場合はifの分岐を追加する必要があります。


<template>
  <div>
    <h1 v-if="level === 1">
      <slot></slot>
    </h1>  
    <h2 v-if="level === 2">
      <slot></slot>
    </h2>
  </div>
</template>
<script>

h1, h2タグだけではなくh3, h4,..とこのコンポーネントで対応することになると明らかにrender関数を利用したほうほ効率的にコードを記述していることがわかります。render関数を使うことなんてないかもと思っていた人もこのコードを見たらrender関数に興味が湧いてきたかもしれません。実際にvue.jsのプラグインのVuetifyなどはrender関数を利用してコンポーネントの作成を行っています。

ここでは簡単な例を使って使用方法の説明を行いましたが、より詳しい説明は公式ドキュメントに記載されています。