[React] React with Typescript

찐새·2022년 8월 9일
0

React

목록 보기
2/21
post-thumbnail

React with Typescript

add Typescript

  • 이미 생성된 create-react-apptypescript를 추가하는 방법은 두 가지가 있다.

전체 재설치

npx create-react-app my-app --template typescript

or

yarn create react-app my-app --template typescript
  • 생성했던 앱을 제거하고 ts 탬플릿을 달아 재설치한다.

ts패키지 추가

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

or

yarn add typescript @types/node @types/react @types/react-dom @types/jest
  • 이미 앱이 존재한다면 ts패키지를 추가한다.
  • 그 후, .js 컴포넌트를 .tsx컴포넌트로 변경한다.
  • js로 compile하기 위한 tsconfig.json을 생성해 컴파일 옵션을 추가한다.

root error

  • ts를 추가한 후 index.tsx에서 아래와 같은 에러가 발생했다.
> const root = ReactDOM.createRoot(document.getElementById("root"));

TS2345: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element | DocumentFragment'.
Type 'null' is not assignable to type 'Element | DocumentFragment'.
  • root 아이디를 가진 엘리먼트 타입을 ts가 모르기 때문에 발생한 에러이다.
  • document 뒤에 as를 추가해 역할을 알려준다.
const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);

With Styled Components

  • ts에서 사용할 수 있는 Styled Components로 재설치한다.
    • npm install --save @types/styled-components
  • Styled Componentsprops를 전달해 사용하려면 그 타입을 정의해줘야 한다.
import styled from "styled-components";

interface ContainerProps {
  bgColor: string;
}

const Container = styled.div<ContainerProps>`
  width: 200px;
  height: 200px;
  background-color: ${(props) => props.bgColor};
`;

function TsContainer({ bgColor }: ContainerProps) {
  return <Container bgColor={bgColor} />;
}
export default TsContainer;

optional, default

  • 타입을 props?:datatype으로 선언하면 선택적으로 사용가능한 프로퍼티가 된다.
  • 컴포넌트 내 프로퍼티 값에 {props ?? default}를 사용하면, 해당 프로퍼티에 값이 없을 경우 default를 기본 프로퍼티값으로 갖는다.
  • 프로퍼티 타입을 optional(props?)로 선언하고, 컴포넌트 프로퍼티에 값을 할당해도 default값을 갖는다.
import styled from "styled-components";

interface ContainerProps {
  bgColor: string;
  borderColor?: string;
  text?: string;
}

const Container = styled.div<ContainerProps>`
  width: 200px;
  height: 200px;
  background-color: ${(props) => props.bgColor};
  border: 2px solid ${(props) => props.borderColor};
  text-align: center;
`;

function TsContainer({
  bgColor,
  borderColor,
  text = "Hello Ts",
}: ContainerProps) {
  return (
    <Container bgColor={bgColor} borderColor={borderColor ?? "black"}>
      {text}
    </Container>
  );
}
export default TsContainer;

theme

  • ts 환경에서 커스텀 theme을 사용하려면 해당 theme의 프로퍼티에 대한 declare가 필요하다.
  • src폴더에 styled.d.ts를 생성한다.
  • styled-components - Create a declarations file의 내용을 복사해 선언 파일에 붙여 넣고, declare module내부에 interface를 정의한다.
// import original module declarations
import "styled-components";

// and extend them!
declare module "styled-components" {
  export interface DefaultTheme {
    textColor: string;
    bgColor: string;
    btnColor: string;
  }
}
  • 커스텀 theme을 생성한다.
import { DefaultTheme } from "styled-components";

export const lightTheme: DefaultTheme = {
  textColor: "black",
  bgColor: "white",
  btnColor: "tomato",
};

export const darkTheme: DefaultTheme = {
  textColor: "white",
  bgColor: "black",
  btnColor: "teal",
};
  • 일반적인 styled-components와 똑같이 사용하면 된다.
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { ThemeProvider } from "styled-components";
import App from "./App";
import { darkTheme, lightTheme } from "./theme";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <ThemeProvider theme={darkTheme /*or lightTheme*/}>
      <App />
    </ThemeProvider>
  </React.StrictMode>
);

// App.js
import styled from "styled-components";

const Wrapper = styled.div`
  background-color: ${(props) => props.theme.bgColor};
`;

const H1 = styled.h1`
  color: ${(props) => props.theme.textColor};
`;

const Btn = styled.button`
  background-color: ${(props) => props.theme.btnColor};
`;

function App() {
  return (
    <Wrapper>
      <H1>Protected</H1>
      <Btn>Hello</Btn>
    </Wrapper>
  );
}

export default App;

With React

state

  • tsuseState()에 넣은 기본값으로 데이터타입을 추론한다.
import { useState } from "react";

function Circle() {
  const [counter, setCounter] = useState(1);
  setCounter("hi"); // error
  return <div />;
}
export default Circle;
  • state의 기본값이 1이므로 counter가 계속 number를 가질 것으로 추론해 "hi"를 대입했을 경우 에러를 일으킨다.
  • 2개 이상의 타입을 가질 경우, generics를 사용해 state가 가질 수 있는 값을 명시한다.
    • generics은 단일 타입이 아닌 다양한 타입에서 작동하도록 하는, 개발자가 요구한 대로 signature를 생성해주는 도구이다.
import { useState } from "react";

function Circle() {
  const [counter, setCounter] = useState<number | string>(1);
  setCounter("hi"); // no error
  setCounter(true); // error
  return <div />;
}
export default Circle;

Form

  • ReactforminputonSubmitonChange로 제어된다.
  • ts 환경에서 해당 이벤트를 호출하면 any 타입이라고 경고하며 에러를 발생시킨다.
  • 각각 어떤 타입의 이벤트인지 정의해야 한다.
import React, { useState } from "react";

function App() {
  const [value, setValue] = useState("");
  const onChange = (e: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value },
    } = e;
    setValue(value);
  };
  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log(`Hi! ${value}`);
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input type={"text"} placeholder="username" onChange={onChange} />
        <button>Log in</button>
      </form>
    </div>
  );
}

export default App;
  • React의 이벤트는 브라우저 고유 이벤트가 아닌 SyntheticEvent(합성 이벤트)이다.
  • 이러한 이벤트 타입은 스스로 찾기 어렵기 때문에 구글링 혹은 React Docs를 살펴야 한다.

구조 분해 할당

  • ES6 문법 중 하나인 구조 분해 할당은 객체가 가진 프로퍼티를 개별적으로 꺼내와 초기화하는 형태이다.
const 구조분해 = {
  info: {
    id: "blabla",
    tag: "div",
  },
  actions: {
    go: "1",
    stop: "2",
  },
};

const {
  info: { id, tag },
  actions: { go, stop },
} = 구조분해;

console.log(id, tag, go, stop);
// blabla div 1 2
  • 프로퍼티 명을 바꿔서 사용할 수도 있다.
const {
  info: { id: 아이디, tag: 태그 },
  actions: { go: 앞으로, stop: 가만히 },
} = 구조분해;

console.log(아이디, 태그, 앞으로, 가만히);
// blabla div 1 2

참고
노마드 코더 - React JS 마스터클래스
Typescript Docs
React Docs - SyntheticEvent
styled-components - Create a declarations file

profile
프론트엔드 개발자가 되고 싶다

0개의 댓글