39. React 기초 (2)

JSLEE·2024년 6월 20일

컴포넌트

먼저, 컴포넌트에 대해 알아보자.
공식 사이트에 따르면, 컴포넌트란, React 컴포넌트는 페이지에 렌더링할 React 엘리먼트를 반환하는 작고 재사용 가능한 코드 조각 이라고 한다.
즉, 우리가 이전 포스트에서 봤었던 App()이라는 함수도 하나의 컴포넌트이다.

컴포넌트에 여러가지 기능을 구현하다보면, 코드가 길어지고, 가독성이 떨어지기 때문에 이를 분리하여 사용한다.


리액트에서는 컴포넌트를 만드는 방법이 2가지 있다.

  1. 클래스형 컴포넌트 : 리액트 초기에 사용, 레거시 코드에서 종종 볼 수 있다.
// ClassCom.tsx
import { Component, ReactNode } from "react";

class ClassComponent extends Component {
  render(): ReactNode {
    return <div>1. 클래스형 컴포넌트</div>;
  }
}

export default ClassComponent;

클래스형 컴포넌트는 반드시 "react" 라이브러리의 Component를 상속해야하고, render()메소드를 통하여 return하여야한다.
또한, export와 import를 통하여 다른 파일에서 접근할 수 있게 한다.

// App.tsx
import ClassComponent from "./ClassCom";

function App() {
  return (
    <div className="container">
      <ClassComponent></ClassComponent>
    </div>
  );
}

export된 클래스형 컴포넌트를 사용하려면, 위처럼 태그(<>) 처럼 사용하면 된다.

  1. 함수형 컴포넌트 : 최근 권장되는 방법이다.

함수형 컴포넌트는 클래스형 컴포넌트보다 더욱 더 간단하다.

// FuncCom.tsx
import React from "react";

function FuncComponent() {
  return <div>2. 함수형 컴포넌트</div>;
}

// 다른 방법 (화살표 함수)
const FuncComponent: React.FC = () => {
  return <div>2. 함수형 컴포넌트</div>;
};

export default FuncComponent;

우리는 타입스크립트를 사용하는 만큼, 함수형 컴포넌트를 생성할 때 return type을 지정해줄 수 있다. 이 때, React.FC 라는 타입을 사용한다. 여기서 FC는 Function Component를 의미한다. React.FC는 이따가 배울 props에 대한 기능을 명시해주는 역할을 한다. (하지만, React.FC는 사용을 지양해야한다고 한다. props에 대해서 :로 데이터타입을 명시해주는 것이 더 좋다고 한다.)

// App.tsx
import FuncComponent from "./FuncCom";

function App() {
  return (
    <div className="container">
      <FuncComponent></FuncComponent>
    </div>
  );
}

export default App;

함수형으로 작성한 뒤, return의 괄호()에 JSX를 작성하고 export하면 된다. 사용방법은 마찬가지로 태그형식으로 사용하면 된다.


todolist 컴포넌트 만들기

먼저 todolist라는 컴포넌트를 생성해보자.

import React from "react"
const TodoList : React.FC = () => {

  const title : string = "오늘 할 일"
  
  return (
    <div className="container">
      <h1>{title}</h1>
    </div>
  )
}

export default TodoList;

사실, 리액트에서는 title 이라는 일반적인 변수 대신에 state를 사용한다.

state / React Hook

state는 데이터의 상태를 의미한다.
state를 통해 데이터를 동적으로 감시할 수 있고, 변경할 때의 동작을 화면에 반영할 수 있다.

state의 사용방법은 다음과 같다.
먼저 React의 useState라는 함수를 사용한다.

import React, { useState } from 'react'

이 useState를 React Hook이라고 부르는데,
Hook이란 클래스형 컴포넌트의 코드를 함수형 컴포넌트에서 이용할 수 있도록 해주는 라이브러리이다.

Hook 중에서도 state에 관련한 Hook이 useState이다.

useState 함수는 [ 값, set함수 ]의 배열을 반환한다.

const [todo, setTodo] = useState('할 일1')

