오늘은 지난 강의에 이어 새로운 기능을 추가해보면서 리액트에 대해서 더 자세하게 배워보는 시간이다
API 호출과 최적화에 대한 강의가 진행되는데 오늘 강의도 열심히 꼼꼼하게 공부하고 기록해두어야겠다
그럼 오늘도 화이팅!
# React에서 API 호출하기
useEffect를 이용하여 컴포넌트 Mount 시점에 API를 호출하고 해당 API의 결과값을 일기 데이터의 초기값으로 이용해보자
임시 API를 사용하기 위해서 jsonplaceholder 사이트를 이용한다
https://jsonplaceholder.typicode.com/
사용할 API의 주소는 다음과 같다
https://jsonplaceholder.typicode.com/comments
그럼 App.js 가 Mount 되자마자 api를 호출하도록 useEffect를 사용해서 로직을 작성해보겠다
const getData = async () => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/comments"
).then((res) => res.json());
console.log(res);
};
useEffect(() => {
getData();
}, []);
콘솔을 확인해보니 마운트 되자마자 api를 호출해서 데이터를 불러오는 것을 확인할 수 있다
# API 호출 데이터 사용하기
호출해서 받은 데이터를 일기 데이터로 활용해보자
api 호출을 통해서 받은 데이터를 일기 데이터의 형식에 맞게 20개를 잘라서
새로운 일기 데이터 배열로 넣어주는 코드를 추가하였다
새로운 일기 데이터 배열은 setData 에 넣어주어 새로운 배열로 렌더링 시킨다
const getData = async () => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/comments"
).then((res) => res.json());
const initData = res.slice(0, 20).map((it) => {
return {
author: it.email,
content: it.body,
emotion: Math.floor(Math.random() * 5) + 1,
create_date: new Date().getTime(),
id: dataId.current++,
};
});
setData(initData);
};
useEffect(() => {
getData();
}, []);
api 호출을 통해 받아온 데이터로 일기 리스트가 작성된 것을 확인할 수 있다!
# React 구글 확장 프로그램
React Developer Tools
React Developer Tools 를 설치하면 리액트로 개발 시 매우 유용하다
컴포넌트 구조, 렌더링 되고 있는 컴포넌트 등의 다양한 기능을 제공하는 확장 프로그램이니
꼭 설치해서 개발 시 사용해보도록 하자
# 최적화 1 - useMemo
연산 결과값을 재사용하는 방법에 대해서 알아보자
현재 일기 데이터를 분석하는 함수를 제작하고 해당 함수가 일기 데이터의 길이가 변화하지 않을 때 값을 다시 계산하지 않도록 하는 Memoization을 이해하자
# Memoization
이미 계산 해 본 연산 결과를 기억 해 두었다가 동일한 계산을 시키면 다시 연산하지 않고 기억 해 두었던 데이터를 반환 시키게 하는 방법이다
# useMemo 적용 전
일기 리스트들의 감정점수를 분석하는 함수 getDiaryAnalysis 를 구현해보자
1. 3~5 사이의 감정 점수를 갖는 일기들
2. 1~2 사이의 감정 점수를 갖는 일기들
3. 좋은 감정 점수를 갖는 일기들의 비율을 구해보자
//감정점수 분석 함수
const getDiaryAnalysis = () => {
console.log("일기 분석 시작!");
const goodCount = data.filter((it) => it.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100;
return { goodCount, badCount, goodRatio };
};
const { goodCount, badCount, goodRatio } = getDiaryAnalysis();
return (
<div className="App">
{/* <Lifecycle /> */}
<DiaryEditor onCreate={onCreate} />
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList diaryList={data} onRemove={onRemove} onEdit={onEdit} />
</div>
);
}
해당 함수를 적용시키면 화면 상에서 어떻게 보일까?
전체 일기 개수를 기준으로 구하고자 하는 세 가지가 화면에 렌더링 되고 있다
콘솔창을 확인해보니 getDiaryAnalysis 가 두번 호출되고 있는데 그 이유는,
처음 렌더링 시에는 빈 배열을 가지고 시작하기 때문에 getDairyAnalysis 의 모든 값은 0이다
이때 한번 호출된다
그 후 api 호출을 진행하며 리렌더링 되는데 다시 한번 호출되기 때문에 불필요한 두번의 호출이 일어나고 있음을 알 수 있다
이러한 방법은 불필요한 연산을 증가시키는 비효율적 방법이다
그렇다면 필요시에 저장된 값을 바로 불러올 수 있도록
useMemo 함수를 사용해서 최적화를 시켜보자
# useMemo 적용 후
useMemo 함수는 첫번째 인자로 콜백 함수를 받아서 콜백함수 return 값의 연산을 최적화 할 수 있도록 한다
두번째 인자로는 배열을 전달받는다 -> useEffect 처럼 배열 안이 변화할 때만 useMemo 함수의 콜백함수가 다시 실행된다
//감정점수 분석 함수
const getDiaryAnalysis = useMemo(() => {
console.log("일기 분석 시작!");
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]);
이렇게 useMemo 함수를 적용해보고 확인해보니
에러가 발생했다
그 이유는 무엇일까?
useMemo 함수가 return 하는 값이 getDiaryAnalysis에 담기면 getDiaryAnalysis 는 더이상 함수가 아니다
함수가 더 이상 아니라는 것을 잊지 말아야 한다!
//감정점수 분석 함수
const getDiaryAnalysis = useMemo(() => {
console.log("일기 분석 시작!");
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]);
const { goodCount, badCount, goodRatio } = getDiaryAnalysis;
값으로 사용하니 일기의 길이가 변할 때만 useMemo가 정상적으로 동작하는 것을 확인할 수 있다
# 최적화 2 - React.memo
부모 컴포넌트가 랜더링되면 리렌더 될 필요가 없는 자식 컴포넌트도 렌더링되어 연산의 낭비가 발생한다
이런 경우 각각의 자식 컴포넌트에게 업데이트 조건을 걸어줄 수 있다
이렇게 함수형 컴포넌트에 업데이트 조건을 거는 것 리액트의 기능이 바로
React.memo
이다
React.memo 는 고차 컴포넌트이다
고차 컴포넌트란 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수를 뜻한다
const MyComponent = React.memo(function MyComponent(props) {
/* props를 사용하여 렌더링 */
});
# React.memo 실습 1
React.memo 실습을 위해 컴포넌트를 하나 생성하자
const OptimizeTest = () => {
const [count, setCount] = useState(1);
const [text, setText] = useState("");
return (
<div style={{ padding: 50 }}>
<div>
<h2>count</h2>
<Countview count={count} />
<button onClick={() => setCount(count + 1)}>+</button>
</div>
<div>
<h2>text</h2>
<Textview text={text} />
<input value={text} onChange={(e) => setText(e.target.value)} />
</div>
</div>
);
};
OptimizeTest 컴포넌트 내에는 Countview 컴포넌트와 Textview 컴포넌트가 존재한다
어떤식으로 렌더링이 일어나고 있는지 확인하기 위해 각각의 자식 컴포넌트에서 콘솔을 찍어보자
const Textview = ({ text }) => {
useEffect(() => {
console.log(`Update Text : ${text}`);
});
return <div>{text}</div>;
};
const Countview = ({ count }) => {
useEffect(() => {
console.log(`Update count : ${count}`);
});
<div>{count}</div>;
};
Count 값만 증가시키면서 콘솔창을 확인해보니
Text 컴포넌트도 함께 렌더링 되고 있는 것을 확인할 수 있다
이를 최적화하기 위해서 React.memo를 사용해보자
const Textview = React.memo(({ text }) => {
useEffect(() => {
console.log(`Update Text : ${text}`);
});
return <div>{text}</div>;
});
Textview 컴포넌트에 React.memo를 적용시킨 후 다시 콘솔창을 확인해보니
동일하게 count 컴포넌트만 증가시에
count 컴포넌트만 렌더링 되고 있음을 확인할 수 있다
# React.memo 실습 2
또 다른 React.memo 실습을 진행해보자
객체인 obj와 단순 숫자인 count로 두개의 컴포넌트를 생성한다
const CounterA = React.memo(({ count }) => {
useEffect(() => {
console.log(`CounterA Update - count : ${count}`);
});
return <div>{count}</div>;
});
const CounterB = React.memo(({ obj }) => {
useEffect(() => {
console.log(`CounterB Update - count : ${obj.count}`);
});
return <div>{obj.count}</div>;
});
이전의 방법과 마찬가지로 OptimizeTest 컴포넌트에 적용시킨 후 렌더링이 어떻게 일어나는지 확인해보자
두개의 버튼을 클릭해도 값은 변하지 않는다
콘솔창을 확인해보면 A button을 눌렀을 때는 렌더링이 일어나지 않지만
B button을 누르자 렌더링이 계속해서 일어난다
같은 값인데 왜 렌더링이 일어나는 것일까?
B는 객체 형식이기 때문이다
객체는 얕은 비교를 진행하기 때문에 React.memo를 사용해도 계속해서 렌더링이 일어나게 된다
즉 객체의 주소에 의한 비교를 실행하기 때문에 같은 값이라도 서로 다른 것으로 인식함
그렇다면 React.memo 의 areEqual 함수를 사용해서 깊은 비교를 실행해보자
const areEqual = (preProps, nextProps) => {
if (preProps.obj.count === nextProps.obj.count) {
return true;
}
return false;
};
객체의 count 값을 비교해서 같으면 true를, 다르면 false를 반환하도록 로직을 작성한다
해당 함수를 사용하여
const MemoizedCounterB = React.memo(CounterB, areEqual);
를 구현하고 MemoizedCounterB를 기존의 CounterB 컴포넌트에서 변경한다
그 후 다시 B button을 눌러서 콘솔을 확인해보니 렌더링이 일어나지 않음을 확인할 수 있다!
# 새로 알게 된 점
리액트 컴포넌트의 최적화 방법에 대해서 처음 배웠는데 유용하지만 어려웠다
불필요한 렌더링은 연산의 낭비를 발생시키기 때문에 이를 해결하기 위한 리액트의 다양한 기능이 있음을 알게 되었다
간단하게 React.memo 와 useMemo 를 적용시키면 최적화가 일어난다는 것 역시 유용한 기능인 것 같다
아무리 유용한 기능이더라도 사용하는 방법이 어려우면 잘 사용하지 않게 될 것이다
하지만 오늘 배운 최적화 기능들은 사용법도 매우 간단하면서도 유용한 기능을 제공하기에 잘 알아두고 필요한 상황에 적절하게 사용하면 더 나은 시스템을 개발할 수 있을 것이다
최적화 기능에 대해 처음 배웠기 때문에 복습이 필요하겠지만 전에 개발했던 리액트 프로젝트에 해당 내용을 적용시켜 봐야겠다
그래서 불필요한 렌더링을 줄이고 자주 사용하는 값은 저장하여 연산의 최적화를 시켜볼 것이다
단순히 구현만 하고 끝나는 것이 아니라 최적화를 통해 더 나은 시스템을 개발하기 위해 노력해야겠다
'TIL > 프로그래머스 데브코스' 카테고리의 다른 글
클라우딩 어플리케이션 엔지니어링 TIL Day 32 - 감정 일기장 만들기 (1) (0) | 2024.02.23 |
---|---|
클라우딩 어플리케이션 엔지니어링 TIL Day 31 - 리액트 기본 간단한 일기장 만들기 (4) (2) | 2024.02.23 |
클라우딩 어플리케이션 엔지니어링 TIL Day 29 - React 기본 간단한 일기장 프로젝트 (2) (1) | 2024.02.11 |
클라우딩 어플리케이션 엔지니어링 TIL Day 28 - React 기본 간단한 일기장 만들기 (1) (0) | 2024.02.09 |
클라우딩 어플리케이션 엔지니어링 TIL Day 27 - React 기초 (1) | 2024.02.09 |