누산기 유형 변경으로 Typescript 축소
프로 시저 목록 을 수집 한 다음 어셈블리 라인 스타일로 처리하는 RXJS 파이프 와 유사한 연산자를 만들려고합니다 . 그러나 Typescript는 Reduce의 동적 유형을 확인할 수 없습니다 (누산기는 이전 실행을 기반으로 한 유형을 가짐). 이를 수행하는 적절한 방법이 있습니까?
const pipe = (...fns:Function[])=>{
return fns.reduce((prevRes,curFn)=>curFn(prevRes),undefined)
}
pipe(
()=>true,
(res:string)=>"res should be boolean, this should yell at you"
)
답변
variadic tuples로 원하는 제약 조건을 표현할 수 있지만 유형 추론에 대한 불행한 부작용이 있습니다. 모든 콜백 인수에 명시 적으로 주석을 달고 싶다면 좋습니다. 컴파일러가 당신을 위해 추론하기를 기대한다면 다른 답변에서 볼 수 있듯이 가능한 각 함수 수에 대한 큰 오버로드 목록보다 더 잘하지 못할 것입니다.
어쨌든 다음 도우미를 소개하겠습니다.
type Last<T extends any[]> = T extends [...infer _, infer U] ? U : never;
type Prev<T extends any[], K extends keyof T, D> =
K extends keyof [D, ...T] ? [D, ...T][K] : never;
이 Last<T>유형은 튜플 유형을 취하고 T마지막 요소를 반환합니다 (따라서 Last<["a","b","c"]>이어야 함 "c"). 그리고는 Prev<T, K, D>튜플 유형 T과 (숫자 일 예정인) 키 유형을 취하고 K요소 " K-1" 의 요소를 반환하여 for (그래서 이어야 하고 이어야 함 ) D을 전달하면 반환 합니다 .0KPrev<["a","b","c"],1,"d">"a"Prev<["a","b","c",0,"d"]>"d"
이를 사용하여 함수 체인에 해당하는 유형을 나타낼 수 있습니다.
const pipe = <T extends any[]>(
...fns: { [K in keyof T]: (arg: Prev<T, K, undefined>) => T[K] }
) => fns.reduce((prevRes, curFn) => curFn(prevRes), undefined) as Last<T>
로 전달 된 함수에 대한 반환 유형의 정렬 된 목록에 해당하는 pipe()튜플에서 일반화 되기를 원합니다 . 유형 은 우리가 필요로하는 함수 유형의 체인입니다. 의 각 색인 에 대해 결과 함수의 입력 유형은 이전 색인의 요소이거나 이전 색인 ( ) 이없는 경우 출력 유형은 현재 요소 ( )입니다. 의 출력 은 반환 유형 튜플의 마지막 요소 인 just 입니다.Tfns{[K in keyof T]: (arg: Prev<T, K, undefined> => T[K]}KTundefinedPrev<T, K, undefined>TT[K]pipe()Last<T>
어떻게 작동하는지 봅시다. 장점 :
const good = pipe(
() => true,
(res: boolean) => res ? 123 : 456,
);
// const good: 123 | 456
이것은 오류없이 컴파일되며 val유형 인 것으로 추론됩니다 123 | 456. 그리고 나쁜 점 :
const bad = pipe(
() => true,
(res: string) => res // error!
//~~~~~~~~~~~~~~~~~~~~
//Type 'boolean' is not assignable to type 'string'
)
이것은 정확히 원하는 오류입니다. 컴파일러는 res유형이 될 것으로 예상 boolean하지만 string. 그리고 이제 못생긴 사람을 위해 :
const ugly = pipe(
() => "hello", x => x.length
); // any!!!
죄송합니다 x. 콜백 에 주석을 추가 하는 것을 잊었습니다 . x의 반환 형식 에서 컨텍스트 형식으로 입력 할 수있는 대신 () => "hello"컴파일러는을 포기하고 암시 적으로 사용합니다 any. 그리고 이로 인해 전체 튜플 유형 T이로 추론 any[]되고 모든 유형 안전성이 손실됩니다. 이 실패는 필요한 추론의 순환 특성 때문일 수 있습니다. 튜플 유형 T은 콜백 유형에 따라 달라지며 T. 이것이 마술처럼 작동한다면 좋을 것이지만 그렇지 않습니다.
어쨌든 제약 조건을 나타낼 수 있으며 유용 할 수도 있습니다. 추론의 함정에주의하십시오.
코드에 대한 플레이 그라운드 링크
파이프 함수에 대한 RxJS의 구현 을 살펴보십시오 .
export interface UnaryFunction<T, R> { (source: T): R; }
export function pipe<T>(): UnaryFunction<T, T>;
export function pipe<T, A>(fn1: UnaryFunction<T, A>): UnaryFunction<T, A>;
export function pipe<T, A, B>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>): UnaryFunction<T, B>;
export function pipe<T, A, B, C>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>, fn3: UnaryFunction<B, C>): UnaryFunction<T, C>;
export function pipe<T, A, B, C, D>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>, fn3: UnaryFunction<B, C>, fn4: UnaryFunction<C, D>): UnaryFunction<T, D>;
export function pipe<T, A, B, C, D, E>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>, fn3: UnaryFunction<B, C>, fn4: UnaryFunction<C, D>, fn5: UnaryFunction<D, E>): UnaryFunction<T, E>;
export function pipe<T, A, B, C, D, E, F>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>, fn3: UnaryFunction<B, C>, fn4: UnaryFunction<C, D>, fn5: UnaryFunction<D, E>, fn6: UnaryFunction<E, F>): UnaryFunction<T, F>;
export function pipe<T, A, B, C, D, E, F, G>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>, fn3: UnaryFunction<B, C>, fn4: UnaryFunction<C, D>, fn5: UnaryFunction<D, E>, fn6: UnaryFunction<E, F>, fn7: UnaryFunction<F, G>): UnaryFunction<T, G>;
export function pipe<T, A, B, C, D, E, F, G, H>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>, fn3: UnaryFunction<B, C>, fn4: UnaryFunction<C, D>, fn5: UnaryFunction<D, E>, fn6: UnaryFunction<E, F>, fn7: UnaryFunction<F, G>, fn8: UnaryFunction<G, H>): UnaryFunction<T, H>;
export function pipe<T, A, B, C, D, E, F, G, H, I>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>, fn3: UnaryFunction<B, C>, fn4: UnaryFunction<C, D>, fn5: UnaryFunction<D, E>, fn6: UnaryFunction<E, F>, fn7: UnaryFunction<F, G>, fn8: UnaryFunction<G, H>, fn9: UnaryFunction<H, I>): UnaryFunction<T, I>;
export function pipe<T, A, B, C, D, E, F, G, H, I>(fn1: UnaryFunction<T, A>, fn2: UnaryFunction<A, B>, fn3: UnaryFunction<B, C>, fn4: UnaryFunction<C, D>, fn5: UnaryFunction<D, E>, fn6: UnaryFunction<E, F>, fn7: UnaryFunction<F, G>, fn8: UnaryFunction<G, H>, fn9: UnaryFunction<H, I>, ...fns: UnaryFunction<any, any>[]): UnaryFunction<T, {}>;
연산자에 대해 작동하도록 형식 유추를 얻으려면 연산자 서명을 채울 일반 자리 표시자를 사용하여 오버로드 된 함수 서명 무리를 정의해야합니다. 파이프 함수에 전달되는 연산자 수에 따라 사용되는 오버로드가 결정됩니다. 최대 9 개, 그 이상은 any.
여기에서 코드를 사용하여 놀 수있는 간단한 예제를 만들었습니다.
Typescript의 가장 우아한 사용은 아니지만 단일 파이프에 너무 많은 연산자가 필요한 것은 그리 일반적이지 않다는 점을 감안할 때 허용됩니다. 더 필요하면 다른 호출을에 연결할 수 있습니다 pipe.
다행히 최근에 출시 된 Typescript 4.0은 Variadic Tuple 유형 및 추론에 대한 지원을 크게 개선 하여 많은 함수 서명 오버로드가 필요없는 훨씬 더 나은 솔루션을 제공 할 수 있습니다. RxJS 관리자는 이미 로드맵에 포함되어 있습니다.