리액트로 전환하기 - useState

제리·2023년 9월 22일
0

마이버추얼트립

목록 보기
6/9
post-thumbnail

지금까지 VanilaJS로 구현한 프로젝트를 리액트로 리팩토링한다.
VanilaJS 시리즈 둘러보기


Review

지금까지 구현된 것들은 너무나도 작은 기능들이었고, DOM을 직접 제어하는 것은 익숙하고 간단했음에도 마지막 데이터로 화면 그리기를 구현하면서 왜 안되는거지?를 무한 반복했다. 그리고 머리를 싸매며 고민했던 것들도 리액트로 전환하면서 너무나 당연하게 해결되었다. 정확히는 편하게 해결되었다. 다시 이전의 Vanila JS 코드들을 보니 간단한 것들도 어렵게 생각했다는 것을 알았다. 자바스크립트만으로는 분명 구현할 수 없는 부분이 있을 것이라고 억지스러운 한계를 두었던 것 같다. 리액트에서 당연한 것들이 리액트여야만 가능한 것은 아니었다.

리액트의 편리함

속도나 성능을 떠나 구현에 있어 가장 크게 체감되었던 리액트의 장점은 내부 데이터 변경에 따라 동적으로 DOM을 제어하는 것과 코드의 가시성이었다.

함수로도 클래스로도 할 수는 있지만, 되긴 되더라도 덕지덕지 코드가 되어 내가 아니면 알아보기 어려울 것 같았다. 함수 내부, 혹은 클래스 내부 코드에 DOM 제어부터 로직까지 모두 함께 담기다 보니 더욱 구분이 어려웠다. 특히 버튼에 이벤트를 등록할 때는 DOM 선택, 이벤트등록, 분기처리, 그리고 버튼마다 반복으로 코드가 길어졌는데 리액트에서는 DOM에서 바로 이벤트를 등록할 수 있어 깔끔했다.

상태 구조 이미지로 그려보기

useState > useReducer > contextAPI > redux-toolkit -> recoil을 거치면서 정말 다양하게 상태를 관리해보았다. 전반적으로 원리는 비슷하지만 사용법은 모두 달라서 다음 것을 이해하고 다시 돌아오면 이전의 것이 매번 새롭게 느껴졌다. 다양한 라이브러리 중 하나를 선택해야할 때 프로젝트에 맞는 적합한 설계를 머릿속으로 빠르게 그려낼 수 있으면 좋을 것 같아 이미지 자료를 만들었다.


Check

  • PostCSS를 적용한다.
  • 기존 Section, Select, List, Item을 모두 CheckList 컴포넌트 하위에 넣는다.
  • sectionList가 itemList를 갖는다.

Structure

  • (P)라벨은 prop으로 받은 상태를 뜻한다.
  • useState만을 이용해 상태를 관리한다.
  • <Section/> 컴포넌트는 setSection을 사용 없이 전달만 한다.

Contents


01. initialSate 구조 잡기

1번

const sectionList = [
    {
        id: uuidv4(),
        type: null,
        name: '선택',
        title: null,
    },
];

const itemList = [
    { 
        id: uuidv4(), 
        sectionId: ''
        content: '', 
        status: false }
];

2번

const sectionList = [
    {
        id: uuidv4(),
        type: null,
        name: '선택',
        title: null,
        itemList: [],
    },
];

처음에는 2번으로 했다가 state가 깊어져서 이게 맞나 싶은 마음에 1번으로 돌아갔었다. 1번으로 하니 item state 관리는 간단하지만, 결국 section과 연결 짓는 과정은 필요했다. 또, section의 이름이 변경될 수도 있다는 가정하에 고유 id를 이용해 item을 분류해야 한다. 어쨌든 2가지 방법 모두 가능하지만, 무엇보다 아이템이 1개도 없으면 접힘을 구현하기에 당장 section이 itemList를 가지는 것이 더 간편했다.

if (section.itemList.length !== 0) {
	setActive(true);
}

02. 카테고리 추가와 선택 옵션

const Select = ({ data, value, onChangeOption }) => {
    const handleChangeOption = useCallback(
        (e) => onChangeOption(e.target.value),
        [onChangeOption]
    );
    return (
        <select value={value} onChange={handleChangeOption}>
            {data.map((section) => {
                return (
                <option key={section.id} value={section.name}>
                {section.name}
                </option>
                );
            })}
        </select>
    );
};

재사용을 위해 데이터를 받아와서 옵션을 그릴 수 있도록 컴포넌트로 분리했다. option 목록도 section의 name을 이용한다.

const [option, setOption] = useState(sectionList[0].name);
<main>
    {section.map((v, idx) => {
        if (idx === 0) {
            return null;
        }
        return <Section key={v.id} section={v} onChangeItem={setSection} />;
    })}
</main>

기본값을 ‘선택’으로 두고 싶어서 비어있는 section을 미리 만들어 두고, 옵션의 기본값을 0번 index로 설정했다. <Section />렌더링 시 비어있는 기본 section을 제외시키기 위해 index를 이용해 분기했다.


03. custom hook

export const useInput = (initialValue = null) => {
    const [value, setValue] = useState(initialValue);
    const handler = useCallback((e) => {
        setValue(e.target.value);
    }, []);
    return [value, handler, setValue];
};

input에 입력을 제어하려면 value를 위한 state, value를 변경하기 위한 onChange 함수가 필요하다. 이는 반복적으로 활용될 수 있어 커스텀훅으로 만들었다.

profile
DOM과 친해지기

0개의 댓글

관련 채용 정보