let's get IT with DAVINA ๐Ÿ’ป

๊ธฐ์กด React์— TypeScript ์ ์šฉํ•˜๊ธฐ ๋ณธ๋ฌธ

DEV_IN/TypeScript

๊ธฐ์กด React์— TypeScript ์ ์šฉํ•˜๊ธฐ

๋‹ค๋นˆ์น˜์ฝ”๋“œ๐Ÿ’Ž 2023. 3. 3. 19:27

1. TypeScript ์ ์šฉ

(1) ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ TypeScript๋กœ ์ƒ์„ฑํ•˜๊ธฐ

npx create-react-app ํ”„๋กœ์ ํŠธ์ด๋ฆ„ --template typescript

(2) ๊ธฐ์กด React์— TypeScript ์ ์šฉํ•˜๊ธฐ

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

#yarn
yarn add typescript @types/node @types/react @types/react-dom @types/jest

2. TypeScript ์„ค์ •

tsc ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ tsconfig.json์„ ์ƒ์„ฑํ•œ๋‹ค.

tsc --init

์›ํ•˜๋Š” ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ tsconfig.json ์„ ์„ค์ •ํ•œ๋‹ค.

{
    "compilerOptions": {
        "outDir": "build",
        "target": "es6", //์–ด๋–ค๋ฒ„์ „์˜ js๋กœ ์ปดํŒŒ์ผํ• ์ง€ ์ •์˜
        "lib": [
            //์–ด๋–ค ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์ •์˜(api์ž๋™์™„์„ฑ ์ œ๊ณตํ•ด์คŒ)
            "dom", //๋ธŒ๋ผ์šฐ์ €
            "dom.iterable",
            "esnext"
        ],
        "baseUrl": "./src", // ์ถ”๊ฐ€๋œ ๊ฒƒ ./src๋ฅผ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋กœ ์ง€์ •
        "allowJs": true, //ts์•ˆ์—์„œ jsํ—ˆ์šฉ(jsํŒŒ์ผ import๊ฐ€๋Šฅ)
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noFallthroughCasesInSwitch": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx"
    },
    "include": [
        //์ปดํŒŒ์ผํ•  ํด๋” ์ž…๋ ฅ. src์˜ ๋ชจ๋“ ํŒŒ์ผ ํ™•์ธํ•จ
        "src"
    ],
    "exclude": ["node_modules"]
}

https://www.typescriptlang.org/tsconfig

 

TSConfig Reference - Docs on every TSConfig option

From allowJs to useDefineForClassFields the TSConfig reference includes information about all of the active compiler flags setting up a TypeScript project.

www.typescriptlang.org


3. ์Šคํฌ๋ฆฝํŠธ ์ˆ˜์ •

.js / .jsx ํŒŒ์ผ๋“ค์„ .ts / .tsx๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.


index.js → index.tsx

  • โ—๏ธ as HTMLElement์ถ”๊ฐ€
    • getElementById๋กœ ๋ฐ›๋Š” ๊ฐ์ฒด ํƒ€์ž…์„ ์ง€์ •ํ•ด์„œ TS๊ฐ€ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์คŒ
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

const root = ReactDOM.createRoot(
    document.getElementById("root") as HTMLElement //โ—๏ธ as HTMLElement์ถ”๊ฐ€
);
root.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

App.js → App.tsx

useState<Generics>()

state์˜ type์„ ์ง€์ •ํ•  ๋•Œ์—๋Š” ์œ„์™€ ๊ฐ™์ด Generics์•ˆ์— ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์‚ฌ์‹ค ์ดˆ๊ธฐ๊ฐ’์„ ์ง€์ •ํ•ด์ฃผ๋ฉด ์•Œ์•„์„œ ํƒ€์ž…์„ ์œ ์ถ”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ตณ์ด ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ๋ฌด๋ฐฉํ•˜๋‹ค.

๋‹ค๋งŒ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” Generics์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

โ–ถ  ์ƒํƒœ๊ฐ€ null์ผ ์ˆ˜๋„ ์žˆ๊ณ  ์•„๋‹์ˆ˜๋„ ์žˆ์„๋•Œ

โ–ถ  ์ƒํƒœ์˜ ํƒ€์ž…์ด ๊นŒ๋‹ค๋กœ์šด ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด์ด๊ฑฐ๋‚˜ ๋ฐฐ์—ด์ผ ๋•Œ

https://velog.io/@jjburi/TypeScript-useState%EC%97%90%EC%84%9C-type-%EC%A7%80%EC%A0%95

 

[TypeScript] useState์—์„œ type ์ง€์ •

useState์—์„œ type์ง€์ •ํ•˜๊ธฐ

velog.io

 

