인프런 : https://www.inflearn.com/course/한입-리액트
강의 진행 중 사용된 소스코드 입니다.
https://codesandbox.io/s/chapt-1-otxc8
소스 : https://github.com/braverokmc79/react-with-one-bite
https://github.dev/braverokmc79/react-simple-diary
들어가며
1.강의 및 강사 소개
2. React 소개
3.오픈채팅방 입장 방법 및 비밀번호
JavaScript 기본
4.Hello World
5.변수와 상수
6.자료형과 형 변환
7.연산자
8.조건문
9.함수
10.함수표현식 & 화살표 함수
11.콜백함수
12.객체
13.배열
14.반복문
15.배열 내장 함수
JavaScript 응용
16.Truthy & Falsy
17.삼항 연산자
18.단락회로 평가
19.조건문 업그레이드
20.비 구조화 할당
21.Spread 연산자
22.동기 & 비동기
23.Promise - 콜백 지옥에서 탈출하기
24.async & await - 직관적인 비 동기 처리 코드 작성하기
25.API 호출하기
Node.js 기초
26.Node.js란?
27.Node.js & VsCode 설치하기
28.Node.js Hello World & Common JS
29.Node.js 패키지 생성 및 외부 패키지 사용하기
React.js 기초
30.Why React?
31.Create React App
32.JSX
33.State
34.Props
React 기본 - 간단한 일기장 프로젝트
35.프로젝트 소개
36.React에서 사용자 입력 처리하기
37.React에서 DOM 조작하기 - useRef
38.React에서 배열 사용하기 1 - 리스트 렌더링 (조회)
39.React에서 배열 사용하기 2 - 데이터 추가하기
40.React에서 배열 사용하기 3 - 데이터 삭제하기
41.React에서 배열 사용하기 4 - 데이터 수정하기
42.React Lifecycle 제어하기 - useEffect
43.React에서 API 호출하기
44.React developer tools
45.최적화 1 - useMemo
46.최적화 2 - React.memo
47.최적화 3 - useCallback
48.최적화 4 - 최적화 완성
49.복잡한 상태 관리 로직 분리하기 - useReducer
50.컴포넌트 트리에 데이터 공급하기 - Context
React 실전 프로젝트 - 감정 일기장 만들기
51.프로젝트 완성 예시
52.페이지 라우팅 0 - React SPA & CSR
53.페이지 라우팅 1 - React Router 기본
54.페이지 라우팅 2 - React Router 응용
55.프로젝트 기초 공사 1
56.프로젝트 기초 공사 2
57.페이지 구현 - 홈 (/)
58.페이지 구현 - 일기 쓰기 (/new)
59.페이지 구현 - 일기 수정 (/edit)
60.페이지 구현 - 일기 상세 (/diary)
61.(서브 챕터) 흔히 발생하는 버그 수정 하기
62.LocalStorage를 일기 데이터베이스로 사용하기
63.프로젝트 최적화
64.배포 준비 & 프로젝트 빌드하기
65.Firebase로 프로젝트 배포하기
66.Open Graph 설정하기
React 기본 - 간단한 일기장 프로젝트
35.프로젝트 소개
강의 :

36.React에서 사용자 입력 처리하기
강의 :
강의 진행 중 사용된 소스코드 입니다.
https://codesandbox.io/s/chapt-1-otxc8
0527 업데이트
30분 20초 즈음 css 스타일링을 시작하면서 추가되는 span태그에 대한 설명이 누락되었습니다.
다음과 같이 span 태그를 select 태그 위에 넣어주세요
<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>
이 문제를 제보해 주신 tkddyd420님 감사합니다.
https://www.inflearn.com/questions/544801
$ npx create-react-app .

