TypeScript React props에 React props를 전문적으로 입력하는 방법 - 노조 유형 구분과 투쟁

본문은 서로 무관한 네 부분으로 구성되어 있다.
TypeScript와 React를 사용하는 모든 사람들은 도구를 어떻게 입력하는지 알고 있죠?

제1부


우리가 A, B, C, props.check의 세 가지 유효 상태를 가지고 있다고 상상해 보자.
enum Mode {
  easy = 'easy',
  medium = 'medium',
  hard = 'hard'
}

type A = {
  mode: Mode.easy;
  data: string;
  check: (a: A['data']) => string
}

type B = {
  mode: Mode.medium;
  data: number;
  check: (a: B['data']) => number
}

type C = {
  mode: Mode.hard;
  data: number[];
  check: (a: C['data']) => number
}
현재, 우리는 우리의 구성 요소가 유효한 도구만 받아들일 수 있도록 확보해야 한다.
type Props = A | B | C;

const Comp: FC<Props> = (props) => {
  if (props.mode === Mode.easy) {
    const x = props // A
  }

  if (props.mode === Mode.medium) {
    const x = props // B
  }

  if (props.mode === Mode.hard) {
    const x = props // C
  }

  return null
}
복잡한 거 없어요. 그렇죠?
현재 조건문 외에 check을 호출해 보십시오.
const Comp: FC<Props> = (props) => {
  props.check(props.data) // error
  return null
}
그런데 왜 틀렸을까요?
TL;박사
같은 유형의 변수가 반전 위치에 있는 여러 후보 변수는 교차점 유형을 추정할 수 있습니다.
우리에게는
type Intersection = string & number & number[] // never
이것이 바로 never 기대 js 유형의 원인이다.
Destructure는 TS 유형 추정에 적합하지 않다는 것을 잊지 마십시오.
const Comp: FC<Props> = ({ check, data, mode }) => {
  if (mode === Mode.easy) {
    check(data) // error
  }
  return null
}
destructure를 사용하고 싶으면 typeguards를 동시에 사용하십시오.
const isEasy = <M extends Mode>(
  mode: M,
  check: Fn
): check is Extract<Props, { mode: Mode.easy }>['check'] =>
  mode === Mode.easy
