「Vueコンサルが教えたくない７つの真実」を勉強してみた

Vueの便利なテクニック７つ

Youtubeで見つけたので勉強ついでにまとめてみる。

# 出展
Chris Fritz さん

Youtube
https://www.youtube.com/watch?v=7YZ5DwlLSt8

資料（Github）
https://github.com/chrisvfritz/7-secret-patterns

まとめ - 動画とはバージョン違い（英語）
https://www.vuemastery.com/conferences/vueconf-2018/7-secret-patterns-vue-consultants-dont-want-you-to-know-chris-fritz/

# Productivity Boost - 生産性向上
## 1. Smarter Watcher

元のコードはこれ。生成時になにかしてアップデート時にもなにかする。よくやる。

```js:js
created () {
    this.fetchUserList()
}
watch: {
    searchText() {
        this.fetchUserList()
    }
}
```
まず、watchは関数名を文字列で受け取れる。

```js:js
created () {
    this.fetchUserList()
}
watch: {
    searchText: {
        handler: 'fetchUserList'
    }
}
```
immediateをtrueにするとコンポーネントがreadyの時点で実行される！

```js:js
watch: {
    searchText: {
        handler: 'fetchUserList',
        immediate: true
    }
}
```
きゃー！！便利


## 2. Component Registration

コンポーネント中で別のコンポーネントを使うために

```html:html
<BaseInput
  v-mode='searchText'
  @keydown.enter='search'
/>
<BaseButton @click='search'>
  <BaseIcon name='search'/>
</BaseButton>
```

あちこちにimport書くの面倒くさいよね

```js:js
import BaseButton from './base-button'
import BaseIcon from './base-icon'
import BaseInput from './base-input'

export default {
  components: {
    BaseButton,
    BaseIcon,
    BaseInput
  }
}
```

特定のフォルダ内のコンポーネントをprefixとか使って自動で登録するといいよね！

```js:js
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

// Require in a base component context
const requireComponent = require.context(
  ‘./components’, false, /base-[\w-]+\.vue$/
)

requireComponent.keys().forEach(fileName => {
  // Get component config
  const componentConfig = requireComponent(fileName)

  // Get PascalCase name of component
  const componentName = upperFirst(
    camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, ''))
  )

  // Register component globally
  Vue.component(componentName, componentConfig.default || componentConfig)
})
```
“src/main.js” とか “/components/_globals.js” とかに書いとけばいい感じ。

便利ちゃあ便利。バンドルサイズでかくなったりするから使いすぎ厳禁。

```js:js
componentConfig.default || componentConfig
```
おまけ。
こう書いておくとexport defaultしてるときとしてないときとどっちでもいけるよね。ハッピー。

## 3. Module Registration
2と似てる話し。

Vuexモジュールとか使うときにいちいち読み込むの面倒だよね。

```js:js
import auth from './modules/auth'
import posts from './modules/posts'
import comments from './modules/comments'
// ...

export default new Vuex.Store({
  modules: {
    auth,
    posts,
    comments,
    // ...
  }
})
```

１つのフォルダに突っ込んで自動でやっちゃおうぜ。

```js:/modules/index.js
import camelCase from 'lodash/camelCase'
const requireModule = require.context('.', false, /\.js$/)
const modules = {}

requireModule.keys().forEach(fileName => {
  // Don't register this file as a Vuex module
  if (fileName === './index.js') return

  const moduleName = camelCase(
    fileName.replace(/(\.\/|\.js)/g, '')
  )
  // namespaceつけとくと便利だぜ！
  modules[moduleName] = {
    namespaced: true,
    ...requireModule(fileName),
  }
  // namespaceいらなければこんな感じ
  // modules[moduleName] = requireModule(fileName)
})
export default modules
```

シンプルになりまーす。

```js:js
import auth from './modules'

export default new Vuex.Store({
  modules
})
```

うむ。まぁ、うむ。

# Radical Tweaks - 革命的な微調整
## 4. Cleaner Views
１つのコンポーネントで複数ページを使うときのテクニック。

```js:js
data() {
  return {
    loading: false,
    error: null,
    post: null
  }
},
watch: {
  '$route': {
    handler: 'resetData',
    immediate: true
  }
},
methods: {
  resetData() {
    this.loading = false
    this.error = null
    this.post = null
    this.getPost(this.$route.params.id)
  },
  getPost(postId) {
    // ...
  }
}
```
idでページ切り替えるようなときはルートをwatchして初期化処理入れとかないと不安。コンポーネントが再利用されちゃうから。

```js:js
data() {
  return {
    loading: false,
    error: null,
    post: null
  }
},
created () {
  this.getPost(this.$route.params.id)
},
methods: {
  getPost(postId) {
    // ...
  }
}
```
こんなふうにシンプルにかけたら良いんだけど、、、

できます！この魔法の一行を書いとけば。

```html:html
<router-view :key="$route.fullPath"><router-view>
```

フルパスが変わったらコンポーネントが同一でも再描画！

ひぃやっはーー！！！

まぁ、ほんのちょっとパフォーマンス落ちるだろうけど、コンポーネントシンプルに書けるほうが良いよね。ね！

## 5. Transparent Wrappers

とりあえずシンプルな入力があるとすんじゃん？
例えば、BaseInputコンポーネントね。

```html:BaseInput
<template>
  <input
    :value="value"
    @input="$emit('input', $event.target.value)"
  >
</template>
```

で、このコンポーネント使うときにinputイベント以外のイベントを受け取りたい（inputはemitしてるから飛んでくる）と思ったら毎回`.native`書かなきゃいけないじゃん？

