실제 예제가 있는 고급 TypeScript(1부)
게시 됨: 2020-11-06지난 주에 TypeScript에 대한 간략한 소개를 보았고 특히 JavaScript를 확장하는 이 언어가 더 강력한 코드를 만드는 데 어떻게 도움이 되는지에 대해 이야기했습니다. 그것은 단지 소개에 불과했기 때문에 프로젝트에서 사용하기를 원할 수도 있고 아마도 필요할 수도 있는 TypeScript 기능 중 일부에 대해서는 이야기하지 않았습니다.
오늘은 실제 프로젝트에서 TypeScript를 전문적으로 적용하는 방법을 알려 드리겠습니다. 이를 위해 Nelio Content 소스 코드의 일부를 살펴보고 시작 위치와 현재 어떤 제한 사항이 있는지 이해합니다. 다음으로 완전한 형식의 코드가 될 때까지 조금씩 점진적으로 개선하여 원래 JavaScript 코드를 점진적으로 개선할 것입니다.
Nelio Content의 소스 코드를 기반으로 사용
이미 알고 계시겠지만 Nelio Content는 소셜 미디어에서 웹사이트의 콘텐츠를 공유할 수 있는 플러그인입니다. 이 외에도 게시물의 품질 분석, 작성해야 할 예정된 콘텐츠를 추적하는 편집 일정 등과 같이 블로그에서 더 나은 콘텐츠를 지속적으로 생성하는 데 도움이 되는 여러 기능이 포함되어 있습니다. .