위 코드에서, todo는 '할 일1' 이라는 문자열을 가지고 있고, setTodo 메소드를 통해서 todo의 데이터 값을 간접적으로 변경할 수 있다. 또한, set메소드는 매개변수로 (prev)를 가진다.

setTodo((prev) => prev + " new")

prev는 이전 상태의 값을 나타내는 것으로, 위 코드에서는 "할 일1" 이라는 문자열이다.
위처럼 setTodo를 수행하면, "할 일1 new" 라는 문자열이 todo 변수에 들어갈 것이다.

state 변수는 다음과 같이 배열로도 작성할 수 있다.

const [todos, setTodos] = useState<string[]>(['할 일1' , '할 일2', '할 일3'])

만약, 할 일 목록을 수행했는지 체크를 할 수 있도록 구현하고 싶다고 해보자.
그럼, 우리는 할 일에 대해서 isChecked라는 Boolean 값이 필요할 것이다.
따라서, 자료형을 string[]이 아닌, 사용자 정의 타입으로 지정해서 사용해보자.

type Todo = {
  id : number ; 
  text : string ; 
  isChecked : boolean ;
}

이렇게 Todo라는 나만의 데이터 타입을 생성했다.
그에 맞춰서, 데이터 입력값들도 수정해주겠다.

const [todo, setTodo] = useState<Todo[]>([
    { id : 1  , text : "할 일1" , isChecked : false} , 
    { id : 2  , text : "할 일2" , isChecked : false} , 
    { id : 3  , text : "할 일3" , isChecked : false} , 
  ]);

이제, 할 일의 체크 여부에 따라, isChecked를 true로 바꿔주는 동작을 만들면 된다.
그 전에, html에서 위의 값을 출력하려면 다음과 같이 작성했는데,

<li>{todo1[0].text}</li>
<li>{todo1[1].text}</li>
<li>{todo1[2].text}</li>

너무 반복되는 구문이 많다.
하지만 html에서는 반복문을 사용할 수 없다.

리액트에서는 이에 대해서, map함수를 이용하여 해결한다.

map 함수

map함수는, 배열을 순회하면서 각 요소에 대해서 콜백함수를 수행한다.
다음의 예시를 보자.

const MyMap = () => {
  const datas = ["a", "b", "c"];

  return (
    <div>
      <h2>alphabet</h2>
      <ul>
        {datas.map((elem, index) => {
          return <li>{elem}</li>;
        })}
      </ul>
    </div>
  );
};

위 코드에서 map 함수는,

<li>a</li>
<li>b</li>
<li>c</li>

처럼 동작한다.

map의 매개변수로는 해당 배열의 (요소 , 인덱스) 를 가진다.

map함수를 사용할 때에는 주의해야할 사항이 하나 있다. key 속성을 설정해줘야 하는 것이다.
위 코드를 수정해서 다음과 같은 코드를 작성했다.

const MyMap = () => {
  const datas = ["a", "b", "c"];

  return (
    <div>
      <h2>alphabet</h2>
      <ul>
        {datas.map((elem, index) => {
          return <li key={index}>{elem}</li>;
        })}
      </ul>
    </div>
  );
};

위 코드를 살펴보면, <li> 태그에 key라는 속성을 하나 추가해줬다.
이 이유는, React가 변경된 요소들을 식별하고 해당 요소만 업데이트 하는 데에 도움을 줄 수 있기 때문이다.
따라서, key값을 설정해주지 않아도 동작은 하겠지만, 성능에 영향을 미친다는 점에 주의하자.
(map 함수와 key 속성은 항상 같이 사용하도록 하자.)

체크박스 추가하기

먼저 html에서 체크박스를 추가하려면 다음과 같은 태그를 사용한다.

<input type="checkbox" onChange={() => {}}></input>

이를 통해, 체크박스가 선택되면 <del> 태그를 이용해 취소선을 긋는 todolist를 만들어보겠다.

