TypeScript의 빈틈없는 행동 일치

프런트엔드 개발 커뮤니티는 일정한 규모에 이르는 모든 프로젝트에 대해 TypeScript를 사용하는 것이 좋은 생각이라는 것을 이미 알고 있다.언급한 장점은 일반적으로 안전하고 뚜렷하게 기록된 인터페이스를 둘러싸고 생산에 들어가기 전에 오류를 발견하고 안전하게 재구성할 수 있도록 발전한다.
비록 나는 이것들이 모두 타자에 유리한 장점이라는 것에 전적으로 동의하지만, 나는 형사가 과소평가한 장점이 있다고 생각한다.

코드를 안전하게 추가하는 능력
비록 나는 코드를 삭제하는 것이 코드를 작성하는 것보다 더 재미있다고 믿지만, 대부분의 경우 코드를 추가하는 것이다.
새 기능을 추가합니다.기존 기능에 향상된 기능을 추가합니다.기능을 더욱 사용자 정의할 수 있도록 하다.어쨌든 이것은 주로 고객이 원하는 것이다.
그렇다면 왜 우리는 타자 스크립트가 내용을 추가하는 데 얼마나 좋은지 이야기한 적이 없을까.
네, 물건을 이동하고 이름을 바꾸면 컴파일러가 어디서 잊어버린 것이 있는지 알려주는 것이 좋습니다. 하지만 IDE는 따라잡고 있습니다. 자바스크립트 파일에 대해 아주 잘 알고 있습니다.단, 새로운 기능을 추가할 때, switch 문장의 새로운 지점을 처리하는 것을 잊어버리는 것을 알려주는 편집기가 없습니다.
이것이 바로 일치하는 역할을 다하는 곳이다.

무엇이 빈거가 일치합니까?
일부 언어, 예를 들어 OCaml, F# 또는 scala은 모델이 일치하는 개념을 지원한다.이것은 자바스크립트의 switch 문장과 약간 비슷하다. 왜냐하면 하나의 값을 다른 값과 일치시킬 뿐만 아니라 패턴과 일치시킬 수도 있기 때문이다.
빈칸 매칭은 기본적으로 컴파일러가 모든 가능한 상태를 알고 있다면, 매칭에 상태가 없을 때 알려줄 수 있음을 의미합니다.나는 scala 코드를 예로 사용할 것이다. 왜냐하면 이것은 내가 가장 익숙한 언어이기 때문이다.
sealed trait Shape

final case class Circle(radius: Int) extends Shape
final case class Rectangle(width: Int, height: Int) extends Shape

def renderShape(shape: Shape): String = {
  shape match {
    case _:Rectangle => "I am a Rectangle!"
  }
}
Try me in scastie
여기서 컴파일러가 다음 메시지를 보냅니다.

match may not be exhaustive. It would fail on the following input: Circle


좋습니다. JavaScript 개발자로서 default-case eslint 규칙을 이해한 후, 저는 여기에 기본 사례를 추가할 것입니다. 여기까지입니다.
def renderShape(shape: Shape): String = {
  shape match {
    case _:Rectangle => "I am a Rectangle!"
    case _ => "I'm a Circle"
  }
}
Try me in scastie
이 계획은 유효하고 경기 중의 모든 안건이 처리되어 원망하는 사람이 없다.그런데 만약에 우리가 다른 모양을 추가하면 무슨 일이 일어날까요?
final case class Square(length: Int) extends Shape

def renderShape(shape: Shape): String = {
  shape match {
    case _:Rectangle => "I am a Rectangle!"
    case _ => "I'm a Circle"
  }
}

Try me in scastie
옳았어이 프로그램은 여전히 작동할 수 있지만 정상적으로 작동할 수 없다.만약 우리가 정사각형을renderShape 방법에 전달한다면, 그것은 원으로 식별될 것이다. 이것은 우리가 기대하는 것이 아닐 것이다.
물론 코드가 co-located이라면 문제가 되지 않을 수도 있다.다음 코드를 조정해야 한다는 것을 보실 수 있습니다.
그러나 상당히 큰 코드 라이브러리에서 모든 사용법을 알아야 하고 그 중 하나를 잊어버리기 쉽다는 것은 분명하다.컴파일러로 구동할 수 있는 개발 (생각해 봐. 모든 빨간색 물건을 복원하고 작동할 수 있도록 보증하는 것) 은 매우 도움이 된다.
다음은 고정 scala 코드의 모양입니다.
def renderShape(shape: Shape): String = {
  shape match {
    case _:Rectangle => "I am a Rectangle!"
    case _:Circle => "I'm a Circle"
    case _:Square => "I'm a Square"
  }
}