지난달에 플러그인을 시각적으로나 내부적으로 완전히 재설계한 버전 2.0을 게시했습니다. React 인터페이스와 Redux 스토어를 포함하여 오늘날 WordPress에서 사용할 수 있는 모든 새로운 기술(최근 블로그에서 이야기한 내용)을 사용하여 이 버전을 만들었습니다.
따라서 오늘의 예에서는 후자를 개선할 것입니다. 즉, Redux 저장소를 입력하는 방법을 살펴보겠습니다.
Nelio 콘텐츠 편집 캘린더 선택기
편집 캘린더는 요일별로 예약한 블로그 게시물을 보여주는 사용자 인터페이스입니다. 즉, Redux 스토어에는 최소한 두 개의 쿼리 작업이 필요합니다. 하나는 지정된 날짜에 예약된 게시물을 알려주는 작업이고 다른 하나는 게시물 ID가 주어지면 모든 속성을 반환하는 작업입니다.
주제에 대한 게시물을 읽었다고 가정하면 Redux의 선택기가 첫 번째 매개변수로 앱의 모든 정보와 함께 필요한 추가 매개변수가 뒤따르는 state를 수신한다는 것을 이미 알고 있습니다. 따라서 JavaScript의 두 가지 예제 선택기는 다음과 같습니다.
function getPost( state, id ) { return state.posts[ id ]; } function getPostsInDay( state, day ) { return state.days[ day ] ?? []; } state에 posts 와 days 속성이 있다는 것을 어떻게 알 수 있는지 궁금하시다면 매우 간단합니다. 왜냐하면 그것들을 정의한 사람은 바로 저이기 때문입니다. 그러나 여기에 내가 이렇게 구현하기로 결정한 이유가 있습니다.
우리는 두 가지 다른 관점에서 정보에 접근할 수 있기를 원한다는 것을 알고 있습니다. 따라서 데이터를 두 부분으로 구성하는 것이 합리적으로 보입니다.
- 한편으로 우리는 서버에서 가져와 Redux 스토어에 저장한 모든 게시물을 나열한
posts속성을 가지고 있습니다. 논리적으로, 우리는 그것들을 배열에 저장하고 ID가 예상한 것과 일치하는 게시물을 찾기 위해 순차적인 검색을 할 수 있었습니다... 그러나 객체는 더 빠른 검색을 제공하는 사전처럼 동작합니다. - 반면에 특정 날짜에 예약된 게시물에도 액세스해야 합니다. 다시 말하지만, 단일 배열을 사용하여 모든 게시물을 저장하고 특정 날짜에 속하는 게시물을 찾기 위해 필터링할 수 있지만 또 다른 사전이 있으면 더 빠른 조회 솔루션을 제공합니다.
Nelio 콘텐츠의 작업 및 감속기
마지막으로 동적 달력을 원하면 상점에서 저장하는 정보를 업데이트할 수 있는 기능을 구현해야 합니다. 간단하게 하기 위해 우리는 두 가지 간단한 방법을 제안할 것입니다. 하나는 캘린더에 새 게시물을 추가할 수 있는 방법이고 다른 하나는 기존 게시물의 속성을 수정할 수 있는 방법입니다.
Redux 저장소 업데이트에는 두 부분이 필요합니다. 한편으로는 우리가 만들고자 하는 변경을 알리는 작업이 있고 다른 한편으로는 저장소의 현재 상태와 업데이트를 요청하는 작업이 주어지면 현재 상태에 필요한 변경 사항을 적용하는 감속기가 있습니다. 새로운 상태를 생성합니다.
따라서 이를 고려하면 다음과 같은 작업이 스토어에서 수행될 수 있습니다.
function receiveNewPost( post ) { return { type: 'RECEIVE_NEW_POST', post, }; } function updatePost( postId, attributes ) { return { type: 'UPDATE_POST', postId, attributes, } }여기 감속기가 있습니다.
function reducer( state, action ) { state = state ?? { posts: {}, days: {} }; const postIds = Object.keys( state.posts ); switch ( action.type ) { case 'RECEIVE_NEW_POST'; if ( postIds.includes( action.postId ) ) { return state; } return { posts: { ...state.posts, [ action.post.id ]: action.post, }, days: { ...state.days, [ action.post.day ]: [ ...state.days[ action.post.day ], action.post.id, ], }, }; case 'UPDATE_POST'; if ( ! postIds.includes( action.postId ) ) { return state; } const post = { ...state.posts[ action.postId ], ...action.attributes, }; return { posts: { ...state.posts, [ post.id ]: post, }, days: { ...Object.keys( state.days ).reduce( ( acc, day ) => ( { ...acc, [ day ]: state.days[ day ].filter( ( postId ) => postId !== post.id ), } ), {} ), [ post.day ]: [ ...state.days[ post.day ], post.id, ], }, }; } return state; }시간을 내어 모두 이해하고 앞으로 나아가십시오!
JavaScript에서 TypeScript로
가장 먼저 해야 할 일은 이전 코드를 TypeScript로 변환하는 것입니다. TypeScript는 JavaScript의 상위 집합이기 때문에 이미 있습니다. 하지만 이전 함수를 복사하여 TypeScript Playground에 붙여넣으면 암시적 유형이 any 인 변수가 너무 많기 때문에 컴파일러가 상당히 불평한다는 것을 알 수 있습니다. 따라서 몇 가지 기본 유형을 명시적으로 추가하여 먼저 수정해 보겠습니다.
우리가 해야 할 일은 "복잡한"(예: 애플리케이션 상태) any 유형에 명시적으로 추가하고 number 또는 string 또는 다른 변수/인수에 원하는 모든 것을 사용하는 것입니다. 예를 들어 원래 JavaScript 선택기는 다음과 같습니다.
function getPost( state, id ) { return state.posts[ id ]; }명시적 TypeScript 유형을 사용하면 다음과 같이 표시됩니다.
function getPost( state: any, id: number ): any | undefined { return state.posts[ id ]; } 보시다시피 코드를 입력하는 간단한 작업("일반 유형"을 사용하는 경우에도)은 많은 정보를 한눈에 볼 수 있게 해줍니다. 기본 JavaScript에 비해 확실한 개선! 이 경우, 예를 들어 getPost 는 number 를 예상하고(게시물 ID는 정수입니다. 기억하시죠?) 결과는 게시물이 존재하는 경우 무언가가 되고( any ) 존재하지 않으면 아무 것도 반환되지 않습니다( undefined ).
여기에는 컴파일러가 불평하지 않도록 단순 유형을 사용하는 모든 코드 유형에 대한 링크가 있습니다.
TypeScript에서 사용자 정의 데이터 유형 생성 및 사용
이제 컴파일러가 소스 코드에 만족했으므로 이를 개선할 수 있는 방법에 대해 조금 생각할 시간입니다. 이를 위해 저는 항상 우리 영역에 있는 개념을 모델링하는 것으로 시작할 것을 제안합니다.