```html:html
<BaseInput @focus.native="doSomething">
```

ちなみに`.native`書くとコンポーネントのルート要素のイベント取れる感じね。

でも、、、

```html:BaseInput
<template>
  <label>
    {{ label }}
    <input
      :value="value"
      @input="$emit('input', $event.target.value)"
    >
  </label>
</template>
```

まぁ、ルート要素が変わったら破綻するよね。破綻っ！
BaseInput使ってて突然動かなくなるというバグに遭遇する危険大。。。恐怖！！

これが解決策だ！ワン、ツー、スリー！

```html:BaseInput
<template>
  <label>
    {{ label }}
    <input
      :value="value"
      v-on="listeners"
    >
  </label>
</template>

<script>
computed: {
  listeners() {
    return {
      ...this.$listeners,
      input: event => 
        this.$emit('input', event.target.value)
    }
  }
}
</script>
```
リスナーを全部返しちゃう。
v-onをイベント指定せずにオブジェクトだけ指定すると、そこで定義されたすべてのリスナーを監視できる。うわぉ！

```html:html
<BaseInput @focus="doSomething">
```

はい、`.native`さようなら〜
未来の不安も払拭。

もうちょっと。

```html:html
<BaseInput
  placeholder="What's your name?"
  @focus="doSomething"
/>
```
こんな感じでplaceholder書いたらどうなるの？

```html:BaseInput
<template>
  <label>
    {{ label }}
    <input
      :value="value"
      v-on="listeners"
    >
  </label>
</template>
```
こいつのlabelにplaceholder設定される。

Vueでは、propsに書いてないattributeはコンポーネントのルート要素に設定される。。。知らなかった、、、

俺は、placeholderをinput要素に渡したいんだ！！

つ `v-bind=$attrs"`

```html:BaseInput
<template>
  <label>
    {{ label }}
    <input
      v-bind=$attrs"
      :value="value"
      v-on="listeners"
    >
  </label>
</template>
```

うぉぉ！まぶしい！！！

propsに指定されてないattributeは全部ここにバインドされるぜ。

**ここ大事！**
これを使うときには、attributeを引き継ぐデフォルトの挙動をオフにするために、
`inheritAttrs: false`
をコンポーネント（ここではBaseInput）に指定しときましょう。

# Unlocked Posibilities - 開放された可能性
## 6. Single-Root Components

このエラーよく遭遇するよね。まじで。

```
(Emitted value instead of an instance of Error)
  Error compiling template:

  <div></div>
  <div></div>

  - Component template should contain exactly one root element. 
    If you are using v-if on multiple elements, use v-else-if 
    to chain them instead.
```

コンポーネントのルート要素は１つじゃなきゃだめってやつ。

こういうときに困るんだよね、実は。

```html:NavBarRoutes
<template>
  <li
    v-for="route in routes"
    :key="route.name"
  >
    <router-link :to="route">
      {{ route.title }}
    </router-link>
  </li>
</template>
```

```html:html
<template>
  <ul>
    <NavBarRoutes :routes="persistentNavRoutes"/>
    <NavBarRoutes
      v-if="loggedIn"
      :routes="loggedInNavRoutes"
    />
    <NavBarRoutes
      v-else
      :routes="loggedOutNavRoutes"
    />
  </ul>
</template>

```
NavBarRoutesのなかでリストのli要素をv-forで複数作って返す感じ。これはエラーになる。

render関数をつかって書くファンクショナルコンポーネントでは、実は**複数ルートを返せる**！What!!?

```vue:vue
functional: true,
render(h, { props }) {
  return props.routes.map(route =>
    <li key={route.name}>
      <router-link to={route}>
        {route.title}
      </router-link>
    </li>
  )
}
```
JSX便利（そこじゃない

まぁ、render関数知らなきゃいけないのぐらいが面倒なところかな。

## 7. Rendering non-HTML

HTMLじゃなくてWebGLとか別のやつをレンダリングする物を使うときのテクニック。

ここではMapGL（地図を描画できる超クールなやつ。熱いやつ。）を例として。

```js:js
// Init
const map = new mapboxgl.Map(/* ... */)

map.on('load', () => {
  // Data
  map.addSource(/* ... */)

  // Layers
  map.addLayer(/* ... */)
  map.addLayer(/* ... */)

  // Hover effects
  map.on('mousemove', /* ... */)
  map.on('mouseleave', /* ... */)
})
```

まぁ、こんなコードを書きますと。

これって、、、宣言的なVueのコードじゃないよね。なんか気に入らないよね。

```html:html
<MapboxMap>
  <MapboxMarkers
    :items="cities"
    primary
  >
    <template slot-scope="city">
      <h3>{{ city.name }}</h3>
    </template>
  </MapboxMarkers>
  <MapboxNavigation/>
</MapboxMap>
```

こんな感じのAPIだったら幸せなのに、、、

はーい、こんなふうにしたらできますよー。

```js:js
created() {
  const { map } = this.$parent
  map.on('load', () => {
    map.addSource(/* ... */)
    map.addLayer(/* ... */)
  })
},
beforeDestroy() {
  const { map } = this.$parent
  map.on('load', () => {
    map.removeSource(/* ... */)
    map.removeLayer(/* ... */)
  })
},
render(h) {
  return null
}
```

*だが、`$parent`はこういうときだけは許すが、基本的には絶対使うなよ！*

うーん、この例はいまいちよく理解できてない。

# さいごに
この７つのうち２つが違うバージョンもGitリポジトリにあるので、気が向いたらその２つもまとめてみよう。