💎

大規模アプリのVue3アップデート対応知見まとめ

2022/05/12に公開約17,800字

はじめに

今参加させてもらっている案件でついにVue3へのアップデートが完了したので、知見をまとめていきます!
僕1人だけではなく、チームでやったので多少曖昧なところもありますが、そこはご愛嬌でお願いします。
マジで一年くらいかかりました。笑
ちなみにこれを全てやれば完璧にVue3対応出来るわけではなく、あくまでこのプロジェクトで躓いた知見を書いているだけでですので、実際に上げる場合はしっかりと公式ドキュメントを確認してください。
特に移行ビルド系は執筆時点で確認していたら、かなりリリースされていたので是非確認してみてください。

前提

  • 新規機能の開発は止めない
  • Options APIからComposition APIへの書き換えなどはしない
  • 特にいつまでにアップデートなどの期限は設けない
  • マイグレーションビルドは使わない
  • webpackerをつかっている
  • TypeScriptは使っていない

とにかく「バージョンを上げたときにVue2系のときと同じ動作をすること」という最小限の要件を決めてスタートしました。

マイグレーションビルドとは

これです。

https://v3.ja.vuejs.org/guide/migration/migration-build.html#概要

Vue2のアプリケーションが安全に動作する状態でVue3に上げたときに生じうる警告などを出してくれるやつですね。「このコンポーネントはちゃんと動いたからVue3」「こっちは使ってるライブラリがVue3に対応してないからまだVue2」などといったこともできるっぽいです。

https://v3.ja.vuejs.org/guide/migration/migration-build.html#コンポーネントごとの設定

ただ、僕らがVue3化対応を始めたのはVue3が出て結構すぐ。
でもこいつはVue3.1から。
なので最初から選択肢にいなかったのですが、今から移行を始める場合はこれを使って徐々にやっていった方がいいと思います笑

やったこと

といっても最初は「なんかVue3来たしやるかー」くらいで始めたので、Vue3移行チームとして人員が割り当てられたわけではなくVueつよつよな業務委託の方1人に適当にお願いしました。笑
以下は書きやすさの都合上僕がやったように書いてありますが、その方にやってもらったことです。

developブランチにVue3用のESLintを適用させてしまう

.eslintrc.json
{
    ...// 省略
    "extends": [
        "plugin:vue/vue3-recommended"
    ],
    "rules": {
      "vue/no-deprecated-destroyed-lifecycle": "off",
      }
}

新規開発を止めたくないという用件があるも、Vue3に上げたときに問題が出るコードが増え続けるのは嫌ですよね。
なのでこのVue3用のlintを思い切って開発用のメインブランチに適用して、なるべく新規開発時にもVue3で動くようなコードを書いてもらうようにしたということです。
これにより、Vue3に上げたときにlintで検知できるレベルの問題のあるコードがわかるようになります!
さらにreviewdogというのを用いて、問題があるコードはPRにコメントがつくようになりました!
素晴らしい!

https://github.com/reviewdog/reviewdog

注意点

Vue3でdestroyed,beforeDestroyが使えなくなる

お気づきの方もいらっしゃるかと思いますが、rulesに"vue/no-deprecated-destroyed-lifecycle": "off",がありますね。
これはdestroyed,beforeDestroyがvue3では使えないので、eslintで反応しないようにしているということです。

大量のエラー

簡単に書いていますが、もちろんVue3のlintを適用した時点で既存のコードに対して大量のエラーが発生します。
ですが、ほぼfilterの廃止とemitsの必須化によるエラーでした。

feature/vue3ブランチを切る

さて、lintを適用し、新規開発は止めたくないので開発用のブランチからvue3移行用のブランチを切っていきます。
以降の作業はfeature/vue3ブランチに対して行われたものだと思ってください!
つまり、新規開発用のdevelopブランチとは別なので都度追従する必要があります…!

Vue3にあげてみる

{
  "dependencies": {
    "vue": "^3.2.0",
    "vue-loader": "^16.0.0",
  },
  "devDependencies": {
    "@vue/compiler-sfc": "^3.2.0",
  }
}