기왕 우리가 코드 라이브러리에 추가 기능을 추가한 이상, 우리는 반드시 테스트를 진행해야 한다. 그렇지?
나는 너에게 길을 안내해 주고 싶다. 별도의 수표는 필요 없다.
typeguards를 사용하는 것보다 안전하거나 더 좋다고 말하는 것은 아닙니다.사실은 그렇지 않다.응용 프로그램의 업무 논리를 변경하고 싶지 않으면 이런 방법을 사용할 수 있다.이 변경 후에 단원 테스트를 작성하라고 요구하는 사람이 없습니다:) 상상해 보십시오. ts에서 check으로 이동하기만 하면 됩니다.TabsWithState으로 전화를 걸 수 있도록 적재량을 초과해야 합니다.
우리는 우리의 연습을 다섯 개의 작은 부분으로 나눈다.
1. 속성이 함수인 키 이름을 가져옵니다.
// Get keys where value is a function
type FnProps<T> = {
  [Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}[keyof T]

// check
type Result0 = FnProps<Props>
2. 모든 함수의 합집합을 실현한다.
type Values<T> = T[keyof T]

type FnUnion<PropsUnion> = Values<Pick<PropsUnion, FnProps<PropsUnion>>>

// | ((a: A['data']) => string) 
// | ((a: B['data']) => number) 
// | ((a: C['data']) => number)
type Result1 = FnUnion<Props>
3. 작은 특정 과부하 계산
type ParametersUnion<PropsUnion> =
  FnUnion<PropsUnion> extends Fn
  ? (a: Parameters<FnUnion<PropsUnion>>[0]) =>
    ReturnType<FnUnion<PropsUnion>>
  : never

// (a: string | number | number[]) => string | number
type Result2 = ParametersUnion<Props>
4. 함수 집합을 다시 불러오기 위해서 우리는 집합이 아니라 집합을 사용해야 한다.그래서 우리는 함수를 합쳐서 구체적이지 않은 재부팅과 합치자

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Overload<PropsUnion> =
  & UnionToIntersection<PropsUnion[FnProps<PropsUnion>]>
  & ParametersUnion<PropsUnion>

// & ((a: A['data']) => string) 
// & ((a: B['data']) => number) 
// & ((a: C['data']) => number) 
// & ((a: string | number | number[]) => string | number)
type Result3 = Overload<Props>
5. 마지막 걸음.우리는 우리의 연합과 중재 기능을 통합시켜야 한다.다시 말하면, 우리는 checkproperty를 덮어쓸 것이다
type OverloadedProps<PropsUnion> =
  & PropsUnion
  & Record<FnProps<PropsUnion>, Overload<PropsUnion>>


// Props & Record<"check", Overload<Props>>
type Result4 = OverloadedProps<Props>
전체 예:
import React, { FC } from 'react'

enum Mode {
  easy = 'easy',
  medium = 'medium',
  hard = 'hard'
}

type A = {
  mode: Mode.easy;
  data: string;
  check: (a: A['data']) => string
}

type B = {
  mode: Mode.medium;
  data: number;
  check: (a: B['data']) => number
}

type C = {
  mode: Mode.hard;
  data: number[];
  check: (a: C['data']) => number
}

type Fn = (...args: any[]) => any

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;


type Props = A | B | C;

// Get keys where value is a function
type FnProps<T> = {
  [Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}[keyof T]

// check
type Result0 = FnProps<Props>

type Values<T> = T[keyof T]

type FnUnion<PropsUnion> = Values<Pick<PropsUnion, FnProps<PropsUnion>>>

// | ((a: A['data']) => string) 
// | ((a: B['data']) => number) 
// | ((a: C['data']) => number)
type Result1 = FnUnion<Props>


type ParametersUnion<PropsUnion> =
  FnUnion<PropsUnion> extends Fn
  ? (a: Parameters<FnUnion<PropsUnion>>[0]) =>
    ReturnType<FnUnion<PropsUnion>>
  : never

// (a: string | number | number[]) => string | number
type Result2 = ParametersUnion<Props>


type Overload<PropsUnion> =
  & UnionToIntersection<PropsUnion[FnProps<PropsUnion>]>
  & ParametersUnion<PropsUnion>

// & ((a: A['data']) => string) 
// & ((a: B['data']) => number) 
// & ((a: C['data']) => number) 
// & ((a: string | number | number[]) => string | number)
type Result3 = Overload<Props>

type OverloadedProps<PropsUnion> =
  & PropsUnion
  & Record<FnProps<PropsUnion>, Overload<PropsUnion>>


// Props & Record<"check", Overload<Props>>
type Result4 = OverloadedProps<Props>

const Comp: FC<OverloadedProps<Props>> = (props) => {
  const { mode, data, check } = props;

  if (props.mode === Mode.easy) {
    props.data // string
  }

  const result = check(data) // string | number

  return null
}
이것은 아이템을 입력하는 잘못된 방식임을 명심하세요.이를 임시 해결 방안으로 간주하다.

제2부분


Stackoverflow의 또 다른 예를 생각해 봅시다.

리액트 아이템 - 차별적 노조 유형과 맞서 싸우다


7월 8일 21일
설명: 1
정답: 4
2

나는 도구와 유사한 두 개의 조립품을 가지고 있지만, 중요한 차이가 하나 있다.tabs이라는 구성 요소는 하나의 도구 tabs만 사용합니다. 이것은 다음과 같은 모양의 대상입니다.
interface ItemWithState {
  name: string
  active: boolean;
}

interface WithStateProps {
  tabs: ItemWithState[];
};
또 다른 유사한...
Open Full Question
우리는 유사한 도구를 가진 두 개의 구성 요소가 있는데 withRouter 속성은 흔히 볼 수 있는 것이다.
interface ItemWithState {
  name: string;
  active: boolean;
}

interface ItemWithRouter {
  name: string;
  path: string;
}

type WithStateProps = {
  tabs: ItemWithState[];
};

type WithRouterProps = {
  withRouter: true;
  baseUrl?: string;
  tabs: ItemWithRouter[];
};

const TabsWithRouter: FC<WithRouterProps> = (props) => null
const TabsWithState: FC<WithStateProps> = (props) => null
또한 다음과 같은 고급 구성 요소가 있습니다.
type TabsProps = WithStateProps | WithRouterProps;

const Tabs = (props: TabsProps) => {
  if (props.withRouter) { // error
    return <TabsWithRouter {...props} />; // error
  }
  return <TabsWithState {...props} />; // error
};

우리는 마지막으로 세 가지 잘못을 저질렀다.
TS에서는 선택 사항이므로 tabs 속성을 가져올 수 없습니다.반대로 공공 속성 withRouter?:never만 얻을 수 있습니다.이것은 예기한 행위다.
해결 방법이 하나 있다.WithStateProps{...props} 유형에 추가할 수 있습니다.
현재 그것은 일을 시작하여 Tabs의 유형을 추정하고 있다.그러나 이것은 작은 단점이 하나 있다. 그것은 우리가 hacks 구성 요소의 불법 도구로 전달할 수 있도록 허락한다.
import React, { FC } from 'react'

interface ItemWithState {
  name: string;
  active: boolean;
}

interface ItemWithRouter {
  name: string;
  path: string;
}

type WithStateProps = {
  withRouter?: never;
  tabs: ItemWithState[];
};

type WithRouterProps = {
  withRouter: true;
  baseUrl?: string;
  tabs: ItemWithRouter[];
};

const TabsWithRouter: FC<WithRouterProps> = (props) => null
const TabsWithState: FC<WithStateProps> = (props) => null

type TabsProps = WithStateProps | WithRouterProps;

const Tabs = (props: TabsProps) => {
  if (props.withRouter) {
    return <TabsWithRouter {...props} />;
  }
  return <TabsWithState {...props} />;
};

const Test = () => {
  return (
    <div>
      <Tabs // With incorrect state props
        baseUrl="something"
        tabs={[{ name: "myname", active: true }]}
      />
    </div>
  );
};
이런 방법은 매우 나쁘다.다른 typeguard를 시도해 봅시다.

interface ItemWithState {
  name: string;
  active: boolean;
}

interface ItemWithRouter {
  name: string;
  path: string;
}

type WithStateProps = {
  tabs: ItemWithState[];
};

type WithRouterProps = {
  withRouter: true;
  baseUrl?: string;
  tabs: ItemWithRouter[];
};

const TabsWithRouter: FC<WithRouterProps> = (props) => null
const TabsWithState: FC<WithStateProps> = (props) => null

type TabsProps = WithStateProps | WithRouterProps;

const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
  : obj is Obj & Record<Prop, unknown> =>
  Object.prototype.hasOwnProperty.call(obj, prop);


const Tabs = (props: TabsProps) => {
  if (hasProperty(props, 'withRouter')) {
    return <TabsWithRouter {...props} />;
  }
  return <TabsWithState {...props} />;
};

const Test = () => {
  return (
    <div>  
      <Tabs // With incorrect state props
        baseUrl="something"
        tabs={[{ name: "myname", active: true }]}
      />
    </div>
  );
};
나는 이런 방법이 더 좋다고 믿는다. 왜냐하면 우리는 어떤 WithStateProps도 사용할 필요가 없기 때문이다.우리의 Animal형은 어떤 추가 선택 도구도 있어서는 안 된다.그러나 그것은 여전히 같은 결점을 가지고 있다.불법 상태는 허용된다.
함수 재부팅을 잊어버린 것 같습니다.이것은 간단한 함수이기 때문에react 구성 요소의 작업 방식과 같습니다.
함수의 교차는 다시 로드된다는 점에 유의하십시오.

// type Overload = FC<WithStateProps> & FC<WithRouterProps>

const Tabs: FC<WithStateProps> & FC<WithRouterProps> = (props: TabsProps) => {
  if (hasProperty(props, 'withRouter')) {
    return <TabsWithRouter {...props} />;
  }
  return <TabsWithState {...props} />;
};

const Test = () => {
  return (
    <div>
      <Tabs // With correct state props
        tabs={[{ name: "myname", active: true }]}
      />
      <Tabs // With incorrect state props
        baseUrl="something"
        tabs={[{ name: "myname", active: true }]}
      />
      <Tabs // WIth correct router props
        withRouter
        tabs={[{ name: "myname", path: "somepath" }]}
      />
      <Tabs // WIth correct router props
        withRouter
        baseUrl="someurl"
        tabs={[{ name: "myname", path: "somepath" }]}
      />
      <Tabs // WIth incorrect router props
        withRouter
        tabs={[{ name: "myname", active: true }]}
      />
    </div>
  );
};
문제: 만약에 우리가 연맹에 다섯 개의 요소가 있다면?
A:조건 분포 유형을 사용할 수 있습니다.
import React, { FC } from 'react'

interface ItemWithState {
  name: string;
  active: boolean;
}

interface ItemWithRouter {
  name: string;
  path: string;
}

type WithStateProps = {
  tabs: ItemWithState[];
};

type WithRouterProps = {
  withRouter: true;
  baseUrl?: string;
  tabs: ItemWithRouter[];
};

const TabsWithRouter: FC<WithRouterProps> = (props) => null
const TabsWithState: FC<WithStateProps> = (props) => null

type TabsProps = WithStateProps | WithRouterProps;

const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
  : obj is Obj & Record<Prop, unknown> =>
  Object.prototype.hasOwnProperty.call(obj, prop);

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Distributive<T> = T extends any ? FC<T> : never

type Overload = UnionToIntersection<Distributive<TabsProps>>

const Tabs: Overload = (props: TabsProps) => {
  if (hasProperty(props, 'withRouter')) {
    return <TabsWithRouter {...props} />;
  }
  return <TabsWithState {...props} />;
};

const Test = () => {
  return (
    <div>
      <Tabs // With correct state props
        tabs={[{ name: "myname", active: true }]}
      />
      <Tabs // With incorrect state props
        baseUrl="something"
        tabs={[{ name: "myname", active: true }]}
      />
      <Tabs // WIth correct router props
        withRouter
        tabs={[{ name: "myname", path: "somepath" }]}
      />
      <Tabs // WIth correct router props
        withRouter
        baseUrl="someurl"
        tabs={[{ name: "myname", path: "somepath" }]}
      />
      <Tabs // WIth incorrect router props
        withRouter
        tabs={[{ name: "myname", active: true }]}
      />
    </div>
  );
};
다음과 같은 방법을 사용할 수도 있습니다.

type Overloading =
  & ((props: WithStateProps) => JSX.Element)
  & ((props: WithRouterProps) => JSX.Element)

이것은 스타일의 문제다.
나는 네가 아직 피곤하지 않기를 바란다.

제3부분

dogName개의 구성 요소가 다음 구속을 갖는다고 가정합니다.
  • canBark이 빈 문자열이거나 설정되지 않은 경우 dogName
  • 이어야 합니다.
  • 빈 문자열이 아닌 경우 canBark은 True
  • 
    type NonEmptyString<T extends string> = T extends '' ? never : T;
    
    type WithName = {
        dogName: string,
        canBark: true,
    }
    
    type WithoutName = {
        dogName?: '',
        canBark: false
    };
    
    type Props = WithName | WithoutName;
    
    React 구성 요소는 일반 함수일 뿐이므로 일반 매개변수를 사용하여 다시 로드할 수 있습니다.
    
    type Overloadings =
        & ((arg: { canBark: false }) => JSX.Element)
        & ((arg: { dogName: '', canBark: false }) => JSX.Element)
        & (<S extends string>(arg: { dogName: NonEmptyString<S>, canBark: true }) => JSX.Element)
    
    const Animal: Overloadings = (props: Props) => {
        return null as any
    }
    
    테스트해 보겠습니다.
    import React, { FC } from 'react'
    
    type NonEmptyString<T extends string> = T extends '' ? never : T;
    
    type WithName = {
        dogName: string,
        canBark: true,
    }
    
    type WithoutName = {
        dogName?: '',
        canBark: false
    };
    
    type Props = WithName | WithoutName;
    
    
    type Overloadings =
        & ((arg: { canBark: false }) => JSX.Element)
        & ((arg: { dogName: '', canBark: false }) => JSX.Element)
        & (<S extends string>(arg: { dogName: NonEmptyString<S>, canBark: true }) => JSX.Element)
    
    const Animal: Overloadings = (props: Props) => {
        return null as any
    }
    
    const Test = () => {
        return (
            <>
                <Animal dogName='' canBark={false} /> // ok
                <Animal dogName='a' canBark={true} /> // ok
                <Animal canBark={false} /> // ok
    
                <Animal dogName='a' canBark={false} /> // error
                <Animal dogName='' canBark={true} /> // error
                <Animal canBark={true} /> // error
            </>
        )
    }
    

    제4부분


    만약 우리가 하나의 구성 요소를 가지고 있다면, 그것은 foobar의 속성이 문자열이길 희망하지만, 속성 foohello이 될 수 없다.
    이를 위해 우리는 foobar 속성에 대해 현식 범형을 사용해야 한다.
    이것은 매우 간단하다.
    import React from 'react'
    
    
    type Props<F extends string = '', B extends string = ''> = {
        foo: F;
        bar: B;
    }
    
    type ConditionalProps<T> = T extends { foo: infer Foo; bar: string } ? Foo extends 'hello' ? never : T : never
    
    const Example = <F extends string, B extends string>(props: ConditionalProps<Props<F, B>>) => {
        return null as any
    }
    
    
    const Test = () => {
        <>
            <Example foo='hello' bar='bye' /> // expected error
            <Example foo='not hello' bar='1' /> // ok
        </>
    
    }
    
    읽어주셔서 감사합니다.

    좋은 웹페이지 즐겨찾기