import React, { useState } from "react";
import { MdAddCircle } from "react-icons/md";
import "./App.css";
import Template from "./components/Template";
import TodoInsert from "./components/TodoInsert";
import TodoList from "./components/TodoList";

export interface Todo {
    id: number;
    text: string;
    checked: boolean;
}

let nextId = 4;

const App = () => {
    const [selectedTodo, setSelectedTodo] = useState<Todo | null>(null);
    const [insertToggle, setInsertToggle] = useState<boolean>(false);

    const [todos, setTodos] = useState<Todo[]>([
        {
            id: 1,
            text: "ํ• ์ผ 1",
            checked: true,
        },
        {
            id: 2,
            text: "ํ• ์ผ 2",
            checked: false,
        },
        {
            id: 3,
            text: "ํ• ์ผ 3",
            checked: true,
        },
    ]);

    const onInsertToggle = () => {
        if (selectedTodo) {
            setSelectedTodo(null);
        }
        setInsertToggle((prev) => !prev);
    };

    const onInsertTodo = (text: string) => {
        if (text === "") {
            return alert("ํ•  ์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.");
        } else {
            const todo = {
                id: nextId,
                text,
                checked: false,
            };
            setTodos((todos) => todos.concat(todo)); //์ƒํƒœ ๋ถˆ๋ณ€์„ฑ ์œ ์ง€ push ๋Œ€์‹  concat
            nextId++;
        }
    };

    const onCheckToggle = (id: number) => {
        setTodos((todos) =>
            todos.map((todo) =>
                todo.id === id ? { ...todo, checked: !todo.checked } : todo
            )
        );
    };

    const onChangeSelectedTodo = (todo: Todo | null) => {
        setSelectedTodo(todo);
    };

    const onRemove = (id: number) => {
        onInsertToggle();
        setTodos((todos) => todos.filter((todo) => todo.id !== id));
    };

    const onUpdate = (id: number, text: string) => {
        onInsertToggle();
        setTodos((todos) =>
            todos.map((todo) => (todo.id === id ? { ...todo, text } : todo))
        );
    };

    return (
        <Template todoLength={todos.length}>
            <TodoList
                todos={todos}
                onCheckToggle={onCheckToggle}
                onInsertToggle={onInsertToggle}
                onChangeSelectedTodo={onChangeSelectedTodo}
            />
            <div className='add-todo-button' onClick={onInsertToggle}>
                <MdAddCircle />
            </div>
            {insertToggle && (
                <TodoInsert
                    selectedTodo={selectedTodo}
                    onInsertToggle={onInsertToggle}
                    onInsertTodo={onInsertTodo}
                    onRemove={onRemove}
                    onUpdate={onUpdate}
                />
            )}
        </Template>
    );
};

export default App;

๋‹ค๋ฅธ component๋“ค์—์„œ props type ์ง€์ •ํ•ด์ฃผ๊ธฐ

ํ•จ์ˆ˜๊ฐ€ props๋กœ ์ „๋‹ฌ๋  ๋•Œ, ๋”ฑํžˆ return์„ ํ•˜๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋ฉด void๋กœ ์„ค์ •! 

interface PropsType {
    todo: Todo;
    onCheckToggle: (id: number) => void;
    onInsertToggle: () => void;
    onChangeSelectedTodo: (todo: Todo | null) => void;
}

const TodoItem = ({
    todo,
    onCheckToggle,
    onInsertToggle,
    onChangeSelectedTodo,
}: PropsType) => {

 const { id, text, checked } = todo;
    return (
...์ƒ๋žต

event type ์„ค์ •ํ•ด์ฃผ๊ธฐ

๐Ÿšซ event ๋Š” target์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€์•Š๋‹ค
.onChange์— React.ChangeEventHandler<HTMLInputElement>๋ฅผ ์ ์šฉํ•ด์ฃผ๋‹ˆ ๋œจ๋Š” ์—๋Ÿฌ..
solve
ChangeEventHandler -> ChangeEvent
    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setValue(e.target.value);
    };
    
    return (
		<input
			placeholder='add your to-do'
 			value={value}
			onChange={onChange}
        ></input>
    const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        onInsertTodo(value);
        setValue("");
        onInsertToggle();
    };
    
    return (
		<form
			onSubmit={
				selectedTodo? () => {
                              onUpdate(selectedTodo.id, value);
                }: onSubmit}
            >
        </form>

'DEV_IN > TypeScript' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

โ›”๏ธ ์ž์ฃผ ๋ณผ ์ˆ˜ ์žˆ๋Š” Errors  (0) 2023.02.17
TypeScript(apply)  (2) 2023.02.17
TypeScript(Interface)  (4) 2023.02.16
TypeScript(Classes)  (4) 2023.02.12
TypeScript (Function)  (3) 2023.02.09
Comments