실전 웹 어플리케이션 개발 3장 React 정리

이수빈·2023년 10월 19일
0

Next.js

목록 보기
6/15
post-thumbnail

리액트에서의 타입

  • 함수형 컴포넌트는 임의의 객체를 props로서 인수로 가지며, JSX.Element 타입의 값을 반환하는 함수가 됨.

  • props로 들어오는 값을 type을 통해 제한 가능

  • children을 가질때는 React.ReactNode로 타입을 지정함.

  • 클래스형 컴포넌트는 render 메소드에서 ReactNode를 리턴합니다. 함수형 컴포넌트는 ReactElement를 리턴합니다.

React.Node vs React.Element vs JSX.Element

  • 포함관계는 다음과 같다

type ReactFragment = Iterable<ReactNode>; // ReactNode의 이터러블 형태

interface ReactPortal extends ReactElement {
        key: Key | null;
        children: ReactNode;
} // Portal은  key값을 가진 ReactNode

type ReactNode = 
ReactElement| string | number |
ReactFragment | ReactPortal | boolean | null | undefined;

ReactNode가 ReactElement를 포괄하는 개념

interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
        type: T;
        props: P;
        key: Key | null;
    }
  • JSX.Element는 React.Element의 특정타입임

  • jsx => babel을 통해 트랜스파일 되면, React.createElement 메서드를 호출.

  • 메서드의 return type은 React.Element임

//jsx
<div>Hello {this.props.toWhat}</div>
<Hello toWhat="World" />
// transpile 
React.createElement('div', null, `Hello ${this.props.toWhat}`); 
React.createElement(Hello, {toWhat: 'World'}, null);

 function createElement<P extends {}>(
        type: FunctionComponent<P> | ComponentClass<P> | string,
        props?: Attributes & P | null,
        ...children: ReactNode[]): ReactElement<P>;
// 마지막 override type임

