스무디 한 잔 마시며 끝내는 리액트 + TDD (11)

y_cat·2022년 12월 19일
0

State

여태까지 다뤘던 Props는 부모 컴포넌트로부터 자식 컴포넌트로 전달되는 데이터로써, 부모 컴포넌트로부터 전달받은 데이터인 Props는 자식 컴포넌트에서 변경할 수 없었다.

State는 Props와는 다르게 한 컴포넌트 안에서 유동적인 데이터를 다룰 때 사용되며, 컴포넌트 안에서 데이터를 변경할 수 있다.


프로젝트에서 활용

이제 할 일 목록 앱에서 할 일 데이터와 할 일 목록 데이터에 관한 동적 데이터를 State로 사용하여 컴포넌트 안에서 다루는 것을 실습해보자.

우선, 할 일 데이터를 다루기 위해 ./src/App.tsx 파일을 열어 다음과 같이 수정한다.

import React, { useState } from 'react';

...

function App() {

  const [toDo, setToDo] = useState('');

  return (
    <Container>
      <Contents>
        <ToDoItem label='추가된 할 일' onDelete={() => alert('삭제')}/>
        <InputContainer>
          <Input placeholder='할 일을 입력해 주세요' onChange={(text) => setToDo(text)}/>
          <Button label="추가" onClick={() => alert(toDo)}/>
        </InputContainer>
      </Contents>
    </Container>
  );
}

export default App;

먼저 컴포넌트 안에서 State로 데이터를 다루기 위해서는 useState라는 리액트 훅(Hook)을 사용해야 한다. useState를 사용하기 위해 최상단에서 React 라이브러리로부터 useState를 추가했다.
그런 다음, 추가한 useState를 사용하여 컴포넌트 안에서 동적으로 변경할 데이터인 할 일 데이터를 const [toDo, setToDo] = useState(''); 로 선언하였다.

React의 useState는 훅(Hook) 함수로써 사용할 변수의 초기값을 매개변수로 전달하여 호출하며 결과값으로는 배열을 반환한다. 반환된 배열에서는 useState 함수를 호출할 때 설정한 초기값이 할당된 변수와 해당 변수를 수정하기 위한 Set 함수가 반환된다.

const 배열 = useState(데이터 초기값);
// 배열[0] : 데이터 초기값이 들어간 변수
// 배열[1] : 데이터를 수정할 수 있는 Set 함수

useState를 사용하여 할당받은 변수는 불변(Immutable)이다. 따라서 해당 값은 직접 수정하는 것이 불가능하며 해당 값을 변경하기 위해서는 반드시 Set 함수를 사용해야 한다.


서버를 구동하고 웹브라우저에서 확인해보면 Input 컴포넌트에 텍스트를 입력하여 추가 버튼을 누르면 alert 창 메시지로 텍스트 그대로 표시되는 것을 알 수가 있다.


이제 이렇게 할당한 하나의 할 일 데이터를 추가할 할 일 목록 데이터로 만들어본다. 할 일 목록 데이터를 만들기 위해 Input 컴포넌트와 App 컴포넌트를 다음과 같이 수정한다.

Input 컴포넌트

...

interface Props {
	readonly placeholder?: string;
	readonly value?: string;
	readonly onChange?: (text: string) => void;
}

export const Input = ({placeholder, value, onChange}: Props) => {
	return (
		<InputBox 
			placeholder={placeholder} 
			value={value}
			onChange={(event) => {
				if(typeof onChange === 'function') {
					onChange(event.target.value);
				}
			}}/>
	)
}

App 컴포넌트

...

const ToDoListContainer = styled.div`
  min-width: 350px;
  height: 400px;
  overflow-y: scroll;
  border: 1px solid #BDBDBD;
  margin-bottom: 20px;
`;

function App() {

  const [toDo, setToDo] = useState('');
  const [toDoList, setToDoList] = useState<string[]>([]);

  const addToDo = (): void => {
    if (toDo) {
      setToDoList([...toDoList, toDo]);
      setToDo('');
    }
  }

  const deleteToDo = (index: number): void => {
    let list = [...toDoList];
    list.splice(index, 1);
    setToDoList(list);
  }

  return (
    <Container>
      <Contents>
        <ToDoListContainer>
          {toDoList.map((item, index) =>
            <ToDoItem key={item} label={item} onDelete={() => deleteToDo(index)} />)}
        </ToDoListContainer>
        <InputContainer>
          <Input placeholder='할 일을 입력해 주세요' value={toDo} onChange={(text) => setToDo(text)} />
          <Button label="추가" onClick={addToDo} />
        </InputContainer>
      </Contents>
    </Container>
  );
}

export default App;

State를 초기화할 때, 빈 배열로 초기화하게 되면 typescript에서 어떤 변수 타입의 배열인지 데이터 추론을 할 수가 없다. 그래서 typescript 제네릭을 통해 데이터를 명시적으로 지정해야 한다.

const [변수명, Set 함수명] = useState<데이터 타입>(데이터 초기값);

그리고 deleteToDo에서 할 일 목록 데이터를 삭제할 때 불변값인 toDoList 변수에서 직접 데이터를 삭제할 수 없다. 그래서 새로운 변수인 list에 toDoList를 복사한 후, 삭제를 위해 전달받은 index 값과 js의 splice 함수를 사용하여 할 일 데이터를 할 일 목록에서 삭제하도록 구현하였다.


그리고 App 컴포넌트 리턴하는 곳에서 ToDoItem 컴포넌트에 key라는 Props를 사용했는데, React에서는 map과 같은 반복문을 사용하여 동일한 컴포넌트를 화면에 표시하는 경우 key를 항상 사용해야 한다.


마지막으로 Input 컴포넌트 정의한 곳에 value라는 Props를 추가하여 새로운 할 일 데이터를 할 일 목록 데이터에 추가하기 위해 수정하였다.


완성화면



Github Repo

profile
토이 프로젝트와 기술들 정리하는 블로그

0개의 댓글