vuex-router-syncをvuex4.x,vue-router4.xに対応させる作業
vuex4.0 docsはこれ
vue-router4.0 docsはこれ.migration guide
まあ、両方とも3.0のちゃんとした書き方を理解してないので、それを見る必要もあるかもしれない。
vuex-router-syncは一つのファイルだけ
初期化時に、vuexに対してregistermodule
で登録してるんだねー
clonRouteはいい感じにstateに保持すべき値を計算してくれる感じ。
store.registerModule(moduleName, {
namespaced: true,
state: cloneRoute(router.currentRoute),
mutations: {
ROUTE_CHANGED(_state: State, transition: Transition): void {
store.state[moduleName] = cloneRoute(transition.to, transition.from)
}
}
})
で、breaking changeなさそうに見える
vuexのstoreにwatch
なんてあるのか
watch(fn: Function, callback: Function, options?: Object): Function
fnが返す値をリアクティブに監視し、値が変わった時にコールバックを呼びます。fnは最初の引数としてストアのステートを、2番目の引数としてゲッターを受け取ります。 Vue のvm.$watchメソッドと同じオプションをオプションのオブジェクトとして受け付けます。
これもbreaking changeなさそうだけど新しい方のドキュメントは見つからないなあ
そして、routerのafterEachでcommitをhookしている
// sync store on router navigation
const afterEachUnHook = router.afterEach((to, from) => {
if (isTimeTraveling) {
isTimeTraveling = false
return
}
currentPath = to.fullPath
store.commit(moduleName + '/ROUTE_CHANGED', { to, from })
})
これも特にbreaking changeなさそうだ
あんまりbreaking change関わるところなさそうなので、一旦バージョン上げて見て様子見する
vue routerで対応
で、RouteLocationNormalized
の値の型が変わっているから、それに対応する必要がある
でもそれで終わりそうな気配
型変えたらビルド通っちゃった
ああ、先にテストから変えとくのが良かったかな。
テストの方は結構変更が必要そう。
vuexもvue-routerも初期化が変わってるから
いやぁ、テストがむずい。
テストのためのmountとかいるのかな
わからないところ
- testのためにcreatAppしたものをmountする必要があるのか(なさそう?)
- どこで出ているかわからないpromise error
-
expect(app.$el.textContent).toBe('/a/b a b')
とかいてあるようなtextContentのvue3でのアクセス方法 - 既存で存在する_hoge参照しているstoreのテストの意味。また、4.0ではそれらのpropsが生えてないので違うテストコードを書く必要があるのか。
で、puppetteer使う必要ある?と思い始めた。
vue3などが軒並み使っているが。。。
いや、document.createElement出いける?
https://github.com/vuejs/vue-router-next/blob/master/tests/mount.ts
app.mountはこれでいけた
const rootEl = document.createElement('div')
document.body.appendChild(rootEl)
app.mount(rootEl)
jsのdocument objectについてあまり理解できてない。
ブラウザにいないときでもつかえるのか。
これなんですよ
Unexpected error when starting the router: TypeError: Cannot read property '_history' of null
at Window.get history
windowオブジェクトはブラウザじゃないとないはずだから、pupeteerいるのかなって
色々考えてはいるが、vue-test-utils-nextも使ったほうがいいのかな、という
なかなか参考になりそうでござるな
Vue Router 4 is asynchronous. This must be considered when testing in a jsdom environment.
You can use Vue Router in your tests. There are some caveats, but there is no technical reason why you cannot use a real router.
vue routerについては、
はじめのpush直後に await router.isReady()
、
それ以降の遷移は await router.push()
と、適切にawaitすることで解決できた。
次にvue appのtextContentを参照したい。
これについてはvue-test-utilsを使うか、そのwrapperの実装を見れば解決しそうである。
vtuの参照はこんな感じ
html() {
// cover cases like <Suspense>, multiple root nodes.
if (this.parentElement['__vue_app__']) {
return this.parentElement.innerHTML
}
return this.element.outerHTML
}
text() {
return this.element.textContent?.trim()
}
const rootEl = document.createElement('div')
document.body.appendChild(rootEl)
const app = createApp({
render: () => h('router-view')
})
app.use(store)
app.use(router)
app.mount(rootEl)
console.log(rootEl.innerHTML)
は現状だと<router-view></router-view>
になっちゃいますわ。
render: () => h('router-view')
これが原因。
RouterViewコンポーネントをちゃんとimportして使おう。
import { RouterView } from 'vue-router'
render: () => h(RouterView)
const app = createApp({
render: () => h(RouterView)
})
app.use(store)
app.use(router)
app.mount(rootEl)
console.log(rootEl.innerHTML)
await router.push('/')
console.log(rootEl.innerHTML)
で、'/a/b'のほうがうまく行かないので、登録したcomponentの記述が間違っている。
const Home = defineComponent({
computed: mapState(moduleName, {
path: (state: any) => state.fullPath,
foo: (state: any) => state.params.foo,
bar: (state: any) => state.params.bar
}),
render() {
h('div', [this.path, ' ', this.foo, ' ', this.bar])
}
})
これが間違ってる。
まあ調べずに適当に書いたからな・・・。
defineComponentの正しい書き方調べるか、他の定義方法かどちらかだろう。
(VueのSFC以外のcomponent定義についてまとめるのおもしろそう。何種類あるのだろう。)
defineComponentはTS向けに型つけるためにつかってるくらいだから、
ここみてsetupとか適切に設定すればいけそうかな読むべきはここだ
こういうノリ
const Home = defineComponent({
setup() {
const text = 'this is text'
return () =>
h('div', ['this.path', ' ', 'this.foo', ' ', 'this.bar', text])
}
})
ここに、stateと連携させる。
普通にuseStateつかうかー
good
const Home = defineComponent({
setup() {
const moduleState: any = useStore().state[moduleName]
const path = moduleState.fullPath
const foo = moduleState.params.foo
const bar = moduleState.params.bar
return () => h('div', [path, ' ', foo, ' ', bar])
}
})
not goodだった。
正しくは
const Home = defineComponent({
setup() {
const store = useStore()
const path = computed(() => store.state[moduleName].fullPath)
const foo = computed(() => store.state[moduleName].params.foo)
const bar = computed(() => store.state[moduleName].params.bar)
return () => h('div', [path.value, ' ', foo.value, ' ', bar.value])
}
})
unsyncがうまく動いてないからそこを直したら終わり。
古いやつだと
store.watch
router.afterEach
これらの返り値としてunsubscribeするための関数が返ってきてたのだけど、
新しい奴らはvoid.
違う関数を使うか、unsubscribeの方法を探して適切にunsubscribeする必要がある。
うそです。勘違いでした。
vue-router
Add a navigation hook that is executed after every navigation. Returns a function that removes the registered hook.
vuexはdocs見つからない
unsyncは正しくできてるみたいだけど、
unsyncできたかどうかを確認するためのテストが今までの書き方だとうまく行かないようだ。
参照していた配列とかは使用が変わっている。
subscribeしたときに追加される場所を突き止めなければいけない。
vue-router-next
afterEachはafterGuards.add
を参照していて、
addするとafterGuardsのhandlersに追加されている。
router.tsの中自体ではafterGuards.list()で参照ができるけど、
そいつは外にexportされてないので外から見ることができない。
だからafterEachに何が登録されているのかを見る手段は現状なさそう。
ちょっとvuexのwatchについても同じくわからん。
今できてる部分でPR作ったで・・・