리액트에서 타입스크립트 코드 작성 방법 / 타입스크립트 + 리액트 사용법
리액트 useState 사용법
리액트 userState로 상태 변수 정의
import { useState } from "react";
// useState 함수로 상태 변수 정의
const [상태변수명, set상태변수명] = useState(""); // string 타입으로 추론 (권장)
또는
const [상태변수명, set상태변수명] = useState<string>(); // string | undefined 유니온 타입으로 추론
제네릭 함수 useState는 전달받은 초기값 타입으로 변수 타입을 추론합니다.
초기값이 없으면 undefined 타입이 되어 값을 할당할 수 없으므로 제네릭을 지정해야 합니다.
상태 변수 수정
// 인풋 값 변경 이벤트 핸들러
// React에서 제공하는 이벤트 객체 타입 사용하여 타입 안전성 확보
const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
set상태변수명(e.target.value);
};
return (
// 인풋 정의
<input
value={상태변수명}
onChange={onChangeInput}
/>
);
상태 변수 값을 인풋에 바인딩하고, 인풋 변경 시 이벤트 핸들러를 실행하는 코드입니다.
배열 상태 변수 정의 및 수정
import { useState, useRef } from "react";
// 배열에 담길 값 타입 정의
interface 인터페이스명 {
id: number;
content: string;
}
// 배열 상태 변수 타입 정의
const [상태변수명, set상태변수명] = useState<인터페이스명[]>([]);
// useRef 함수로 순번 변수 정의
const idRef = useRef(1);
// 배열 값 추가 이벤트 핸들러
const onClickAddBtn = () => {
set상태변수명([
...상태변수명,
{
id : idRef.current++,
content : "값"
}
]);
}
return (
<button onClick={onClickAddBtn}>배열에 값 추가</button>
)
값 추가 버튼 클릭 시, 배열에 객체를 추가하는 코드 예시입니다.
useRef는 값이 바뀌어도 컴포넌트를 리렌더링하지 않아 화면에 영향을 주지 않습니다.
초기값을 null로 설정하고 DOM을 참조하는 것도 가능합니다.
상태 변수 값 변경 훅 정의
import { useEffect } from "react";
useEffect(() => {
console.log(상태변수명);
}, [상태변수명]);
상태 변수 값 변경 시마다 함수가 실행되는 훅을 정의할 수 있습니다.
useEffect 함수 2번째 매개변수인 의존성 배열에 상태 변수를 넣어주면 됩니다.
리액트 함수형 컴포넌트 분리 방법
리액트 함수형 컴포넌트를 분리하며 props, 타입스크립트 타입을 함께 사용하는 예시입니다.
루트 컴포넌트 (App.tsx)
import { useState, useRef, useEffect } from "react";
import "./App.css";
import 자식컴포넌트명 from "./components/자식컴포넌트명";
// 배열 타입 정의
interface Todo {
id: number;
content: string;
}
function App() {
// 배열 생성 및 타입 명시
const [todos, setTodos] = useState<Todo[]>([]);
const idRef = useRef(1);
// 자식컴포넌트에서 string 타입 파라미터를 받아 배열에 값 추가하는 함수
const onClickAdd = (text: string) => {
setTodos([
...todos,
{
id:idRef.current++,
content: text,
},
]);
};
// 자식컴포넌트에서 number 타입 파라미터를 받아 배열에서 삭제하는 함수
const onClickDelete = (id: number) => {
// 전달받은 id와 일치하는 객체를 제외한 배열로 저장
setTodos(todos.filter((todo) => todo.id != id));
}
useEffect(() => {
console.log(todos);
}, [todos]);
return (
<div className="App">
<인풋자식컴포넌트명 onClickAdd={onClickAdd} />
<hr />
<div>
{/* 배열 출력 */}
{todos.map((todo) => (
<div key={todo.id}>{todo.content}</div>
또는
<목록자식컴포넌트명 key={todo.id} {...todo} onClickDelete={onClickDelete} />
또는
<목록자식컴포넌트명 key={todo.id} id={todo.id} content={todo.content} onClickDelete={onClickDelete} />
)}
</div>
</div>
);
}
export default App;
자식컴포넌트에 props를 내려주고, 배열을 화면에 출력하는 루트 컴포넌트 예시입니다.
useState → useReducer 변환
import { useReducer, useRef, useEffect } from "react";
import "./App.css";
import 자식컴포넌트명 from "./components/자식컴포넌트명";
// 배열 타입 정의
interface Todo {
id: number;
content: string;
}
// actions 객체 타입 정의
// 서로소 유니온 타입으로 정의
// 의도치 않은 Action 객체 전달 방지
type Action = {
type: "CREATE";
data: {
id: number;
content: string;
}
} | {
type: "DELETE";
id: number;
}
// 상태 변화 처리 함수
// dispatch 함수에 전달한 매개변수는 action 객체로 받음
function reducer(state: Todo[], action: Action) {
switch (action.type) {
case "CREATE":
return [...state, action.data];
case "DELETE":
return state.filter((it) => it.id !== action.id);
default:
return state;
}
}
function App() {
// 배열 생성 (상태 변화 처리 함수, 초기값 전달)
const [todos, dispatch] = useReducer(reducer, []);
const idRef = useRef(1);
// 자식컴포넌트에서 string 타입 파라미터를 받아 배열에 값 추가하는 함수
const onClickAdd = (text: string) => {
// useState set 함수 → useReducer dispatch 함수 사용
dispatch({
type: "CREATE",
data: {
id: idRef.current++,
content: text
}
});
};
// 자식컴포넌트에서 number 타입 파라미터를 받아 배열에서 삭제하는 함수
const onClickDelete = (id: number) => {
// useState set 함수 → useReducer dispatch 함수 사용
dispatch({
type: "DELETE",
id: id
});
}
useEffect(() => {
console.log(todos);
}, [todos]);
return (
<div className="App">
<인풋자식컴포넌트명 onClickAdd={onClickAdd} />
<hr />
<div>
{/* 배열 출력 */}
{todos.map((todo) => (
<목록자식컴포넌트명 key={todo.id} {...todo} onClickDelete={onClickDelete} />
또는
<목록자식컴포넌트명 key={todo.id} id={todo.id} content={todo.content} onClickDelete={onClickDelete} />
)}
</div>
</div>
);
}
export default App;
useState로 만든 배열을 useReducer로 만들도록 변경한 App.tsx 코드입니다.
useState는 간단한 상태, useReducer는 복잡한 상태를 관리할 때 사용하면 좋습니다.
인풋 자식 컴포넌트
import { useState } from "react";
// Props 타입 정의
interface Props {
// 배열에 값 추가 함수 타입
onClickAdd: (text: string) => void;
}
export default function 인풋자식컴포넌트명(props: Props) {
const [text, setText] = useState("");
// 인풋 변경 시 상태 변경 함수
// React에서 제공하는 이벤트 객체 타입 사용하여 타입 안전성 확보
const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
};
// 배열에 값 추가 버튼 클릭 시 함수
const onClickButton = () => {
// props로 받은 함수 호출 (매개변수로 text 상태 변수 전달)
props.onClickAdd(text);
// 인풋 값 초기화
setText("");
}
return (
<div>
<input value={text} onChange={onChangeInput} />
<button onClick={onClickButton}>배열에 값 추가</button>
</div>
);
}
src/components 폴더 하위에 자식 컴포넌트 tsx 파일을 만듭니다.
버튼 클릭 시마다 부모 컴포넌트에 있는 배열에 객체를 추가하고 있습니다.
목록 자식 컴포넌트
import { useState } from "react";
// Props 타입 정의
interface Props {
id: number;
content: string;
onClickDelete: (id: number) => void;
}
export default function 목록자식컴포넌트명(props: Props) {
const onClickButton = () => {
// 부모에서 받은 삭제 함수 호출
props.onClickDelete(props.id);
};
return (
<div>
{props.id} 번 : {props.content} <button onClick={onClickButton}>삭제</button>
</div>
)
}
부모컴포넌트에서 받은 배열 객체를 출력하고, 삭제 버튼으로 삭제하는 자식컴포넌트 예시입니다.
공통 타입 파일 분리
공통 타입 정의 파일 작성
export interface Todo {
id: number;
content: string;
}
src 폴더에 types 또는 interfaces 폴더를 만들고 .ts 파일을 생성합니다.
다양한 컴포넌트에서 사용하는 타입을 정의 후 export로 내보냅니다.
공통 타입 정의 파일 사용 예시
import { Todo } from "../types/ts파일명";
// Props 타입 정의 (타입 정의 상속)
interface Props extends Todo {
추가키?: 타입
}
export default function 자식컴포넌트명(props: Props) {
return (
<div>
{props.id} : {props.content}
</div>
)
}
위와 같이, 컴포넌트에서 타입 정의 파일을 import하여 사용할 수 있습니다.
리액트 Context API 사용법
루트 컴포넌트 (App.tsx)
import React, { useReducer, useRef, useEffect, useContext } from "react";
import "./App.css";
import 자식컴포넌트명 from "./components/자식컴포넌트명";
// 배열 타입 정의
interface Todo {
id: number;
content: string;
}
// actions 객체 타입 정의
// 서로소 유니온 타입으로 정의
// 의도치 않은 Action 객체 전달 방지
type Action = {
type: "CREATE";
data: {
id: number;
content: string;
}
} | {
type: "DELETE";
id: number;
}
// 상태 변화 처리 함수
// dispatch 함수에 전달한 매개변수는 action 객체로 받음
function reducer(state: Todo[], action: Action) {
switch (action.type) {
case "CREATE":
return [...state, action.data];
case "DELETE":
return state.filter((it) => it.id !== action.id);
default:
return state;
}
}
// 상태 공급 Context 생성 및 타입 정의
export const TodoStateContext = React.createContext<Todo[] | null>(null);
// 상태 변경 Context 생성 및 타입 정의
export const TodoDispatchContext = React.createContext<{
onClickAdd: (text: string) => void;
onClickDelete: (id: number) => void;
} | null>(null);
// useContext 공급 커스텀 훅 생성
// 자식컴포넌트에서 context 내 함수 호출 시 옵셔널 체이닝 사용하지 않기 위해 작성
export function useTodoDispatchContext() {
const context = useContext(TodoDispatchContext);
// 타입 좁히기 (null이 아닌지 체크)
if (!context) throw new Error("TodoDispatchContext가 null 입니다.");
return context;
}
function App() {
// 배열 생성 (상태 변화 처리 함수, 초기값 전달)
const [todos, dispatch] = useReducer(reducer, []);
const idRef = useRef(1);
// 자식컴포넌트에서 string 타입 파라미터를 받아 배열에 값 추가하는 함수
const onClickAdd = (text: string) => {
// useState set 함수 → useReducer dispatch 함수 사용
dispatch({
type: "CREATE",
data: {
id: idRef.current++,
content: text
}
});
};
// 자식컴포넌트에서 number 타입 파라미터를 받아 배열에서 삭제하는 함수
const onClickDelete = (id: number) => {
// useState set 함수 → useReducer dispatch 함수 사용
dispatch({
type: "DELETE",
id: id
});
}
useEffect(() => {
console.log(todos);
}, [todos]);
return (
<div className="App">
<TodoStateContext.Provider value={todos}>
<TodoDispatchContext.Provider value=>
<인풋자식컴포넌트명 />
<hr />
<div>
{/* 배열 출력 */}
{todos.map((todo) => (
<목록자식컴포넌트명 key={todo.id} {...todo} />
또는
<목록자식컴포넌트명 key={todo.id} id={todo.id} content={todo.content} />
)}
</div>
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
</div>
);
}
export default App;
부모컴포넌트에서 props로 데이터를 전달하지 않고, Context 생성 후 하위컴포넌트들에서 전역적으로 사용할 수 있도록 Provider로 값을 공급합니다.
커스텀 훅, Context 생성 코드를 src/context/TodoContext.tsx 등 별도 파일로 분리하면 더 관리하기 좋습니다.
인풋 자식 컴포넌트
import { useState } from "react";
import { useTodoDispatchContext } from "../App";
export default function 인풋자식컴포넌트명() {
// 부모컴포넌트 커스텀 훅 호출하여 context 공급받기
const context = useTodoDispatchContext();
const [text, setText] = useState("");
// 인풋 변경 시 상태 변경 함수
// React에서 제공하는 이벤트 객체 타입 사용하여 타입 안전성 확보
const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
};
// 배열에 값 추가 버튼 클릭 시 함수
const onClickButton = () => {
// context 내 함수 호출 (매개변수로 text 상태 변수 전달)
context.onClickAdd(text);
// 인풋 값 초기화
setText("");
}
return (
<div>
<input value={text} onChange={onChangeInput} />
<button onClick={onClickButton}>배열에 값 추가</button>
</div>
);
}
부모컴포넌트 커스텀 훅 또는 useContext 훅을 통해 Context 값을 받아서 사용합니다.
목록 자식 컴포넌트
import { useState } from "react";
import { useTodoDispatchContext } from "../App";
// Props 타입 정의
interface Props {
id: number;
content: string;
}
export default function 목록자식컴포넌트명(props: Props) {
// 부모컴포넌트 커스텀 훅 호출하여 context 공급받기
const context = useTodoDispatchContext();
const onClickButton = () => {
// context 내 삭제 함수 호출
context.onClickDelete(props.id);
};
return (
<div>
{props.id} 번 : {props.content} <button onClick={onClickButton}>삭제</button>
</div>
)
}
하위 컴포넌트에서 props, context를 모두 사용할 수도 있습니다.