[포스코x코딩온] 웹개발자 풀스택 과정 9기 12주차 회고록 - React useState

🤔 React Hooks?

앞으로 나올 useState,useEffect 같은 hook들은 함수형 컴포넌트에서도 state와 생명주기 기능을 연동 할 수 있게 하기 위해 만들어졌다. 쉽게 말해 이전까지는 클래스형 컴포넌트에서만 가능했던 기능들을 이제는 함수형에서도 사용할 수 있게 된 것이다. 

(원래는 React에서 없던 기능인데 불편함을 고치고자 다른 개발자들이 모여 직접 개발했다고 한다. -> 실제 개발에 참여한 모든 개발자들은 React측에서 모두 채용을 했다고 한다. 대단한 분들..👍)

 

이렇듯 이제는 불편한 클래스형 컴포넌트를 굳이 사용안해도 된다. 나도 두가지 모두 사용을 해봤지만 확실히 함수형이 편하다는 것을 느꼈다. 그래서 나는 함수형 컴포넌트 위주로 블로깅을 할 것이다.4

 


🤔 useState?

useState는 React에서 제공하는 다양한 React Hooks 중에 하나로, 함수형 또는 클래스형의 컴포넌트에서 로컬의 데이터 상태를 관리할 수 있게 만들어 주는 기능이다.

useState는 초기 입력될 상태 값을 인자로 받아서 상태 값과 해당 상태를 업데이트하는 함수를 쌍으로 반환하게 된다.

 

예시를 통해 이해를 해보자.

 

  const [status, setStatus] = useState(0);

 

해석하면 다음과 같다. 

 

status는 저장된 값. 즉 계속 바뀌는 값이다.
setStatus는 status의 값을 변경해주는 함수다. 

 

useState안의 값에는 status의 초기값을 넣어준다. 현재는 0을 넣어주었다. 

 

사용방법은 setStatus를 사용해서 status 값을 업데이트 해주는 방식이다. 제대로된 사용방법은 실습을 통해서 알아보자.

 

 

✏️ 실습!

 

● 실습 내용

1. 할 일을 입력하고 추가

2. 할 일 목록 아래에는 "완료된 할 일 삭제" 버튼이 있어서, 체크박스 선택 후 선택 한 일만 삭제

 


1. useState 설계하기 

const [todos, setTodos] = useState([]); // 할 일 목록 
const [inputTodo, setInputTodo] = useState(''); //input에 쓰여진 값

 

이 실습에는 input에 써진 값을 저장하는 state 한개랑 할일 목록을 저장하는 state 한개, 총 2개의 useState가 필요하다.

 

2. input태그 작성하기

<input
        type='text'
        value={inputTodo}
        onChange={(e) => setInputTodo(e.target.value)}
        placeholder='할 일 입력...'
></input>

 

react에서는 input 태그의 value값을 구할 때 onChange이벤트는 국룰(?)이라고 한다. 

 

e.target.value - 현재 input 태그의 value값
setInputTodo(e.target.value) - inputTodo의 값을  현재 input태그의 value값으로 바꾼다.

 

3. 완료 버튼 로직 짜기

<button onClick={addTodo}>추가</button>

 

완료 버튼을 누르면 할일 목록(todos)에 추가 되게 만들어야 한다. 

여기서 중요한 점은 이전의 목록들도 유지한 상태에서 할일을 추가해야 한다는 점이다.

 

const addTodo = () => {
    if (inputTodo !== '') {
      // input에 작성한 것이 있을 시에만 작동
      // 새로운 객체를 만들고 이 객체를 todos에 넣는다.
      const newTodo = {
        id: Date.now(),
        text: inputTodo,
        checked: false,
      };
      setTodos([...todos, newTodo]);
      // todos에 넣었으면 다시 input창은 빈 문자열로 바뀜
      setInputTodo('');
    }
  };

 

일단은 조건문을 주어서 input에 값이 있어야지만 목록에 추가되도록 하였다. 

 

로직은 간단하다. 

id: 각 엘리먼트의 고유값
text: input에 입력된 값
checked: 체크박스 체크 여부

완료 버튼을 누를때마다 newTodo라는 객체 변수를 만들어서 객체 안에 데이터를 넣어주면 된다. 여기서 id값을 왜 넣는지 궁금할 것이다.  그 이유는 좀 이따 설명하겠다.

 

