Book Study/생활코딩! React 리액트 프로그래밍

4일차 리액트 기초(08장) - 생활코딩! React 리액트 프로그래밍

swJman 2025. 3. 20. 01:30

08 Create

 

이번 장은 CRUD 중 생성 부분을 실습해 보는 장이다. 교재 내용과 비슷하게 나도 list를 하나 만들어서 CRUD를 해볼까 한다. 아래와 같이 코드를 좀 확장해 보았다.

function List(props) {
  const ltag = props.list.map((item) => (
    <li key={item.id}>{item.name}</li>
  ));

  return (
    <ul>
      {ltag}
    </ul>
  );
}

function Action(props) {
  if(props.type == 'ADD') {
    return (
      <div>
        <h2>Add</h2>
        <input type='text' name='name' placeholder='name' />          
        <button onClick={
          evt => {
            const input = evt.target.parentElement.querySelector("input[name='name']");
            const name = input.value;
            props.callback(props.type, name);
            input.value = '';
          }}>OK</button>
      </div>
    );
  }
  else if(props.type == 'UPDATE') {
    return (
      <div>
        <h2>Update</h2>
      </div>
    );    
  }
  else if(props.type == 'DELETE') {
    return (
      <div>
        <h2>Delete</h2>
      </div>
    );        
  }
}

function App() {
  const [isClicked, setIsClicked] = useState(false);
  const handler = ()=>{alert(`I'm a handler. more clicked: ${isClicked}`); setIsClicked(true);};

  const [list, _] = useState([{id:1, name:'list1'}, {id:2, name:'list2'}, {id:3, name:'list3'}]);
  const [listVer, setListVer] = useState(0);
  const [actionType, setActionType] = useState('NONE');
  const actionCallback = (type, ret) => {
    if(type == 'ADD') {
      list.push({id:list.length+1, name:ret});
      setListVer(listVer+1);
    }
  };

  return (
    <div>
      <Header Other='Custom' onClickHandler={handler} />
      <List list={list} />
      <p>
        <ul style={{listStyle:'none'}}>
          <li onClick={() => setActionType('ADD')}>Add</li>
          <li onClick={() => setActionType('UPDATE')}>Update</li>
          <li onClick={() => setActionType('DELETE')}>Delete</li>
        </ul>
        <Action type={actionType} callback={actionCallback}/>
      </p>
    </div>
  );
}

간단히 설명해 보면 단순히 배열 내용을 표시하는 List라는 컴포넌트를 만들었고, List에 대한 CUD를 처리하기 위해 Action이라는 컴포넌트를 만들었다. App 컴포넌트는 has-a 관계로써 이들을 컴포지션하는 역할을 한다. 실행해 보면 아래 그림처럼 나온다.

Add를 누르면

여기에 practice1이라는 텍스트를 입력하고 OK를 누르면

교재 내용과 골자는 동일하다.

 


교재와 다른 점은 리스트 아이템 추가 시 리렌더링 하는 방식이다. 교재에서는 화면 갱신을 위해 아래와 같은 작업을 한다.

const [list, setList] = useState([{id:1, name:'list1'}, {id:2, name:'list2'}, {id:3, name:'list3'}]);

...
...
...

const newItem = {id:4, name:'practice1'};
const newList = [...list];
newList.push(newItem);
setList(newList);

...
...

list 객체를 deep copy하고 있다. 원소들까지 깊은 복사를 하는지 레퍼런스로 처리하는지는 모르겠으나 어쨌든 낭비적인 요소다.

그래서, 본인은 아래와 같이 list의 버전 변수를 이용하여 화면 갱신 이벤트가 발생할 수 있도록 하였다.

const [list, _] = useState([{id:1, name:'list1'}, {id:2, name:'list2'}, {id:3, name:'list3'}]);
const [listVer, setListVer] = useState(0);

...
...

const newItem = {id:4, name:'practice1'};
list.push(newItem);
setListVer(listVer+1);

...
...

교재의 코드와 다른 점이 느껴지는가? list의 크기가 커지면 커질수록 성능 차이는 명확할 것이다.

아무래도 나는 서버 개발자이다 보니 이런 부분에 민감한 것 같다.

 

--- 25.4.30 추가

위와 같은 버저닝 방식은 immutable 규칙을 위배한다고 한다. 왜냐하면 state로 들어간 list 객체의 내용을 직접 수정하기 때문이라고. 개인적으로는 어차피 리스트를 바꾸게 되면 리렌더링이 되는 범위가 리스트 하부 전체가 될 것이고, 브라우저의 UI thread는 싱글이기 때문에 list 자료구조의 stale 문제만 잘 케어하면 성능을 포기하면서까지 immutable 규칙을 지켜야 할까 생각이 들더라. 그런데, 조금 복잡한 실습 프로젝트를 진행하다보니 list를 state로 넣어서 나이브하게 쓸 일 자체가 없더라는...