이어서...
지금까지의 코드 ↓
App.js
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
import './App.css';
import { useEffect, useRef, useState } from 'react';
function App() {
const [data,setData]=useState([]);
const dataId = useRef(0);
const getData= async()=>{
const res = await fetch("https://jsonplaceholder.typicode.com/comments").then((res)=>res.json());
console.log(res);
const initData= res.slice(0,20).map((it)=>{
return{
author: it.email,
content: it.body,
emotion: Math.floor(Math.random()*5)+1,
created_date : new Date().getTime(),
id: dataId.current++,
}
})
setData(initData);
}
useEffect(()=>{
getData();
},[]);
const onCreate = (author,content,emotion)=>{
const created_date = new Date().getTime();
const newItem={
author,
content,
emotion,
created_date,
id: dataId.current,
}
dataId.current+=1;
setData([newItem,...data])
}
const onDelete = (targetId)=>{
console.log(`${targetId}가 삭제되었습니다`);
const newDiaryList= data.filter((it)=>it.id!==targetId);
setData(newDiaryList);
}
const onEdit = (targetId,newContent)=>{
setData(
data.map((it)=>it.id===targetId?{...it,content:newContent}:it)
)
}
return (
<div className="App">
<DiaryEditor onCreate={onCreate}/>
<DiaryList diaryList={data} onDelete={onDelete} onEdit={onEdit}/>
</div>
);
}
export default App;
DiaryEditor.js
import React, { useRef,useState } from "react";
const DiaryEditor=({onCreate})=>{
const authorInput = useRef();
const contentInput = useRef();
const [state,setState]= useState({
author:"",
content:"",
emotion:1,
});
const handleChangeState=(e)=>{
setState({
...state,
[e.target.name]:e.target.value,
})
};
const handleSubmit = ()=>{
if(state.author.length<1){
authorInput.current.focus();
return;
}
if(state.content.length<5){
contentInput.current.focus();
return;
}
onCreate(state.author,state.content,state.emotion);
alert("저장 성공!");
setState({
author:"",
content:"",
emotion:1
})
}
return(
<div className="DiaryEditor">
<h2>오늘의 일기</h2>
<div>
<input
ref={authorInput}
name="author"
value={state.author}
onChange={handleChangeState}/>
</div>
<div>
<textarea
ref={contentInput}
name="content"
value={state.content}
onChange={handleChangeState}/>
</div>
<div>
<span>오늘의 감정점수 : </span>
<select name="emotion" value={state.emotion} onChange={handleChangeState}>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
<option value={5}>5</option>
</select>
</div>
<div>
<button onClick={handleSubmit}>일기 저장하기</button>
</div>
</div>
)
}
export default DiaryEditor;
DiaryList.js
import DiaryItem from "./DiaryItem";
const DiaryList = ({diaryList,onDelete,onEdit})=>{
return(
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다</h4>
<div>
{diaryList.map((it)=>(
<DiaryItem key={it.id} {...it} onDelete={onDelete}
onEdit={onEdit}/>
))}
</div>
</div>
)
}
DiaryList.defaultProps={
diaryList: [],
}
export default DiaryList;
DiaryItem.js
import { useRef, useState } from "react";
const DiaryItem=({
author,
content,
emotion,
created_date,
id,
onDelete,
onEdit,
})=>{
const localContentInput = useRef();
const [isEdit,setIsEdit]=useState(false);
const toggleIsEdit=()=>setIsEdit(!isEdit);
const [localContent,setLocalContent] = useState(content);
const handelDelete =()=>{
if(window.confirm(`${id}번째 일기를 삭제하시겠습니까?`)){
onDelete(id);
}
}
const handleQuitEdit = ()=>{
setIsEdit(false);
setLocalContent(content);
}
const handleEdit = ()=>{
if(localContent.length<5){
localContentInput.current.focus();
return;
}
if(window.confirm(`${id}번째 일기를 수정하시겠습니까?`)){
onEdit(id,localContent);
toggleIsEdit();
}
}
return(
<div className="DiaryItem">
<div className="info">
<span>작성자 :{author} | 감정점수 :{emotion}</span>
<br/>
<span className="date">{new Date(created_date).toLocaleDateString()}</span>
</div>
<div className="content">
{isEdit?<>
<textarea ref={localContentInput}value={localContent} onChange={(e)=>setLocalContent(e.target.value)}/>
</>:<>{content}</>}
</div>
<div>
{isEdit?<><button onClick={handleQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button></>:
<><button onClick={handelDelete}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button></>}
</div>
</div>
)
}
export default DiaryItem;
App.css
.DiaryEditor{
border: 1px solid gray;
text-align: center;
padding: 20px;
}
.DiaryEditor input,textarea{
margin-bottom: 20px;
width: 300px;
padding: 5px;
}
.DiaryEditor textarea{
padding: 20px;
}
.DiaryEditor select{
width: 200px;
padding: 5px;
margin-bottom: 10px;
}
.DiaryEditor button{
width: 200px;
padding: 10px;
cursor: pointer;
}
/* LIST */
.DiaryList{
border: 1px solid gray;
padding: 20px;
margin-top: 20px;
}
.DiaryList h2 {
text-align: center;
}
/* ITEM */
.DiaryItem{
background-color: rgb(240, 240, 240);
margin-bottom: 10px;
padding: 20px;
}
.DiaryItem .info{
border-bottom: 1px solid gray;
padding-bottom:10px ;
margin-bottom: 10px;
}
.DiaryItem .date{
color: gray;
}
.DiaryItem .content {
font-weight: bold;
margin-bottom: 30px;
margin-top: 30px;
}
이제 어느 정도 원하는 바를 다 갖춘 일기장을 완성하였다!
하지만 여기서 코드 최적화를 조금 더 진행해 볼 것이다.
useMemo?
연산 결괏값들을 재사용한다
Memoization?
말 그대로 이미 계산해본 연산 결과를 기억해 두었다가 동일한 계산을 시키면 다시 연산하지 않고
기억해두웠던 데이터를 반환시키게 한다
이 개념들을 이용해 최적화를 한번 해볼 것이다.
우리는 일기에 감정점수를 가지고 있다 이 점수를 이용해
1,2는 기분 나쁨 3,4,5는 기분 좋음으로 판단하여
기분 좋은 일기의 개수, 기분 나쁜 일기의 개수, 기분 좋은 일기의 비율을 화면에 띄어 볼 것이다
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(); //객체비구조화할당
getDiaryAnalysis라는 일기 분석 함수를 하나 만들어주고 그 값들을 객체로 리턴을 해주자
지역 변수들이기 때문에 객체 비구조화 할당으로 값을 받아주자
return (
<div className="App">
<DiaryEditor onCreate={onCreate}/>
<div>전체일기 : {data.length}</div>
<div>기분 좋은 일기 개수: {goodCount}</div>
<div>기분 나쁜 일기 개수: {badCount}</div>
<div>기분 좋은 일기 개수 비율: {goodRatio}</div>
<DiaryList diaryList={data} onDelete={onDelete} onEdit={onEdit}/>
</div>
);
DiaryEditor 컴포넌트와 DiaryList사이에 넣어주자
잘 뜬다 근데 여기서 문제는 아니지만 불편한 점이 있다
일기를 수정하면 전체일기가 늘어나지 않으므로 좋은 일기, 나쁜 일기 분석한 값이 바뀌지 않지만
렌더링이 되는 걸 볼 수 있다..
그럼 일기의 길이가 변경되어 즉 추가나 삭제가 되어 비율이 바뀔 때만 getDiaryAnalysis이 렌더링 되게 해 보자
이를 도와주는 게 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의 콜백함수처럼 들어가게 된다
그리고 두 번째 인자로는 useEffect처럼 defendency array를 넣어준다 행동도 똑같다 이 배열의 값이
바뀌며 callback함수가 실행되는 것이다
그럼 우리는 일기의 개수가 변화할 때만 렌더를 바라므로 저 배열에 data.length를 넣어주며 되는 것이다
그럼 이렇게 실행해 보자
Uncaught TypeError: getDiaryAnalysis is not a function
어라 getDiaryAnalysis가 더 이상 함수가 아니라는 에러메세지가 뜬다 그렇다 useMemo를 사용하면 그 함수는 더이상 함수가 아니다
왜냐하면 useMemo 기능은 함수를 전달받아서 그 callback함수가 리턴하는 값을 리턴하기 때문에
getDiaryAnalysis는 더 이상 함수가 아닌 값인 것이다
const {goodCount,badCount,goodRatio}=getDiaryAnalysis;
그러니 함수가 아닌 값으로 사용해야 한다
그럼 내가 원했던 것처럼 수정 시에는 렌더가 일어나지 않지만 일기개수가 변화한다면 리렌더가 일어나는 걸 볼 수 있다!
useMemo를 사용해 함수의 연산 최적화를 해보았다!
..💬
useEffect와 굉장히 비슷하다 생각해서 차이점을 찾아보았다
useMemo의 경우 "생성"함수에 관련된 기능으로 생성자 함수가 고비용(처리 시간이 오래 걸리는 등)인 경우 렌더링마
다 계산하는 것은 처리 시간이 오래 걸리므로 값을 기억해놓고 의존성이 변경되었을 경우에만 다시 계산해주는 기능이다
useEffect의 경우는 api 호출, 타이머 등 렌더링 과정에서 한 번만 호출해도 될 기능들이 렌더링 되어 실행되거나, 호출과정에서 렌더링에 영향을 끼칠 수 있는 것을 모아서 따로 처리하기 위한 기능이다
둘의 가장 큰 차이점은 useEffect는 해당 컴포넌트의 렌더링이 완료된 후에 실행되지만, useMemo는 렌더링 중에 실행되어진다
오.. 이런 기능 들을 프로젝트의 필요한 곳에 알맞게 사용해야 한다는 생각이 들었다.. 어렵댜..
또 useMemo를 사용하면 그 함수는 더 이상 함수가 아닌 값! 이라는 점!
'개발새발🐶🐾🐥🐾 > React' 카테고리의 다른 글
[React Project: 간단한 일기장(8)] 최적화3 - useCallback (0) | 2023.01.05 |
---|---|
[React Project: 간단한 일기장(7)] 최적화2 - React.memo (0) | 2023.01.05 |
[React] 리액트 useEffect? feat. Lifecycle (+예제코드) (1) | 2023.01.04 |
[React Project: 간단한 일기장(5)] 배열사용해서 데이터 조작하기3- 데이터 삭제및 수정 (0) | 2023.01.03 |
[React Project: 간단한 일기장(4)] 배열사용해서 데이터 조작하기2- 데이터 추가 (1) | 2023.01.03 |