sitateru tech blog: Vue

sitateru tech blog

シタテルの技術やエンジニアの取り組みを紹介するテックブログです。

ラベル Vue の投稿を表示しています。 すべての投稿を表示
ラベル Vue の投稿を表示しています。 すべての投稿を表示

2018年11月29日木曜日

Vueでwatchが動いてくれない

11月 29, 2018

こんにちは、シタテルの藤本です。
主にSCS(Sitateru-Control-System)という生産管理システムのバックエンド(Rails)を担当しています。

SCSではRailsでのViewは基本hamlで書かれていますが現在Vueへと移行しようとしています。
最近私もVueを触る機会があり、watchを使用していてハマってしまったところを2点ほど書いておきます。

  1. Objectをwatchするときはdeepをつけよう
  2. = で追加してはリアクティブになりませんよ

1. Objectをwatchするときはdeepをつけよう

以下のようにdata(user_nameusers)がいて
その値が変更されたことを監視するようにしたい場合にwatchを以下のように書きます

  data () {
    return {
      user_name: '太郎',
      users: {}
    }
  }

  watch: {
    user_name: function (new_value, old_value) {
      alert(`user_name watch!!${this.user_name}`)
    },
    users: function (new_value, old_value) {
      alert(`users watch!!${this.users['name']}`)
    }
  }

  methods: {
    changeJiro () {
      this.user_name = '二郎'    
      this.$set(this.users, 'name', '二郎')    
    },
    changeSaburo () {
      this.user_name = '三郎'    
      this.$set(this.users, 'name', '三郎')
    }
  }

上記の状態でchangeJiro or changeSaburoを呼び出すと
user_nameしか監視されません。

Objectを監視したい場合には以下のようにwatchに対してdeepオプションを付与する必要があります。

    users: {
      handler: function (new_value, old_value) {
        alert(`users watch!!${this.users['name']}`)
      },
      deep: true
    }

公式の以下に記載があります。
watchについて

2. = で追加してはリアクティブになりませんよ

もう1点ハマったところでObjectの値をセットする際の処理の仕方です。

  1. とほとんど同じ内容ですがmethodsでのusersへのセットをuser_nameと同様に=に変更しています。

    data () {
     return {
       user_name: '太郎',
       users: {}
     }
    }
    
    watch: {
     user_name: function (new_value, old_value) {
       alert(`user_name watch!!${this.user_name}`)
     },
     users: {
       handler: function (new_value, old_value) {
         alert(`users watch!!${this.users['name']}`)
       },
       deep: true
     }
    }
    
    methods: {
     changeJiro () {
       alert(`check!! ${this.users['name']}`)
       this.user_name = '二郎'
       this.users['name'] = '二郎' //←ここが=でのセットに変わっている
     },
     changeSaburo () {
       alert(`check!! ${this.users['name']}`)
       this.user_name = '三郎'
       this.users['name'] = '三郎' //←ここが=でのセットに変わっている
     }
    }

こちらも同様にchangeJiro or changeSaburoを呼び出すと
user_nameしか監視されません。
ただしusers['name']を確認してみると値はセットされています。

原因としてはVue.jsはプロパティの追加または削除を検出できず、リアクティブになっていないため監視できないとのことです。
対応方法としてはわかっているもので2つあります。

  1. 最初から定義しておく
  2. setにて追加を行う

1. 最初から定義しておく

Objectのプロパティを最初から定義しておくことで監視することが出来るようになります。
dataに定義しているusersを以下のように修正します。

users: { name: '太郎' }

2. setにて追加を行う

こちらは1. に書いてある通りで、methodを以下のように修正します。

changeJiro () {
  this.user_name = '二郎'    
  this.$set(this.users, 'name', '二郎')    
},
changeSaburo () {
  this.user_name = '三郎'    
  this.$set(this.users, 'name', '三郎')
}

もっと詳細については以下をご参照ください。
リアクティブの探求

また配列に関しても同様の注意が必要で以下に詳しく書かれております。
配列の変化を検出

終わりに

1人でわりといい時間考え込んでましたが周りに聞いたら即解決しました。
悩みすぎるのはよくないと反省すると共に心強い味方がいることに大変感謝した次第です。
またVueの公式は大変日本語でも充実しているのでしっかり読もうと思いました。

以上です、これからVueをご利用される方の助けになれば幸いです。

2018年11月21日水曜日

Visual Studio CodeでNuxt.jsにデバッガをかける

11月 21, 2018

こんにちは!
シタテル株式会社CTOの和泉です。

シタテルではフロントエンドの開発にNuxt.jsを使っています。

デバッガをかけてたいときはどうしたらいいんだろうと思って調べていたら良さそうなやり方を見つけたので自分の記録がてら書いておきます。

参考:
https://codeburst.io/debugging-nuxt-js-with-visual-studio-code-724920140b8f

手順

Nuxt.jsのプロジェクトを作る部分は割愛します。

1. package.json に コマンド追加

