React + TS(props, setTimeout/setInterval, typeof/keyof)

박정호·2022년 11월 1일
0

Game Project

목록 보기
12/13
post-thumbnail

🚀 Start

간단한 숫자야구, 반응속도체크, 가위바위보 게임을 구현해보면서 props, setTimout/setInterval, typeof/keyof, 고차함수는 어떻게 타이핑되는지 알아보자.

1️⃣ props 타입지정

👉 함수형 컴포넌트의 props

부모 컴포넌트에서 자식 컴포넌트로 props를 전달하는 과정은 다음과 같다.

// 부모컴포넌트
<Try tryInfo={v} />

// 자식컴포넌트
const Try = ({tryInfo}) => {
  return (...);
};

props에 타입을 지정해주려면 다음과 같이 작성할 수 있다.

const Try = ({tryInfo}: {tryInfo: TryInfo}) => {
  return (...);
};

하지만, 다음과 같이 작성할 것을 추천한다.

  • React에서는 generic으로 간단하게 함수형 컴포넌트의 props에 대한 타입을 적을 수 있다. 함수형 컴포넌트는 생략해서 사용을 해왔지만 FunctionComponent 타입으로 제네릭을 포함하고 있다. 이 제네릭 요소에 아래와 같이 props의 타입을 정의를 해주면 된다.
const Try: FunctionComponent<{tryInfo: TryInfo}> = ({ tryInfo }) => {
  return (...);
};

👉 클래스형 컴포넌트의 props

✅ 클래스형 컴포넌트에서는 React.Component의 generic 첫번째 인자가 props 타입을 정의하는 자리이다. 만약에 state에 대한 정의가 필요없다면, props에 대한 타입 정의만 넣어주면 된다.

class Try extends React.Component<{ tryInfo: TryInfo }> {
  render() {...}
}

state에 대한 타입정의만 필요한 경우에는 props자리에 {} 빈 객체로 표기해줘야 한다.)

class Try extends React.Component<{}, State> {
	render() {... }
}

함수형 컴포넌트에서는 useState hooks를 사용하기 때문에 state자리는 필요가 없다.


2️⃣ setTimeout / setInterval

  • setTimeout을 이용해 일정 시간이 지난 후에 함수를 실행하는 방법
  • setInterval을 이용해 일정 시간 간격을 두고 함수를 실행하는 방법

타입스크립트가 현재 본인을 실행중인 환경을 node.js 인지 browser 인지 구분 못하는 경우가 있다.

  • 따라서, window.setTimeout() 처럼 window를 명시적으로 사용해준다.

명시적으로 window.setTimeout()window.clearTimeout() 을 호출한다.

  • 이 작업은 TypeScript에 프론트엔드 웹 앱용 코드를 작성 중임을 알려줍니다.

화면의 렌더링에는 영향을 주지 않는 useRef 를 사용해서 setTimeout에 대한 정보를 변수로 저장한다.

  • useRef는 값이 변경되어도 re-render가 안되기 때문에 View와 관련된 것이 아니라면 useRef를 쓰고, 화면의 상태와 관련된 것이라면 useState를 쓴다.

경우에 따라 알맞은 useRef 의 타입정의를 골라서 타입정의를 한다.

  • 기존의 useRef(null)의 경우 내부의 useRefcurrentread-only 속성이기 때문에 값을 넣을 수 없다.
  • 하지만 useRef<number|null>(null) 로 정의를 해주게 되면, React.refObject.current에서 React.MutableRefObject<number | null>.current로, current가 변경가능한 mutable한 속성이 된다.

✏️ setTimeout() 사용 예시

// use ref to store the timer id
const refTimer = useRef<number | null>(null);

// trigger the timer
const startTimer = () => {
    if (refTimer.current !== null) return;
    refTimer.current = window.setTimeout(() => {
       /* do something here */
    }, 5000);
};

// stop the timer
const stopTimer = () => {
    if (refTimer.current === null) return;
    window.clearTimeout(refTimer.current);
    refTimer.current = null;
};

✏️ setInterval() 사용 예시

const intervalref = useRef<number | null>(null);

// Start the interval
const startInterval = () => {
    if (intervalref.current !== null) return;
    intervalref.current = window.setInterval(() => {
      /* ...*/
    }, 1000);
};

// Stop the interval
const stopInterval = () => {
    if (intervalref.current) {
      window.clearInterval(intervalref.current);
      intervalref.current = null;
    }
};

💡 잠깐) useRef 세가지 타입 정의

참고하자! TypeScript React에서 useRef의 3가지 정의와 각각의 적절한 사용법

<@types/react/index.d.ts>

// intialValue의 타입과 useRef의 타입을 일치시켰을때 Mutable한 RefObject가 된다.
function useRef<T>(initialValue: T): MutableRefObject<T>;
//MutableRefObject interface

function useRef<T = undefined>(): MutableRefObject<T | undefined>;

interface MutableRefObject<T> {
    current: T;
}

function useRef<T>(initialValue: T|null): RefObject<T>;
// RefObject interface
interface RefObject<T> {
    readonly current: T | null;
}

4️⃣ key of / typeof

두 object의 값들은 상수, 즉 변하지 않는 값이므로 as const 고정시켜주자.

  • rspCoords : 가위, 바위, 보 이미지가 나타날 좌표 (coordinate)
  • scores: 승부 결과를 계산해줄 가위,바위,보 각각의 고유한 값

