//📌 Context 생성
// 내보내줘야 다른 곳에서 가져올 수 있다.
export const DiaryStateContext = React.createContext();
<DiaryStateContext.Provider value={data}>
로 감싸주기value={data}
전달하기 return (
//📌Constext.Provider, value로 값을 써준다.
<DiaryStateContext.Provider value={data}>
<div className="App">
<DiaryEditor onCreate={onCreate} />
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수: {goodCount}</div>
<div>기분 나쁜 일기 개수: {badCount}</div>
<div>기분 좋은 일기 비율: {goodRatio}</div>
<DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />{" "}
{/* 더미 리스트 프롭스로 전달 */}
</div>
</DiaryStateContext.Provider>
);
};
export default App;
함수만의 컨텍스트들을 만들어주기
Provider도 컴포넌트라서 함수를 다 넣어주면 한번에 다 리랜더링되어서 지금까지 최적화한게 다 풀린다.
함수 props에 전달하기 전에 함수들 묶어주기
memoizedDispatches 함수 전달
- 왜 memo로 묶어줌?
- 하나의 함수가 리랜더링될때 객체 자체가 리랜더링 된다.그래서 useMemo로 리랜더링 되지 않게 묶어 줘야한다.
각 컴포넌트에서 context에 함수 가져오기(프롭스 드릴링 해결)
import React, {
useRef,
useEffect,
useMemo,
useCallback,
useReducer,
} from "react";
import "./App.css";
import DiaryEditor from "./DiaryEditor";
import DiaryList from "./DiaryList";
//1️⃣ 외부에 reducer함수를 넣는다.
const reducer = (state, action) => {
switch (action.type) {
case "INIT": {
return action.data;
}
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;
}
};
//📌 Context 생성
// 내보내줘야 다른 곳에서 가져올 수 있다.
export const DiaryStateContext = React.createContext();
//📌 여러개 만들어줘도 된다.
export const DiaryDispatchContext = React.createContext();
export const App = () => {
const [data, dispatch] = useReducer(reducer, []);
const dataId = useRef(0);
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,
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 });
}, []);
// 📌memoizedDispatches 함수 묶기
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 (
//📌Constext.Provider, value로 값을 써준다.
<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={data} 삭제*/}
<DiaryList onEdit={onEdit} onRemove={onRemove} />{" "}
{/* 더미 리스트 프롭스로 전달 */}
</div>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
};
export default App;
import React, { useContext, useRef, useState } from "react";
//📌 context 함수 이름 import
import { DiaryDispatchContext } from "./App";
const DiaryItem = ({ author, content, created_date, emotion, id }) => {
//📌 useContext로 사용, 비구조 할당으로 불러오기
const { onRemove, onEdit } = useContext(DiaryDispatchContext);
// 수정하고 있는지 수정이 끝났는지.
const [isEdit, setIsEdit] = useState(false);
//수정 상태 토글기능
const toggleIsEdit = () => setIsEdit(!isEdit);
//수정버튼 눌렀을때 textarea 함수설정
const [localContent, setLocalContent] = useState(content);
const localContentInput = useRef();
//삭제 버튼 함수 분리
const handleRemove = () => {
if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
onRemove(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 />
{/* new Date 괄호안에 값을 넣어주고 .toLocaleString() 붙이면 인간이 알아보는 시간이 나온다*/}
<span className="date">{new Date(created_date).toLocaleString()}</span>
</div>
<div className="content">
{isEdit ? (
<textarea
//* 인풋 포커스 쓸려면 꼭 ref값알려주기
ref={localContentInput}
//값은 상태 기본값
value={localContent}
//함수로는 지금 타켓의 값으로 기본값을 바꿔준다.
onChange={(e) => setLocalContent(e.target.value)}
></textarea>
) : (
<>{content}</>
)}
</div>
{isEdit ? (
<>
<button onClick={handleQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
) : (
<>
<button onClick={handleRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
</>
)}
</div>
);
};
export default React.memo(DiaryItem);
//📌 컨텍스트 import
import { useContext } from "react";
import { DiaryStateContext } from "./App";
import DiaryItem from "./DiaryItem";
const DiaryList = () => {
//* 📌 Context 가져오기
const diaryList = useContext(DiaryStateContext);
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{/* props 순환 */}
{diaryList.map((it) => (
<DiaryItem
key={it.id}
{...it}
/> /* 모든 it의 데이터가 prop으로 들어간다. */
))}
</div>
</div>
);
};
/* 알고보기 값들이 undefinde으로 온다면? 에러남!! 기본값= 빈값 설정 : 에러 안나게 하려고*/
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;