Closed32

vuex-router-syncをvuex4.x,vue-router4.xに対応させる作業

spicespice

vuex4.0 docsはこれ
vue-router4.0 docsはこれ.migration guide

まあ、両方とも3.0のちゃんとした書き方を理解してないので、それを見る必要もあるかもしれない。

spicespice

vuex-router-syncは一つのファイルだけ
https://github.com/vuejs/vuex-router-sync/blob/master/src/index.ts

初期化時に、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)
      }
    }
  })

https://vuex.vuejs.org/ja/api/#registermodule

で、breaking changeなさそうに見える
https://next.vuex.vuejs.org/guide/modules.html#dynamic-module-registration

spicespice

vuexのstoreにwatchなんてあるのか
https://vuex.vuejs.org/ja/api/#watch

watch(fn: Function, callback: Function, options?: Object): Function

fnが返す値をリアクティブに監視し、値が変わった時にコールバックを呼びます。fnは最初の引数としてストアのステートを、2番目の引数としてゲッターを受け取ります。 Vue のvm.$watchメソッドと同じオプションをオプションのオブジェクトとして受け付けます。

これもbreaking changeなさそうだけど新しい方のドキュメントは見つからないなあ

spicespice

そして、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なさそうだ
https://next.router.vuejs.org/api/#addroute-2

spicespice

あんまりbreaking change関わるところなさそうなので、一旦バージョン上げて見て様子見する

spicespice

ああ、先にテストから変えとくのが良かったかな。
テストの方は結構変更が必要そう。
vuexもvue-routerも初期化が変わってるから

spicespice

いやぁ、テストがむずい。
テストのためのmountとかいるのかな

spicespice

わからないところ

  • testのためにcreatAppしたものをmountする必要があるのか(なさそう?)
  • どこで出ているかわからないpromise error
  • expect(app.$el.textContent).toBe('/a/b a b')とかいてあるようなtextContentのvue3でのアクセス方法
  • 既存で存在する_hoge参照しているstoreのテストの意味。また、4.0ではそれらのpropsが生えてないので違うテストコードを書く必要があるのか。
spicespice

app.mountはこれでいけた

  const rootEl = document.createElement('div')
  document.body.appendChild(rootEl)
  app.mount(rootEl)

jsのdocument objectについてあまり理解できてない。
ブラウザにいないときでもつかえるのか。

spicespice

これなんですよ

Unexpected error when starting the router: TypeError: Cannot read property '_history' of null
        at Window.get history

windowオブジェクトはブラウザじゃないとないはずだから、pupeteerいるのかなって

spicespice

vue routerについては、
はじめのpush直後に await router.isReady()
それ以降の遷移は await router.push()
と、適切にawaitすることで解決できた。

spicespice

次にvue appのtextContentを参照したい。
これについてはvue-test-utilsを使うか、そのwrapperの実装を見れば解決しそうである。

spicespice

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()
  }
spicespice
  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>になっちゃいますわ。

spicespice

render: () => h('router-view')
これが原因。
RouterViewコンポーネントをちゃんとimportして使おう。
import { RouterView } from 'vue-router'
render: () => h(RouterView)

spicespice
  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の記述が間違っている。

spicespice
  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定義についてまとめるのおもしろそう。何種類あるのだろう。)

spicespice

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])
    }
  })
spicespice

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])
    }
  })
spicespice

unsyncがうまく動いてないからそこを直したら終わり。
古いやつだと
store.watch
router.afterEach
これらの返り値としてunsubscribeするための関数が返ってきてたのだけど、
新しい奴らはvoid.
違う関数を使うか、unsubscribeの方法を探して適切にunsubscribeする必要がある。

spicespice

うそです。勘違いでした。

vue-router

Add a navigation hook that is executed after every navigation. Returns a function that removes the registered hook.

vuexはdocs見つからない

spicespice

unsyncは正しくできてるみたいだけど、
unsyncできたかどうかを確認するためのテストが今までの書き方だとうまく行かないようだ。
参照していた配列とかは使用が変わっている。
subscribeしたときに追加される場所を突き止めなければいけない。

spicespice

vue-router-next

afterEachはafterGuards.addを参照していて、
addするとafterGuardsのhandlersに追加されている。
router.tsの中自体ではafterGuards.list()で参照ができるけど、
そいつは外にexportされてないので外から見ることができない。
だからafterEachに何が登録されているのかを見る手段は現状なさそう。

このスクラップは2020/12/06にクローズされました