Try me in scastie
주의해라, 우리는 방금 묵인 상황을 완전히 벗어났다.만약 우리가 지금 삼각형을 추가한다면, 그것은 다시 오류를 보일 것이다.

TypeScript에서 이를 어떻게 구현합니까?
좋습니다. 하지만 TypeScript는 패턴 일치를 지원하지 않습니다. 그러면 TS에서 이를 어떻게 실현해야 합니까?
TypeScript 컴파일러는 결합 유형을 정확히 일치시킬 때 실제로 상당히 똑똑하다는 사실이 증명되었다.
태그가 있는 결합을 사용하는 것이 좋습니다. 즉, 각 멤버가 텍스트 유형을 정의하는 감별기의 결합을 의미합니다.
type Circle = {
    kind: 'circle'
    radius: number
}

type Rectangle = {
    kind: 'rectangle'
    width: number
    height: number
}

type Shape = Circle | Rectangle

const renderShape = (shape: Shape): string => {
    switch (shape.kind) {
        case 'circle':
            return 'I am a circle'
    }
}
TypeScript playground
이 예에서 종류 필드는 감별기로 사용된다. 모든 형태는 그것에 의해 유일하게 표시된다.
위 코드를 사용하면 다음과 같은 오류가 발생할 수 있습니다.

Function lacks ending return statement and return type does not include 'undefined'.(2366)


명시적 반환 유형이 제거되고 tsconfig.json에서 noImplicitReturns이 열려 있어도 오류가 발생합니다.

Not all code paths return a value.(7030)


그래서 컴파일러는 우리가 뭔가를 잊어버렸다는 것을 정말 알려주고 싶었다. 아주 좋았다.
마찬가지로 우리는 여기에 기본 사례를 추가하는 함정에 빠져서는 안 된다.나는 심지어 TypeScript 파일에 앞에서 언급한 eslint 규칙을 사용하지 않을 것이다. 왜냐하면 나는 그것이 아무리 해도 컴파일러가 포착할 수 없는 내용을 많이 증가시켰다고 생각하지 않기 때문이다.
컴파일러는case 블록에서 형식을 줄일 수 있기 때문에 shape.radius 내부에서 case 'circle'에 접근할 수 있지만 외부에서 접근할 수 없습니다.
작은 경고는 shape param에서 객체 분해를 사용할 수 없는 것 같습니다.TypeScript는 결합 유형의 모든 멤버에 하나의 형태가 있는 경우에도 적용되지 않습니다.
const renderShape = ({ kind, ...shape }: Shape): string => {
    switch (kind) {
        case 'circle':
            return `I am a circle with ${shape.radius}`
    }
}
TypeScript playground
React 구성 요소를 사용할 때, 특히 이 점을 기억해야 한다. 왜냐하면 그들의 도구는 왕왕 많이 분해되기 때문이다.
이러한 점을 고려하여 우리의 코드는 다음과 같다.
const renderShape = (shape: Shape): string => {
    switch (shape.kind) {
        case 'circle':
            return 'I am a circle'
        case 'rectangle':
            return 'I am a rectangle'
    }
}
Typescript playground
Typescript는 이것에 대해 매우 만족합니다. 새로운 모양을 추가하면 컴파일할 때 오류가 발생합니다.🎉