"scripts": {
  "debug": "node --inspect node_modules/.bin/nuxt",

2. デバッガの設定

デバッガを開く
画像左下の虫マークをクリック

実行の設定
図中「Launch Program」右の歯車をクリックして実行設定を開く

実行の追加
右下「Add Configuration」をクリックして「Launch via NPM」を選択すると実行設定が追加されます。追加後はlaunch.jsonファイルを保存してください。

3. デバッガの実行

デバッガの実行
追加された「Launch via NPM」を選択して再生ボタンを押すとデバッガが実行されます。

デバッガの実行
ブレークポイントは debuggerと記述

デバッガの実行
ブレークしたところで変数などを確認できます。

以上、簡単ですがNuxt.jsにデバッガをかけられるようになりました。

2018年11月20日火曜日

独自マークアップ言語によるケアラベル(品質表示ネーム)エディター

11月 20, 2018

こんにちは、シタテルの茨木です。

衣服には必ずケアラベル(品質表示ネーム)というものがついています。洗濯の方法などが書いてあるアレですね。

凝ったデザインにすることもありますが、大体はパターンが決まっています。
今回は、独自のマークアップ言語でケアラベルを作れるようにしてみた、という内容です。

完成イメージ

  • 中央の入力欄で独自マークアップを入力すると、左にケアラベルがリアルタイムプレビューされる
  • 画像としてケアラベルをダウンロードできる
  • 右欄でCSSによるデザイン微調整が可能(おまけ)

ソースコード

https://github.com/tibaraki/care-label

目次

  1. マークアップをパースして配列に
  2. 配列からDOMを描画
  3. CSSを適用

マークアップをパースして配列に

本件の肝です。

パーサコンビネータparsimmonを使用します。

パーサコンビネータは文法要素の組み合わせで言語(文書)構造を定義していきます。
たとえば正規表現も文書構造を定義するものですが、正規表現は再帰が表現できないという限界があります。
今回使うようなパーサは再帰を記述できるので、理論上あらゆるプログラミング言語の文法チェックが可能です。

今回はこれをつかって、独自マークアップ言語の文法を定義してみます。

下記は入力例&出力例&パーサの本体です。
parseにマークアップを投げるとパース後の配列を返してくれます。

@id1
  aaa
@id2
  ccc
  @id2-1
    ddd
    eee
    @id2-1-1
      xxx
    fff
  @id2-2
    ggg
    hhh
[
  [
    "@id1",
    [
      [
        "aaa"
      ]
    ]
  ],
  [
    "@id2",
    [
      [
        "ccc"
      ],
      [
        "@id2-1",
        [
          [
            "ddd"
          ],
          [
            "eee"
          ],
          [
            "@id2-1-1",
            [
              [
                "xxx"
              ]
            ]
          ],
          [
            "fff"
          ]
        ]
      ],
      [
        "@id2-2",
        [
          [
            "ggg"
          ],
          [
            "hhh"
          ]
        ]
      ]
    ]
  ]
]
import {regex, string, lazy, seq} from 'parsimmon'

function lexeme(p) { return p.skip(regex(/[ \n]*/)) }

const lparen = lexeme(string('{'))
const rparen = lexeme(string('}'))

const elem = lazy('', () => { return block.or(line) })

const id   = lexeme(regex(/@[\w-]*/i))
const atom = regex(/[^\{\}\n ]+/).skip(regex(/ */))

const line  = regex(/[\n ]*/).then(atom.many()).skip(regex(/\n+/))
const block = regex(/[\n ]*/).then(seq(id, lparen.then(elem.many()).skip(rparen)))

const root = block.many()

export default {
  preserve(string) {
    let value = ''
    let level = 0
    string.split(/\r\n|\r|\n/).forEach((line) => {
      const indent = Math.floor(line.match(/^ */)[0].length / 2)
      if (level < indent) {
        value += "{".repeat(indent - level) + "\n" + line + "\n"
      } else if (level > indent) {
        value += "}".repeat(level - indent) + "\n" + line + "\n"
      } else {
        value += line + "\n"
      }
      level = indent
    })
    value += "}".repeat(level)
    return value
  },
  parse(string) {
    return root.parse(this.preserve(string)).value
  }
}

文書構造の定義

jsの前半部、constの並ぶ箇所は文書構造の定義です。
意味としては下記のような感じです。
elemとblockが相互参照して再帰しているのがわかるかと思います。

const elem = lazy('', () => { return block.or(line) })

elemはblockまたはlineで構成される

const block = regex(/[\n ]*/).then(seq(id, lparen.then(elem.many()).skip(rparen)))

blockはidで始まり、lparen{とrparen}で囲まれた複数個のelemで構成される

preserve

preserveは前処理です。

文書構造の定義でしれっと{}でブロックを定義していましたが、今回作りたいマークアップはインデントでネストを表現する方式なので、前処理でインデントを{}に変換しています。

ここもパーサでできればカッコいいのですが、うまいやり方は思いつきませんでした。pythonとかどうしてるんですかね?

配列からDOMを描画

vueで書きます。パース後の配列(persed)とバインドしておけば、リアルタイムに再描画されて楽です。

    div#view(:style="viewsize" ref="view")
      div(v-for="elem in parsed")

        div(v-if="check(elem, /^@mixings/)" :class="classname(elem)")
          table
            tr(v-for="mixing in take(elem)")
              td(v-for="i in columns(take(elem))") {{ mixing[i-1] || "" }}

        div(v-else-if="check(elem, /^@marks/)" :class="classname(elem)")
          div(v-for="mark in take(elem)")
            img(v-if="isValidMarkId(mark[0])" :src="`/img/${mark[0]}.jpg`")

        div(v-else-if="check(elem, /^@/)" :class="classname(elem)")
          div(v-for="e in take(elem)") {{ e.join(' ') }}

基本的には@hogeをそのままcssのclass(.hoge)として適用し、cssでデザインを定義していく戦略なので、あまり複雑なことはやりません。

ただし、@marksは画像(洗濯マーク)に差し替える必要があるのと、@mixingsはtableでレイアウトしたかったので、vue内で特別扱いしてあげます。

CSSの適用

CSSも書き直したらリアルタイムに反映されてほしいので、更新時に動的に差し替えに行きます。

    applyStyle() {
      const old = document.getElementById('inserted-style')
      old && old.parentNode.removeChild(old)
      const obj = document.createElement('style')
      obj.setAttribute('id', 'inserted-style')
      obj.appendChild(document.createTextNode(this.style))
      document.getElementsByTagName('head')[0].appendChild(obj)
    },

あまりキレイじゃないですが、head要素に無理やり差し込みます。

その他

HTMLからの画像化は、下記ライブラリを使用しています。
https://github.com/tsayen/dom-to-image
どうもフォントまわりの挙動が怪しく、OS/ブラウザによっては画像出力が上手くいかない場合があります。

まとめ

一通り作ってから、yamlでも良かったのでは、とちょっと思ってしまいました。

とはいえ、非エンジニアにはyamlも辛いでしょうし、目的特化して打鍵の少ない文法を定義したい、というところに独自マークアップの需要はあるかもしれません。WYSIWYGに発展できたりするといいですね。

ご参考になれば幸いです。

2018年11月19日月曜日

スネーク?キャメル?ケバブ?命名規則のカオスが発生

11月 19, 2018

こんにちは!
SCSチームのいしづかです。

シタテルでは主にRails + Vue.jsにてシステムを開発しています。

生産管理を行うシタテルコントロールシステム(SCS)もRailsで書かれており、Viewは基本的にhaml + scssです。

SCS全体のAPI + SPA化によるマイクロサービス化計画も進んでいる中、ここ最近作られている画面はhamlの中にVue.jsを埋め込むという方法が取られています。

その埋め込み方法は別の機会に書くとして、そこで出てきた 命名規則のアレコレ についてレポートしたいと思います。

Railsはパスカル + スネーク、Javascriptはキャメル、htmlはケバブ・・・

表題の通り、いろいろ混ざりました。

Railsでは、ネームスペースやクラス名はパスカルケース(ClassName)、それ以外のメソッド名などはスネークケース(method_name)で書きます。

定数などはアッパーケース(CONST_VALUE)で書きますね。いつの間にか3種類使い分けていました。

私自身、前職ではC#.NETでWindowsアプリケーションを作っていたのでパスカルケースを見ると安心します(笑)

Vueを使いだしてから、メソッド名はキャメルケース(methodName)、htmlやcssのidやクラスなどはケバブケース(class-name)が登場してきました。

・・・ついに1つのRailsプロジェクトに、ほぼすべての命名規則が揃ってしまったのです。

RailsとVueで分かれていればまだよかったかもしれませんが、Railsで書いたAPIのjsonがスネークケースで出力されてしまうため、Vue側にもスネークケースが進出してしまいました。

そこで、さすがにルールを設けようということになったのです。

統一ルール発令

Railsで使っているパスカルケース・スネークケースは崩すことができません。これはこのまま。

Vue側は データに関わるもの(data・computed)はスネークケース、処理に関わるもの(methods)はキャメルケース、html・css周りはケバブケース というルールに統一しました。

なんとなく複雑っぽいですが、メソッドとhtml周りはそのままで、データに関わるところだけちょっとRailsに寄せたという形です。

これでRails側はこれまでのソースを流用できますし、Vue側ではJsonをパースしてバインドする際、スネークケースが出てきてもOKです。

Vueが入ってきて発生した命名規則の混乱は、この統一ルール発令によってひとまずしばらくは沈静化するでしょう。。。

API + SPA化計画では、フロントはキャメル統一の予定

とはいえ!Javascriptは基本的にキャメルケース。
現在実装中のSCS APIでは、RailsからJsonを出力する際にキャメルケースに変換するように実装しています。

これでSPA側でデータを受け取る時だけスネークケースにしなくても済みそうです。

まとめ

これまでとは異なる言語やプラットフォームを使う時、既存のものとの整合性が取れなくなるときがありますね。

今回、私たちは「命名規則がカオスになる」という形でそれが表れました。

シタテルではそんなカオスとも仲良くしながら(闘いながら?)、日々新しいものにも取り組みつつシステムを構築しています。