TypeScript側でのStateの永続化
Async Actionを待機するためのThunkDispatchでのawait で以下は不要になった
Reduxを入れたのはいいけどコマンド・関数実行毎に別プロセス?になるのでその度にStoreが初期化される
こんな感じのユーティリティを置いてNeovim側にStoreを永続化して、必要なタイミングでTSから復元する
code:store.vim
let s:state = {}
function! fzf_preview#remote#store#persist_store(state) abort
let s:state = a:state
endfunction
function! fzf_preview#remote#store#restore_store() abort
return s:state
endfunction
ストアを保存・復元するためのMiddlewareを作成して専用のアクションが飛んできたらNeovimと通信して諸々する
pluginCall がPromiseなせいでMiddlewareが非同期で実行されてしまう
そのためにStoreの変更を検知するために store.subscribe() を使う必要がある(後に書いてるconnect)
code:neovim-store.ts
import { Middleware, MiddlewareAPI, Dispatch, AnyAction } from "redux"
import type { RootState } from "@/store"
import { pluginCall } from "@/plugin"
import { vimVariableModule } from "@/module/vim-variable"
import { executeCommandModule } from "@/module/execute-command"
import { persistModule } from "@/module/persist"
export const neovimStoreMiddleware: Middleware<{}, RootState, Dispatch> = ({
getState,
dispatch
}: MiddlewareAPI<Dispatch, RootState>) => (next: Dispatch<AnyAction>) => async (action: AnyAction) => {
if ((action as typeof persistModule"actions""restoreStore").type === persistModule.actions.restoreStore.type) {
const restoredStore: Partial<RootState> = await pluginCall("fzf_preview#remote#store#restore_store")
dispatch(vimVariableModule.actions.restore(restoredStore.vimVariable))
dispatch(executeCommandModule.actions.restore(restoredStore.executeCommand))
}
if ((action as typeof persistModule"actions""persistStore").type === persistModule.actions.persistStore.type) {
await pluginCall("fzf_preview#remote#store#persist_store", getState())
}
return next(action)
}
middlewareをasyncにせざるを得ないので、どのタイミングでStoreの復元が完了するか分からないからrestoreが終わったタイミングを通知するconnectorを用意する
code:connect.ts
import { store, Store } from "@/store/store"
type CreateSelector<S, R> = (store: Store) => (s?: S) => R
export const createConnect = <S, R>(
createSelector: CreateSelector<S, R>,
process: (selected: R) => void,
option?: { once: boolean }
) => (s?: S) => {
const selector = createSelector(store)
let selected = selector(s)
const unsubscribe = store.subscribe(() => {
const newSelected = selector(s)
if (selected !== newSelected) {
selected = newSelected
process(newSelected)
if (option && option.once) {
unsubscribe()
}
}
})
}
connectorの使い方はこんな感じ
code:base.ts
export const baseHandler = (lines: Array<string>) => {
const connect = createConnect(
createExecuteCommandSelector,
({ commandName, options }) => {
const expectKey = lines.shift()
if (commandName && expectKey !== undefined) {
pluginCall("fzf_preview#remote#handler_to_process#receive_from_remote_plugin", [
createProcessesFunctionName(commandName, expectKey),
expectKey,
lines,
options.processesName
])
}
},
{ once: true }
)
connect()
dispatch(persistModule.actions.restoreStore())
}