14
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AsiaQuestAdvent Calendar 2018

Day 19

vue.js で jsx を始めよう 〜TODO アプリで触る jsx〜

Last updated at Posted at 2018-12-18

vue.js で jsx を始めよう 〜TODO アプリで触れる jsx〜

はじめに

AsiaQuest 株式会社所属のフロントエンドエンジニアです
数奇な運命に導かれて最近はhtml/cssしか書いてない

対象読者

vue.js の単一ファイルコンポーネントで開発したことがある人
jsx になじみがない人

TL;DR

記法は違えど感覚は <template> にかなり近い
react 書いてる気持ちになる

jsx で vue を書くメリット

vue.js好きですか? 僕はまあまあ好きです
vue は富豪的なフレームワークで独自の api が多く、
いずれは jquery と同じ道を辿るかも…という懸念があります

となるとフレームワークへの依存性を薄めた実装をしておけば
=> リファクタしやすい
=> 別ライブラリで書き直す時が来ても大丈夫!

そこで react でおなじみの jsx で vue.js を書いてみましょう

  • 他の仮想 DOM ライブラリと同じ感覚で書ける
  • 将来的に vue.js を剥がしやすくなる
  • もうvue.jsエンジニア(そんな言葉があるか知らんけど)とは呼ばせない

こんなメリットがあります

TODO アプリを jsx 表記で作ってみる

nuxt.js でチャチャっと環境を用意します

$ npx create-nuxt-app <my-project>

vue で jsx を使うパッケージが最初から同梱されているためです
今回主に触るのは components ディレクトリです、pagesなどは適宜やってください

render 関数を使って jsx を返せる => template タグを使わず書ける

お馴染みの template 記法を jsx で書き直してみましょう

vue sample

<template>
  <div>Todo</div>
</template>
components/TodoView.vue

<script>
export default {
  render(h) {
    return <div>Todo</div>
  }
}
</script>

render 関数を使って div 要素を return することで、template 記法に近い感覚で書けます
※詳しくはこちら https://jp.vuejs.org/v2/guide/render-function.html

複数行書く場合は jsx 部分を () で囲みます

components/TodoView.vue

render(h) {
  return (
    <div>
      <h1>Todo</h1>
    </div>
  )
}

そしてこの場合、 <template> タグがないので .vue ファイルにする必要もありません
<script> をとって .js として保存しても vue コンポーネントとして扱うことができます

.js の場合 <style> タグでスタイルをコンポーネントに紐付けることはできません、別途 css module などの導入が必要です

v-if での出し分けを jsx で実装してみる

v-ifjsx で実装してみましょう
todoDataという配列の長さに応じて要素を出し分けます

{} で括った中に式を書くことが出来ます
&& で式を評価し、満たした場合に jsx を返します
これで v-if のような出し分けを再現できます

vue sample

<template>
  <div>
    <div v-if="todoData.length > 0">
      <div>item found</div>
    </div>

    <div v-if="todoData.length === 0">
      <div>no item found</div>
    </div>
  </div>
<template>
components / TodoView.vue

render(h) {
  return (
    <div>
      {this.todoData.length > 0 && (
        <div>
          <div>item found</div>
        </div>
      )}

      {this.todoData.length === 0 && <div>no item found</div>}
    </div>
  )
}

ビジネスロジックと view を分離してみる

template を持たないコンポーネントが明示的に作れるようになったので、 ビジネスロジックだけ提供するコンポーネントを .js 、見た目を担保するコンポーネントを .vue として完全に分けてみます

こんなイメージです

.js ( data, methods, computed... )

↓ data, methods, computedをpropsで渡す

.vue ( propsを受け取って描画・スタイル設定 dataやmethodsは持たない )

data や methods は全部 TodoContainer.js に持たせ、残りは props を元にレンダリングするだけのコンポーネントにします
挙動に関して記述されたファイルがひとつだけになので、機能追加や変更時に 1 ファイルを参照するだけですみます

先ほど作った TodoView.vue を読み込み、props を付与して render で返すだけの .js コンポーネントを作ります

components / TodoContainer.js

import TodoView from './TodoView'

export default {
  data() {
    return { todoData: [] }
  },
  render() {
    return <TodoView todoData={this.todoData} />
  }
}

以後、datamethod は全てこのコンポーネントに書いていきます
TodoView 側も props を受け取れるようにしておきましょう

components / TodoView.vue

props: {
  todoData: {
    type: Array,
    default: () => []
  }
},

メソッドの実装

Todo アプリの挙動を考えます

  • input にテキストを入力すると TodoContainerdata: tempText 文字列が書き換わる
  • button を押下すると data: tempText を元にオブジェクトを生成、 TodoContainerdata: todoData 配列に追加される

こんな動きにしましょう

今回は $emit も使わず親 > 子のみのフローとします
data, methods は全て TodoContainer に書いて TodoViewprops として渡します

components / TodoContainer.js

import TodoView from './TodoView'

let id = 0
export default {
  data() {
    return {
      tempText: '',
      todoData: []
    }
  },
  methods: {
    updateTempText(text) {
      this.tempText = text
    },
    addItem() {
      const newItem = {
        id: id++,
        name: this.tempText,
        done: false
      }
      this.todoData = this.todoData.concat(newItem)
    }
  },
  render() {
    return (
      <TodoView
        todoData={this.todoData}
        handleInput={this.updateTempText}
        handleSubmit={this.addItem}
      />
    )
  }
}

イベント検知して親のメソッドを叩く

入力部分のフォームを作ります

components/TodoView.vue

<div>
  <form>
    <input />
    <button type="submit">ADD</button>
  </form>

  {/* 略 */}
</div>

props で受け取った関数をイベント検知して叩きます
onSubmitonKeyupと属性をベタ書きして見えますが、実際の html 出力時に属性としては残りません

components / TodoView.vue

props: {
  todoData: {
    type: Array,
    default: () => []
  },
  handleInput: {
    type: Function,
    default: () => {}
  },
  handleSubmit: {
    type: Function,
    default: () => {}
  }
},
render(h) {
  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault()
          this.handleSubmit()
        }}
      >
        <input onKeyup={e => this.handleInput(e.target.value)} />
        <button type="submit">ADD</button>
      </form>

      {/* 略 */}
    </div>
  )
}

これで input 入力時に TodoContainerdata を書き換え、submit時に todo を追加する部分が実装できました

v-for を実装する

Todo 部分の view を作ります
v-for を使わず、map() を使って実装します

todoDataをループして各要素ごとに<div>{ name }</div>を返すことで、v-forと同様の挙動を再現することができます

vue sample

<template>
  <div v-if="todoData.length > 0">
    <div v-for="item in todoData">
      {{ item.name }}
    </div>
  </div>
</template>
components / TodoView.vue

{
  this.todoData.length > 0 && (
    <div>
      {this.todoData.map(item => (
        <div>{item.name}</div>
      ))}
    </div>
  )
}

class を使ったスタイリング

.vue ファイルでは通常の vue と同様、<style>タグを使ったスタイリングが可能です
react とは異なり、属性名はclassNameではなくclass表記になります

おしまい

これでごく基本的な説明は終了です
責務ごとにコンポーネントを分けたり、<style>で css をつけたり、todo の 削除機能などは通常の vue.js 同様に試してみてください

今回作成した内容は下記リポジトリへアップしています
ぜひご覧になってくださいね
https://github.com/masakitm/vue-jsx/blob/master/components/TodoContainer.js
https://github.com/masakitm/vue-jsx/blob/master/components/TodoView.vue

14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?