가위바위보 버튼을 클릭시 computChoice라는 함수가 호출되면서 imgCoords를 인자로 받아온다. type을 만들어서 이 값의 타입을 정의해보자.

const computerChoice = (imgCoords: ImgCoords) => {...}

1️⃣ 값을 넣어주는 기본적인 방법

2️⃣ typeof 를 통해 rspCoords의 타입을 이용한다.

3️⃣ key of를 통해 rspCoords 의 키값을 이용한다.

4️⃣ 다음과 같이 작성하여 ImgCoords의 값 변경에도 유동적으로 변환되게 하고, 또한 타입을 명시할 수 있다.

💡중요) type of, keyof ,Mapped Type

type of
: 값 공간에 있는 값에 typeof 연산자를 적용하여 값 공간의 값을 타입 공간에서 타입으로 이용할 수 있다.

let s = "hello";
let n: typeof s; // let n : string

const add1 = (x:number)=>x+1
type MyAdd1Type = typeof add1 // (x:number)=>number


// Error (<>안에는 타입이 들어가야 하기 때문이다.)
function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<f>; 


// 정상동작
function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;

type P = {
    x: number;
    y: number;
}

keyof
: 이미 존재하는 오브젝트를 사용한 타입 지정이 가능하다.

// no index signature
type Point = { x: number; y: number };
type P = keyof Point; // type P = 'x' | 'y'

// with index signature
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish; // type A = number

// 자바스크립트 오브젝트 키는 스트링 타입으로 반드시 강제변환되기 때문에 숫자도 허용한다.
type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // type M = string | number

강제 형 변환
: TypeScript는 기본적으로 객체의 프로퍼티를 읽을 때, string타입의 key 사용을 허용하지 않기 때문에 as 를 통해서 강 형 변환을 해준다.

const obj = {
  foo: "hello",
}

let propertyName = "foo"

console.log(obj[propertyName]) // compile error!
const scores = { 
  r : 1,
  s : 0, 
  p : -1
} as const 
// 추론된 타입 👉 {r: 1, s: 0, p: -1}

Object.keys(scores)
// 추론된 타입 👉 string[]

// 따라서 더 명확한 타입선언을 위해 type assertion을 해준다.
Object.keys(scores)  as ['r', 's', 'p']
// 추론된 타입 👉 ["r", "s", "p"]
// 동적으로 assertion 하기위해 typeof 나 keyof를 사용해도 좋다.

참고 : TypeScript에서 string key로 객체에 접근하기


Mapped Type
: 기존 정의된 타입을 새로운 타입으로 변환해주는 문법

  • 유니온 타입의Mapped Type
type Test = 'A' | 'B' | 'C';
type MappedTest = { [K in Test]: number };
const mappedTest: MappedTest = {
  A: 1,
  B: 2,
  C: 3,
}
  • 인터페이스, 타입을 이용한 Mapped Type
type Subset<T> = {
  [K in keyof T]?: T[K];
}

const mappedTest: Subset<MappedTest> = {
  A: 1, // 생략 가능
  B: 2, // 생략 가능
  C: 3, // 생략 가능
}

참고로 computerChoice의 타입 선언을 보면 undefined가 포함된다.

  • 타입스크립트에서는 가위,바위,보를 제외하고 undefined가 나올 수 있다고 추론을 한다. 따라서, !를 사용하여 절대로 undefined가 나오지 않는다는 강한 명시를 해준다.

💡 중요) 타입스크립트에서의 !?

!는 두가지로 사용

Null이 아닌 어선셜 연산자(Non-null assertion operator)
: Null이 아닌 어선셜 연산자는 피연산자가 null이 아니라고 컴파일러에게 전달하여 일시적으로 Null 제약조건을 완화한다.

즉, null일 가능성이 존재한다고 추론하는 경우 ! 연산자를 추가하여 무조건 값이 할당되니 null을 못오게 한다.

console.log(obj.lastName!.toString());

확정 할당 어선셜(Definite Assignment Assertions)

: 값이 무조건 할당되어있다고 컴파일러에게 전달하여 값이 없어도 변수 또는 객체를 사용할 수 있다.

  • 변수 x의 값을 number 타입으로 설정 후 값을 할당하지 않고 사용하면 다음 경고문이 출력.
  • x의 타입을 설정할 때 : 앞에 ! 연산자를 사용하면 경고문이 사라집니다
let x! : number;
console.log(x + x);

or
let x : number;
console.log(x! + x);

? 연산자에 대한 자세한 내용은 타입스크립트 물음표(?), 선택적 프로퍼티, 옵셔널 체이닝

일반적으로는 onClick = {onClickBtn}을 많이 보겠지만, 다음은 인자를 가지고 있고 함수에 함수를 부르고 있는 모습이다.

⭐️ 타입스크립트에서는 함수는 변수에 담긴 함수 표현식이고, 이때 함수표현식이란 일종의 값을 의미한다. 따라서, 함수의 반환값으로 함수를 사용하는 것이 가능하다. 이처럼 어떤 함수가 또 다른 함수를 반환할 때 그 함수를 고차함수라고 한다.

const add = (a: number) => {
   return (b: number) => {
      return a + b;
   };
};

// 곧바로 실행
console.log(add(3)(8)); // 11

// 한 번 걸쳤다가 실행
const first = add(3);
console.log(first(8)); // 11

고차함수에 대해서 더 알아보기


출처 및 참고하기 좋은 사이트

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글