운행 시 주의사항
형식은 실행할 때 존재하지 않습니다. 모든 안전성은 컴파일할 때만 존재합니다.우리가 100% typescript 코드 라이브러리를 사용하면 이 함수의 유일한 호출자이므로 문제없습니다.현실 세계에서 상황은 때때로 그렇지 않다.우리는 함수를 호출하기 위해 비형식적인 자바스크립트 코드가 있을 수도 있고, 입력의 원본을 제어할 수도 없습니다.
예를 들어, 우리가 rest 서비스를 호출했다고 가정하면, 이것은 우리가 과장하고 싶은 두 가지 모양을 제공한다. 우리는 이미 백엔드 팀과 관계를 맺었다. 우리는 먼저 원형과 직사각형을 주목한 다음에 사각형을 추가할 것이다.React을 사용하여 다음과 같은 작은 어플리케이션을 선보일 예정입니다.
export const App = () => {
    const [shapes, setShapes] = React.useState()

    React.useEffect(() => {
        getShapes().then(setShapes)
    }, [])

    if (!shapes) {
        return <Loading />
    }

    return (
        <Grid>
            {shapes.map((shape) => (
                <Shape {...shape} />
            ))}
        </Grid>
    )
}

const Shape = (props: Shape): JSX.Element => {
    switch (props.kind) {
        case 'circle':
            return <Circle radius={props.radius} />
        case 'rectangle':
            return <Rectangle width={props.width} height={props.height} />
    }
}
다행이다. 이것은 미래의 증명이다. 우리가 다른 모양을 추가하면 타자 스크립트가 우리에게 어떻게 해야 하는지 알려줄 것이다.
여기서는 전체 애플리케이션이 실행 중임을 확인할 수 있습니다.

정의되지 않은 반격
그러나 그 다음에 또 다른 일이 발생했다. 백엔드 팀의 속도가 예상보다 빨랐다.😮. 그들은 스퍼트 속도가 매우 빨라서 스퀘어를 즉각 실시하기로 결정했다.이것은 그들에게 있어서 빠른 승리였다. 그들은 새로운 API 부차적인 버전을 발표했다.
우리 앱이 왜 그래요?
그것은 매우 비참하게 죽을 것이다.부차적인 백엔드 버전이 우리 프로그램 전체를 붕괴시켰다. 왜냐하면 이런 기이한 유형의 스크립트 모드 때문이다😢. 이런 상황이 발생한 것은 현재 우리의 switch 문장이 실패했고 기본 지점이 없기 때문에 undefined으로 돌아왔기 때문이다.UndefinedReact이 보여줄 수 없는 소수의 사물 중 하나이기 때문에 우리는 유명한 잘못으로 죽는다.

Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.


현장 관람:

영원히 구하러 가지 마라
TypeScripts 유형 시스템에서는 bottom type이 될 수 없습니다.그것은 영원히 일어나지 않을 일을 나타낸다. 예를 들어, 항상 이상을 던지거나 무한 순환을 가진 함수는 영원히 되돌아오지 않을 것이다.
이것은 무슨 도움이 됩니까?
typescript가 switch 문장에서 모든case로 형식을 축소하면 모든case가 덮어쓰이면 나머지는never 형식이어야 합니다.우리는 작은 조수가 생겼다고 단언할 수 있다.
const UnknownShape = ({ shape }: { shape: never }) => <div>Unknown Shape</div>

const Shape = (props: Shape): JSX.Element => {
    switch (props.kind) {
        case 'circle':
            return <Circle radius={props.radius} />
        case 'rectangle':
            return <Rectangle width={props.width} height={props.height} />
        default:
            return <UnknownShape shape={props} />
    }
}
이 방법에는 두 가지 장점이 있다.
  • 은 실행할 때 실패하지 않습니다. 다른 모든 모양을 표시하고, 새로 추가된 모양을 표시합니다.
  • Shape 유형에 Square를 추가하면 백엔드 팀을 따라잡고 그것을 실현하기를 원하기 때문에 TypeScript에서 컴파일 오류를 얻을 수 있습니다.너는 here을 볼 수 있다.
    현재 유형이 '여태껏' 로 축소되지 않았기 때문에 (정사각형이 남아 있기 때문에) 알 수 없는 형태의 도구 유형이 일치하지 않습니다.

  • 결론
    모든 언어에서, 빈틈없는 일치는 코드를 더욱 안전하게 추가할 수 있는 좋은 도구이다.입력을 완전히 제어할 때 기본 지점을 생략하는 것이 좋은 선택인 것 같습니다.만약 상황이 그렇지 않다면, TypeScript는 최종적으로 JavaScript일 뿐이기 때문에, 실행할 때 never guard를 사용하여 보호하는 것은 좋은 선택이다.

    좋은 웹페이지 즐겨찾기