DiaryEditor.js
import { useState } from "react";
const DiaryEditor = () => {
const [state, setState] = useState({
author: "",
content: "",
emotion: 3
});
const handlerChangeState = (e) => {
console.log(e.target.name, e.target.value);
setState({ ...state, [e.target.name]: e.target.value })
}
const handleSubmit = (e) => {
console.log(state);
alert("저장 성공");
}
return (<div className="DiaryEditor">
<h2>오늘의 일기</h2>
<div>
<input
name="author"
value={state.author}
onChange={handlerChangeState}
/>
{/* onChange={(e) => {
// setState({...state, author: e.target.value })
// */}
</div>
<div>
<textarea
name="content"
value={state.content}
onChange={handlerChangeState}
/>
</div>
<div>
<span>오늘의 감정점수 : </span>
<select name="emotion"
value={state.emotion}
onChange={handlerChangeState}>
<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>
<option value={6}>6</option>
</select>
</div>
<div>
<button onClick={handleSubmit}>일기 저장하기</button>
</div>
</div>);
};
export default DiaryEditor;
App.css
.DiaryEditor{
border: 1px solid grey;
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;
}
37.React에서 DOM 조작하기 - useRef
강의 :
https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103524

import { useLayoutEffect, useRef, useState } from "react";
const DiaryEditor = () => {
const authorInput = useRef();
const contentInput = useRef();
const [state, setState] = useState({
author: "",
content: "",
emotion: 3
});
const handlerChangeState = (e) => {
console.log(e.target.name, e.target.value);
setState({ ...state, [e.target.name]: e.target.value })
}
const handleSubmit = (e) => {
if (state.author.length < 1) {
//alert("작성자는 최소 1글자 이상 입력해주세요.");
authorInput.current.focus();
return;
}
if (state.content.length < 5) {
//alert("일기 본문은 최소 5글자 이상 입력해주세요.");
contentInput.current.focus();
return;
}
alert("저장 성공");
}
return (<div className="DiaryEditor">
<h2>오늘의 일기</h2>
<div>
<input
name="author"
ref={authorInput}
value={state.author}
onChange={handlerChangeState}
/>
{/* onChange={(e) => {
// setState({...state, author: e.target.value })
// */}
</div>
<div>
<textarea
name="content"
ref={contentInput}
value={state.content}
onChange={handlerChangeState}
/>
</div>
<div>
<span>오늘의 감정점수 : </span>
<select name="emotion"
value={state.emotion}
onChange={handlerChangeState}>
<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>
<option value={6}>6</option>
</select>
</div>
<div>
<button onClick={handleSubmit}>일기 저장하기</button>
</div>
</div>);
};
export default DiaryEditor;
38.React에서 배열 사용하기 1 - 리스트 렌더링 (조회)
강의 :


DiaryEditor.js
import { useLayoutEffect, useRef, useState } from "react";
const DiaryEditor = () => {
const authorInput = useRef();
const contentInput = useRef();
const [state, setState] = useState({
author: "",
content: "",
emotion: 3
});
const handlerChangeState = (e) => {
console.log(e.target.name, e.target.value);
setState({ ...state, [e.target.name]: e.target.value })
}
const handleSubmit = (e) => {
if (state.author.length < 1) {
//alert("작성자는 최소 1글자 이상 입력해주세요.");
authorInput.current.focus();
return;
}
if (state.content.length < 5) {
//alert("일기 본문은 최소 5글자 이상 입력해주세요.");
contentInput.current.focus();
return;
}
alert("저장 성공");
}
return (<div className="DiaryEditor">
<h2>오늘의 일기</h2>
<div>
<input
name="author"
ref={authorInput}
value={state.author}
onChange={handlerChangeState}
/>
{/* onChange={(e) => {
// setState({...state, author: e.target.value })
// */}
</div>
<div>
<textarea
name="content"
ref={contentInput}
value={state.content}
onChange={handlerChangeState}
/>
</div>
<div>
<span>오늘의 감정점수 : </span>
<select name="emotion"
value={state.emotion}
onChange={handlerChangeState}>
<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>
<option value={6}>6</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={it.id} {...it} />
))
}
</div>
</div>
)
}
DiaryList.defaultProps = {
diaryList: [],
}
export default DiaryList;
DiaryItem.js
const DiaryItem = ({ author, content, emotion, create_date }) => {
return (
<div className="DiaryItem">
<div className="info">
<span>
작성자 : {author} | 감정점수 : {emotion}
</span>
<br />
<span className="date"> {new Date(create_date).toLocaleString()}</span>
</div>
<div className="content">{content}</div>
</div >
);
};
export default DiaryItem;
39.React에서 배열 사용하기 2 - 데이터 추가하기
강의 :



