본문 바로가기

TypeScript

React + Typescript

반응형

리액트에서 타입스크립트 사용하기

 

npx create-react-app [파일명] --typescript

타입스크립트로 리액트 컴포넌트를 작성할때는 .tsx로 작성

 

 

1. 타입스크립트로 리액트 컴포넌트 만들기

함수형 컴포넌트가 function키워드가 아닌 React.FC라는 타입으로 화살표 함수로 작성시 두가지 이점

 

1. children이라는 props가 기본적으로 탑재되어있다.

2. contextTypes, defaultProps, displayName이 자동완성된다.

 

단점 - defaultProps 제대로 작동되지 않을 수 있다. (function 키워드에서는 제대로 작동함)

 

import React from "react";

type GreetingsProps = {
    name: string;
    age?: number; // '?' ->optional props
    onClick: (name: string) => void;
};

//함수형
function Greetings({ name, onClick }: GreetingsProps) {
    const handleClick = () => onClick(name);

    return (
        <>
            <div>{name}</div>
            <button onClick={handleClick}>click me</button>
        </>
    );
}

 

 

2. 타입스크립트로 리액트 상태관리하기 

2-1) useState, 이벤트

 

실습 1) counter

 

import React, { useState } from "react";

//type CounterProps = {};

function Counter() {
    const [count, setCount] = useState<number>(0); //generic 생략도 가능 0으로 유추해서 알아서 number로 인식
    const onIncrease = () => setCount(count + 1);
    const onDecrease = () => setCount(count - 1);

    return (
        <div>
            <h1>{count}</h1>
            <div>
                <button onClick={onIncrease}>+1</button>
                <button onClick={onDecrease}>-1</button>
            </div>
        </div>
    );
}

export default Counter;

 

- 큰 차이는 없다. -> 다만 차이는, 확장자가 tsx라는 것

 

 

실습 2) form

//MyForm.tsx
import React, { useState } from "react";

// type Params = {
//     name: string;
//     description: string;
// };

// type MyForm = {
//     onSubmit: (form: Params) => void;
// }; //아래 와 같은 의미

type MyFormProps = {
    onSubmit: (form: { name: string; description: string }) => void;
};

function MyForm({ onSubmit }: MyFormProps) {
    const [form, setForm] = useState({
        name: "",
        description: "",
    });

    const { name, description } = form;

    //e 타입은 onChange props에서 마우스커서를 올리면 얻을 수 있다.
    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        setForm({
            ...form,
            [name]: value,
        });
    };

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        onSubmit(form);
        setForm({ name: "", description: "" });
    };

    return (
        <form onSubmit={handleSubmit}>
            <input name="name" value={name} onChange={onChange} />
            <input name="description" value={description} onChange={onChange} />
            <button type="submit">등록</button>
        </form>
    );
}

export default MyForm;

 

//App.tsx
import React from "react";
import MyForm from "./MyForm";

function App() {
    const onSubmit = (form: { name: string; description: string }) => {
        console.log(form);
    };
    
    return <MyForm onSubmit={onSubmit} />;
}

export default App;

 

 

실습 3) reducer

 

import React, { useReducer } from "react";

//action에 대한 type을 하나의 타입(Action)에 다 정리하고
//action의 타입을 해당 타입으로 지정해서 코드 작성
type Action = { type: "INCREASE" } | { type: "DECREASE" };

//복잡ver. state가 객체라면? -> type alias or interface 사용하기
//=> ReducerSample에서 확인
function reducer(state: number, action: Action): number {
    switch (action.type) {
        case "INCREASE":
            return state + 1;
        case "DECREASE":
            return state - 1;
        default:
            throw new Error("unhandled action type");
    }
}

function CounterReducer() {
    const [count, dispatch] = useReducer(reducer, 0);
    const onIncrease = () => dispatch({ type: "INCREASE" });
    const onDecrease = () => dispatch({ type: "DECREASE" });

    return (
        <div>
            <h1>{count}</h1>
            <div>
                <button onClick={onIncrease}>+1</button>
                <button onClick={onDecrease}>-1</button>
            </div>
        </div>
    );
}

export default CounterReducer;

 

 

실습 4) 복잡한 reducer

-ctrl + spaceBar = 어떤값을 넣어야하는지 자동완성을 보여줌

- state의 타입, return 타입 정의하기

 

import React, { useReducer } from "react";

type Color = "red" | "orange" | "yellow";

type State = {
    count: number;
    text: string;
    color: Color;
    isGood: boolean;
};

type Action =
    | { type: "SET_COUNT"; count: number }
    | { type: "SET_TEXT"; text: string }
    | { type: "SET_COLOR"; color: Color }
    | { type: "TOGGLE_GOOD" };