もっともシンプルなアップデート方法ですね。
多分vue-cliを使用されている場合は、@vue/cli-serviceをあげる必要があると思います。
書いてあるバージョンは適宜変更してください。
Vueは現在3.2まで出ているので、あげる際は一気に上げた方がいいと思います。(<script setup>を使うため。)
vue-loaderは16以上、Vueと@vue/compiler-sfcのバージョンは合わせてください。
参考:

https://v3.ja.vuejs.org/guide/migration/migration-build.html#インストール

Vue-router4へ

{
  "dependencies": {
    "vue-router": "4",
  },
}

こちらを参考に上げていく。

https://router.vuejs.org/guide/migration/

Router to createRouter

router.js
- import Router from 'vue-router'
+ import { createRouter } from 'vue-router'
 
- const router = new Router({
+ const router = createRouter({
  ...
 })

https://router.vuejs.org/guide/migration/#new-router-becomes-createrouter

Historyモードが変わった

router.js
- import { createRouter } from 'vue-router'
+ import { createRouter, createWebHistory } from 'vue-router'
 
 const router = createRouter({
- mode: 'history',
+ history: createWebHistory(),

mode: "history"の挙動が"history": createWebHistory()に移されました。

https://router.vuejs.org/guide/migration/#new-history-option-to-replace-mode

path: '*'の廃止

router.js
  // 404
  {
    name: 'Page404',
-     path: '*',
+     path: '/:pathMatch(.*)*',
    component: Page404,
  }

https://router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes

scrollBehavior

router.js
  history: createWebHistory(),
  routes: routes,
  scrollBehavior() {
-    return { x: 0, y: 0 }   
+    return { top: 0, left: 0 }
  },
})

https://router.vuejs.org/guide/migration/#scrollbehavior-changes

router.pushが非同期になった

これが多分一番やばい。
正直Vue3に上げた今も果たして、果たして大丈夫なのかあんまりわかってないところがある。。

beforeRouteUpdate内でrouter.queryなどを参照すると一つ前のクエリパラメータが参照されてしまったりする。

https://router.vuejs.org/guide/migration/#all-navigations-are-now-always-asynchronous

Vuex4へ

{
  "dependencies": {
    "vuex": "4.0.0",
  },
}

これを参考に。

https://vuex.vuejs.org/ja/guide/migrating-to-4-0-from-3-x.html

storeの定義をcreateStoreに

store.js
- import Vuex from 'vuex'
+ import { createStore } from 'vuex'

- export default new Vuex.Store({
+ export const store = createStore({

})

Vue3のエントリを変更

Vue2とVue3のインスタンス定義の仕方が違うので、それを合わせます。
というか僕がやったんじゃないので定かではないですが、VueRouterを上げるタイミングでこれが必須だったかも。。

import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'

const app = createApp(App)

app
  .use(router)
  .use(store)

app.mount('#app')

https://v3.ja.vuejs.org/guide/migration/global-api.html#新しいグローバル-api-createapp

対応ライブラリの確認

さぁここからが正念場ですね。。
このプロジェクトでは大体以下のVueライブラリを使用していました。
まぁマイナーそうなやつは外しました。笑

  • vue-i18n
  • vee-validate
  • vue-chartjs

vue-i18n

移行ビルドがあることを今発見してしまった。。笑

https://vue-i18n.intlify.dev/guide/migration/vue2.html#migration-from-vue-2

当然僕らは使ってないので愚直にアップデートしました。

{
  "dependencies": {
    "vue-i18n": "^9.0.0",
  },
}

createI18nへ

- import VueI18n from 'vue-i18n'
+ import { createI18n } from 'vue-i18n'

- export const i18n = new VueI18n({
+ export const i18n = new createI18n({

})

t関数がglobalに生えたので変更

このプロジェクトでは例えば、Vuexのstore内でi18n対応したい場合、plugins/i18nからi18nインスタンスをインポートしてやっていました。
ですが、createI18nによって作られるインスタンスはVueI18nと別物になったので、関数のアクセス方法が変わりました。

store.js
import { i18n } from '../plugins/i18n'

- const hoge = i18n.t('key')
+ const hoge = i18n.global.t('key')

まぁこれはちょっと修正範囲が大きすぎるので、

i18n.js

// 頭文字を大文字に変えた
- export const i18n = new createI18n({
+ export const I18n = new createI18n({

})

+ export default i18n = I18n.global
- import { i18n } from '/plugins/i18n'
+ import { I18n } from '/plugins/i18n'

- app.use(i18n)
+ app.use(I18n)

こうすればi18n.jsとエントリを変更するだけで済む。

<i18n>タグの変更

i18n-tというタグ名に変わり、pathもkeypathという名前に変わった。

- <i18n path="" >
+ <i18n-t keypath="">
  
- </i18n>
+ </i18n-t>

https://vue-i18n.intlify.dev/guide/migration/breaking.html#rename-to-i18n-tfrom-i18n

tが文字列しか返さなくなった

例えばこのように日本語ファイルを定義していた場合、

ja.json
  {
    "items": [
      {
        "name": "アイテム1"
      },
      {
        "name": "アイテム2"
      },      
    ],
  }

以下のように使えるが、v.9.0.0からtが文字列しか返さなくなってしまった。
tmに変えることで同じことをすることが出来る。

- <div v-for="item in $t('items')" :key="item.name" >
+ <div v-for="item in $tm('items')" :key="item.name" >
  {{ item.name }}
</div>

https://vue-i18n.intlify.dev/guide/migration/breaking.html#translation-api-return-value

vee-validate

こいつはもう本当に変わってしまった。。
v3とv4で完全に別ライブラリだと思ってもらった方がいいです。

{
  "dependencies": {
    "vee-validate": "^4.0.0",
  },
}

さて、いつものように公式ドキュメントを…

I plan to release a few posts on the changes and maybe collect them in a guide but there is no direct upgrade route as the API is completely different, plus it does not support Vue 2.
いくつか記事を書いてそれをガイドとしてまとめる予定やけど、APIも完全に違うしVue2をサポートしてへんから直接アップグレードする道は今のところないやで。

https://github.com/logaretm/vee-validate/issues/2849#issuecomment-668583443

Vue3化対応への道
〜完〜

と、まぁおふざけはこの辺にしておきましょう。

どうやって対応させるか

VeeValidateを別のライブラリに置き換えるのは正直不可能です。
まぁ出来なくもないですが、めちゃくちゃ時間がかかりますし、新規開発も並行して動いているんです。
そこで例の優秀な同僚がやってくれました!

ValidationObserverとValidationProviderを擬似コンポーネントに置き換える

veeValidateの主なコンポーネントはこの2つです。
が、こいつらはv4にはもういません…(南無)
なのでこいつらと同じインターフェースを持つ擬似的なValidationObserverとValidationProviderを作成します。
ちなみに「何故setupで書いてないんだ」みたいなツッコミは野暮です。

ValidationObserver.vue
<template>
  <Form v-slot="{ handleSubmit, meta }" ref="form" :as="tag">
    <slot :handleSubmit="handleSubmit" :valid="meta.valid" />
  </Form>
</template>

<script>
import { Form } from 'vee-validate'
export default {
  name: 'ValidationObserver',
  components: {
    Form,
  },
  props: {
    tag: {
      type: String,
    },
  },
  methods: {
    reset() {
      return this.$refs.form.reset()
    },
    validate() {
      return this.$refs.form.validate()
    }
  }
}
</script>
ValidationProvider.vue
<template>
  <Field
    v-slot="{ errors, value: val, handleChange }"
    :name="vid || name"
    :rules="rules"
    :label="name"
  >
    <slot :errors="errors" :value="val" :handleChange="handleChange" />
  </Field>
</template>

<script>
import { Field } from 'vee-validate'
export default {
  name: 'ValidationProvider',
  components: {
    Field,
  },
  props: {
    rules: {
      type: [Object, String, Function],
      required: true,
    },
    name: {
      type: String,
      required: true,
    },
    vid: {
      type: String,
    }
  },
}
</script>

これで既存の記法を大体維持したまま、移行することが出来ます!
ただし、これでもまだ少し変更が必要です。。

  1. ValidationProviderにv-modelなどで値を渡す必要がある
- <ValidationProvider v-slot="{ errors }" :name="$t('models.user.lastName')" rules="required">
+ <ValidationProvider v-slot="{ errors }" :name="$t('models.user.lastName')" rules="required" v-model="last_name">
  <TextField v-model="last_name" />
</ValidationProvider>

そもそも Fieldタグ自体が、子にinputタグを持つことを「複雑なケース」として扱っているので、まぁこの使い方自体がトリッキーなんですね。

https://vee-validate.logaretm.com/v4/api/field#using-v-model
  1. handleSubmitの引数
    handleSubmitがFormコンポーネントから渡しているので、引数が変わり、第一引数が$eventになります。
<ValidationObserver v-slot="{ handleSubmit, valid }">
- <form @submit.prevent="handleSubmit(onSubmit)">
+ <form @submit.prevent="handleSubmit($event, onSubmit)">
  ...

https://vee-validate.logaretm.com/v4/guide/components/handling-forms#using-handlesubmit

とまぁこんな感じでValidationProvider、ValidationObserverを使ってる全コンポーネントを直さないといけなかったので、正直一番この修正がキツかったです。。

その他の対応

  • 定義済みバリデーションルールが@vee-validate/rulesに切り出された
yarn add @vee-validate/rules
- import * as rules from 'vee-validate/dist/rules'
+ import AllRules from '@vee-validate/rules'
  • i18nファイルが@vee-validate/i18nに切り出された
yarn add @vee-validate/i18n
- import ja from 'vee-validate/dist/locale/ja.json'
- import en from 'vee-validate/dist/locale/en.json'
+ import ja from '@vee-validate/i18n/dist/locale/ja.json'
+ import en from '@vee-validate/i18n/dist/locale/en.json'
  • それに伴ってlocalizeの方法が変わった
import {
+ configure,
- localize,
  defineRule
} from 'vee-validate'

+ import {
+   localize,
+ } from '@vee-validate/i18n'

- localize({ ja, en })
+ configure({
+   generateMessage: localize({
+    ja,
+    en,
+  }),
+ })
  • extendがdefineRuleに名前が変わった
Object.keys(AllRules).forEach(rule => {
-  extend(rule, AllRules[rule])
+  defineRule(rule, AllRules[rule])
})

いや、これだけでも結構あるな、、
本当にこれ全部優秀な同僚に丸投げしてたので、やはり優秀だなという感じですね。。

Vue-chartjs

vue-chartjsはライブラリ自体のVue3対応が本当に最近終わったばかりで、このライブラリを使ったままVue3にすることはできませんでした。なので僕らのチームはこのライブラリを剥がしました。笑
vue-chartjsのインターフェースを保ったまま、chartjsをラップする新たなライブラリ的なのをチームの方が作ってくれました。
なお、現在はv4にあげることでVue3対応できるのでそのままアップデートすれば良さそうです。

https://vue-chartjs.org/migration-to-v4/

これにてライブラリ対応が終わったので、これからいよいよVue自体が吐くエラー修正に入っていきます。

Vue3のエラー対応

まぁこれも結構大変でしたね。。
破壊的変更対応の他に、「なんかよくわからんけど画面がレンダリングされないしエラーも出てない!」とか、「staging環境でだけ画面が表示されない、、」とか色々ありました。

破壊的変更

https://v3.ja.vuejs.org/guide/migration/introduction.html#破壊的変更

対応する前に

まず、Vue3の対応は困難を極めました。
なので対応する前に自分がつまずいた「そんなのわかるわけないだろ!!」というようなものをあげておきますので、皆様の役に立てれば幸いです。

Vue.js devtoolsのバグ

信じられないかもしれませんが、chrome拡張のVue.js devtoolsが結構バグまみれです。

https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd/related?hl=ja

僕が遭遇したものは

  • devToolのVuexのタブを開いていないと表示されない画面がある。
  • devToolのβ版の方をつかうと表示されない画面がある。
  • devToolをONにしていると表示されない画面がある。
  • devToolとvue i18nのせいでめちゃくちゃコンソールエラーが出る。

この中のいくつかはすでに直っているかもしれませんが、お気をつけてください。
どうしても直せないバグがあった場合、devtoolを切って試してみるのをお勧めします。

追えないエラーログ

Vue3に変えて画面が描画されなくなる不具合では、以下Warningが出る。

runtime-core.esm-bundler.js:73 [Vue warn]: Unhandled error during execution of render function 
  at <Page>
  at <RouterView> 
  at <App>
Unhandled error during execution of render function 
runtime-core.esm-bundler.js:73 [Vue warn]: Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core 

これの辛いところはVueがどういう理由でエラーが出てるのか一切教えてくれないことだ。
どのコンポーネントでエラーが出ているかは教えてくれるが、どのタグで起きてるかは教えてくれない。
これがどういうことになるかというと、例えばtemplateタグだけで1000行くらいあるページコンポーネントで上記のエラーが出ると、1000行の中であたりをつけてそれっぽいタグをコメントアウトしてリロードし、エラーが消えるかどうか確認、消えたら今コメントアウトした部分にエラーの原因があるのでその中でまた当たりをつけてコメントアウトし、、という非常に地道な作業をしなければならない。。

ちなみにここに僕のぼやきがある。

https://zenn.dev/link/comments/dc2125a8b4a3bb

ローカル環境で表示され、ステージングで表示されない画面

大体のWarningがプロダクションビルドやCIでバグとして出現しだす。
普通逆だろうと思うが、例えばこれ

[Vue warn]: Maximum recursive updates exceeded in component . This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.

ローカルだとWarningで済むものが、なんとステージング環境ではそのまま実行されつづけてout of memoryになる。
しかもローカルではなぜか普通に表示されるのでプロダクションビルドしてみないと気づけない。
正直、Vue3にあげた時点でコンソールには大量のエラーとWarningが出るので、Warningの対応は後回しになりがちだが、必ず全部解消しておいた方がいい
このほかにもi18nのライブラリのWarningを放置していたらCI環境でだけテストが落ちるとかそういうことがあった。
とにかくWarningは放置してはいけない

定義されていないdataへのアクセス

Vue2では定義されていないdataにアクセスしてもundefinedが返るだけで何もエラーは起きませんでした。
しかし、Vue3ではWarningを吐くようになります。
「そんなコードがあるのがおかしい!」と言われればおっしゃる通りなのですが、例えばmixinを複数のコンポーネントで使っていると、

// mixin
computed: {
  layoutColor() {
    // colorはコンポーネント側でdataで定義されてたり、されてなかったりする
    return this.color ? 'blue' : this.color
  }
}

めっちゃ適当なんですがこんな感じでmixinを定義すると、colorがdataで定義されてるコンポーネントでは大丈夫なんですが、定義されてないコンポーネントで使おうとするとWarningが出ます。
そして上記で言った通り、そういうものはプロダクションビルドでのみ表示されないという挙動をするコンポーネントになります。。
こういう場合、うちでは暫定対応としてmixinを使っているコンポーネントに定義されていない値をdataやcomputedにnullで定義しました。

では実際の破壊的変更対応に行ってみましょう!

methodにいなきゃいけない関数がcomputedに定義されてる

これが正直一番多くて笑っちゃった。
Vue2はこれでも動く衝撃。
Vue3からthisがProxyに変わったのでそれが原因なのか動かなくなる。

v-modelの破壊的変更対応

破壊的変更: カスタムコンポーネントで使用する場合に、v-model のプロパティとイベントのデフォルト名が変更されます。
プロパティ: value -> modelValue
イベント: input -> update:modelValue
破壊的変更: v-bind の .sync 修飾子とコンポーネントの model オプションは削除され、v-model の引数に置き換えられます。

https://v3.ja.vuejs.org/guide/migration/v-model.html

とのことなので、v-modelを受け付けるようにしていたカスタムコンポーネントに変更を加える必要があります。

props: {
-    value: {
+    modelValue: {
      required: true
    },
...
methods: {
    onChange(value) {
-       this.$emit('input', value)
+       this.$emit('update:modelValue', value)
    }
  }

あとはmodelオプション(以下のやつ)が削除されましたので、もうこのオプションは動きません。

model: {
  prop: 'title',
  event: 'change'
},

これをどうするかというと、子コンポーネントで

props: {
    // これはこのまま
    title: {
      required: true
    },
...
methods: {
    onChange(value) {
-       this.$emit('change', value)
+       this.$emit('update:title', value)
    }
  }

親コンポーネントで

<Parent v-model:title="pageTitle"/>

これでできるようになります。
v-model:"バインドしたい子コンポーネントのprops名"って感じです。
v-model自体は

<Parent :title="pageTitle" @update:title="pageTitle = $event"/>

これのシンタックスシュガーで、titleの部分を変えることでv-modelを一つのコンポーネントで複数使えるようになります。

templateタグのv-forのkey属性

破壊的変更: <template v-for> における key は、(子の要素ではなく)<template> タグに配置する必要があります。

https://v3.ja.vuejs.org/guide/migration/key-attribute.html#概要

公式からそのまま抜粋

<!-- Vue 2.x -->
<template v-for="item in list">
  <div v-if="item.isVisible" :key="item.id">...</div>
  <span v-else :key="item.id">...</span>
</template>

<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
  <div v-if="item.isVisible">...</div>
  <span v-else>...</span>
</template>

templateタグにv-forを指定する場合、今まではtemplateの子要素にkeyをバインドする必要がありました。
子要素でv-ifで条件分岐する場合、条件の数だけkeyをバインドする必要があったので面倒だったのですが、templateタグにkeyをバインドすれば済むようになりました。

ライフサイクルメソッドの対応

destroyedがunmountedに、beforeDestroyがbeforeUnmountに変更されたので書き換えます。以上!

set,deleteが不要になった

Vueは今まで配列の要素やオブジェクトのプロパティの直接の変更が検出できませんでした。
そのためset,deleteなどのメソッドを使ってVueに変更を教えてやる必要がありました。

this.$set(this.obj, 'key', 'value')

しかし、これはVueがJavaScriptのProxyオブジェクトを使用するようになったので不要になりました!
で、これらのset,deleteメソッドが削除されたので以下のように直接変更するように対応が必要です。笑

this.obj.key = 'value'

これでOK。

セレクトボックスのデフォルトプロパティ

多分この辺が影響してると思う。

https://v3.ja.vuejs.org/guide/migration/attribute-coercion.html#概要

以下のようなnullをvalueにバインドしているoptionタグが初期値としてselectタグにnullを渡してもヒットしなくなる。

<select :value="null">
  <option :value="null">初期値</option>
</select>

ので、空文字を渡してあげよう。

<select value="">
  <option value="">初期値</option>
</select>

そもそもTypeScript使ってたら普通にこれoptionのvalueにnullは渡せないからエラーだよなとか思ったりしている。。

filtersの削除

filterが削除されたのでmethodに移す!

https://v3.ja.vuejs.org/guide/migration/filters.html#概要

trantisionのクラス名の変更

https://v3.ja.vuejs.org/guide/migration/transition.html#概要

.name-enterを.name-enter-fromに、.name-leaveを.name-leave-fromに変えるだけ!

storybookが壊れた

これはちょっと面倒なので別記事にまとめます。笑

まとめ

とまぁこんな感じでうちではfeature/vue3ブランチに色々やって、developブランチを都度feature/vue3ブランチにマージして対応、最後にfeature/vue3をdevelopにマージしてみんなでデバッグ、リリースという形でVue3対応を終えました!
今からやるとしたら、やっぱりもうちょっと移行ビルドをつかって楽にやるかなー。
あとやる前にコンソールのwarningを全て潰しておくのとProxyの理解を深めておく、おかしいと思ったらdevtoolを切る。笑

これからどうする?

まだまだ開発環境を良くしていきたいので、

  • 既存コンポーネントのsetup化
  • composableとしてページネーションなどの共通ロジックを切り出していく
  • Webpakerを剥がしていい感じのバンドラーに変える
  • TypeScriptを入れる

などやっていく予定です!
なので、BtoBの大規模なプロジェクトでVue3で開発体験良く、フルリモートでフレキシブルになんでもやっていきたいぜ!というような方を募集していますので少しでも興味ある方は僕までお声かけください!
開発メンバーのほとんどが副業かフリーランスなので、その辺りの融通も効いてとても働きやすいです!
僕のTwitterのリンクを貼っておきますので気軽にDMください!

https://twitter.com/engineerYodaka

最後に

最後まで読んでいただきありがとうございました!
僕はフリーランスでフロント、バック、インフラなどなんでもやるWebデベロッパーをしているものです。
最近はフロント、特にReactにお熱でこんな感じの「Reactors」というReactコミュニティを立ち上げたのでReactに興味あるみなさん是非入ってください!!(Vueの記事でReactのコミュニティの宣伝をするという暴挙)
人が増えたらTwitterコミュニティの枠を飛び出して勉強会など色々やろうと思ってます!

https://twitter.com/i/communities/1519997134153850883

Discussion

ログインするとコメントできます