setTodos([...기존 배열, 추가 할 배열]);

데이터를 다 넣었으면 이후 기존의 목록 뒤에 추가하면 된다. 여기서 spread 함수가 사용된다.

spread 함수를 사용하면 기존의 배열을 유지한 상태로 새로운 배열이 추가된다. 

 

4. 목록의 list 렌더링하기

<ul>
    {todos.map((todo) => {
      return (
        <li key={todo.id}>
          <input type='checkbox' checked={todo.checked} onChange={() => toggleTodo(todo.id)} />
          {todo.text}
        </li>
      );
    })}
</ul>

 

'할 일 목록'을 렌더링 할때는 map 함수를 사용해서 출력한다. 여기서 id값을 왜 썻는지 이유가 나온다. 

 

 

react는 기본적으로 엘리먼트를 구분하기 위해 key값을 추가하도록 권장한다. 만약 key값을 추가하지 않는다면 실행은 될지라도 console.log에 에러 메시지가 뜬다.

 

 

그래서 대부분 map 함수를 사용해서 index값을 key값으로 설정한다. 하지만 그러면 심각한 오류가 발생한다. 

react 공식 문서에서도 index값을 사용하는 것을 "지양"한다고 명시되어 있다.

 

만약 index값으로 사용하면 무슨일이 발생하나 찾아봤다.

 

● 하나의 리스트를 수정하거나 삭제할 때 모든 리스트가 리렌더링되는 현상이 발생한다.

- 예를 들어 0번 인덱스에 "철수"라는 데이터가 있고 1번 인덱스에 "영희"라는 데이터가 있다고 가정하자

"철수"의 데이터를 삭제하면 1번에 있던 "영희"의 데이터가 0번으로 이동한다.-

이 부분에 대해 자세하게 써논 블로그가 있으니 참고하자

 

따라서 key값을 구분하기 위해 클릭한 시간의 Data.now()로 설정해 두었다.

<li> 태그 안에 input 타입이 "checkbox"가 있는것을 볼 수 있다. 이 체크박스는 이후에 할 일을 완료했는지 안했지는 확인하기 위한 용도로 쓰일 것이다. 

 

5. 완료된 일들을 삭제하기

<input type='checkbox' checked={todo.checked} onChange={() => toggleTodo(todo.id)} />

 

 

우리가 할일 목록을 만들때 "checked"의 초기값을 "false"로 만들어 주었다. 따라서 현재 체크박스는 해제된 상태이다.

input을 만들었으니 마찬가지로 "onChange" 이벤트를 걸어줘 상태가 바뀔 때마다 "toggleTodo"함수를 작동시켜준다.

 

// 체크박스 on/off
  const toggleTodo = (id) => {
    setTodos(
      todos.map((todo) => {
        // 내가 체크한 박스의 id가 todos에 있는 id와 동일하다면 todo의 나머지 요소는 변하지 않고 checked요소만 반대로 해준다.
        return todo.id === id ? { ...todo, checked: !todo.checked } : todo;
      })
    );
  };

 

체크박스를 클릭하면 내가 체크한 박스의 id와 todos에 있는 id와 동일한 것만 골라서 나머지 요소는 변하지 않게 하고 checked만 반대로 바꿔준다. 

 

<button onClick={removeTodo}>완료된 할 일 삭제</button>

 

이렇게 체크된 박스들은 "removeTodo"함수를 통해 삭제해준다.

 

 // 선택된 체크박스만 삭제
  const removeTodo = () => {
    const result = todos.filter((value) => value.checked === false);
    setTodos(result);
  };

 

삭제를 해줄 때는 filter 함수를 통해 삭제해 준다. filter 함수는 반환값이 true인 값만 리턴해주는 특징이 있다. 

따라서 Todos의 목록중 checked가 "false"인 목록만 반환해준다. 

 

 


마치며...

어떻게 보면 간단한 실습이었지만 처음 리액트를 만져보는 것이기에 배운점이 많았다.

 

  1. <input>태그를 만들때는 state로 "value"는 초기값으로 만들어주고 onChage이벤트를 사용해서 바꿔주자.
  2. map함수를 사용해 리스트를 출력할 때는 key값에 index가 아닌 고유한 값을 만들어서 넣어주자