如何关联在高阶函数之间定义的泛型类型?

时间:2018-11-17 16:56:37

标签: typescript generics higher-order-functions

我正在创建一个Redux存储增强器,该增强器具有一个序列化Redux状态的功能。我将构建商店并为更改设置订阅-在每次更改时,我将序列化状态。对于此MCVE,我将忽略订阅方面,而仅立即调用序列化函数。

但是,由于该函数的高阶性质,我无法将状态的通用类型(需要序列化功能)与商店创建者返回的通用类型相关联:

// Copied and reduced from Redux 4.0.1

type Reducer<S = any> = (
  state: S | undefined,
) => S

type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }

interface Store<S = any> {
  getState(): S
}

type StoreEnhancer<Ext = {}, StateExt = {}> = (
  next: StoreEnhancerStoreCreator
) => StoreEnhancerStoreCreator<Ext, StateExt>

type StoreEnhancerStoreCreator<Ext = {}, StateExt = {}> = <
  S = any,
>(
  reducer: Reducer<S>,
  preloadedState?: DeepPartial<S>
) => Store<S & StateExt> & Ext

// My reduced code

interface Config<S> {
  serialize: (state: S) => string;
}

const storage = <S>(config: Config<S>): StoreEnhancer => createStore => (reducer, preloadedState) => {
  const { serialize } = config;

  const theStore = createStore(reducer, preloadedState);

  const state = theStore.getState();
  const serializedState = serialize(state);

  return theStore;
}

playground

错误是:

const serializedState = serialize(state);
                                  ^~~~~

Argument of type 'S & {}' is not assignable to parameter of type 'S'.

该错误消息令人讨厌,因为我非常确定两个S是无关的;将StoreEnhancerStoreCreator中的定义更改为使用X而不是S会更改此错误消息。

如何将我的通用类型参数与StoreEnhancerStoreCreator上定义的通用参数连接?

3 个答案:

答案 0 :(得分:2)

问题在于,storage被声明为(间接地通过StoreEnhancer返回一个StoreEnhancerStoreCreator,这是一个通用函数,必须对所有S都有效。但是,对storage的给定调用会产生一个商店创建者,该商店创建者仅适用于一个S:传递的S中的config

在我看来,产生StoreEnhancerStoreCreator的唯一方法是从serialize中本身是通用的S函数开始。我不确定这在您的情况下是否有意义。也许熟悉Redux的人会更好地了解该怎么做。

答案 1 :(得分:1)

我同意@ matt-mccutchen指出的问题。 StoreEnhancerStoreCreator是泛型函数,所以(reducer, preloadedState) => {是带有S泛型类型参数的泛型函数,这就是为什么编译器报告两种S类型之间不兼容的原因。 / p>

我将提出的解决方案是使StoreEnhancerStoreCreator不是通用的,据我所知,减速器和有效负载应具有与Config相同的通用类型参数。此解决方案需要向StoreEnhancerStoreCreatorStoreEnhancer添加一个额外的类型参数:

type Reducer<S = any> = (
  state: S | undefined,
) => S

type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }

interface Store<S = any> {
  getState(): S
}

type StoreEnhancer<S, Ext = {}, StateExt = {}> = (
  next: StoreEnhancerStoreCreator<S>
) => StoreEnhancerStoreCreator<S, Ext, StateExt>

type StoreEnhancerStoreCreator<S, Ext = {}, StateExt = {}> = (
  reducer: Reducer<S>,
  preloadedState?: DeepPartial<S>
) => Store<S> & Ext

// My Code

interface Config<S> {
  serialize: (state: S) => string;
}

const storage = <S>(config: Config<S>): StoreEnhancer<S> => createStore => (reducer, preloadedState) => {
  const { serialize } = config;

  const theStore = createStore(reducer, preloadedState);

  const state = theStore.getState();
  const serializedState = serialize(state);

  return theStore;
}

答案 2 :(得分:0)

Titian Cernicova-Dragomir's answer使我步入正轨:

  

此解决方案需要向StoreEnhancerStoreCreatorStoreEnhancer添加一个额外的类型参数

但是,适当的类型参数已经存在;我只是不了解如何正确使用它。具体来说,StoreEnhancer返回带有默认类型参数的StoreEnhancerStoreCreator。如果我改为设置类型参数,则现在知道将返回哪种状态:

const storage = <S>(config: Config<S>): StoreEnhancer =>
  (createStore: StoreEnhancerStoreCreator<{}, S>) =>
  //            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ set this type
    (reducer, preloadedState) => {