Movatterモバイル変換


[0]ホーム

URL:


LoginSignup
652

Go to list of users who liked

722

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

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.

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

Last updated atPosted at 2018-09-19

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

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
created(){this.fetchUserList()}watch:{searchText(){this.fetchUserList()}}

まず、watchは関数名を文字列で受け取れる。

js
created(){this.fetchUserList()}watch:{searchText:{handler:'fetchUserList'}}

immediateをtrueにするとコンポーネントがreadyの時点で実行される!

js
watch:{searchText:{handler:'fetchUserList',immediate:true}}

きゃー!!便利

2. Component Registration

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

html
<BaseInputv-mode='searchText'@keydown.enter='search'/><BaseButton@click='search'><BaseIconname='search'/></BaseButton>

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

js
importBaseButtonfrom'./base-button'importBaseIconfrom'./base-icon'importBaseInputfrom'./base-input'exportdefault{components:{BaseButton,BaseIcon,BaseInput}}

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

js
importVuefrom'vue'importupperFirstfrom'lodash/upperFirst'importcamelCasefrom'lodash/camelCase'// Require in a base component contextconstrequireComponent=require.context(./components,false,/base-[\w-]+\.vue$/)requireComponent.keys().forEach(fileName=>{// Get component configconstcomponentConfig=requireComponent(fileName)// Get PascalCase name of componentconstcomponentName=upperFirst(camelCase(fileName.replace(/^\.\//,'').replace(/\.\w+$/,'')))// Register component globallyVue.component(componentName,componentConfig.default||componentConfig)})

“src/main.js” とか “/components/_globals.js” とかに書いとけばいい感じ。

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

js
componentConfig.default||componentConfig

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

3. Module Registration

2と似てる話し。

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

js
importauthfrom'./modules/auth'importpostsfrom'./modules/posts'importcommentsfrom'./modules/comments'// ...exportdefaultnewVuex.Store({modules:{auth,posts,comments,// ...}})

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

/modules/index.js
importcamelCasefrom'lodash/camelCase'constrequireModule=require.context('.',false,/\.js$/)constmodules={}requireModule.keys().forEach(fileName=>{// Don't register this file as a Vuex moduleif(fileName==='./index.js')returnconstmoduleName=camelCase(fileName.replace(/(\.\/|\.js)/g,''))// namespaceつけとくと便利だぜ!modules[moduleName]={namespaced:true,...requireModule(fileName),}// namespaceいらなければこんな感じ// modules[moduleName] = requireModule(fileName)})exportdefaultmodules

シンプルになりまーす。

js
importauthfrom'./modules'exportdefaultnewVuex.Store({modules})

うむ。まぁ、うむ。

Radical Tweaks - 革命的な微調整

4. Cleaner Views

1つのコンポーネントで複数ページを使うときのテクニック。

js
data(){return{loading:false,error:null,post:null}},watch:{'$route':{handler:'resetData',immediate:true}},methods:{resetData(){this.loading=falsethis.error=nullthis.post=nullthis.getPost(this.$route.params.id)},getPost(postId){// ...}}

idでページ切り替えるようなときはルートをwatchして初期化処理入れとかないと不安。コンポーネントが再利用されちゃうから。

js
data(){return{loading:false,error:null,post:null}},created(){this.getPost(this.$route.params.id)},methods:{getPost(postId){// ...}}

こんなふうにシンプルにかけたら良いんだけど、、、

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

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

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

ひぃやっはーー!!!

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

5. Transparent Wrappers

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

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

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

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

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

でも、、、

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

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

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

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
<BaseInput@focus="doSomething">

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

もうちょっと。

html
<BaseInputplaceholder="What's your name?"@focus="doSomething"/>

こんな感じでplaceholder書いたらどうなるの?

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

こいつのlabelにplaceholder設定される。

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

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

v-bind=$attrs"

BaseInput
<template><label>    {{ label }}<inputv-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.

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

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

NavBarRoutes
<template><liv-for="route in routes":key="route.name"><router-link:to="route">      {{ route.title }}</router-link></li></template>
html
<template><ul><NavBarRoutes:routes="persistentNavRoutes"/><NavBarRoutesv-if="loggedIn":routes="loggedInNavRoutes"/><NavBarRoutesv-else:routes="loggedOutNavRoutes"/></ul></template>

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

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

vue
functional: true,render(h, { props }) {  return props.routes.map(route =><likey={route.name}><router-linkto={route}>        {route.title}</router-link></li>  )}

JSX便利(そこじゃない

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

7. Rendering non-HTML

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

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

js
// Initconstmap=newmapboxgl.Map(/* ... */)map.on('load',()=>{// Datamap.addSource(/* ... */)// Layersmap.addLayer(/* ... */)map.addLayer(/* ... */)// Hover effectsmap.on('mousemove',/* ... */)map.on('mouseleave',/* ... */)})

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

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

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

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

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

js
created(){const{map}=this.$parentmap.on('load',()=>{map.addSource(/* ... */)map.addLayer(/* ... */)})},beforeDestroy(){const{map}=this.$parentmap.on('load',()=>{map.removeSource(/* ... */)map.removeLayer(/* ... */)})},render(h){returnnull}

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

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

さいごに

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

652

Go to list of users who liked

722
5

Go to list of comments

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
652

Go to list of users who liked

722

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?


[8]ページ先頭

©2009-2025 Movatter.jp