게시물에 대한 사용자 정의 유형 만들기
우리는 우리 가게가 주로 게시물을 포함할 것이라는 것을 알고 있으므로 첫 번째 단계는 게시물이 무엇인지, 그리고 우리가 가지고 있는 정보를 모델링하는 것이라고 주장합니다. 우리는 지난 주에 사용자 정의 유형을 만드는 방법을 이미 보았으므로 오늘 포스트 개념으로 한 번 시도해 보겠습니다.
type Post = { id: number; title: string; author: string; day: string; status: string; isSticky: boolean; }; 여기서 놀랄 일은 없겠죠? Post 는 숫자 id , 텍스트 title 등과 같은 몇 가지 속성을 가진 객체입니다.
Redux 스토어가 가지고 있는 또 다른 중요한 정보는 짐작하시겠지만 상태입니다. 이전 섹션에서 이미 속성에 대해 논의했으므로 State 유형의 기본 모양을 정의해 보겠습니다.
type State = { posts: any; days: any; }; State 유형 개선
이제 우리는 State 에 두 가지 속성( posts 및 days )이 있다는 것을 알고 있지만 any 것이든 될 수 있기 때문에 그것들이 무엇인지에 대해서는 많이 알지 못합니다. 우리는 두 속성 모두 사전이 되기를 원한다고 말했습니다. 즉, 특정 쿼리(게시물에 대한 posts ID 또는 날짜에 대한 days )가 주어지면 관련 데이터(각각 게시물 또는 게시물 목록)가 필요합니다. 객체를 사용하여 사전을 구현할 수 있다는 것을 알고 있지만 TypeScript에서 사전을 어떻게 표현합니까?
TypeScript 문서를 살펴보면 상당히 일반적인 상황을 처리하기 위한 여러 유틸리티 유형이 포함되어 있음을 알 수 있습니다. 특히, 우리가 원하는 것으로 보이는 Record 라는 유형이 있습니다. 키에 특정 Keys 유형이 있고 값이 Type 인 키/값 쌍을 사용하여 변수를 입력할 수 있습니다. 이 유형을 예제에 적용하면 다음과 같이 됩니다.
type State = { posts: Record<number, Post>; days: Record<string, number[]>; }; 컴파일러의 관점에서 Record 유형은 Keys 값(이 예에서는 posts 의 경우 number , days 의 경우 string )이 주어지면 결과가 항상 Type 의 객체가 되는 방식으로 작동합니다(이 경우에는 a Post 또는 number[] ). 문제는 그것이 우리 사전이 작동하는 방식이 아니라는 것입니다. ID를 사용하여 특정 게시물을 찾을 때 컴파일러가 관련 Post 을 찾을 수도 있고 찾지 못할 수도 있음을 알기를 원합니다. 또는 undefined .
운 좋게도 또 다른 유틸리티 유형인 Partial 유형을 사용하여 이 문제를 쉽게 고칠 수 있습니다.
type State = { posts: Partial< Record<number, Post> >; days: Partial< Record<string, number[]> >; };유형 별칭으로 코드 개선하기
우리 상태에서 posts 속성을 살펴보세요... 무엇이 보이나요? Post 형 게시물을 숫자로 인덱싱하는 사전 맞죠? 이제 직장에서 이 코드를 검토하는 자신의 모습을 상상해 보십시오. 이러한 유형이 발생하면 인덱싱된 게시물의 number 가 인덱싱된 게시물의 ID일 것이라고 가정할 수 있습니다. 하지만 이는 가정일 뿐입니다. 확실히 하려면 코드를 검토해야 합니다. 그리고 days 은 어떻습니까? "숫자 목록을 인덱싱하는 임의의 문자열." 별로 도움이 되지 않죠?
TypeScript 유형은 컴파일러 검사 덕분에 더 강력한 코드를 작성하는 데 도움이 되지만 그 이상을 제공합니다. 의미 있는 유형을 사용하면 코드가 더 잘 문서화되고 유지 관리가 더 쉬워집니다. 의미 있는 유형을 만들기 위해 기존 유형의 별칭을 지정해 볼까요?
예를 들어 게시물 ID( number )와 날짜( string )가 도메인과 관련이 있다는 것을 알고 있으면 다음 유형 별칭을 쉽게 만들 수 있습니다.
type PostId = number; type Day = string;다음 별칭을 사용하여 원래 유형을 다시 작성합니다.
type Post = { id: PostId; title: string; author: string; day: Day; status: string; isSticky: boolean; }; type State = { posts: Partial< Record<PostId, Post> >; days: Partial< Record<Day, PostId[]> >; }; 코드의 가독성을 향상시키기 위해 사용할 수 있는 또 다른 유형 별칭은 Dictionary 유형으로, 편리한 구조 뒤에 Partial 및 Record 사용의 복잡성을 "숨깁니다":
type Dictionary<K extends string | number, T> = Partial< Record<K, T> >;소스 코드를 더 명확하게 만들기:
type State = { posts: Dictionary<PostId, Post>; days: Dictionary<Day, PostId[]>; }; 그리고 그게 다야! 당신은 그것을 가지고 있습니다! 단지 세 개의 간단한 유형 별칭으로 우리는 주석을 사용하는 것보다 분명히 더 나은 방식으로 코드를 문서화할 수 있었습니다. 우리를 뒤따르는 모든 개발자는 포스트가 PostId 를 사용하여 Post 유형의 객체를 인덱싱하는 사전이고, days 가 주어지면 목록 posts 식별자를 반환하는 데이터 구조라는 것을 한 눈에 알 수 있을 것 Day . 당신이 나에게 묻는다면 그것은 꽤 굉장합니다.
그러나 유형 정의 자체가 더 좋을 뿐만 아니라… 모든 코드에서 이러한 새로운 유형을 사용한다면:
function getPost( state: State, id: PostId ): Post | undefined { return state.posts[ id ]; }또한 이 새로운 의미 계층의 이점을 얻습니다! 여기에서 입력된 코드의 새 버전을 볼 수 있습니다.
아, 그런데 유형 별칭은 컴파일러의 관점에서 "원래" 유형과 구별할 수 없다는 점을 명심하십시오. 이것은 예를 들어 PostId 와 number 가 완전히 상호 교환 가능함을 의미합니다. 따라서 PostId 를 number 에 할당하거나 그 반대의 경우(이 작은 예에서 볼 수 있듯이) 컴파일러가 오류를 트리거할 것으로 기대하지 마십시오. 그것들은 단순히 우리의 소스 코드에 의미 체계를 추가하는 역할을 합니다.
다음 단계
보시다시피 TypeScript 유형을 사용하여 JavaScript 코드를 점진적으로 입력할 수 있으며 그렇게 하면 품질과 가독성이 향상됩니다. 오늘의 포스트에서 우리는 React + Redux 애플리케이션의 실제 구현의 예를 좀 더 자세히 보았고 상대적으로 적은 노력으로 개선할 수 있는 방법을 보았습니다. 하지만 아직 갈 길이 멉니다.
다음 포스트에서는 현재 any 유형을 사용하는 나머지 변수/인수를 모두 입력하고 일부 고급 TypeScript 기능도 배울 것입니다. 이 첫 번째 부분이 마음에 드셨기를 바라며, 마음에 드셨다면 친구 및 동료들과 공유해 주십시오.
Unsplash에서 Danielle MacInnes의 추천 이미지.