App.js
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
import './App.css';
import { useState } from 'react';
import { useRef } from 'react';
// const dummyList = [
// {
// id: 1,
// author: "홍길동",
// content: "하이1 ",
// emotion: 2,
// create_date: new Date().getTime()
// },
// {
// id: 2,
// author: "이순신",
// content: "하이2 ",
// emotion: 3,
// create_date: new Date().getTime()
// },
// {
// id: 3,
// author: "테스터",
// content: "하이3 ",
// emotion: 4,
// create_date: new Date().getTime()
// }
// ]
function App() {
const [data, setData] = useState([]);
const dataId = useRef(0);
const onCreate = (author, content, emotion) => {
const create_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
create_date,
id: dataId.current
};
dataId.current += 1;
setData([newItem, ...data]);
}
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
<DiaryList diaryList={data} />
</div>
);
}
export default App;
DiaryEditor.js
import { useRef, useState } from "react";
const DiaryEditor = ({ onCreate }) => {
const authorInput = useRef();
const contentInput = useRef();
const [state, setState] = useState({
author: "",
content: "",
emotion: 3
});
const handlerChangeState = (e) => {
// console.log(e.target.name, e.target.value);
setState({ ...state, [e.target.name]: e.target.value })
}
const handleSubmit = (e) => {
if (state.author.length < 1) {
//alert("작성자는 최소 1글자 이상 입력해주세요.");
authorInput.current.focus();
return;
}
if (state.content.length < 5) {
//alert("일기 본문은 최소 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
name="author"
ref={authorInput}
value={state.author}
onChange={handlerChangeState}
/>
{/* onChange={(e) => {
// setState({...state, author: e.target.value })
// */}
</div>
<div>
<textarea
name="content"
ref={contentInput}
value={state.content}
onChange={handlerChangeState}
/>
</div>
<div>
<span>오늘의 감정점수 : </span>
<select name="emotion"
value={state.emotion}
onChange={handlerChangeState}>
<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>
<option value={6}>6</option>
</select>
</div>
<div>
<button onClick={handleSubmit}>일기 저장하기</button>
</div>
</div>);
};
export default DiaryEditor;
40.React에서 배열 사용하기 3 - 데이터 삭제하기
강의 :
App.js
const onDelete = (targetId) => {
console.log(`${targetId} 가 삭제되었습니다.`);
const newDiaryList = data.filter(item => item.id !== targetId);
setData(newDiaryList);
}
<DiaryList diaryList={data} onDelete={onDelete} />
DiaryList.js
const DiaryList = ({ diaryList, onDelete }) => {
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it) => (
<DiaryItem key={it.id} {...it} onDelete={onDelete} />
))
}
</div>
</div>
)
}
DiaryItem.js
const DiaryItem = ({ id, author, content, emotion, create_date, onDelete }) => {
return (
<div className="DiaryItem">
<div className="info">
<span>
작성자 : {author} | 감정점수 : {emotion}
</span>
<br />
<span className="date"> {new Date(create_date).toLocaleString()}</span>
</div>
<div className="content">{content}</div>
<button onClick={() => {
if (window.confirm(`${id} 번째 일기를 정말 삭제하시겠습니까?`)) {
onDelete(id);
}
}}>삭제하기</button>
</div >
);
};
export default DiaryItem;
41.React에서 배열 사용하기 4 - 데이터 수정하기
강의 :

