2주 차에 배운 핵심 내용을 정리해보고자 한다.
React를 사용하는 이유
렌더링 퍼포먼스가 좋다
렌더링을 최적화해야 하는 이유
- 자바스크립트 요소를 수정(Reflow)하거나 스타일을 수정(Repaint)하면 렌더링이 다시 처음부터 시작하여 많은 시간이 소요된다,
- 자식 요소를 바꾸려면 부모 요소들도 리렌더링을 해야 한다.
- 렌더링을 최소한으로 하는 것이 프론트엔드의 주요한 과제였다.
React의 렌더링 장점
- Reflow랑 Repaint를 React가 알아서 자동으로 내부적으로 최적화해서 실행해 준다
- 개발자는 비즈니스 로직에 집중할 수 있다.
- 가상돔을 이용해서 렌더링을 자동으로 최적화한다.
가상돔
- 가상의 데이터만 가지고 있고, 수정 시 진짜 DOM을 건드리지 않는다
- 렌더링 과정을 거치지 않아 매우 빠르다
가상돔 업데이트 과정
- 가상돔 2개(리액트 변경사항만 반영하는 가상 DOM, DOM 복사본)를 배교해서 어느 부분이 달라졌는지 계산한다(Diffing)
- 바뀐 부분만 실제 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()
- 본질 : 상태 업데이트 함수
- 장점
- 리렌더링 시 재생성 안됨
- 리듀서에서 변하지 않고 재생성도 안되기 때문에 함수 props로 내려줄 때 useCallback 안 써도 됨
- 업데이트 함수 묶어서 내릴 수 있음(dispatch)
- 함수 여러 개 내려줄 필요 없고 dispatch만 내리면 됨, 업데이트하는 경우가 많으면 엄청 편할 듯
- 실무에서는 useReducer 안 쓰고 redux, recoil, zustand 사용한다고 함
- 가독성
- 함수들을 묶어서 정리해 두니까 보기가 편함
- 너무 지저분해 보였는데 편안해짐
- 상태 변경의 목적을 action type으로 쉽게 이해할 수 있음
- 함수들을 묶어서 정리해 두니까 보기가 편함
- 정리
- 상태 업데이트 함수이고 업데이트되는 상황들을 묶어서 하나의 함수 안에서 처리하는 것
- 리렌더링 시 재생성 안됨
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로 사용하지 말자
- 사용방법 (고차 컴포넌트 방식으로 사용하기)
- context 컴포넌트 생성
- 그 안에 povider 컴포넌트 생성
- povider 컴포넌트 안에 children 넣음
- 상태가 필요한 부분만 감싸서 사용한다.
메모이제이션 훅 3가지
- useCallback : 함수 메모이제이션
- 이 훅은 당시 함수의 상태를 스냅숏으로 저장한다.
- 주의사항
- 함수 정의 당시 상태 스냅샷
- 함수 안에 값이 변해도 반영이 안 되기 때문에 의존성 배열에 값을 넣어준다.
- 메모이제이션을 남발하면 오히려 성능이 안 좋아질 수 있음
- 함수 안에 값이 변해도 반영이 안 되기 때문에 의존성 배열에 값을 넣어준다.
- 불필요한 메모이제이션
- 컴포넌트에 상태가 적으면 어차피 다시 렌더링 되기 때문에 불필요할 수 있음
- 해당 상태가 바뀔 때만 함수가 다시 재정의 되니 상태가 많이 저장되었을 경우 효과가 좋다
- useMemo : 값 메모이제이션
- React.memo : 컴포넌트 렌더링 메모이제이션
- 내려받는 props가 변하지 않으면 리렌더링 안됨
- 데이터를 많이 출력해야 하는 목록 같은 경우에 유용하게 사용할 수 있음
- 컴포넌트에 상태가 적으면 어차피 다시 렌더링 되기 때문에 불필요할 수 있음
- 함수 정의 당시 상태 스냅샷
- 주의사항
- 이 훅은 당시 함수의 상태를 스냅숏으로 저장한다.
// 1. useCallback의 스냅샷
// 이럴경우 count 값이 바껴도 항상 동일한 값만 나옴
// 함수가 정의되는 그 순간의 count 값을 저장하기 떄문에 count가 바껴도 함수는 모름
const increment = useCallback(()=>{
setCount(count+1);
},[])
const increment = useCallback(()=>{
setCount(count+1);
},[count])
zustand
새롭게 시작하는 프로젝트는 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,..
- 이벤트 객체 타입에 주의하자 : 요소마다 다름
반응형