{todo1.map((todo, index) => {
              return (
                <li key={index}>
                  <input
                    type="checkbox"
                    onChange={() => {(handleCheckedChange(todo.id)) ;}}
                  ></input>

                  {todo.isChecked ? <del>{todo.text}</del> : <span>{todo.text}</span>}
                </li>
              );

handleCheckedChange(todo.id) 함수는, todo의 id를 매개변수로 받아, 해당 id의 isChecked의 Boolean 값을 반전시켜주는 함수이다.

새로운 게시물 추가 // react-bootstrap 사용하기

텍스트박스와 버튼을 추가해서, 할일 목록이 추가되도록 해보자.

        <div>
          <input
            type="text"
            placeholder="할 일 입력"
            style={{ marginRight: "10px", writingMode: "horizontal-tb" }}
          />
          <button>추가</button>
        </div>

작성했던 html에 위의 코드를 추가했다.
button의 디자인이 별로이면, bootstrap을 이용할 수 있다.

공식 문서에 따라, 먼저 설치부터 수행하자.

npm install react-bootstrap bootstrap

설치가 끝났다면, import를 수행한다.

import { Button } from 'react-bootstrap';

그리고, index.html의 head에 <link>를 추가해준다.

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
  integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
  crossorigin="anonymous"
/>

이제 bootstrap을 사용해보자.

버튼을 생성할 때, 다음처럼 사용하면 된다.

<Button>추가</Button>

또한, 다음과 같이 스타일과 동작도 설정할 수 있다.

<Button variant="success" onClick={}>추가</Button>

이제 본격적으로, 추가 버튼을 눌렀을 때, 할 일이 추가되도록 해보자.

생각해보면 텍스트박스에 할 일을 적고 난 뒤, 그 텍스트값을 새로운 할 일 목록에 추가해야 한다.
따라서, 버튼을 구현하기에 앞서 텍스트 값을 다루기 위한 새로운 state를 만들어보자.

const [newTodo, setNewTodo] = useState<string>('')

빈 값을 가지는 newTodo 라는 state 변수를 만들었다.
이렇게 state 변수를 만들었다고 해서, 텍스트값이 newTodo로 들어가진 않는다.
따라서, html에서 텍스트박스가 newTodo의 값을 바꾸도록 해야하는데, 이 때에는 onChange()를 사용한다.

// 텍스트박스 
<input
type="text"
placeholder="할 일 입력"
  style={{ marginRight: "10px", writingMode: "horizontal-tb" }}
  onChange={ (e)=> setNewTodo(e.target.value)}
  value={newTodo}
/>

onChange나 onClick 속성을 사용할 때에는, 매개변수로 이벤트 객체 (e)가 전달된다.
이벤트 객체에는 다양한 속성들을 사용할 수 있다. 현재 이벤트가 어떤 이벤트인지 ('click', 'keydown') 알 수 있는 e.type 이라는 속성도 있고, 이벤트가 발생한 시간을 알 수 있는 e.timestamp 도 있다.
그 중에서, 위 코드에서 사용한 이벤트 객체의 e.target 은 이벤트가 발생한 DOM 요소를 의미한다.
즉, 텍스트박스를 의미하고, 텍스트 박스의 값(value)을 setNewTodo()의 매개변수로 활용한 것이다.
이렇게 newTodo 라는 state 변수에 우리는 텍스트 박스의 값을 할당하였다.

그럼 이제 버튼을 눌렀을 때, 할 일 목록에 newTodo 변수를 추가해보도록 하자.

<Button variant="success" onClick={()=>{addTodo()}}>추가</Button>

추가 버튼을 눌렀다면, addTodo() 라는 함수가 실행되도록 작성했다.
그럼 이제 addTodo를 구현해보자.

const addTodo = ()=>{
  if(newTodo.trim() !== ''){
    setTodo([...todo, { id : Date.now(), text : newTodo, isChecked: false}])
    setNewTodo('');
  }
}

먼저, 텍스트박스가 공백 ( )이라면 할 일 목록에 추가되지 않도록 하기 위하여 if문을 사용했다.
그리고 난 뒤, 기존 할 일 목록이 담긴 state 변수인 todo에 새로운 값(newTodo)을 추가하여 todo를 재정의 하였다.
이 그러니까, 위 코드가 수행되면 다음과 같은 상태일 것이다.

todo = [
  {id : 1, text : "할 일1" , isChecked : false },
  {id : 2, text : "할 일2" , isChecked : false },
  {id : 3, text : "할 일3" , isChecked : false }, // 기존 todo 목록들 
  {id : 17189471234567, text : "할 일4" , isChecked : false} // 새로운 todo 목록
  ]

우리가 보통 어플리케이션을 이용할 때, 텍스트박스를 입력하고 등록을 하면 해당 텍스트박스는 비워진다.
따라서, 마지막으로 setNewTodo가 아무런 텍스트를 가지지 않도록 newTodo값을 ''로 변경해준다.

하지만 위처럼 newTodo 값을 비운다고 하더라도, 우리가 만든 리액트페이지에는 할 일4라는 텍스트가 지워지지 않는다. 따라서, 다시 텍스트박스(<input/>) 로 돌아가서 value를 수정해줘야한다.

// 텍스트박스 
<input
type="text"
placeholder="할 일 입력"
  style={{ marginRight: "10px", writingMode: "horizontal-tb" }}
  onChange={ (e)=> setNewTodo(e.target.value)}
  value={newTodo} // value를 newTodo로 동기화
/>

우리가 추가 버튼을 누른다면, 할 일 목록에 newTodo가 추가된 뒤, newTodo는 ''라는 값을 가지기 때문에 텍스트 박스는 비워질 것이다.


시계 추가하기

todolist 웹페이지에 할 일 목록만 나열되어 있으면 허전하다.
따라서, 하단에 현재 시간이 계속해서 나타나도록 해보자.

기본적인 타이머의 원리는 다음과 같다.

const Timer : React.FC = () =>{
  const [seconds, setSeconds] = useState<number>(0) ;

  return (
    <div>
      <h2>타이머 : {seconds}초</h2>
      <button onClick={()=>{
        setInterval(()=>{
          setSeconds((prev)=> prev + 1)
        }, 1000)
      }}>버튼</button>
    </div>
  )
}

동작원리
1. 버튼이 클릭된다면, setInterval함수가 실행된다.
2. setInterval함수는 1초주기로 setSeconds 함수를 실행시킨다.
3. setSeconds는 이에 따라 seconds값을 1초씩 증가시킨다.
4. seconds 값은 state 변수이므로 계속해서 감시되고, 계속해서 반영된다.

이제, 타이머가 아닌 현재 시간을 나타내보도록 하자.

const Clock : React.FC = () =>{
  const [time, setTime] = useState(new Date()) ;

  setInterval(()=>{
    setTime(new Date())
  } , 1000)


  time.toLocaleString()
  return (
    <div>
      <h5> 현재시간 : {time.toLocaleTimeString()}</h5>
    </div>
  )
}

이렇게 페이지에서 현재 시간을 표시할 수 있다.


항목 삭제하기

먼저, 각 항목 뒤에 삭제버튼이 나오도록 다음의 TSX를 작성한다.

<button className="del-button" onClick={() => 
                    removeTodo(todo.id)
}>삭제</button>

이제 removeTodo의 동작을 구현해보자.

  const removeTodo = (id : number) =>{
    setTodos(todos.filter((todo)=> {
      return todo.id !== id
    }))
  }

위 함수는, 할 일(배열)을 순회하면서 해당 조건에 맞지 않는 요소들을 다시 배열로 반환한다.
무슨말이냐면, 내가 만약 id : 1인 요소를 removeTodo(1)로 삭제한다고 하자.
todos.filter에서는,

[ 
  {id : 1 , text : 할일1} , 
  {id : 2 , text : 할일2} , 
  {id : 3 , text : 할일3} 
]

위 배열을 순회하면서, todo.id !== id 에 맞는 요소들인

[
  {id : 2 , text : 할일2}, 
  {id : 3 , text : 할일3}
]

위 배열을 반환한다.
즉, 할 일 목록에서 id가 1인 요소를 제외하고, 다시 배열을 생성한다는 것이다.

다음 포스트에서는, 항목들의 이름을 클릭했을 때 상세정보가 모달형식으로 나타나도록 해보자.

profile
공부한 내용들을 정리하기 위해 사용하는 블로그입니다.

0개의 댓글