function reducer(state: State, action: Action): State {
    switch (action.type) {
        case "SET_COUNT":
            return {
                ...state,
                count: action.count,
            };
        case "SET_TEXT":
            return {
                ...state,
                text: action.text,
            };
        case "SET_COLOR":
            return {
                ...state,
                color: action.color,
            };
        case "TOGGLE_GOOD":
            return {
                ...state,
                isGood: !state.isGood,
            };
        default:
            throw new Error("unhandled action type");
    }
}

function ReducerSample() {
    const [state, dispatch] = useReducer(reducer, {
        count: 0,
        text: "hello",
        color: "red",
        isGood: true,
    });

    const setCount = () => dispatch({ type: "SET_COUNT", count: 5 });
    const setText = () => dispatch({ type: "SET_TEXT", text: "bye" });
    const setColor = () => dispatch({ type: "SET_COLOR", color: "orange" });
    const toggleGood = () => dispatch({ type: "TOGGLE_GOOD" });

    return (
        <div>
            <p>
                <code>count:</code> {state.count}
            </p>
            <p>
                <code>text:</code> {state.text}
            </p>
            <p>
                <code>color:</code> {state.color}
            </p>
            <p>
                <code>isGood:</code> {state.isGood ? "true" : "false"}
            </p>
            <div>
                <button onClick={setCount}>SET_COUNT</button>
                <button onClick={setText}>SET_TEXT</button>
                <button onClick={setColor}>SET_COLOR</button>
                <button onClick={toggleGood}>TOGGLE_GOOD</button>
            </div>
        </div>
    );
}

export default ReducerSample;

 

 

실습 5) context API 사용하기

 

1. context 만들기

 

type FooValue = {
    foo: number;
};

const FooContext = createContext<FooValue | null>(null); //(default value로 null을 넣어놓은 것)
 

 

 

2. context 만들기 - 기존에 만들어놓은 SampleContext 활용하기

 

//context.tsx
import React, { createContext, Dispatch, useContext, useReducer } from "react";

type Color = "red" | "orange" | "yellow";

type State = {
    count: number;
    text: string;
    color: Color;
    isGood: boolean;
};

type Action =
    | { type: "SET_COUNT"; count: number }
    | { type: "SET_TEXT"; text: string }
    | { type: "SET_COLOR"; color: Color }
    | { type: "TOGGLE_GOOD" };

function reducer(state: State, action: Action): State {
    switch (action.type) {
        case "SET_COUNT":
            return {
                ...state,
                count: action.count,
            };
        case "SET_TEXT":
            return {
                ...state,
                text: action.text,
            };
        case "SET_COLOR":
            return {
                ...state,
                color: action.color,
            };
        case "TOGGLE_GOOD":
            return {
                ...state,
                isGood: !state.isGood,
            };
        default:
            throw new Error("unhandled action type");
    }
}

type SampleDispatch = Dispatch<Action>; //dispatch는 react에 내장되어있음

//상태만 다루는 context
const SampleStateContext = createContext<State | null>(null);
const SampleDispatchContext = createContext<SampleDispatch | null>(null); //아래와 같음
// const SampleDispatchContext = createContext<Dispatch<Action> | null>(null);

type SampleProviderProps = {
    children: React.ReactNode;
};

export function SampleProvider({ children }: SampleProviderProps) {
    const [state, dispatch] = useReducer(reducer, {
        count: 0,
        text: "hello",
        color: "red",
        isGood: true,
    });

    return (
        <SampleStateContext.Provider value={state}>
            <SampleDispatchContext.Provider value={dispatch}>
                {children}
            </SampleDispatchContext.Provider>
        </SampleStateContext.Provider>
    );
}

//custom Hook
export function useSampleState() {
    const state = useContext(SampleStateContext);
    if (!state) throw new Error("Cannot find SampleProvider");
    return state;
}

export function useSampleDispatch() {
    const dispatch = useContext(SampleDispatchContext);
    if (!dispatch) throw new Error("Cannot find SampleProvider");
    return dispatch;
}

 

- context 만들때 제네릭 설정하기

- 커스텀 훅 만들때 throw new error로 state나 dispatch가 유효하지 않을 때(null) 경우의 error 만들기

'TypeScript' 카테고리의 다른 글

[TypeScript 강의노트] - 고급타입  (0) 2021.01.22
[TypeScript 강의노트] - generic  (0) 2021.01.21
[TypeScript 강의노트] - class  (0) 2021.01.21
[TypeScript 강의노트]  (0) 2021.01.21