1) 사용자 입력 및 배열 리스트 처리하기
2) React Lifecycle과 API
3) React App 프로처럼 성능 최적화하기 with 도구 사용
4) React 컴포넌트 트리에 전역 데이터 공급하기
한시간 째 나는 시작도 못하고 애만 먹고 있다..
진짜 이거 왜이러는거냐? 다 맞게 했는데 ㅠㅠ
강의 빨리 듣고 실습하고 싶은데 하지도 못하고.. 억울하다
해결은 안됐지만 난 시간이 없으니까
무시하고 그냥 한다 !!
const DiaryEditor=()=>{
return <div className="DiaryEditor"></div>;
};
export default DiaryEditor;
ㄴDiaryEditor.js 파일 생성
(DiaryEditor.js)
import { useState } from "react";
const DiaryEditor=()=>{
const [state,setState]=useState({
author:"",
content:"",
});
return(
<div className="DiaryEditor">
<h2>오늘의 일기</h2>
<div>
<input
name="author"
value={state.author}
onChange={(e)=> { //값이 바뀌었을 때 수행하는 event
setState({
author:e.target.value, //author만 바꾸고
content:state.content, //content는 유지한다
});
}}
/>
</div>
<div>
<textarea value={state.content} //여러줄을 입력받을 수 있음
onChange={(e)=>{
setState({
content:e.target.value, //content만 바꾸고
author:state.content, //author는 유지
});
}}
/>
</div>
</div>
);
};
export default DiaryEditor;
import { useState } from "react";
const DiaryEditor=()=>{
const [state,setState]=useState({
author:"",
content:"",
emotion:1, //감정 설정값 = 1
});
const handleChangeState =(e)=>{
setState({
...state,
[e.target.name]: e.target.value,
});
};
return(
<div className="DiaryEditor">
<h2>오늘의 일기</h2>
<div>
<input
name="author"
value={state.author}
onChange={handleChangeState}
/>
</div>
<div>
<textarea name="content"
value={state.content} //여러줄을 입력받을 수 있음
onChange={handleChangeState}
/>
</div>
<div> //감정점수 설정
<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>
);
};
export default DiaryEditor;
(App.css)
.DiaryEditor{
border: 1px solid gray;
text-align: center;
padding: 20px;
}
.DiaryEditor input, textarea{
margin-bottom: 20px;
width: 500px;
padding: 10px;
}
.DiaryEditor textarea{
height: 150px;
}
.DiaryEditor select{
width: 300px;
padding: 10px;
margin-bottom: 20px;
}
.DiaryEditor button{
width: 500px;
padding: 10px;
cursor: pointer;
}
ㄴ 스타일링 해주기
난 해주면 뭐함? 보이지가 않는데.. ㅠㅠ
const handleSubmit=()=>{
if(state.author.length <1){
alert("작성자는 최소 1글자 이상을 입력해주세요");
return; //아니면 중단
}
if(state.content.length <5){
alert("작성자는 최소 5글자 이상을 입력해주세요");
return;
}
alert("저장 성공");
}
handleSubmit 함수를 고쳤는데 alert는 잘 안쓰니까 focus로 써보면 !
-last code-
import React, { useRef, useState } from "react";
const DiaryEditor=()=>{
const authorInput=useRef(); //ref=dom에 접근할 수 있음
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(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능
return; //아니면 중단
}
if(state.content.length <5){
contentInput.current.focus();
return;
}
alert("저장 성공");
}
return(
<div className="DiaryEditor">
<h2>오늘의 일기</h2>
<div>
<input
ref={authorInput}
name="author"
value={state.author}
onChange={handleChangeState}
/>
</div>
<div>
<textarea name="content"
ref={contentInput}
value={state.content} //여러줄을 입력받을 수 있음
onChange={handleChangeState}
/>
</div>
<div>
<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;
오류를 드디어 해결함
근데 왜
아무것도 안뜰까?
울고싶다 ....
미친 드디어 ㅠㅠㅠㅠ
-last code-
(App.js)
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
const dummyList = [
{
id:1,
author:"일서연",
content:"하이 1",
emotion:1,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
},
{
id:2,
author:"이서연",
content:"하이 2",
emotion:2,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
},
{
id:3,
author:"삼서연",
content:"하이 3",
emotion:3,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
}
];
const App =() => {
return (
<div className="App">
<DiaryEditor/>
<DiaryList diarList ={dummyList}/>
</div>
);
};
export default App;
(DiaryEditor.js)
import React, { useRef, useState } from "react";
const DiaryEditor=()=>{
const authorInput=useRef(); //ref=dom에 접근할 수 있음
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(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능
return; //아니면 중단
}
if(state.content.length <5){
contentInput.current.focus();
return;
}
alert("저장 성공");
}
return(
<div className="DiaryEditor">
<h2>오늘의 일기</h2>
<div>
<input
ref={authorInput}
name="author"
value={state.author}
onChange={handleChangeState}
/>
</div>
<div>
<textarea name="content"
ref={contentInput}
value={state.content} //여러줄을 입력받을 수 있음
onChange={handleChangeState}
/>
</div>
<div>
<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;
(DairyList.js)
import DiaryItem from "./DiaryItem.js";
const DiaryList=({diaryList})=>{
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.lenght}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it)=> (
<DiaryItem key={it.id} {...it}/>
))}
</div>
</div>
);
};
DiaryList.defaultProps={
diaryList:[],
};
export default DiaryList;
(DiaryItem.js)
const DiaryItem=({author, content, created_date,emotion,id})=>{
return <div className="DiaryItem">
<div className="info">
<span>작성자 : {author} | 감정점수 : {emotion}
</span>
<br/>
<span className="date">{new Date(created_date).toLocaleString()}</span>
</div>
<div className="content">{content}</div>
</div>
}
export default DiaryItem;
(App.css)
.DiaryEditor{
border: 1px solid gray;
text-align: center;
padding: 20px;
}
.DiaryEditor input, textarea{
margin-bottom: 20px;
width: 500px;
padding: 10px;
}
.DiaryEditor textarea{
height: 150px;
}
.DiaryEditor select{
width: 300px;
padding: 10px;
margin-bottom: 20px;
}
.DiaryEditor button{
width: 500px;
padding: 10px;
cursor: pointer;
}
/* List */
.DiaryList{
border:1px solid gray;
padding: 20px;
margin-top: 20px;
}
.DiaryList h2{
text-align: center;
}
/* Item */
.DairyItem{
background-color: rgb(240,240,240);
margin-bottom: 10px;
padding: 20px;
}
.DairyItem .info{
border-bottom: 1px solid gray;
padding-bottom: 10px;
margin-bottom: 10px;
}
.DairyItem.date{
color: gray;
}
.DairyItem .content{
font-weight: bold;
margin-bottom: 30px;
margin-top: 30px;
}
ㄴ 스타일링을 줘봤다
근데 CSS 는 왜 코드 블럭 앞에 css라고 써도 색깔이 안바꾸ㅣ지?
스터디 시간에 물어봐야겠음 ㅡㅡ
3번째 강의는 약간 받아쓰기..하는 느낌이었다 아무래도 한번 더 봐야지 필기를 하던 이해를 하던 뭘 할 수 있을 것 같다 힝
-결과
이렇게 쓰고 저장하기 누르면
우왕
이렇게 추가해줘야 하지만
리액트에선 같은 레벨끼리는 데이터를 주고받을 수 없다
(리액트는 단방향으로만 데이터가 흐른다 !)
이럴땐 app 컴포넌트에 state로 아래의 레벨들에게 데이터를 전달한다 !
setData가 새로운 아이템을 추가해서 app component에서 추가된 data를 DiaryList로 내려감
=> 단방향 데이터 흐름을 사용해도 데이터를 주고받게할 수 있따 !!
문제가 생겨따
왜 난 저장이 안된는 것임?
-last code-
(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(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능
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}
placeholder="작성자"
type="text"
/>
</div>
<div>
<textarea name="content"
ref={contentInput}
value={state.content} //여러줄을 입력받을 수 있음
onChange={handleChangeState}
placeholder="일기"
type="text"
/>
</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})=>{
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it)=> (
<DiaryItem key={`diaryitem_${it.id}`} {...it}/>
))}
</div>
</div>
);
};
DiaryList.defaultProps={
diaryList:[],
};
export default DiaryList;
(index.js)
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
(DiaryItem.js)
const DiaryItem=({author, content, created_date,emotion,id})=>{
return(
<div className="DiaryItem">
<div className="info">
<span>작성자 : {author} | 감정점수 : {emotion} |
</span>
<br/>
<span className="date">{new Date(created_date).toLocaleString()}</span>
</div>
<div className="content">{content}</div>
</div>
);
};
export default DiaryItem;
(App.js)
import { useRef, useState } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
/*const dummyList = [
{
id:1,
author:"일서연",
content:"하이 1",
emotion:1,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
},
{
id:2,
author:"이서연",
content:"하이 2",
emotion:2,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
},
{
id:3,
author:"삼서연",
content:"하이 3",
emotion:3,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
}
];*/
const App =() =>{
const [data,setData]=useState([]);
const dataId=useRef(0);
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]); //newItem추가 하고 원래 배열에 있던걸 하나하나 나열
};
return (
<div className="App">
<DiaryEditor onCreate={onCreate}/>
<DiaryList diarList ={data}/>
</div>
);
};
export default App;
코드샌드박스에서 다 비교해도 저장이 안된다..
오ㅐㅈㅣ??????
App.js에다가 삭제 함수를 넣어주었다
const onDelete=(targetId)=>{
console.log(`${targetId}가 삭제되었습니다`);
const newDiaryList=data.filter((it)=>it.id!==targetId);
console.log(newDiaryList);
setData(newDiaryList);
};
DiaryList.js에다가도
const DiaryList=({onDelete, diaryList})=>{
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it)=> (
<DiaryItem key={`diaryitem_${it.id}`} {...it} onDelete={onDelete}/>
))}
</div>
</div>
);
};
삭제가 들어가게 해주었ㄷㅏ
<button>수정하기</button>
수정하기 버튼 만들구
<button onClick={handleRemove}>삭제하기</button>
onClick에 있던 if문을 없애고
const handleRemove=()=>{
if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)){
onRemove(id);
}
}
함수에 넣어주어따
const [isEdit,setIsEdit]=useState(false);
isEdit
=> 수정할 수 있는지 없는지를 알려주는 boolean형
이게 true이면 수정중으로 간주해서 수정하면 되고
false이면 그냥 수정,삭제 버튼으로 랜드 하면 됨
const toggleIsEdit=()=> setIsEdit(!isEdit);
toggleIsEdit
이 호출이 되면 setIsEdit
이 호출이 되면서 not연산을 통해서 isEdit이 true였다면 false로, false였다면 true로 바뀌는 반전 연산을 해줌
<button onClick={handleRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
밑에다가는 요렇게 넣어주었다
const handlQuitEdit=()=>{
setIsEdit(false);
setLocalContent(content);
}
handlQuitEdit
함수 설정하고
<button onClick={handlQuitEdit}>수정 취소</button>
에다가 넣어서 수정해준당
-last code-
(DiaryItem.js)
import { useRef, useState } from "react";
const DiaryItem=({
onEdit,
onRemove,
author,
content,
created_date,
emotion,
id
})=>{
const [isEdit,setIsEdit]=useState(false);
const toggleIsEdit=()=> setIsEdit(!isEdit);
const localContentInput=useRef();
const [localContent,setLocalContent]=useState("");
const handleRemove=()=>{
if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)){
onRemove(id);
}
};
const handlQuitEdit=()=>{
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).toLocaleString()}
</span>
</div>
<div className="content">
{isEdit?(
<>
<textarea
ref={localContentInput}
value={localContent}
onChange={(e)=>setLocalContent(e.target.value)}/>
</>
):(
<>{content}</>
)}
</div>
{isEdit?(
<>
<button onClick={handlQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
):(
<>
<button onClick={handleRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
</>
)}
</div>
);
};
export default DiaryItem;
(App.js)
import { useRef, useState } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
/*const dummyList = [
{
id:1,
author:"일서연",
content:"하이 1",
emotion:1,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
},
{
id:2,
author:"이서연",
content:"하이 2",
emotion:2,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
},
{
id:3,
author:"삼서연",
content:"하이 3",
emotion:3,
created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
}
];*/
function App(){
const [data,setData]=useState([]);
const dataId=useRef(0);
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]); //newItem추가 하고 원래 배열에 있던걸 하나하나 나열
};
const onRemove=(targetId)=>{
console.log(`${targetId}가 삭제되었습니다`);
const newDiaryList=data.filter((it)=>it.id!==targetId);
console.log(newDiaryList);
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 onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
</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(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능
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}
placeholder="작성자"
type="text"
/>
</div>
<div>
<textarea name="content"
ref={contentInput}
value={state.content} //여러줄을 입력받을 수 있음
onChange={handleChangeState}
placeholder="일기"
type="text"
/>
</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=({onEdit, onRemove, diaryList})=>{
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it)=> (
<DiaryItem key={it.id} {...it} onEdit={onEdit} onRemove={onRemove}/>
))}
</div>
</div>
);
};
DiaryList.defaultProps={
diaryList:[],
};
export default DiaryList;
대박 어렵다..
그리고 아직도 내 오류는 안고쳐짐
왤까? 진짜 완벽히 똑같은데 .. 나도 화면으로 바뀌는거 보고싶은데
안보여서 재미가 없다 ㅠㅡㅜ
Lifecycle
이란?
-생애주기
React component도 생애주기를 가짐
-탄생
-변화
-죽음
-
탄생 - 화면에 나타나는거 mount
변화 - 업데이트(리렌더) update
죽음 - 화면에서 사라짐 unmount
use키워드를 앞에 붙여서 클래스형이 갖고있떤 거를 함수가 채가는거
use함수 - 매개변수로 첫번째는 callback함수를, 두번째는 의존성배열을 씀
(Lifecycle.js)를 만들고 코드를 넣어준다
import React,{useEffect,useState} from 'react';
const Lifecycle=()=>{
const [count,setCount]=useState(0);
const[text,setText]=useState("");
useEffect(()=>{
console.log("Mount!");
},[]);
useEffect(()=>{
console.log("Update!");
});
return (
<div style={{padding:20}}>
<div>
{count}
<button onClick={()=>setCount(count+1)}>+</button>
</div>
<div>
<input value={text} onChange={(e)=>setText(e.target.value)}/>
</div>
</div>
);
};
export default Lifecycle;
이렇게 하면
맨 위에 +버튼 누르면 console에 update라고 뜬다
useEffect(()=>{
console.log(`count is update : ',$count`);
},[count])
useEffect(()=>{
console.log(`text is update : ${text}`);
},[text]);
함수 추가 했떠니
업데이트 대박 잘됨
count 5 넘기면 경고 메시지 띄우게 해봄
useEffect(()=>{
console.log(`count is update : ',${count}`);
if(count>5){
alert("count가 5를 넘었습니다. 따라서 1로 초기화합니다");
setCount(1);
}
},[count])
ㄴ 옹 ㅋ
(Lifecycle.js)
import React,{useEffect,useState} from 'react';
const UnmounTest=()=>{
return <div>Unmount Testing Componetent</div>
}
const Lifecycle=()=>{
const [isVisible, setIsVisible]=useState(false);
const toggle=()=>setIsVisible(!isVisible);
return (
<div style={{padding:20}}>
<button onClick={toggle}>ON/OFF</button>
{isVisible && <UnmounTest/>}
</div>
);
};
export default Lifecycle;
이렇게 싹 수정해줌
여기에서
{isVisible && <UnmounTest/>}
-> isVisible값이 true가 되면 단락회로평가를 못해서 뒤에꺼가 값이 true가 된다. false이면 단락회로평가를 하게 돼서 unmounttest도 못하게 된다
한번 누르면
한번 더 누르면 사라짐
누르면 mount나오게 하기
const UnmounTest=()=>{
useEffect(()=>{
console.log("Mount!");
},[])
return <div>Unmount Testing Componetent</div>
}
unmount도 해보았음
const UnmounTest=()=>{
useEffect(()=>{
console.log("Mount!");
return ()=>{
// unmount시점에 실행되게 됨
console.log("unmount!");
}
},[]);
return <div>Unmount Testing Componetent</div>
}
(App.js)
function App(){
const [data,setData]=useState([]);
const dataId=useRef(0);
const getData =async()=>{ //promise를 반환하는 비동기함수
const res = await fetch('https://jsonplaceholder.typicode.com/comments'
).then((res)=>res.json());
console.log(res);
};
useEffect(()=>{
getData();
},[])
API 추가함
그리고 나서 보니까
아까 500개 json에 있던것들을 다 불러옴
(APp.js)
const getData =async()=>{ //promise를 반환하는 비동기함수
const res = await fetch('https://jsonplaceholder.typicode.com/comments'
).then((res)=>res.json());
const initData = res.slice(0,20).map((it)=>{//20개 추리기
return{
author : it.email,
content : it.body,
emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
created_date:new Date().getTime(),
id:dataId.current++
}
})
setData(initData);
};
getData 함수를 만들어서 내장객체 fetch와 async, await를 이요해서 일기 데이터의 초기값을 정해봄
리액트 개발시에 효율성을 높여주는 개발자 도구 !
두번째
highlight눌러주면
글자 쓰면 색 하이라이트가 나온다는데 왜 난 안나오냐??
Memorization
-> 이미 계산해 본 연산결과를 기억해두었다가
똑같은 계산을 시키면 다시 연산을 안하고 기억해 두었던 데이터를 반환시키게 하는 방법
기분이 좋은 건 몇갠지, 안좋은건 몇갠지, 기분이 좋은거의 비율은 어케 되는지 -> 함수 구현
(App.js)에다가 구현
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">
<DiaryEditor onCreate={onCreate}/>
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
</div>
);
결과값은 잘 나온다
ㅅㅐ로 고침함녀
두번 나온다
한번 호출하고 분석하고 한번 더 호출하기 때문
->낭비 -> useMemo를 쓰자
useMemo
=> 첫번째 인자로 콜백함수를 받아서 함수가 리턴하는 연산을 최적화 할 수 있도록 도와줌 & 두번째로 배열을 전달해야함
(App.js에 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]);
const {goodCount,badCount,goodRatio}=getDiaryAnalysis; //꼭 함수 말고 값으로 해주자
setCount
하려고 하면
먼저 app component
에 count
값이 바뀌고 state
가 업데이트 되어서 app component
가 리랜드 됨 -> 부모 component가 리랜더 되면 자식 component도 리랜더 되기 때문에 countview
와 textview
도 리랜더 된다
=> 여기서 연산의 낭비 발생 !!
그러니까 업데이트 조건을 걸어주면 낭비가 줄어든다 !
=> 업데이트 조건 걸어주는게 React.memo
https://ko.legacy.reactjs.org/docs/getting-started.html -> 리액트 나무위키 급의 페이지
리액트는 고차 컴포넌트다 !
ㄴ 근데 고차 컴포넌트란?
낡은걸 주면 새걸 주는 좋은 친구인듯
똑같은 props를 받으면 똑같은 props를 내보냄=리랜더링 하지 않겠다
OptimizeTest.js 생성함
const OptimizeTest = ()=>{
return <div style={{padding:50}}></div>
}
export default OptimizeTest;
App.js에 <OptimizeTest/>
해주고 Crtl S 하면
상단에 줄이 생김 !!
import React, { useState,useEffect} from "react";
const Textview=({text})=>{
return <div>{text}</div>
}
const CountView = ({count})=>{
return <div>{count}</div>
}
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>
}
export default OptimizeTest;
OptimizeTest.js를 이렇게 바꿔줌 그랬더니
이렇게 count랑 text가 생겨났다
const Textview=({text})=>{
useEffect(()=>{
console.log(`Update :: Text : ${text}`);
});
return <div>{text}</div>;
};
const CountView = ({count})=>{
useEffect(()=>{
console.log(`Update :: Count : ${count}`);
});
return <div>{count}</div>;
};
console을 넣어줌
잘 나오는군 ㅎㅎ
메모 사용
const Textview=React.memo(({text})=>{
useEffect(()=>{
console.log(`Update :: Text : ${text}`);
});
return <div>{text}</div>;
});
prop인 text가 바뀌지 않으면 절대 랜더링이 안일어난다
카운트에도 사용했떠니
const Textview=React.memo(({text})=>{
useEffect(()=>{
console.log(`Update :: Text : ${text}`);
});
return <div>{text}</div>;
});
const CountView = React.memo(({count})=>{
useEffect(()=>{
console.log(`Update :: Count : ${count}`);
});
return <div>{count}</div>;
});
솔직히 뭐가 다른건지 잘 모르겠지만 콘솔은 잘 나온다
import React, { useState,useEffect} from "react";
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>
});
const OptimizeTest = ()=>{
const [count,setCount]=useState(1);
const [obj,setObj]=useState({
count : 1,
});
return (
<div style={{padding:50}}>
<div>
<h2>Counter A</h2>
<CounterA count = {count}/>
<button onClick={()=> setCount(count)}>A button</button>
</div>
<div>
<h2>Counter B</h2>
<CounterB obj = {obj}/>
<button onClick={()=> setObj({
count : obj.count
})}>B button</button>
</div>
</div>
);
};
export default OptimizeTest;
OptimizeTest.js를 갈아엎음
CounterA랑 B에 React.memo잘 넣어주고 실행했는데
CounterA 누르면 아무것도 출력도 변화도 없다
CounterB 누르면 console에 찍히긴 함
-> props가 객체여서 (객체는 얕은비교여서 그럼)
값에 의한 비교가 아닌 주소에 의한 비교 !!
-last code-
(OptimizeTest.js)
import React, { useState,useEffect} from "react";
const CounterA = React.memo(({count})=>{
useEffect(()=>{
console.log(`CounterA Update - count : ${count}`)
})
return <div>{count}</div>
});
const CounterB = ({obj})=>{
useEffect(()=>{
console.log(`CounterB Update - count : ${obj.count}`)
})
return <div>{obj.count}</div>
};
const areEqual = (prevProps,nextProps)=>{
if(prevProps.obj.count === nextProps.obj.count){
return true; //이전 props와 현재 props가 같다 = 리랜더링을 일으키지 마라
}
return false; //이전 props와 현재 props가 다르다 = 리랜더링을 일으켜라
}
const MemoizedCounterB = React.memo(CounterB,areEqual); //리랜더링을 할지 말지 결정
const OptimizeTest = ()=>{
const [count,setCount]=useState(1);
const [obj,setObj]=useState({
count : 1,
});
return (
<div style={{padding:50}}>
<div>
<h2>Counter A</h2>
<CounterA count = {count}/>
<button onClick={()=> setCount(count)}>A button</button>
</div>
<div>
<h2>Counter B</h2>
<MemoizedCounterB obj = {obj}/>
<button onClick={()=> setObj({
count : obj.count
})}>B button</button>
</div>
</div>
);
};
export default OptimizeTest;
ㄴ 이렇게 코드를 짰을 때
CounterB 버튼 눌러도 아무것도 변화 X 리랜더링 안하는것
(중요!)useCallback
은 콜백의 메모이제이션된 버전을 반환할 것입니다.
-last code-
(App.js)
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
const App=()=>{
const [data,setData]=useState([]);
const dataId=useRef(0);
const getData =async()=>{ //promise를 반환하는 비동기함수
const res = await fetch('https://jsonplaceholder.typicode.com/comments'
).then((res)=>res.json());
const initData = res.slice(0,20).map((it)=>{//20개 추리기
return{
author : it.email,
content : it.body,
emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
created_date:new Date().getTime(),
id:dataId.current++
};
});
setData(initData);
};
useEffect(()=>{
getData();
},[]);
const onCreate=useCallback((author,content,emotion)=>{
const created_date=new Date().getTime();
const newItem={
author,
content,
emotion,
created_date,
id:dataId.current
};
dataId.current+=1;
setData((data)=>[newItem,...data]); //newItem추가 하고 원래 배열에 있던걸 하나하나 나열
},[]);
const onRemove=(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)
);
};
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]);
const {goodCount,badCount,goodRatio}=getDiaryAnalysis;
return (
<div className="App">
<DiaryEditor onCreate={onCreate}/>
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
</div>
);
};
export default App;
나머지 최적화 하는 작업을 하였다 !!
onEdit과 onRemove를 최적화 시킴
(App.js)
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
const App=()=>{
const [data,setData]=useState([]);
const dataId=useRef(0);
const getData =async()=>{ //promise를 반환하는 비동기함수
const res = await fetch('https://jsonplaceholder.typicode.com/comments'
).then((res)=>res.json());
const initData = res.slice(0,20).map((it)=>{//20개 추리기
return{
author : it.email,
content : it.body,
emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
created_date:new Date().getTime(),
id:dataId.current++
};
});
setData(initData);
};
useEffect(()=>{
getData();
},[]);
const onCreate=useCallback((author,content,emotion)=>{
const created_date=new Date().getTime();
const newItem={
author,
content,
emotion,
created_date,
id:dataId.current
};
dataId.current+=1;
setData((data)=>[newItem,...data]); //newItem추가 하고 원래 배열에 있던걸 하나하나 나열
},[]);
const onRemove=useCallback((targetId)=>{
setData(data=>data.filter((it)=>it.id!==targetId));
},[]);
const onEdit=useCallback((targetId,newContent)=>{
setData((data)=>
data.map((it)=>
it.id===targetId?{...it, content:newContent}:it)
);
},[]);
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]);
const {goodCount,badCount,goodRatio}=getDiaryAnalysis;
return (
<div className="App">
<DiaryEditor onCreate={onCreate}/>
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
</div>
);
};
export default App;
(DiaryItem.js)
import React,{ useEffect, useRef, useState } from "react";
const DiaryItem=({
onEdit,
onRemove,
author,
content,
created_date,
emotion,
id
})=>{
useEffect(()=>{console.log(`${id}번 째 아이템 렌더 !`);
});
const [isEdit,setIsEdit]=useState(false);
const toggleIsEdit=()=> setIsEdit(!isEdit);
const localContentInput=useRef();
const [localContent,setLocalContent]=useState("");
const handleRemove=()=>{
if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)){
onRemove(id);
}
};
const handlQuitEdit=()=>{
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).toLocaleString()}
</span>
</div>
<div className="content">
{isEdit?(
<>
<textarea
ref={localContentInput}
value={localContent}
onChange={(e)=>setLocalContent(e.target.value)}/>
</>
):(
<>{content}</>
)}
</div>
{isEdit?(
<>
<button onClick={handlQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
):(
<>
<button onClick={handleRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
</>
)}
</div>
);
};
export default React.memo(DiaryItem);
App component 속 onCreate, onEdit, onRemove 와 같은 상태 변화 처리 함수들이 코드가 길어져서 복잡해보임 -> 바깥으로 분리하자 ! =useReducer 사용
여기서 {type} 이런 애들은 action 객체라고 부름 = 상태변화를 설명할 객체, 상태변화가 나타나면 이 객체들은
reducer로 보내짐-> switch문에서 나오는 반환값이 새로운 상태가 됨 => count에 state가 업데이트 됨!!
이번 시간에는 reducer 함수를 만들고 원래에 있던 함수 속 내용들을 지우면서 복잡해보이지 않게 해주는 작업을 함
const reducer=(state,action)=>{
switch(action.type){
case 'INIT':{
return action.data //새로운 state
}
case 'CREATE':{
const created_date=new Date().getTime();
const newItem={
...action.data,
created_date
}
return [newItem,...state];
}
case 'REMOVE':{
return state.filter((it)=>it.id!==action.targetId);
}
case 'EDIT':{
return state.map((it)=>it.id===action.targetId?
{...it,content:action.newContent}:it
);
}
default:
return state;
}
}
ㄴreducer 함수
-last code-
(App.js)
import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
const reducer=(state,action)=>{
switch(action.type){
case 'INIT':{
return action.data //새로운 state
}
case 'CREATE':{
const created_date=new Date().getTime();
const newItem={
...action.data,
created_date
}
return [newItem,...state];
}
case 'REMOVE':{
return state.filter((it)=>it.id!==action.targetId);
}
case 'EDIT':{
return state.map((it)=>it.id===action.targetId?
{...it,content:action.newContent}:it
);
}
default:
return state;
}
}
const App=()=>{
//const [data,setData]=useState([]);
const [data,dispatch]=useReducer(reducer, [])
const dataId=useRef(0);
const getData =async()=>{ //promise를 반환하는 비동기함수
const res = await fetch('https://jsonplaceholder.typicode.com/comments'
).then((res)=>res.json());
const initData = res.slice(0,20).map((it)=>{//20개 추리기
return{
author : it.email,
content : it.body,
emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
created_date:new Date().getTime(),
id:dataId.current++
};
});
dispatch({type:"INIT",data:initData})
};
useEffect(()=>{
getData();
},[]);
const onCreate=useCallback((author,content,emotion)=>{
dispatch({type:'CREATE',
data:{author,content,emotion,id:dataId.current}
});
dataId.current+=1;
},[]);
const onRemove=useCallback((targetId)=>{
dispatch({type:"REMOVE",targetId})
},[]);
const onEdit=useCallback((targetId,newContent)=>{
dispatch({type:"EDIT",targetId,newContent})
},[]);
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]);
const {goodCount,badCount,goodRatio}=getDiaryAnalysis;
return (
<div className="App">
<DiaryEditor onCreate={onCreate}/>
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
</div>
);
};
export default App;
이거를
이거로 바꾸면 좋다
강의에서는
이렇게 Context.Provider가 DiaryEditor과 DiaryList를 묶고 있는데
나는
왜 이러지?
오 나왔당
-last code-
export const DiaryStateContext = React.createContext();
export const DiaryDispatchContext = React.createContext();
추가해줌
return (
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider>
<div className="App">
<DiaryEditor onCreate={onCreate}/>
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
</div>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
리턴 속에도 바꿔주었당
그랬더니
옹 ㅋ
(App.js)
import React,
{ useCallback,
useEffect,
useMemo,
useReducer,
useRef,
} from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
const reducer=(state,action)=>{
switch(action.type){
case 'INIT':{
return action.data //새로운 state
}
case 'CREATE':{
const created_date=new Date().getTime();
const newItem={
...action.data,
created_date
}
return [newItem,...state];
}
case 'REMOVE':{
return state.filter((it)=>it.id!==action.targetId);
}
case 'EDIT':{
return state.map((it)=>it.id===action.targetId?
{...it,content:action.newContent}:it
);
}
default:
return state;
}
};
export const DiaryStateContext = React.createContext();
export const DiaryDispatchContext = React.createContext();
const App=()=>{
const [data,dispatch]=useReducer(reducer, []);
const dataId=useRef(0);
const getData =async()=>{ //promise를 반환하는 비동기함수
const res = await fetch('https://jsonplaceholder.typicode.com/comments'
).then((res)=>res.json());
const initData = res.slice(0,20).map((it)=>{//20개 추리기
return{
author : it.email,
content : it.body,
emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
created_date:new Date().getTime(),
id:dataId.current++
};
});
dispatch({type:"INIT",data:initData})
};
useEffect(()=>{
getData();
},[]);
const onCreate=useCallback((author,content,emotion)=>{
dispatch({type:'CREATE',
data:{author,content,emotion,id:dataId.current}
});
dataId.current+=1;
},[]);
const onRemove=useCallback((targetId)=>{
dispatch({type:"REMOVE",targetId})
},[]);
const onEdit=useCallback((targetId,newContent)=>{
dispatch({type:"EDIT",targetId,newContent})
},[]);
const memoizedDispatches = useMemo(()=>{
return {onCreate,onRemove,onEdit}
},[]);
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]);
const {goodCount,badCount,goodRatio}=getDiaryAnalysis;
return (
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider value={memoizedDispatches}>
<div className="App">
<DiaryEditor onCreate={onCreate}/>
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
</div>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
};
export default App;
(DiaryEditor.js)
import React, { useContext, useRef, useState } from "react";
import { DiaryDispatchContext } from "./App";
const DiaryEditor=()=>{
const {onCreate}=useContext(DiaryDispatchContext)
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(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능
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}
placeholder="작성자"
type="text"
/>
</div>
<div>
<textarea name="content"
ref={contentInput}
value={state.content} //여러줄을 입력받을 수 있음
onChange={handleChangeState}
placeholder="일기"
type="text"
/>
</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 React.memo(DiaryEditor);
(DiaryList.js)
import { useContext } from "react";
import DiaryItem from "./DiaryItem";
import { DiaryStateContext } from "./App";
const DiaryList=()=>{
const diaryList=useContext(DiaryStateContext);
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it)=> (
<DiaryItem key={it.id} {...it} />
))}
</div>
</div>
);
};
DiaryList.defaultProps={
diaryList:[],
};
export default DiaryList;
(DiaryItem.js)
import React,{ useContext, useRef, useState } from "react";
import { DiaryDispatchContext } from "./App";
const DiaryItem=({
author,
content,
created_date,
emotion,
id
})=>{
const {onRemove,onEdit}=useContext(DiaryDispatchContext)
const [isEdit,setIsEdit]=useState(false);
const toggleIsEdit=()=> setIsEdit(!isEdit);
const localContentInput=useRef();
const [localContent,setLocalContent]=useState("");
const handleRemove=()=>{
if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)){
onRemove(id);
}
};
const handlQuitEdit=()=>{
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).toLocaleString()}
</span>
</div>
<div className="content">
{isEdit?(
<>
<textarea
ref={localContentInput}
value={localContent}
onChange={(e)=>setLocalContent(e.target.value)}/>
</>
):(
<>{content}</>
)}
</div>
{isEdit?(
<>
<button onClick={handlQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
):(
<>
<button onClick={handleRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
</>
)}
</div>
);
};
export default React.memo(DiaryItem);
리액트 나무위키ㅋㅋㅋ 잘 복습하고 갑니당
이제 css 돌아가죠...?