useReducer:
컴포넌트 내부에 새로운 State를 생성하는 React Hook, 모든 useState는 useReducer로 대체 가능
상태 관리 코드를 컴포넌트 외부로 분리할 수 있음
- 리액트 컴포넌트의 주요 역할은 UI를 렌더링하는 것,
따라서 스테이트(onCreate, onUpdate 등)를 관리하는 코드가 너무 많아지면 주객 전도된 상황이 된다
dispatch 호출, reducer 호출, 액션 객체 reducer로 전달
- dispatch로 상태 변화 요청, 요청 내용은 안에 있는 객체(1씩 증가) <- 액션객체
- reduce함수에서 새로운 state의 값을 반환하면 state의 값이 변경된다
function reducer(state, action) {
switch(action.type){
case "INCREASE":
return state + action.data;
case "DECREASE":
return state - action.data;
default:
return state;
}
}
- switch문이 if문보다 가독성있다
투두리스트 코드에 적용한 예시
최적화
useMemo와 연산최적화
- 동일한 연산의 결과를 반복적으로 계산하지 않고, 최초 계산 결과를 저장해 재사용하는 기법
// 실습을 위한 불필요한 연산
const getAnalyzedData = () => {
const totalCount = todos.length;
const doneCount = todos.filter((todo)=>todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount
}
};
const {totalCount, doneCount, notDoneCount} = getAnalyzedData();
- 필터로 인해 수가 많아질수록 연산이 길어진다
- 리렌더링마다 다시 위 연산을 하게 된다(서치바에 검색어만 입력해도 연산 진행)
const {totalCount, doneCount, notDoneCount} =
useMemo(() => {
// memoization하고 싶은 연산을 이 곳에 삽입
// 리턴값 그대로 반환
console.log("연산");
const totalCount = todos.length;
const doneCount = todos.filter((todo)=>todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount
}
}, [todos]);
- useMemo도 deps에 들어가는 값이 바뀌면 콜백 함수를 다시 실행한다(useEffect와 동일)
- 콜백함수가 반환하는 값을 useMemo는 그대로 다시 반환
- useMemo에 있는 연산은 딱 한 번만 수행되도록 바뀐다
- memo안의 연산이 아예 한 번만 실행되는 것이 아닌, 검색어를 넣을 때에 실행되는 것만 방지하는 것이므로 deps([]안에)에 todos를 입력한다
- useMemo훅을 이용하면 특정조건, deps를 이용한 특정 조건이 만족하지 않을 때에 다시 수행하지 않도록 만들 수 있다
React Memo와 컴포넌트 렌더링 최적화
- 리액트 도구의 설정에서 하이라이트 표시로 리렌더링의 유무를 알 수 있다
import "./Header.css";
import { memo } from "react";
const Header = () => {
return <div className="Header">
<h3>오늘은 📅</h3>
<h1>{new Date().toDateString()}</h1>
</div>;
};
const memoizedHeader = memo(Header);
export default memoizedHeader;
- memoized를 했기 때문에 헤더에서 불필요한 리렌더링이 발생하지 않는다
import { memo } from "react";
export defalut memo(TodoItems);
- 투두아이템이 계속 리렌더링되는데, 이는 props가 바뀌었다고 인식하기 때문이다
(객체의 변수는 값이 같아도 주소에 저장하기 때문에 다른 값으로 인식해서 리렌더링 발생)
// 교차 컴포넌트 (HOC)
export default memo(TodoItem, (prevProps, nextProps) => {
// 반환값에 따라, Props가 바뀌었는지 안바뀌었는지 판단
// T -> Props 바꾸지 않음 -> 리렌더링 X
// F -> Props 바뀜 -> 리렌더링 O
if (prevProps.id !== nextProps.id) return false;
if (prevProps.isDone !== nextProps.isDone) return false;
if (prevProps.content !== nextProps.content) return false;
if (prevProps.date !== nextProps.date) return false;
return true;
});
- if문으로 props의 변동을 일일히 다 확인해서
인수로 받은 컴포넌트의 props가 변경되지 않았을 때에는 리렌더링하지 않도록 최적화해서 반환한다
- HOC(Higher Order Component)를 한 번 호출하는 것만으로도 컴포넌트에 새로운 기능을 부여할 수 있다
useCallback과 함수 재생성 방지
- 메모 메서드는 컴포넌트의 props가 바뀌었는지의 유무를 얕은 비교로 판단한다
>> 객체 타입의 값을 props로 전달하면 제대로된 최적화가 이루어지지 않는다
const onDelete = useCallback(()=>{}, [])
- callback 함수의 첫번째 인수로 재생성 방지를 원하는 함수를 넣고, 두번째 인수로는 depth를 넣는다
- useCallback은 첫번째 인수callback를 그대로 생성해서 반환한다(변수에 저장 가능)
- 생성되는 함수를 depth가 변경되었을 때만 다시 생성되도록 한다 (=함수의 메모이제이션)
const onDelete = useCallback((targetId)=>{
dispatch({
type: "DELETE",
targetId: targetId,
});
}, [])
- 마운트 시에만 함수가 생성된다(리렌더링이 발생해도 함수 재생성이 되지 않음)
1. 기능구현 - 2. 최적화 순서로 진행되어야 함
최적화가 필요한 것만(=복잡한 것만) 최적화하기
Context
- Context는 보통 컴포넌트 외부에 생성
(리렌더링마다 다시 생성될 필요가 없기 때문)
- provider는 컨텍스트가 공급할 데이터를 설정하거나 데이터를 공급받을 컴포넌트를 설정하기 위해 사용하는 props
>> 사실상 컴포넌트
const TodoContext = createContext();
return (
<div className="App">
<Header />
<TodoContext.Provider
value={
{todos,
onCreate,
onDelete,
}}
>
<Editor onCreate={onCreate}/>
<List
todos={todos}
onUpdate={onUpdate}
onDelete={onDelete}
/>
</TodoContext.Provider>
</div>
);
}
- 프로바이더 컴포넌트 아래에 있는 모든 컴포넌트들은 투두 컨텍스트의 데이터를 공급받을 수 있다
- value에 공급할 데이터를 설정
return (
<div className="App">
<Header />
<TodoContext.Provider
value={
{todos,
onCreate,
onDelete,
}}
>
<Editor />
<List />
</TodoContext.Provider>
</div>
);
- props 제거
import {useState, useRef, useContext} from "react";
import { TodoContext } from "../App";
const Editor = () => {
const {onCreate} = useContext(TodoContext);
- useContext은 context로부터 공급된 데이터를 반환해주는 함수
하지만 위의 과정을 실행하면 최적화가 풀리게 되는 문제가 발생한다
>> context를 분리하여 문제 해결
- todos의 값이 변경되면 모든 컴포넌트가 리렌더링되는 문제가 발생한다
- 이 문제를 해결하기 위해 컴포넌트를 분리한다
import {
useRef,
useState,
useReducer,
useCallback,
createContext,
useMemo,
} from "react";
export const TodoStateContext = createContext();
export const TodoDispatchContext = createContext();
return (
<div className="App">
<Header />
<TodoStateContext.Provider value={todos}>
<TodoDispatchContext.Provider value={memoizedDispatch}>
<Editor />
<List />
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
</div>
);
'코딩 공부 > React.js 리액트' 카테고리의 다른 글
감정 일기장 - 레이아웃 설정 (0) | 2024.11.27 |
---|---|
감정일기장 - 페이지 라우팅 (3) | 2024.11.27 |
5주차 퀴즈 (0) | 2024.11.13 |
[5주차 - 2] 투두리스트 기능 구현 (1) | 2024.11.13 |
[5주차 - 1] 투두리스트 UI 구현 (2) | 2024.11.12 |