経緯
React 用に作られた状態管理ライブラリ typeless が良さげだったのですが
最近使っているのが Vue なので Vue と typeless を組み合わせたいなと思いました。
完成品
github:ytoune/practice-20191019-typeless-with-vue-hooks
動作例
hooks を使う
<template>
<div>
<p>count: {{ count }}</p>
<p><button @click="countUp">+1</button></p>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { Component, Prop } from 'vue-property-decorator'
import { useActions } from 'typeless'
import { useModule, CounterActions, getCounterState } from '~/store'
@Component({
hooks() {
useModule()
const acts = useActions(CounterActions)
const state = getCounterState.useState()
const count = state.count
const countUp = acts.startCount
return { count, countUp }
},
})
export default class CountUp extends Vue {
count!: number
countUp!: Function
}
</script>
問題点
typeless は React の hooks に依存しています。
具体的にはソースコードに import * as React from 'react'
と書かれています。
今回は Vue 用に React の API を再現します。
ディレクトリ構造
- workspace
|- src
| |- index.ts
| |- App.vue
# その他省略
|- package.json
|- tsconfig.json
# その他省略
パスの設定
TypeScript と Parcel のパスの設定を行います。
(今回は Parcel を使っています)
{
"compilerOptions": {
/* 略 */
"paths": {
"~/*": ["*"],
"react": ["src/react/core.ts"],
"react-dom": ["src/react/dom.ts"]
},
/* 略 */
},
/* 略 */
}
{
/* 略 */
"alias": {
"react": "./src/react/core.ts",
"react-dom": "./src/react/dom.ts"
}
/* 略 */
}
Vue 用に React の API を再現する
実装優先でパフォーマンスなどを無視しているところが多いです。
既存のライブラリ
できる限り既存の遺産を再利用したいですね。
-
Vue フレームワークの創始者である Evan You さんが似たようものを作り始めているみたいです。
useState
などの一部の hooks が実装されています。 -
github:TotooriaHyperion/vue-hooks
上記の fork です。
useMemo
などが追加されています。 -
github:zephraph/vue-context-api
createContext
やProvider
などの React の ContextAPI の Vue 向け実装です。
ただしuseContext
がないです。
不足している実装を埋める
useReducer
export const useReducer = (reducer: any, initialState: any) => {
const [s, update] = useState(initialState)
const dispatch = (action: any) => {
update(reducer(s, action))
}
return [s, dispatch]
}
useLayoutEffect
とりあえず useEffect を複製してます。
export const useLayoutEffect = useEffect
useContext
vue-context-api では、値を context.value に代入しています。
これを useContext で取り出します。
+const getcontext = '#context'
export const createContext = (defaultValue: any) => {
const context = {}
return {
+ [getcontext]: context,
Provider: Provider(context, defaultValue),
// Consumer: Consumer(context)
Consumer: null,
}
}
export function useContext(context: any) {
return context[getcontext].value
}
unstable_batchedUpdates
すべてのインスタンスを $forceUpdate
してます。
ゴリ押しです。
export const unstable_batchedUpdates = (fn: () => void) => {
fn()
for (const ins of list) ins.$forceUpdate()
}
export const batchedUpdates = unstable_batchedUpdates
const list: any[] = []
Vue.use(function(Vue: any) {
Vue.mixin({
beforeCreate() {
list.push(this)
},
beforeDestroy() {
const i = list.indexOf(this)
if (~i) {
list.splice(i, 1)
}
},
})
})
とりあえず上記を実装でも動くみたいです。