🤸‍♀️ 개발 회고/유데미 x Next.js 2기

[유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 - 2주차 학습 회고

늘코딩 2024. 7. 28. 14:32

2주 차에 배운 핵심 내용을 정리해보고자 한다.

 

React를 사용하는 이유

렌더링 퍼포먼스가 좋다

렌더링을 최적화해야 하는 이유

  • 자바스크립트 요소를 수정(Reflow)하거나 스타일을 수정(Repaint)하면 렌더링이 다시 처음부터 시작하여 많은 시간이 소요된다, 
  • 자식 요소를 바꾸려면 부모 요소들도 리렌더링을 해야 한다.
  • 렌더링을 최소한으로 하는 것이 프론트엔드의 주요한 과제였다.

React의 렌더링 장점

  • Reflow랑 Repaint를 React가 알아서 자동으로 내부적으로 최적화해서 실행해 준다
  • 개발자는 비즈니스 로직에 집중할 수 있다.
  • 가상돔을 이용해서 렌더링을 자동으로 최적화한다.

가상돔

  • 가상의 데이터만 가지고 있고, 수정 시 진짜 DOM을 건드리지 않는다
  • 렌더링 과정을 거치지 않아 매우 빠르다

가상돔 업데이트 과정

  1. 가상돔 2개(리액트 변경사항만 반영하는 가상 DOM, DOM 복사본)를 배교해서 어느 부분이 달라졌는지 계산한다(Diffing)
  2. 바뀐 부분만 실제 DOM에 반영한다 (재조정)

 

확장성 높은 컴포넌트 만들기(feat. typescript)

  • 각 요소의 특징을 하나의 큰 타입으로 묶어서 편하게 props를 내려준다.
type TInputProps = React.ComponentPropsWithoutRef<"input">;
const Input =({...rest}:TInputProps) => {
	return (
    	<>
            <input
                className="w-[240px] h-44[px] ..."
                {...rest}
            />
        </>
    )
}

 

Tailwind 스타일 사용자 지정 클래스

//tailwind.css

@layer components{
	.item-middle{
    	@apply flex items-center justify-center min-h-screen;
    }
}

// className="item-middle" 이렇게 사용 가능

 

 

React 배치 업데이트 

React가 state 변경을 포괄적으로 이해해서 반영하는 것

setCount(count+1)
setCount(count+1)
setCount(count+1)
// 이렇게 작성할 경우 마지막에 한번 반영됨, 결과 : +1

반영하지 않는 법

  • 업데이트 안에 콜백함수 형태로 바꾸면 상태를 개별적으로 변경하여 적용할 수 있다.
  • 항상 최신 상태가 콜백함수 매개변수로 들어온다
setCount((prev)=> prev+1)
setCount((prev)=> prev+1)
setCount((prev)=> prev+1)
// 이러면 개별적으로 적용됨, 결과 : +3

 

 

이미지 업로드

  • 외부 혹은 내부에서 파일을 import 하고 객체로 사용한다.
  • 웹팩과 같은 번들러가 애플리케이션의 정적 파일을 하나의 번들 파일로 묶어주는데, 애플리케이션이 로딩될 때 필요한 파일수가 줄어들어 성능이 향상된다. 이미지 파일을 import 구문으로 가져오면 웹팩은 이를 애플리케이션 번들에 포함시키고 최적화된 경로를 생성해 준다.

 

useReducer()

  • 본질 : 상태 업데이트 함수
  • 장점
    1. 리렌더링 시 재생성 안됨
      1. 리듀서에서 변하지 않고 재생성도 안되기 때문에 함수 props로 내려줄 때 useCallback 안 써도 됨
    2. 업데이트 함수 묶어서 내릴 수 있음(dispatch)
      1. 함수 여러 개 내려줄 필요 없고 dispatch만 내리면 됨, 업데이트하는 경우가 많으면 엄청 편할 듯
      2. 실무에서는 useReducer 안 쓰고 redux, recoil, zustand 사용한다고 함
    3. 가독성
      1. 함수들을 묶어서 정리해 두니까 보기가 편함
        1. 너무 지저분해 보였는데 편안해짐
      2. 상태 변경의 목적을 action type으로 쉽게 이해할 수 있음
    4. 정리
      1. 상태 업데이트 함수이고 업데이트되는 상황들을 묶어서 하나의 함수 안에서 처리하는 것
import { TTodoType } from "../Todo";

// 1. 액션에 들어가는 타입 지정하기
export type TTodoAction =
  | { type: "ADD_TODO"; payload: string }
  | { type: "DELETE_TODO"; payload: number }
  | { type: "CHECK_TODO"; payload: number };

// 2. 액션에 따른 업데이트 정의
// action : 상태 업데이트 정보 객체
export const reducer = (todos: TTodoType[], action: TTodoAction) => {
  switch (action.type) {
    case "ADD_TODO":
      return [
        ...todos,
        {
          id: Math.random(),
          text: action.payload,
          isComplated: false,
        },
      ];
    case "CHECK_TODO":
      return todos.map((todo) => {
        if (todo.id === action.payload) {
          return { ...todo, isComplated: !todo.isComplated };
        } else {
          return todo;
        }
      });
    case "DELETE_TODO":
      return todos.filter((todo) => todo.id !== action.payload);
  }
  return todos;
};

// 3. 밖에서 함수 호출

 

 

ContextAPI

  • 사용법 :
    • 상태 내려받을 컴포넌트들을 감싼 후 useContext로 받아서 사용하기
  • 문제 : 리렌더링 문제
    • ContextAPI에 감싸지는 컴포넌트들은 ContextAPI 상태가 바뀌면 다 리렌더링 된다.
    • 자주 변경되는 상태는 context로 사용하지 말자
  • 사용방법 (고차 컴포넌트 방식으로 사용하기)
    1. context 컴포넌트 생성
    2. 그 안에 povider 컴포넌트 생성
    3. povider 컴포넌트 안에 children 넣음
    4. 상태가 필요한 부분만 감싸서 사용한다.

 

메모이제이션 훅 3가지

  1. useCallback : 함수 메모이제이션
    1. 이 훅은 당시 함수의 상태를 스냅숏으로 저장한다.
      1. 주의사항
        1. 함수 정의 당시 상태 스냅샷
          1. 함수 안에 값이 변해도 반영이 안 되기 때문에 의존성 배열에 값을 넣어준다.
            • 메모이제이션을 남발하면 오히려 성능이 안 좋아질 수 있음
        2. 불필요한 메모이제이션
          1. 컴포넌트에 상태가 적으면 어차피 다시 렌더링 되기 때문에 불필요할 수 있음
            1. 해당 상태가 바뀔 때만 함수가 다시 재정의 되니 상태가 많이 저장되었을 경우 효과가 좋다
          1. useMemo : 값 메모이제이션
          2. React.memo : 컴포넌트 렌더링 메모이제이션
            1. 내려받는 props가 변하지 않으면 리렌더링 안됨
            2. 데이터를 많이 출력해야 하는 목록 같은 경우에 유용하게 사용할 수 있음
// 1. useCallback의 스냅샷
// 이럴경우 count 값이 바껴도 항상 동일한 값만 나옴
// 함수가 정의되는 그 순간의 count 값을 저장하기 떄문에 count가 바껴도 함수는 모름
const increment = useCallback(()=>{
	setCount(count+1);
},[])

const increment = useCallback(()=>{
	setCount(count+1);
},[count])

 

 

zustand

npm trends

새롭게 시작하는 프로젝트는 zustand를 많이 사용하는 추세이이니 배워두도록 하자

1. zustnad 설치하기

https://docs.pmnd.rs/zustand/getting-started/introduction 

2. zustand 생성하기

  • use... 이름으로 짓는 것이 관례
  • 콜백 매개변수에 반환되는 값이 하나의 상태가 된다
  • 매개변수의 set의 자리 역할이 setter의 역할이기 때문에 이름을 set으로 한다.(바꿔도 됨)
type CountStore = {
	count : number;
	increment : () => void;
	decrement : () => void;
}

export const useCountStore = create<CountStore>(set =>({
	count:0;
	increment : () =>set({count:1}),
	decrement : () =>set((state)=> ({count:state.count+1})),
}))

3. zustand 사용하기

const count = useCountStore = ((state)=>state.count)
const increment = useCountStroe = (state=>state.increment)

4. zustand 장점

  • zustand를 안 쓰는 컴포넌트는 같이 리렌더링이 되지 않는다
  • Provider처럼 자식 컴포넌트들을 감싸지 않는다
    • useState를 전역변수처럼 사용하는 느낌, redux만 쓰다가 zustand 쓰니까 대박 편하다

 

라우팅

  • 목적 : URL별로 다른 페이지 보여주기
  • 예전 라우팅 방법
<BrowserRouter>
	<APP />
</BrowserRouter>
// router
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/post" element={<Post />} />
  <Route path="/product" element={<ProductList />} />
  <Route path="/product/:id" element={<ProductDetail />} />
</Routes>
  • 최신 라우팅 방법(6.4v이상)
    • 버전이 바뀌면서 새로운 기능이 많이 추가됐다.
      • 페이지 이동 시 데이터 불러오기, 에러페이지 등록하기 등등
  • 동적 라우팅
    • 같은 페이지를 가리키는데 안에 데이터만 다르게 보여주는 것
  • url 파라미터 가져오기
    • useParams()
  • url 스트링쿼리 가져오기
    • useSearchParams()
  • 현재 경로 알아내기
    • useLocation()
  • url 이동하기
    • Link태그
    • useNavigation

 

이벤트 처리는 이벤트 객체를 적극 활용하자.

  • 이벤트 발생된 요소의 정보가 저장되어 있으니 바로 처리하기 좋음 name, value,..
  • 이벤트 객체 타입에 주의하자 : 요소마다 다름 

 

 

반응형