declare global {
    namespace JSX {
        interface Element extends React.ReactElement<any, any> { }
        interface ElementClass extends React.Component<any> {
            render(): React.ReactNode;
        }

FC, VFC

  • Function Component, Void Function Component의 약자(children 여부에 따라 갈렸음)

  • 18버전부터는 FC를 사용하고 children을 사용할때는 prop type안에서 정의해야함.

useEffect 심화정리

useEffect Clean Up 실행순서

  • mount, update, unmount 될때 실행됨 => 컴포넌트 렌더링 이후 useEffect안에 코드가 실행되는 형태임(비동기적으로 동작한다의 의미!!)

  • mount시 : 컴포넌트 렌더링이 끝난 이후 useEffect 코드 실행

  • update시 (컴포넌트 리렌더링) => 컴포넌트 리렌더링(재평가) 먼저 실행, 이후에 useEffect가 unmount 되면서 cleanup 함수 실행 => 이후 update 되면서 mount 실행

  • 만약 아래코드에서 input값을 변경한다면? => state가 변하기 때문에 리렌더링 발생함.

  • 의존성 배열 (deps)가 빈값이라면? 컴포넌트가 umount시에만 cleanup이 실행됨.

import './App.css';
import { useEffect, useState } from 'react';

function App() {
  const [inputText, setInputText] = useState('');
  
  console.log('first');

  useEffect(() => {
    console.log('mount');
    return () => {console.log('unmount');}
  }, [input]);

  console.log('second');

  return (
    <div className='App'>
      <input
        value={input}
        onChange={(e) => {
          setInput(e.target.value);
        }}></input>
    </div>
  );
}


// 정답순서
/*
first
second
unmount
mount
*/

컴포넌트 중첩구조에서 useEffect 실행순서

  • 컴포넌트 렌더링이 완료된 시점에 useEffect는 실행된다.

  • 중첩구조에서 Inner 컴포넌트부터 렌더링이 완료된 시점에 useEffect가 바깥방향으로 실행됨.

  • 병렬구조에서는 Outer1, Outer2 => App 순서대로 실행

<App>
	<Outer>
		<Inner/>
	</Outer>
</App>

<App>
	<Outer1/>
	<Outer2/>
</App>
  • 2가지 코드 결과는 같지만 효율성이 다름
useEffect(() => {

  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  
  return () => clearInterval(id);
  
}, [count]); // count가 변할때마다 useEffect의 값이 clear되고 재등록되는 과정을 반복

useEffect(() => {

  const id = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);
  
  return () => clearInterval(id); // 초기에만 useEffect를 등록
  
}, []);

effect가 업데이트 시마다 실행되는 이유

  • useEffect의 클린업 함수는 컴포넌트가 마운트되는 순간 뿐만아니라 모든 리렌더링 시에 cleanup 이후 새로운 effect를 등록함.

  • deps를 통해 이를 최적화가능함.

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // props.friend.id가 바뀔 때만 재구독합니다.
function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  
  // { friend: { id: 100 } } state을 사용하여 마운트합니다.
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 첫번째 effect가 작동합니다.

// { friend: { id: 200 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 다음 effect가 작동합니다.

// { friend: { id: 300 } } state로 업데이트합니다.
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 이전의 effect를 정리(clean-up)합니다.
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 다음 effect가 작동합니다.

// 마운트를 해제합니다.
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 마지막 effect를 정리(clean-up)합니다.
  

useEffect 올바른 활용

localStorage 관련작업 활용

  • localStorage에 관한 작업(setItem, getItem) 은 동기적으로 실행됨. => 읽기 쓰기 데이터가 커짐에 따라 시간이 걸립니다.

  • 함수형 컴포넌트 안에 직접 전달하기 보단 useEffect안에서 의존성이 변했을 때만 호출하도록 하는게 바람직함.

함수를 이펙트 안으로 옮기기

  • query가 변할때마다 호출이 되야하지만, useEffect에서 deps에서 누락됨.

  • useEffect 블록안에 해당 Effect에서 사용하는 함수를 정의하는게 직관적임.

  • 공통으로 사용하는 함수라면, 컴포넌트 바깥 or 안쪽에 useCallback 사용가능

function SearchResults() {
  const [query, setQuery] = useState('react');
  
  // 이 함수가 길다고 상상해 봅시다
  function getFetchUrl() {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }

  // 이 함수도 길다고 상상해 봅시다
  async function fetchData() {
    const result = await axios(getFetchUrl());
    setData(result.data);
  }

  useEffect(() => {
    fetchData();
  }, []);

  
  function SearchResults() {
  const [query, setQuery] = useState('react');

  useEffect(() => {
    function getFetchUrl() {
      return 'https://hn.algolia.com/api/v1/search?query=' + query;
    }

    async function fetchData() {
      const result = await axios(getFetchUrl());
      setData(result.data);
    }

    fetchData();
  }, [query]); //
  // ...
}

ref)
useEffect : https://choyeon-dev.tistory.com/entry/React-useEffect-%EC%8B%A4%ED%96%89-%EC%8B%9C%EC%A0%90-%EC%A7%9A%EA%B3%A0-%EA%B0%80%EA%B8%B0
https://simsimjae.tistory.com/401
https://ko.legacy.reactjs.org/docs/hooks-effect.html

useCallback이 해결하지 못하는 문제

출처 : https://nookpi.tistory.com/141

  • 이벤트핸들러를 다룰때 => data가 변하면 결국에 handleClick이라는 이벤트 핸들러가 리렌더링됨 => 메모이제이션 불가

  • deps를 비워둔다면? 항상 초기의 data값을 참조하게됨.

해결책 : Context API + useReducer를 사용해서 콜백 함수를 외부로 분리하고, 컴포넌트 내부에서는 dispatch로 해당 콜백 함수를 호출하는 방법 (dispatch 함수는 렌더링시 변하지 않기 때문)

=> 단점 : 관리할 코드가 너무 많아짐... 최적화때문에 굳이...?

  • ref를 통한 해결책..

=> useEvent라는 훅이 개발되다가 중단됨.(현재 다시 진행중이라나..)

useLayoutEffect

  • 렌더링이 진행되기전 동기적으로 실행되는 hook임. => 보통 초기값 설정할때 많이 사용함. (CSR에서 Fetching하는 부분이 존재하기 때문에, 화면깜빡임 방지하는 용도)

  • useEffect로 사용 후 문제가 생길 시 사용하는 것을 권장함

  • 서버 렌더링을 사용하는 경우, js가 다운로드 되기 전까지는 useLayoutEffect useEffect 어느것도 실행되지 않음.

useImperativeHandle, useRef

  • useRef의 활용 => dom접근 + 비제어컴포넌트

  • useImperativeHandle hook => 컴포넌트에 ref가 전달될 때, 부모의 ref에 대입될 값을 설정 할 때 사용함.

  • Child는 forwardRef로 Wrapping 되어 부모컴포넌트로부터 ref값을 전달받음.

  • useImperativeHandle hook 에서 부모의 ref로부터 참조 할 수 있는 값을 지정함. 즉 자식에서 정의된 함수를 부모로부터 호출 가능함. (자식에서 부모 ref를 설정가능함)

  • deps 정의가능함.

import React, { useState, useRef, useImperativeHandle } from 'react'

const Child = React.forwardRef((props, ref) => {
  const [message, setMessage] = useState<string | null>(null)

  // useImperativeHandle에서 부모의 ref로부터 참조할 수 있는 값을 지정
  useImperativeHandle(ref, () => ({
    showMessage: () => {
      const date = new Date()
      const message = `Hello, it's ${date.toLocaleString()} now`
      setMessage(message)
    },
  }))

  return <div>{message !== null ? <p>{message}</p> : null}</div>
})

const Parent = () => {
  const childRef = useRef<{ showMessage: () => void }>(null)
  const onClick = () => {
    if (childRef.current !== null) {
      // 자녀의 useImperativeHandle에서 지정한 값을 참조
      childRef.current.showMessage()
    }
  }

  return (
    <div>
      <button onClick={onClick}>Show Message</button>
      <Child ref={childRef} />
    </div>
  )
}

export default Parent

ref) 공식문서 : https://react.dev/reference/react/useImperativeHandle

useDebugValue

  • 디버깅할때 사용하는 훅, React Developer Tools 와 함께 사용함

  • 개발자도구의 Components 탭에 표시됨, format지정가능함.

useDebugValue(value, format?)

ref) https://react.dev/reference/react/useDebugValue

profile
응애 나 애기 개발자

0개의 댓글