useMemo
함수 컴포넌트에서 발생하는 연산을 최적화한다. 렌더링할 때 특정 값이 바뀌었을 때만 연산을 실행하고, 그 값이 바뀌지 않는다면 이전에 연산했던 결과를 그대로 사용.
1. 연산 최적화 하고자하는 함수에 useMemo를 붙여 콜백함수로 만들어준다.
const getDiaryAnalysis = useMemo(
() => {
const goodCount = data.filter((it) => it.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100;
return { goodCount, badCount, goodRatio };
},
[data.length] //data.length가 바뀔때만 앞의 콜백함수를 수행하고, 그렇지 않으면 수행했던 리턴값을 계속 사용
);
useMemo는 useEffect와 유사하다.
차이점은 useEffect는 html이 다 실행되고나서 실행되는 것이고, useMemo는 렌더링될 때 실행된다는 것이다.
실행시점의 차이가 있다.
2. 함수사용
함수를 사용할 때는 ()를 붙이지 않는다. useMemo를 사용하면 콜백함수의 값이 getDiaryAnalysis에 저장된다. 그래서
getDiaryAnalysis는 값으로 사용한다.
const { goodCount, badCount, goodRatio } = getDiaryAnalysis;
React.memo
부모 컴포넌트가 리렌더링 되면 하위 컴포넌트는 자동으로 리렌더링 된다. 하지만 그 자식 컴포넌트에 변동사항이 없다면 연산낭비이다. 그래서 컴포넌트의 props가 바뀌지 않는다면 리렌더링 하지 않도록 한다.
const TextView = React.memo(({ text }) => { //props인 text가 변할때만 TextView 컴포넌트가 렌더링 된다.
useEffect(() => {
console.log(`update :: Text : ${text}`);
});
return <div>{text}</div>;
});
* 이 props가 객체라면?
자바스크립트에서는 객체를 얕은비교(주소비교)를 하기 때문에 객체인 값이 변하지 않더라도 다른 값으로 인식한다.
이것을 막기 위해 areEqual이라는 함수를 사용하는데, areEqual은 React.memo의 비교함수로 작용하게 된다.
//객체를 props로 가지고 있는 컴포넌트 CounterB
const CounterB = ({ obj }) => {
return <div>{obj.count}</div>;
};
//areEqual함수를 사용하여 이전과 이후의 obj.count를 비교하여 같다면 true, 같지않다면 false 반환
const areEqual = (preProps, nextProps) => {
return preProps.obj.count === nextProps.obj.count;
};
//CounterB는 areEqual의 판단에 따라 리렌더링을 할지 말지 결정하는 메모화된 컴포넌트가 된다.
const MemoizedCounterB = React.memo(CounterB, areEqual);
const OptimizeTest = () => {
const [obj, setobj] = useState({
count: 1,
});
return (
<MemoizedCounterB obj={obj} />
)
}
useCallBack
컴포넌트가 리렌더링 될 때마다 함수도 같이 재생성된다. useCallback을 사용하면 제일 처음 마운트될 때 함수를 한번 생성하고 이것을 재사용하도록 할 수 있다.
const onCreate = useCallback((author, content, emotion) => {
setData((data) => [newItem, ...data]);
}, []); //[] : 마운트될 때만 함수 생성되도록
setData안에 최신 데이터 인자를 갖는 콜백함수를 넣어줘야 최신의 데이터를 인자를 통해 참조할 수 있다.
useReducer
useReducer는 기존에 컴포넌트 내에서 useState로 상태관리를 하던 것을 컴포넌트 밖으로 분리하여 사용할 수 있게 한다. 이렇게 상태변화로직(상태와 상태변화 처리함수)을 분리하여 컴포넌트를 가볍게 사용할 수 있다.
1. reduer 함수 만들기
이 함수는 현재상태와 액션 객체를 파라미터로 받는다.
reducer에서 반환하는 값이 현재 상태값이 된다.
* 액션객체는 상태를 업데이트하는데 필요한 정보를 가지고 있으며 type 속성을 가지는 객체이다.
const reducer = (state, action) => {
switch (action.type) { //action의 type속성에 따라 리턴을 다르게
case "INIT": {
return action.data;
}
case "CREATE": { //기존 컴포넌트 내부의 상태변화 함수에서 작성했던 것을 reducer함수 내부에서 수행
const created_date = new Date().getTime();
const newItem = {
...action.data,
created_date,
};
return [newItem, ...state];
}
default:
return state;
}
};
2. useReducer 사용하기
이 useReducer에서 data는 우리가 사용할 상태를 선언하는 것이고, dispatch는 액션을 발생시키는 함수이다.
useReducer의 첫번째 파라미터는 앞에서 만들었던 reducer 함수이고, 두번째 파라미터는 초기 상태이다.
* dispatch가 계속 이해가 안되었는데, 그냥 상태값을 바꾸고 싶을 때는 dispatch라는 함수를 사용하고 그 파라미터를 액션으로 사용한다는 의미이다.
const [data, dispatch] = useReducer(reducer, []);
//초기값이 빈 배열인 data 상태
3. dispatch 사용하기
기존에 사용했던 상태변경함수 대신 dispatch를 사용하여 상태를 변경한다.
type 속성은 reducer 함수에서 수행할 타입을 지정해주는 것이고, data 속성에는 그 수행에 필요한 정보들을 넣는다.
* dispatch를 호출하면 알아서 현재의 state를 자동으로 reducer 함수가 참조한다.
const getData = async () => {
//setData(initData); 기존 상태 변화 함수에서 dispatch로 변경
dispatch({ type: "INIT", data: initData });
};
const onCreate = useCallback((author, content, emotion) => {
// const created_data = new Date().getTime(); 기존 함수에서 사용했던 것을 reducer에서 작성
// const newItem = {
// author,
// content,
// emotion,
// created_data,
// id: dataId.current, //0
// };
// dataId.current += 1;
// setData((data) => [newItem, ...data]);
dispatch({
type: "CREATE",
data: { author, content, emotion, id: dataId.current }, //data에는 필요한 인자들만 넘겨줌
});
dataId.current += 1;
}, []);
context API
context API를 사용하여 상태를 전역으로 사용하여 불필요한 propsdrilling을 제거할 수 있다.
1. context 만들기
최상위 컴포넌트에서 context를 만든다.
export const DiaryStateContext = React.createContext(); //export를 붙여줘야 외부에서 사용 가능
export const DiaryDispatchContext = React.createContext();
2. context 사용하기
context provider를 사용하여 전역으로 데이터를 보내주는데 value에는 전달할 데이터를 넣는다.
* 상태와 함수는 context를 다르게 사용해야한다. Provider도 컴포넌트 이므로 data의 값이 변경되면 리렌더링 되므로 최적화했던것이 풀어진다 -> context 중첩으로 사용하기
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider value={memoizedDispatches}>
<div className="App">
// 내용작성
</div>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
* 함수를 context로 관리하여 Provider로 보낼 때는 함수들이 불필요하게 재생성되지 않도록 useMemo를 사용하여 함수들을 묶어서 보낸다.
const memoizedDispatches = useMemo(() => {
return { onCreate, onRemove, onEdit };
}, []);
3. context에서 상태 사용하기
상태가 필요한 컴포넌트에서 context를 사용하여 상태를 꺼내온다.
const diaryList = useContext(DiaryStateContext);'React' 카테고리의 다른 글
| throttle과 debounce 사용하기 (2) | 2025.06.21 |
|---|---|
| 리액트 useState 렌더링 관련 알게 된 것들 : 객체 상태 관리, 상태의 초기 값 (0) | 2024.05.06 |
| [한입리액트] useRef : 컴포넌트가 리렌더링 되더라도 값이 초기화되지않고 유지시키고 싶을 때 사용 (2) | 2023.07.27 |
| [React-Query] 2. useMutation 사용하기(공부중...) (0) | 2023.06.27 |
| [React-Query] 2. 리액트쿼리 특징과 useQuery 사용하기 (0) | 2023.06.01 |