App.js
~
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} diaryList={data} onRemove={onRemove} />
</div>
);
~
DiaryList.js
import DiaryItem from "./DiaryItem";
const DiaryList = ({ diaryList, onRemove, onEdit }) => {
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it) => (
<DiaryItem onEdit={onEdit} key={it.id} {...it} onRemove={onRemove} />
))
}
</div>
</div>
)
}
DiaryList.defaultProps = {
diaryList: [],
}
export default DiaryList;
DiaryItem.js
import { useState } from 'react';
import { useRef } from 'react';
const DiaryItem = ({ id, author, content, emotion, create_date, onRemove, onEdit }) => {
const [isEdit, setIsEdit] = useState(false);
const toggleIsEdit = () => setIsEdit(!isEdit);
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);
setIsEdit(false);
}
}
return (
<div className="DiaryItem">
<div className="info">
<span>
작성자 : {author} | 감정점수 : {emotion}
</span>
<br />
<span className="date"> {new Date(create_date).toLocaleString()}</span>
</div>
<div className="content">
{isEdit ? (
<>
<textarea
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 DiaryItem;
42.React Lifecycle 제어하기 - useEffect
강의 :
UnmountTest
import React, { useEffect } from 'react';
import { useState } from 'react';
const UnmountTest = () => {
useEffect(() => {
console.log("Mount!");
return () => {
//Unmount 시점에 실행되게 됨
console.log("Unmount!");
}
}, []);
return <div>Unmount Test Component</div>
}
const Lifiecycle = () => {
const [isVisible, setIsVisible] = useState(false);
const toggle = () => setIsVisible(!isVisible);
// const [count, setCount] = useState(0);
// const [text, setText] = useState("");
// useEffect(() => {
// console.log("mount!");
// });
// useEffect(() => {
// console.log(`count is update : ${count}`);
// if (count > 5) {
// alert("count 가 5를 넘었습니다.따라서 1로 초기화 합니다.");
// setCount(1);
// }
// }, [count]);
// useEffect(() => {
// console.log(`text is update : ${text}`);
// }, [text]);
return (
<div style={{ padding: 20 }}>
<button onClick={toggle}>ON/OFF</button>
{isVisible && <UnmountTest />}
</div>
);
};
export default Lifiecycle;
43.React에서 API 호출하기
강의 :

App.js
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
import './App.css';
import { useState, useEffect } from 'react';
import { useRef } from 'react';
// const dummyList = [
// {
// id: 1,
// author: "홍길동",
// content: "하이1 ",
// emotion: 2,
// create_date: new Date().getTime()
// },
// {
// id: 2,
// author: "이순신",
// content: "하이2 ",
// emotion: 3,
// create_date: new Date().getTime()
// },
// {
// id: 3,
// author: "테스터",
// content: "하이3 ",
// emotion: 4,
// create_date: new Date().getTime()
// }
// ]
//http://jsonplaceholder.typicode.com/posts/1/comments
function App() {
const [data, setData] = useState([]);
const dataId = useRef(0);
const getData = async () => {
const res = await fetch('http://jsonplaceholder.typicode.com/comments')
.then(data => data.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,
create_date: new Date().getTime(),
id: dataId.current++
}
})
setData(initData);
}
useEffect(() => {
getData();
}, [])
const onCreate = (author, content, emotion) => {
const create_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
create_date,
id: dataId.current
};
dataId.current += 1;
setData([newItem, ...data]);
}
const onRemove = (targetId) => {
console.log(`${targetId} 가 삭제되었습니다.`);
const newDiaryList = data.filter(item => item.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 onEdit={onEdit} diaryList={data} onRemove={onRemove} />
</div>
);
}
export default App;
44.React developer tools
소스 :
react developer tools 다운로드 링크입니다.

45.최적화 1 - useMemo
소스 :


데이터 변하지 않으면 기존 메모값을 그대로 사용한다.
여기서는 data.length 데이터 길이값을 기준으로 설정 했다.
const getDiaryAnalisys = 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 } = getDiaryAnalisys;
~
46.최적화 2 - React.memo
강의 :
강의 진행 중 사용된 소스코드 입니다.
https://codesandbox.io/s/chapt-11-9ppqm
OptimizeTest.js
import React, { useState, useEffect } from 'react';
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>
});
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;
객체일경우 깊은 복사
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 areEqual = (prevProps, nextProps) => {
//return true //이전 프롭스 현재 프롭스가 같다 -> 리랜더링을 일이키지 않게 된다.
//return flase // 이전 프롭스와 현재 프롭스가 다르면 -> 리랜더링을 일으킨다.
return prevProps.obj.count === nextProps.obj.count;
}
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;
47.최적화 3 - useCallback
강의 :
DiaryEditor.js
const DiaryEditor = ({ onCreate }) => {
~
export default React.memo(DiaryEditor);
App.js
~
const onCreate = useCallback((author, content, emotion) => {
console.log(" onCreate 합수 실행");
const create_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
create_date,
id: dataId.current
};
dataId.current += 1;
//setData([newItem, ...data]); =>
setData((data) => [newItem, ...data]);
}, []);
~
참조 :
[React] 리액트 Hooks : useCallback() 함수 사용법
48.최적화 4 - 최적화 완성
강의 :
DiaryItem.js
~ export default React.memo(DiaryItem);
App.js
~
const onRemove = useCallback((targetId) => {
// const newDiaryList = data.filter((it) => it.id !== 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
)
);
}, []);
~
49.복잡한 상태 관리 로직 분리하기 - useReducer
강의 :


