繁体   English   中英

Generics 和 TypeScript 中的类型推断

[英]Generics and Type Inference in TypeScript

(跟进这个问题

我有一个 function 接受配置 object 与一些 state 定义(这是一个 Z9ED39E2EA561586B76E8 机器)

例子:

const machine = createStateMachine({
  initial: 'inactive',
  states: stateNode({
    inactive: {
      on: { ACTIVATE: 'active' },
    }),
    active: stateNode({
      on: { DEACTIVATE: 'inactive' },
      effect(send) {
        send('DEACTIVATE') // Only accepts "DEACTIVATE".
      },
    }),
  },
});

用户可以通过调用send来切换当前的 state,有两个地方可用:

  • 从配置内的effect function 返回
  • createStateMachine function 返回

我想从配置中尽可能多地推断, stateNode是一个中介 function 有助于类型推断:

function stateNode<T extends Record<keyof T, PropertyKey>>({on, effect}: {
  on: T, effect?: (send: (action: keyof T) => void) => void}) {
  return { on, ...effect && { effect } }
}

到目前为止一切正常,但我想让“on”属性成为可选的——有什么想法吗?

这是带有完整代码的 TS Playground 的链接

从概念上讲,您需要做的就是on当前需要的任何地方都设置为可选,并在您以编程方式使用on类型的任何地方使用NonNullable<T>实用程序类型,这样就可以了:

type States<T extends Record<keyof T, { on?: any }>> = { [K in keyof T]: {
  on?: Record<keyof (NonNullable<T[K]['on']>), keyof T>,
  effect?(send: (action: keyof NonNullable<T[K]['on']>) => void): void;
} }

function createStateMachine<
  T extends States<T>>(states: {
    initial: keyof T,
    states: T
  }) {
  return function send(arg: KeysOfTransition<NonNullable<T[keyof T]['on']>>) {
  };
}

function stateNode<T extends Record<keyof T, PropertyKey>>({ on, effect }: {
  on?: T, effect?: (send: (action: keyof T) => void) => void
}) {
  return { ...on && { on }, ...effect && { effect } }
}

不幸的是,这并不完全奏效:

const a = createStateMachine({
  initial: 'inactive',
  states: {
    inactive: stateNode({
      on: {
        ACTIVATE: 'active'
      }
    }),
    frozen: stateNode({ effect() { console.log("Entered Frozen") } }), // error!
//  ~~~~~~
// Type 'Record<string | number | symbol, string | number | symbol>' is not assignable to 
// type 'Record<string | number | symbol, "inactive" | "active" | "frozen">'.
    active: stateNode({
      on: {
        DEACTIVATE: 'inactive',
      },
      effect: send => {
        send('DEACTIVATE')
      },
    })
  },
});
// const a: (arg: string | number | symbol) => void

当您使用具有on属性的东西调用stateNode时,编译器会正确推断T ,但是当您忽略它时,编译器会推断Record<PropertyKey, PropertyKey> ,而这根本不是您想要的。 即使我可以编译, PropertyKey也会让编译器忘记你的所有状态的名称,你最终会得到一些无用的东西,比如从createStateMachine()出来的(arg: PropertyKey) => void 您希望T undefined{}Record<never, any>或其他内容。 好吧,我尝试使用stateNode的单个调用签名,但上下文类型不断导致T被推断为这种不幸的宽类型。


相反,我建议将stateNode() 重载的 function以便我们可以更好地控制省略on属性时发生的情况:

function stateNode<T extends Record<keyof T, PropertyKey>>(param: {
  on: T;
  effect?: ((send: (action: keyof T) => void) => void) | undefined;
}): typeof param;
function stateNode(param: { effect?: () => void, on?: undefined }): typeof param;
function stateNode<T extends Record<keyof T, PropertyKey>>({ on, effect }: {
  on?: T, effect?: (send: (action: keyof T) => void) => void
}) {
  return { ...on && { on }, ...effect && { effect } }
}

如果您在调用stateNode()时指定on ,则使用第一个调用签名并且行为与之前相同。 如果您忽略on则使用第二个呼叫签名。 这里根本没有T类型可以推断; 相反,编译器将effect视为可选的无参数 function (因为,正如您在评论中所说,这就是您想要的)。 在这两种情况下,output 与输入的类型相同。 让我们看看这是否可以改善:

const a = createStateMachine({
  initial: 'inactive',
  states: {
    inactive: stateNode({
      on: {
        ACTIVATE: 'active'
      }
    }),
    frozen: stateNode({ effect() { console.log("Entered Frozen") } }),
    active: stateNode({
      on: {
        DEACTIVATE: 'inactive',
      },
      effect: send => {
        send('DEACTIVATE')
      },
    })
  },
});
// const a: (arg: "ACTIVATE" | "DEACTIVATE") => void

看起来不错!

Playground 代码链接

暂无
暂无

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

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