TIL

241129 TIL

GoJay 2024. 11. 29. 22:01
  • 감정 일기 만들기 with React, TypeScript
    • 타입스크립트를 써보니, 공통되는 타입 정의를 어디서 어떻게 만들고 불러와서 사용할지에 대한 고민이 추가적으로 생기게 됐다. 여러 글을 찾아보니 별도로 types 파일을 만들어서 정의하는 것 같긴 한데, 이걸 페이지 단위로 할지, 기능 단위로 할지, 아니면 통합할지 등등, 여러 결정의 요소가 있는 것 같다. 좀 더 여러 레퍼런스 찾아보면서 공부를 잘해봐야겠다.
    • 이벤트 핸들러를 붙일 때 매개 변수로 들어오는 이벤트 객체 e에도 타입을 붙여줘야 하는데, 리액트에서는 보통 React.MouseEvent<HTMLButtonElement>와 같은 식으로 쓰는 것 같다. 이벤트 유형에 따라 MouseEvent가 아니라 다른 값이 올 수 있다(e.g. onChange 이벤트에선 React.ChangeEvent가 사용된다). <>로 감싸진 부분은 타입 변수로 활용되는 제네릭인데, 아마 DOM 요소를 나타내는 HTMLElement 객체가 전달되는 것 같다. 자세한 내부 동작까진 몰라도, 리액트에서 이벤트 객체에 위와 같은 양식으로 타입을 정의한다는 건 잘 알아두자.
    • useState로 정의한 상태의 setState 함수를 Props로 전달해 줄 때에도 타입을 정의해줘야 하며, React.Dispatch<React.SetStateAction<string>>와 같은 형식으로 사용되는 것 같다. 초기값에 다라 React.SetStateAction에 전달되는 제네릭 변수는 string이 아니라 다른 값을 가질 수 있다.
    • 훅으로 정의하는 변수의 초기값을 넘기기 애매할 때(e.g. 객체 형태의 상태인데 프로퍼티에 대한 값이 아직 생성되지 않은 경우) 인자를 넘기지 않으면 보통 undefined 타입이 포함이 된다. 그렇게 해도 상관은 없지만, 가능하면 null을 넣어서 초기화해 주는 걸로 코드 습관을 들여놓자.
    • 그리고 그렇게 하다 보면 <실제 정의해야 할 데이터 타입 | null>과 같은 유니언 타입으로 제네릭을 미리 정의해둬야 하는 상황이 꽤 많이 생기는 것 같다. 아마도 리액트에서 타입스크립트를 쓸 때 많이 나오는 패턴인 것 같은데, 개인적으론 실제 사용할 때 추론된 타입을 원하는 타입으로 좁혀서 쓰는 과정이 좀 귀찮고 번거롭기도 해서, 가능하면 이렇게 하지 않고 더 좋게 초기화하는 방법이 있었으면 좋겠다 싶다.
    • 이 부분은 분명 내 학습이 부족해 모르는 거지, 뭔가 방법이 있을 것 같긴 하다. 잘 찾아보고 더 공부해 봐야겠다.
    • 타입스크립트를 처음 쓰니까 진짜 바보가 된 기분이다. '이렇게 짜려면 어떻게 해야 하지'라는 생각에 막막해지는 시점이 너무 많았다. 아직은 완전 기초적인 문법도 구글링이랑 GPT에 의존하는 중인데, 너무 기죽지 말아야겠다. 누구나 처음은 있으니까. 계속 반복하고, 익숙해질 때까지 더 많이 경험해 보고, 공부의 깊이도 더 깊게 파보자.
  • <모던 리액트 Deep Dive>
    • useEffect 훅은 의존성 배열에 있는 값이 변경됐을 때, 그 변화와 연결고리를 가지는 어떠한 부수 효과를 처리하는 것이다.
    • useEffect는 리렌더링이 종료된 이후에 실행된다. useEffect 내부에 콜백으로 전달한 함수가 아니라, 컴포넌트 본문에 바로 선언-실행한 함수는 렌더링 과정 중간에 실행되며, 따라서 어떠한 부수 효과를 만들어낼지 장담할 수 없다. useEffect는 이렇게 렌더링 과정 중 변화를 방지하기 위한 것이기 때문에 목적에 맞게 사용해야 한다.
    • 컴포넌트가 마운트 됐는지를 확인하는 목적으로 빈 배열([])을 의존성 배열로 넘기는 건 바람직한 방법은 아니다. 의존성 배열로 관리되는 state, props와 콜백 함수 사이의 관계가 마땅치 않은 거라면 반드시 그 위치에서 그 방식으로 useEffect를 활용하는 게 최선인지 고민해봐야 한다. 만약에 컴포넌트가 리렌더링 되는 트리거가 되는 부모(또는 상위) 컴포넌트에서 어떠한 동작을 처리해서 리렌더링 여부를 감지할 수 있는 거라면 하위에선 useEffect를 제거하는 게 좋다.
    • 빈 배열이 아닐 때에도 마찬가지다. 의존성 배열에 넣은 값과 내부의 콜백 함수는 어떠한 관계성을 가져야 한다. 그것이 useEffect의 본질에 가깝다. 정말로 이 상태와 이 함수의 실행이 연관된 것인지 잘 고민해 보자.
    • 훅에 콜백으로 전달하는 함수가 너무 길고 무거워지거나, 또는 의존성 배열에 너무 많은 값이 들어가게 된다면 훅을 두 개 이상으로 쪼개는 걸 고려해 보자.
    • useEffect 내부의 콜백 함수에 비동기 fetch 요청 로직이 포함된다면 클린업 함수에서 abortController 등으로 이전 요청을 취소해 주는 것이 좋다. useEffect 내부의 비동기 함수는 의존성 배열 변경 시 반복적으로 생성되기 때문에, 클린업 함수로 직전 요청을 취소시키자. 참고로, abortControllerfetch로 전달한 통신 요청을 취소할 수 있게 해 준다. 자세한 내용은 MDN 문서를 참고하자.
    • useMemouseCallback은 둘 다 불필요한 리렌더링을 방지하여 성능 최적화를 하는 데 사용된다. useMemo는 값을 메모이제이션하고, useCallback은 함수를 의존성 배열의 값이 변경되었을 때에만 재생성하도록 해준다.
    • useMemo는 값을, useCallback은 함수를 기억한다는 차이가 있지만, 사실 둘의 역할은 거의 유사하다. 실제로 useMemouseCallback이 해주는 역할을 그대로 구현할 수 있다. 하지만, 코드에 혼란이 생길 수 있기 때문에 함수를 재생성 방지할 땐 useCallback을 사용하자.
    • 컴포넌트에서 관리해야 하는 값을 useState의 상태로 정의하면 state의 변경을 직접 할 수 없고 setState로만 할 수 있다. 또한, state를 바꿈과 동시에 컴포넌트가 리렌더링 된다는 사이드 이펙트가 발생한다.
    • 경우에 따라서는 컴포넌트가 리렌더링 되더라도 기존의 값을 계속 기억하고 있으면서, 컴포넌트가 만든 여러 인스턴스에서 독립적인 값으로 관리하고, 값이 변경돼도 컴포넌트가 리렌더링 되지 않도록 하는 특별한 변수가 필요한 경우가 있다. 이럴 때 useRef 훅을 사용한다.
    • useRef{ current: initialValue } 형태로 전달한 초기값을 기억하고 있고, current 프로퍼티에 접근하여 값을 직접 변경할 수 있다. 해당 값은 useMemo로 메모이제이션 된 것처럼 동작하여 리렌더링 되더라도 이전 값이 계속 기억된다. 또한, useRef로 생성한 값은 변경이 되더라도 컴포넌트를 리렌더링 하지 않는다.
    • useRef를 사용하는 대표적인 상황은 DOM에 접근하고 싶을 때이다. DOM 요소의 ref 속성에 useRef로 정의한 값을 연결하면 해당 변수는 값을 등록한 DOM의 요소를 가리킨다. 해당 값을 통해 원하는 시점에 리렌더링 절차 없이 DOM의 요소를 직접 조작할 수 있다(다만, 리액트의 큰 컨셉을 위반하는 방식이기 때문에 바람직하진 않은 것 같다).
    • useContext는 상위 컨텍스트에 정의된 Context.Provider에 정의된 값을 가져오기 위한 훅이다. 상위에 여러 Context.Provider가 있다면 가장 가까운 상위의 Context를 참조한다.
    • Context는 상태 관리를 위한 API가 아니라, 단순히 부모-자식 컴포넌트 간에 상태를 주입해 주는 API일 뿐이다. 상태 관리 API라면 어떠한 상태를 기반으로 다른 상태를 만들어 주거나, 또는 필요에 따라 상태 변화 과정을 최적화할 수 있어야 한다. 하지만, Context API는 최적화에는 전혀 관여하지 못하고, 필요하다면 memo를 사용해줘야 한다.
    • Context가 영향을 미치는 범위는 최대한 좁게 잡아주는 것은 개발자의 혼란도 막고, 효과적으로 상태를 관리하는 방법 중 하나이다.
    • useReduceruseState와 유사한 역할을 한다. 다른 점은, useReducersetState가 아니라, 사전에 정의된 dispatcher 함수로 상태를 관리한다는 것이다.
    • useState는 커스텀 훅이 아니면 컴포넌트 내부에서만 상태 관리를 위한 로직을 처리할 수 있었는데, useReducerdispatcher 함수를 컴포넌트 밖에 정의하고, useRedcuer의 첫 번째 인자로 해당 함수를 연결하여, 상태 변화에 대한 로직을 컴포넌트 밖으로 분리할 수 있다.
    • 그를 통해 state를 사용하는 로직과, state의 상태를 변경하는 비즈니스 로직을 분리해서 효과적으로 관리할 수 있다.

'TIL' 카테고리의 다른 글

241201 TIL  (2) 2024.12.02
241130 TIL  (1) 2024.12.01
241128 TIL  (1) 2024.11.29
241127 TIL  (1) 2024.11.27
241126 TIL  (0) 2024.11.27