App.js
import { useEffect, useMemo, useRef, useCallback, useReducer } from "react";
import "./App.css";
import DiaryEditor from "./DiaryEditor";
import DiaryList from "./DiaryList";
import OptimizeTest from "./OptimizeTest";
const reducer = (state, action) => {
switch (action.type) {
case "INIT":
return action.data;
case "CREATE": {
const create_date = new Date().getTime();
const newItem = { ...action.data, create_date }
return [newItem, ...state];
}
case "REMOVE":
return state.filter(item => item.id !== action.targetId)
case "EDIT":
return state.map(item => item.id === action.targetId ? { ...item, content: action.newContent } : item)
default:
return state;
}
}
const App = () => {
// const [data, setData] = useState([]);
const dataId = useRef(0);
const [data, dispatch] = useReducer(reducer, []);
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(() => {
setTimeout(() => {
getData();
}, 1500);
}, []);
const onCreate = (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(() => {
if (data.length === 0) {
return { goodcount: 0, badCount: 0, goodRatio: 0 };
}
console.log("일기 분석 시작");
const goodCount = data.filter((it) => it.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100.0;
return { goodCount, badCount, goodRatio };
}, [data.length]);
const { goodCount, badCount, goodRatio } = getDiaryAnalysis;
return (
<div className="App">
<OptimizeTest />
<DiaryEditor onCreate={onCreate} />
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
</div>
);
};
export default App;
50.컴포넌트 트리에 데이터 공급하기 - Context
강의 :


App.js
export const DiaryStateContext = React.createContext();
export const DiaryDispatchContext = React.createContext();
~
const memorizedDispatches = useMemo(() => {
return { onCreate, onRemove, onEdit }
}, [])
~
return (
<DiaryStateContext.Provider value={data} >
<DiaryDispatchContext.Provider value={memorizedDispatches}>
<div className="App">
<OptimizeTest />
<DiaryEditor onCreate={onCreate} />
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
<DiaryList />
</div>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
~
DiaryList.js
import React, { useContext } from 'react';
import DiaryItem from "./DiaryItem";
import { DiaryStateContext } from "./App";
const DiaryList = ({ onRemove, onEdit }) => {
const diaryList = useContext(DiaryStateContext);
~
DiaryEditor.js
import React, { useRef, useState, useEffect, useContext } from "react";
import { DiaryDispatchContext } from "./App";
const DiaryEditor = () => {
const { onCreate } = useContext(DiaryDispatchContext)
~
DiaryItem.js
import React, { useState, useRef, useContext } from 'react';
import { DiaryDispatchContext } from './App';
const DiaryItem = ({ id, author, content, emotion, create_date }) => {
const { onRemove, onEdit } = useContext(DiaryDispatchContext);
~
★ Redux store 사용법과 비교해 볼것
https://github.dev/braverokmc79/performance-exhibition/tree/main/client
1)index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './components/App';
import { Provider } from 'react-redux';
import { applyMiddleware, legacy_createStore as createStore } from 'redux';
import promiseMiddleware from 'redux-promise';
import ReduxThunk from 'redux-thunk';
import Reducer from './_reducers';
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore);
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={createStoreWithMiddleware(Reducer, devTools)}>
< App />
</Provider >
);
2) _actions/types.js
export const LOGIN_USER = 'login_user'; export const LOGIN_KAKAO_USER = 'login_kakao_user'; export const LOGIN_NAVER_USER = 'login_naver_user'; export const REGISTER_USER = 'register_user'; export const AUTH_USER = 'auth_user'; export const LOGOUT_USER = 'logout_user';
3) _actions/user_actions.js
import axios from 'axios';
import { LOGIN_USER, LOGIN_KAKAO_USER, LOGIN_NAVER_USER, REGISTER_USER, AUTH_USER, LOGOUT_USER } from './types';
import { USER_SERVER } from '../components/Config.js';
/** 유저 로그인 */
export function loginUser(dataTomSubmit) {
const request = axios.post(`${USER_SERVER}/login`, dataTomSubmit)
.then((res) => {
return res.data;
}).catch((Error) => {
console.error("에러 :", Error);
});
return {
type: LOGIN_USER,
payload: request
}
}
/** 카카오 유저 로그인 */
export function loginUserKakao(dataTomSubmit) {
const request = axios.post(`${USER_SERVER}/login/kakao`, dataTomSubmit)
.then((res) => {
return res.data;
}).catch((Error) => {
console.error("에러 :", Error);
});
return {
type: LOGIN_KAKAO_USER,
payload: request
}
}
/** 네이버 유저 로그인 */
export function loginUserNaver(dataTomSubmit) {
const request = axios.post(`${USER_SERVER}/login/naver`, dataTomSubmit)
.then((res) => {
return res.data;
}).catch((Error) => {
console.error("에러 :", Error);
});
return {
type: LOGIN_NAVER_USER,
payload: request
}
}
/** 유저 등록 */
export function registerUser(dataTomSubmit) {
const request = axios.post(`${USER_SERVER}/register`, dataTomSubmit)
.then(res => res.data);
return {
type: REGISTER_USER,
payload: request
}
}
/** 유저 권한확인 */
export function auth() {
const request = axios.post(`${USER_SERVER}/auth`)
.then(res => res.data);
return {
type: AUTH_USER,
payload: request
}
}
/** 로그아웃 */
export function logoutUser() {
const request = axios.get(`${USER_SERVER}/logout`)
.then(response => response.data);
return {
type: LOGOUT_USER,
payload: request
}
}
4) _reducers/index.js
import { combineReducers } from 'redux';
import user from './user_reducer';
const rootReducer = combineReducers({
user,
});
export default rootReducer;
5) _reducers/user_reducer.js
import { LOGIN_USER, LOGIN_KAKAO_USER, LOGIN_NAVER_USER, REGISTER_USER, AUTH_USER, LOGOUT_USER } from '../_actions/types';
export default function user_reducers(state = {}, action) {
switch (action.type) {
case LOGIN_USER:
return { ...state, loginSuccess: action.payload }
//카카오 로그인
case LOGIN_KAKAO_USER:
return { ...state, loginSuccess: action.payload }
//네이버 로그인
case LOGIN_NAVER_USER:
return { ...state, loginSuccess: action.payload }
case REGISTER_USER:
return { ...state, register: action.payload }
case AUTH_USER:
return { ...state, userData: action.payload }
case LOGOUT_USER:
return { ...state }
default:
return state;
}
}
6) KakaoLoginHandler.js
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
~
const KakaoLoginHandler = () => {
const dispatch = useDispatch();
~
~
//4. nodejs 서버로로 데이터를 넘겨준다
dispatch(loginUserKakao(dataToSubmit))
.then(response => {
if (response.payload.loginSuccess) {
window.localStorage.setItem('userId', response.payload.userId);
console.log("loginSuccess :", response.payload);
naigate("/");
} else {
console.log("로그인 실패 :");
}
})
.catch(err => {
console.log("에러 :", err);
});
~














댓글 ( 4)
댓글 남기기