繁体   English   中英

如何推断外部函数的参数类型

[英]How to infer an external function's argument type

type GlobalState = {
  yourDetails: {
    firstName: string,
    lastName: string,
  }
}

const globalState = {
  yourDetails: {
    firstName: 'test',
    lastName: 'test1',
  }
}

type Action<T> = (state: GlobalState, payload: any) => T;

type Actions<T> = Record<string, Action<T>>;

type ActionOutput<T extends (...arg: any[]) => any> = T extends (arg: any, arg1: infer G) => any ? (payload: G) => any : (payload: any) => any;

type ActionsOutput<T> = Record<keyof T, ActionOutput<T>>; // how to pass acording function type?

function generator<T, G extends Actions<T>>(payload: G): {
  actions: ActionsOutput<G>
} {
  return {
    actions: Object.entries(payload).reduce((prev, [key, callback]) => {
      return {
        ...prev,
        [key]: (actionPayload) => {
          callback(globalState, actionPayload);
        },
      }
    }, {} as ActionsOutput<G>),
  };
}

function updateNameAction(state: GlobalState, payload: {
  firstName: string,
  lastName: string,
}) {
  return {
    ...state,
    yourDetails: {
      ...payload,
    }
  }
}

const { actions } = generator<GlobalState, { updateNameAction: any }>({
  updateNameAction
})

actions.updateNameAction({
  test: '123'
})

如何infer函数的参数。 因此,当调用上述示例的action时,如果有效负载的类型不匹配,它将引发错误。

工作进行中链接。

所以我会试着在这里解释得更好一点。

打字稿与 JavaScript

使用 TypeScript 时,您必须记住它在运行时几乎没有任何内容。 所以你可以定义所有漂亮的类型和泛型,但是当你运行代码时它们就消失了!

你唯一可以坚持的是类。 这些映射到 JavaScript,但即使在这种情况下,“智能转换”也消失了,所以:

class MyClass {
   constructor(readonly value: string){};
}

// the definitions below both work in TS
const correctInstantiation = new MyClass('the value');
const wrongInstantiation = { value: 'the other value' };

console.log(correctInstantiation instanceof MyClass); // this logs true
console.log(wrongInstantiation instanceof MyClass); // this logs false

如您所见,我使用构造函数定义了类并添加了readonly (但您也可以使用publicprivate ),这样构造函数参数作为类字段工作,您可以在创建实例时直接传递它们。

泛型实例

这是即使在像 Java这样的全类型语言中也做不到的事情。 由于在运行时 Generic 消失了,您需要以某种方式显式传递实际类型,因此:

// this just won't compile
function wrongFunction<T>(payload: T) {
  return payload instanceof T; // can't do this!!
}

包起来

因此,对于您的情况,我能想到的唯一解决方案是在包装器中定义Actions ,该包装器还声明了payload类型,例如:

class FunnyPayload {
  constructor(readonly pun: string) {}
}

class BoringPayload {
  constructor(readonly remark: string) {}
}

type ActionDefinition<PayloadType, OutputType> = {
  payloadType: any,
  action: Action<PayloadType, OutputType>,
}

const actionsDictionary = {
  be_funny: {
    payloadType: FunnyPayload,
    action: (payload: FunnyPayload) => {
      console.log(`Funny Guys says: ${payload.pun}`);
      console.log(`ONG so funny!`);
    }
  },
  be_boring: {
    payloadType: BoringPayload,
    action: (payload: BoringPayload) => {
      console.log(`Boring Guys says: ${payload.remark}`);
      console.log(`ONG so boring!`);
    }
  },
} as { [key: string]: ActionDefinition<any, any> };

在这里,我为每个可能的payload创建了特定的类(您也可以使用原始类型,例如numberstring ,但对于更复杂的对象,您需要一个类,请记住智能转换问题!)

然后,如果您想生成带有有效负载检查的Action列表,您可以执行以下操作:

const generatedActions: Action<any, any>[] = Object.keys(actionsDictionary).map(key => {
  const definition = actionsDictionary[key]
  return (payload: any) => {
    if(!(payload instanceof definition.payloadType)) throw new Error(`Payload mismatch!`);
    return definition.action(payload);
  }
});

测试它

现在您将拥有休闲场景:

generatedActions[0](new FunnyPayload('** something silly! **')); // works
generatedActions[1](new BoringPayload('** something serious... **')); // works
generatedActions[0](new BoringPayload('** something serious... **')); // nope! wrong playload!
generatedActions[0]({ pun: '** something hilarious! **' }); // nope! no instantiation!

这是工作示例

最后只是个人品味提示:当您定义泛型时,只需给它们一个更具解释性的名称,例如PayloadTypeOutputType这样您将始终知道您指的是什么,像TV等简单的字母。越是泛型越容易混淆添加!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM