Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 5 years have passed since last update.
「Vueコンサルが教えたくない7つの真実」を勉強してみた
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
元のコードはこれ。生成時になにかしてアップデート時にもなにかする。よくやる。
created(){this.fetchUserList()}watch:{searchText(){this.fetchUserList()}}まず、watchは関数名を文字列で受け取れる。
created(){this.fetchUserList()}watch:{searchText:{handler:'fetchUserList'}}immediateをtrueにするとコンポーネントがreadyの時点で実行される!
watch:{searchText:{handler:'fetchUserList',immediate:true}}きゃー!!便利
2. Component Registration
コンポーネント中で別のコンポーネントを使うために
<BaseInputv-mode='searchText'@keydown.enter='search'/><BaseButton@click='search'><BaseIconname='search'/></BaseButton>あちこちにimport書くの面倒くさいよね
importBaseButtonfrom'./base-button'importBaseIconfrom'./base-icon'importBaseInputfrom'./base-input'exportdefault{components:{BaseButton,BaseIcon,BaseInput}}特定のフォルダ内のコンポーネントをprefixとか使って自動で登録するといいよね!
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” とかに書いとけばいい感じ。
便利ちゃあ便利。バンドルサイズでかくなったりするから使いすぎ厳禁。
componentConfig.default||componentConfigおまけ。
こう書いておくとexport defaultしてるときとしてないときとどっちでもいけるよね。ハッピー。
3. Module Registration
2と似てる話し。
Vuexモジュールとか使うときにいちいち読み込むの面倒だよね。
importauthfrom'./modules/auth'importpostsfrom'./modules/posts'importcommentsfrom'./modules/comments'// ...exportdefaultnewVuex.Store({modules:{auth,posts,comments,// ...}})1つのフォルダに突っ込んで自動でやっちゃおうぜ。
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シンプルになりまーす。
importauthfrom'./modules'exportdefaultnewVuex.Store({modules})うむ。まぁ、うむ。
Radical Tweaks - 革命的な微調整
4. Cleaner Views
1つのコンポーネントで複数ページを使うときのテクニック。
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して初期化処理入れとかないと不安。コンポーネントが再利用されちゃうから。
data(){return{loading:false,error:null,post:null}},created(){this.getPost(this.$route.params.id)},methods:{getPost(postId){// ...}}こんなふうにシンプルにかけたら良いんだけど、、、
できます!この魔法の一行を書いとけば。
<router-view:key="$route.fullPath"><router-view>フルパスが変わったらコンポーネントが同一でも再描画!
ひぃやっはーー!!!
まぁ、ほんのちょっとパフォーマンス落ちるだろうけど、コンポーネントシンプルに書けるほうが良いよね。ね!
5. Transparent Wrappers
とりあえずシンプルな入力があるとすんじゃん?
例えば、BaseInputコンポーネントね。
<template><input:value="value"@input="$emit('input', $event.target.value)"></template>で、このコンポーネント使うときにinputイベント以外のイベントを受け取りたい(inputはemitしてるから飛んでくる)と思ったら毎回.native書かなきゃいけないじゃん?
<BaseInput@focus.native="doSomething">ちなみに.native書くとコンポーネントのルート要素のイベント取れる感じね。
でも、、、
<template><label> {{ label }}<input:value="value"@input="$emit('input', $event.target.value)"></label></template>まぁ、ルート要素が変わったら破綻するよね。破綻っ!
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をイベント指定せずにオブジェクトだけ指定すると、そこで定義されたすべてのリスナーを監視できる。うわぉ!
<BaseInput@focus="doSomething">はい、.nativeさようなら〜
未来の不安も払拭。
もうちょっと。
<BaseInputplaceholder="What's your name?"@focus="doSomething"/>こんな感じでplaceholder書いたらどうなるの?
<template><label> {{ label }}<input:value="value"v-on="listeners"></label></template>こいつのlabelにplaceholder設定される。
Vueでは、propsに書いてないattributeはコンポーネントのルート要素に設定される。。。知らなかった、、、
俺は、placeholderをinput要素に渡したいんだ!!
つv-bind=$attrs"
<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つじゃなきゃだめってやつ。
こういうときに困るんだよね、実は。
<template><liv-for="route in routes":key="route.name"><router-link:to="route"> {{ route.title }}</router-link></li></template><template><ul><NavBarRoutes:routes="persistentNavRoutes"/><NavBarRoutesv-if="loggedIn":routes="loggedInNavRoutes"/><NavBarRoutesv-else:routes="loggedOutNavRoutes"/></ul></template>NavBarRoutesのなかでリストのli要素をv-forで複数作って返す感じ。これはエラーになる。
render関数をつかって書くファンクショナルコンポーネントでは、実は複数ルートを返せる!What!!?
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(地図を描画できる超クールなやつ。熱いやつ。)を例として。
// Initconstmap=newmapboxgl.Map(/* ... */)map.on('load',()=>{// Datamap.addSource(/* ... */)// Layersmap.addLayer(/* ... */)map.addLayer(/* ... */)// Hover effectsmap.on('mousemove',/* ... */)map.on('mouseleave',/* ... */)})まぁ、こんなコードを書きますと。
これって、、、宣言的なVueのコードじゃないよね。なんか気に入らないよね。
<MapboxMap><MapboxMarkers:items="cities"primary><templateslot-scope="city"><h3>{{ city.name }}</h3></template></MapboxMarkers><MapboxNavigation/></MapboxMap>こんな感じのAPIだったら幸せなのに、、、
はーい、こんなふうにしたらできますよー。
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つもまとめてみよう。
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme