这个和 rxjs 的 pipe 有异曲同工之处,参数无限多,每个参数还会影响后续类型推导,这个要做类型推导是无解的,但是有权衡解法。目前只能把简单情况枚举出来,先人肉列出足够多的参数覆盖大部分使用情况,参数再多就 fallback 到丢失类型推导的下策方案。这里当然用到的函数声明类型重载。
实现效果:
线上 typescript playground 版本
第一步 先定义一个辅助类型
useStore
除末尾参数外,每个参数其实都是一个二元组,第一个元素是 Store<T>,第二个是一个接受 T 返回新类型 U 的函数。
type S<T, U> = [Store<T>, (t:T) => U];
第二步 从一个 store 一个 fn 的情况开始,逐步列出更多参数的情况
最终列出几个看你自己的需求喽。 一个 store 长这样,记得尾部分号,因为是定义函数重载,函数体在后面。
function useStore<T1, U1>(s1: S<T1, U1>, fn: (a: U1) => any): void;
两个长这样:
function useStore<T1, U1, T2, U2>(
s1: S<T1, U1>,
s2: S<T2, U2>,
fn: (a: U1 & U2) => any
): void;`
三个
function useStore<T1, U1, T2, U2, T3, U3>(
s1: S<T1, U1>,
s2: S<T2, U2>,
s3: S<T3, U3>,
fn: (a: U1 & U2 & U3) => any
): void;
最后兜底,防止参数超过定义数量 ts 报错。由于没法定义元素最后一个参数为特定类型,只能这样将就了。或者不定义兜底也行,相当于在 ts 层面不支持无限参数,反正也是 any 乱飞。
function useStore(...args: (S<any, any> | ((a: any) => any))[]): void;
合起来:
class Store<T> {
state: T
constructor(state: T) {
this.state = state
}
get(): T {
return this.state
}
}
type S<T, U> = [Store<T>, (t:T) => U];
function useStore<T1, U1>(s1: S<T1, U1>, fn: (a: U1) => any): void;
function useStore<T1, U1, T2, U2>(s1: S<T1, U1>, s2: S<T2, U2>, fn: (a: U1 & U2) => any): void;
function useStore<T1, U1, T2, U2, T3, U3>(s1: S<T1, U1>, s2: S<T2, U2>, s3: S<T3, U3>, fn: (a: U1 & U2 & U3) => any): void;
function useStore(...args: (S<any, any> | ((a: any) => any))[]): void;
function useStore(...args: any[]) {
const stores = args.slice(0, -1) as S<any, any>[];
const fn = args.slice(-1)[0] as (a: any) => any;
const state: any = {};
stores.forEach(([store, getState]) => Object.assign(state, getState(store.get())));
fn(state);
}
const s1 = new Store({ a: 1 })
const s2 = new Store({ b: 2 })
useStore(
[s1, state => ({
a: state.a
})],
[s2, state => ({
b: state.b
})],
[s1, state => ({
c: String(state.a * state.a)
})],
// [s2, state => ({
// d: state.b * state.b
// })],
function (props) {
type C = typeof props.c;
props.b;
}
);
useStore(
[s1, state => ({
test: state.a
})],
props => console.log(props.test)
);