{"id":16141196,"url":"https://github.com/jeonggoncho/react-practice-diary","last_synced_at":"2026-05-08T06:15:40.822Z","repository":{"id":239195110,"uuid":"739243759","full_name":"JeonggonCho/React-practice-Diary","owner":"JeonggonCho","description":"📕 React를 이용하여 제작한 'Diary 클론코딩'입니다.","archived":false,"fork":false,"pushed_at":"2024-01-26T14:33:40.000Z","size":12195,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-13T00:38:09.946Z","etag":null,"topics":["clone-coding","javascript","jsx","react"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JeonggonCho.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-05T05:21:59.000Z","updated_at":"2024-05-10T14:25:26.000Z","dependencies_parsed_at":"2024-05-10T16:48:17.592Z","dependency_job_id":null,"html_url":"https://github.com/JeonggonCho/React-practice-Diary","commit_stats":null,"previous_names":["jeonggoncho/react-practice-diary"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeonggonCho%2FReact-practice-Diary","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeonggonCho%2FReact-practice-Diary/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeonggonCho%2FReact-practice-Diary/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JeonggonCho%2FReact-practice-Diary/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JeonggonCho","download_url":"https://codeload.github.com/JeonggonCho/React-practice-Diary/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247535006,"owners_count":20954570,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clone-coding","javascript","jsx","react"],"created_at":"2024-10-09T23:54:43.912Z","updated_at":"2026-05-08T06:15:35.772Z","avatar_url":"https://github.com/JeonggonCho.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 일기장\n\n본 프로젝트는 [\"한입 크기로 잘라 먹는 리액트 강의\"](https://www.udemy.com/course/winterlood-react-basic/)의 \"일기장 만들어 보기\"를 클론한 프로젝트로 \"리액트\"의 기초 지식을 학습하는 것을 목표하였습니다.\n\n\u003cbr\u003e\n\n## 목차\n\n1.   [React에서 사용자 입력 처리 - useState](#1-react에서-사용자-입력-처리---usestate)\n2.   [React에서 DOM 조작하기 - useRef](#2-react에서-dom-조작하기---useref)\n3.   [React에서 리스트 사용하기1 - 리스트 렌더링(조회)](#3-react에서-리스트-사용하기1---리스트-렌더링조회)\n4.   [React에서 리스트 사용하기2 - 데이터 추가](#4-react에서-리스트-사용하기2---데이터-추가)\n5.   [React에서 리스트 사용하기3 - 데이터 삭제](#5-react에서-리스트-사용하기3---데이터-삭제)\n6.   [React에서 리스트 사용하기4 - 데이터 수정](#6-react에서-리스트-사용하기4---데이터-수정)\n7.   [React Lifecycle 제어하기 - useEffect](#7-react-lifecycle-제어하기---useeffect)\n8.   [React에서 API 호출하기](#8-react에서-api-호출하기)\n9.   [최적화1 연산 결과 재사용 - useMemo](#9-최적화1-연산-결과-재사용---usememo)\n10.  [최적화2 컴포넌트 재사용 - React.memo](#10-최적화2-컴포넌트-재사용---reactmemo)\n11.  [최적화3 컴포넌트 \u0026 함수 재사용 - useCallback](#11-최적화3-컴포넌트--함수-재사용---usecallback)\n12.  [최적화4 - React.memo + useCallback](#12-최적화4---reactmemo--usecallback)\n13.  [복잡한 상태 관리 로직 분리하기 - useReducer](#13-복잡한-상태-관리-로직-분리하기---usereducer)\n14.  [컴포넌트 트리에 데이터 공급하기 - Context](#14-컴포넌트-트리에-데이터-공급하기---context)\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 학습내용\n\n-   사용자 입력 및 배열 리스트 처리하기\n-   React Lifecycle과 API\n-   React App 프로처럼 성능 최적화하기 with 도구 사용\n-   React 컴포넌트 트리에 전역 데이터 공급하기\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 1. React에서 사용자 입력 처리 - useState\n\n### 1-1. 사전 준비\n\n-   `npx create-react-app emotional_diary` 입력으로 프로젝트 생성\n-   프로젝트 폴더의 컨텐츠들을 한 단계 상위 폴더로 이동시키고 기존 생성 폴더 삭제\n-   잘 사용되지 않는 파일들 정리 (logo.svg, App.test.js, reactWebVitals.js, setupTest.js)\n\n\u003cbr\u003e\n\n### 1-2. 목표\n\n![다양한 사용자 입력 처리하기](./README_img/다양한_사용자_입력_처리하기.png)\n\n-   `DiaryEditor`라는 컴포넌트 만들기\n    -   한 줄 입력 처리하기 (input)\n    -   여러 줄 입력 처리하기 (textarea)\n    -   선택 박스 입력 처리하기 (select)\n    -   사용자 입력 데이터 핸들링하기\n\n\u003cbr\u003e\n\n### 1-3. DiaryEditor 컴포넌트가 필요한 것\n\n![DiaryEditor 컴포넌트가 필요한 것](./README_img/DiaryEditor_컴포넌트가_필요한_것.png)\n\n-   작성자\n-   일기 본문\n-   감정 점수\n\n\u003cbr\u003e\n\n### 1-4. 작성자 및 일기 본문 입력받기\n\n### - 작성자 입력받기\n\n-   useState(상태)와 input 태그 활용\n\n```jsx\n// 작성자 입력받는 코드\n\nimport { useState } from \"react\";\n\nconst [author, setAuthor] = useState(\"\");\n\n\u003cdiv\u003e\n    \u003cinput\n        value={author}\n        onChange={(e) =\u003e {\n            setAuthor(e.target.value);\n        }}\n    /\u003e\n\u003c/div\u003e;\n```\n\n-   input 태그의 값을 useState의 author로 설정\n-   onChange 속성을 통해 값이 바뀔 때마다 이벤트가 발생\n-   이벤트 발생 시, 콜백함수를 수행하고 매개변수로 이벤트 객체 e를 보냄\n-   함수 내부에서 상태 변화 함수 setAuthor가 이벤트 타겟 값(e.target.value)를 받아 author로 보냄\n-   바뀐 author 값이 다시 input 태그의 value로 들어가서 화면에 렌더링됨\n\n\u003cbr\u003e\n\n### - 일기 본문 입력 받기\n\n-   앞선 작성자 입력받기와 동일\n-   input 태그 대신 textarea 태그 활용\n\n```jsx\n// 일기 본문 입력받는 코드\n\nimport { useState } from \"react\";\n\nconst [content, setContent] = useState(\"\");\n\n\u003cdiv\u003e\n    \u003ctextarea\n        value={content}\n        onChange={(e) =\u003e {\n            setContent(e.target.value);\n        }}\n    /\u003e\n\u003c/div\u003e;\n```\n\n-   작성자 입력과 일기 본문 입력 모두 문자열 상태 값을 가짐\n-   onChange 속성을 이용\n-   상태 변화 함수에 e.target.value를 전달\n\n\u003cbr\u003e\n\n### - 비슷한 동작의 State 묶기\n\n-   위의 작성자 입력, 일기 본문 입력과 같이 유사하게 동작하는 State는 따로 두지 않고 하나의 State로 묶어 줄 수 있음\n\n```jsx\n// State 묶기\n\nimport { useState } from \"react\";\n\nconst [state, setState] = useState({\n    author: \"\",\n    content: \"\",\n});\n\n\u003cdiv\u003e\n    \u003cdiv\u003e\n        \u003cinput\n            name=\"author\"\n            value={state.author}\n            onChange={(e) =\u003e {\n                setState({\n                    ...state,\n                    author: e.target.value,\n                    // content: state.content,\n                });\n            }}\n        /\u003e\n    \u003c/div\u003e\n    \u003cdiv\u003e\n        \u003ctextarea\n            value={state.content}\n            onChange={(e) =\u003e {\n                setState({\n                    ...state,\n                    // author: state.author,\n                    content: e.target.value,\n                });\n            }}\n        /\u003e\n    \u003c/div\u003e\n\u003c/div\u003e;\n```\n\n-   초기에 author의 값 공백(\"\"), content도 공백(\"\")임\n-   이후 값이 변화하면 변화하는 값은 e.target.value로 이벤트를 통해 변화한 값을 상태 변화 함수에 전달\n-   같이 묶여있지만 값이 변화하지 않은 값은 state.(원래 값)으로 함께 객체로 전달\n-   하지만 더 많은 객체가 하나의 State에 묶여있을 경우, 상태 변화 함수로 전달하는 객체가 길어질 수 있음\n-   따라서 Spread 연산자(...)를 활용하여 바뀌는 값 외에는 ...state로 처리할 수 있음\n    -   주의사항 : 기존의 객체(...state)를 앞에 서술해야 함\n    -   뒤에 서술할 경우, 업데이트 순서가 바뀌기에 잘못된 결과 발생\n\n\u003cbr\u003e\n\n### - onChange 속성 합치기\n\n```jsx\n// onChange 속성의 콜백함수 묶어내기\n\nconst handleChangeState = (e) =\u003e {\n    setState({\n        ...state,\n        [e.target.name]: e.target.value,\n    });\n};\n\n\u003cdiv\u003e\n    \u003cdiv\u003e\n        \u003cinput name=\"author\" value={state.author} onChange={handleChangeState} /\u003e\n    \u003c/div\u003e\n    \u003cdiv\u003e\n        \u003ctextarea name=\"content\" value={state.content} onChange={handleChangeState} /\u003e\n    \u003c/div\u003e\n\u003c/div\u003e;\n```\n\n-   '[e.target.name]: e.target.value'를 통해 이벤트가 발생하는 태그를 구분함\n\n\u003cbr\u003e\n\n### 1-5. 감정 점수 선택\n\n-   useState와 select 태그(+ option 태그)를 활용\n-   앞선 input과 textarea의 속성과 똑같이 적용됨\n\n```jsx\n// 선택 입력\n\nconst [state, setState] = useState({\n    author: \"\",\n    content: \"\",\n    emotion: 1,\n});\n\nconst handleChangeState = (e) =\u003e {\n    setState({\n        ...state,\n        [e.target.name]: e.target.value,\n    });\n};\n\n\u003cdiv\u003e\n    \u003cselect name=\"emotion\" value={state.emotion} onChange={handleChangeState}\u003e\n        \u003coption value={1}\u003e1\u003c/option\u003e\n        \u003coption value={2}\u003e2\u003c/option\u003e\n        \u003coption value={3}\u003e3\u003c/option\u003e\n        \u003coption value={4}\u003e4\u003c/option\u003e\n        \u003coption value={5}\u003e5\u003c/option\u003e\n    \u003c/select\u003e\n\u003c/div\u003e;\n```\n\n\u003cbr\u003e\n\n### 1-6. 저장 버튼\n\n```jsx\n// DiaryEditor.js\n\nconst handleSubmit = () =\u003e {\n    console.log(state);\n    alert(\"저장 성공\");\n};\n\n\u003cdiv\u003e\n    \u003cbutton onClick={handleSubmit}\u003e일기 저장하기\u003c/button\u003e\n\u003c/div\u003e;\n```\n\n-   버튼 클릭 시, 클릭했기 때문에 button의 onClick 속성의 handleSubmit 함수가 실행\n-   콘솔로 현재 작성된 state 객체를 출력\n-   alert 메서드로 저장 성공 알림 띄우기\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 2. React에서 DOM 조작하기 - useRef\n\n### 2-1. 목표\n\n-   리액트에서 DOM 조작하기\n    -   일기 저장 버튼 클릭 시, 작성자와 일기가 정상적으로 입력되었는지 확인\n    -   정상적인 입력 아니라면 focus하기\n\n\u003cbr\u003e\n\n### 2-2. 정상적인 입력이 아닐 경우, alert 띄우기\n\n-   handleSubmit 수정하기\n-   조건문으로 작성자와 본문내용의 길이에 따라 alert 띄우기\n-   alert 실행 후, 이후 코드를 실행하지 않도록 return 추가\n\n```jsx\n// DiaryEditor.js\n\nconst handleSubmit = () =\u003e {\n    if (state.author.length \u003c 1) {\n        alert(\"작성자는 최소 1글자 이상 입력해주세요\");\n        return;\n    }\n\n    if (state.content.length \u003c 5) {\n        alert(\"일기 본문은 최소 5글자 이상 입력해주세요\");\n        return;\n    }\n\n    alert(\"저장 성공\");\n};\n```\n\n-   하지만, 입력이 정상이 아니더라도 `alert`를 띄우는 것은 `UX 경험적으로 좋지 않음`\n\n\u003cbr\u003e\n\n### 2-3. 정상적인 입력이 아닐 경우, focus 주기\n\n-   `useRef` 사용\n\n```jsx\n// useRef import 해오기\nimport {useRef, useState} from \"react\";\n\n// useRef 사용하여 DOM 요소 접근 가능한 기능 부여\nconst authorInput = useRef();\nconst contentInput = useRef();\n\n\n// 조건문에 따라 불만족 시, 해당 현재요소에 focus하기\nconst handleSubmit = () =\u003e {\n  if (state.author.length \u003c 1) {\n    authorInput.current.focus();\n    return;\n  }\n\n  if (state.content.length \u003c 5) {\n    contentInput.current.focus();\n    return;\n  }\n\n  alert(\"저장 성공\");\n};\n\n\n\u003cdiv\u003e\n  \u003cinput\n    // ref 속성으로 DOM 연결\n    ref={authorInput}\n    name=\"author\"\n    value={state.author}\n    onChange={handleChangeState}\n  /\u003e\n\u003c/div\u003e\n\u003cdiv\u003e\n  \u003ctextarea\n    // ref 속성으로 DOM 연결\n    ref={contentInput}\n    name=\"content\"\n    value={state.content}\n    onChange={handleChangeState}\n  /\u003e\n\u003c/div\u003e\n```\n\n-   useRef()를 지정하면 해당 변수는 `mutableRefObject`가 됨\n-   mutableRefObject : HTML DOM 요소에 접근할 수 있는 기능\n\n\u003cbr\u003e\n\n![리액트 DOM 조작](README_img/리액트_DOM_조작하기.gif)\n\n\u003c리액트 DOM 조작 결과\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 3. React에서 리스트 사용하기1 - 리스트 렌더링(조회)\n\n### 3-1. 목표\n\n-   `DiaryList` 컴포넌트 만들기\n    -   `배열`을 이용하여 list 렌더링 해보기\n    -   개별적인 컴포넌트 만들어보기\n\n\u003cbr\u003e\n\n### 3-2. 더미데이터 만들고 리스트 컴포넌트에 props로 보내기\n\n```jsx\n// App.js\n\nconst dummyList = [\n    {\n        id: 1,\n        author: \"조정곤\",\n        content: \"하이~1\",\n        emotion: 5,\n        created_date: new Date().getTime(),\n    },\n    {\n        id: 2,\n        author: \"김철수\",\n        content: \"하이~2\",\n        emotion: 2,\n        created_date: new Date().getTime(),\n    },\n    {\n        id: 3,\n        author: \"이영수\",\n        content: \"하이~3\",\n        emotion: 3,\n        created_date: new Date().getTime(),\n    },\n];\n\n\u003cdiv className=\"App\"\u003e\n    \u003cDiaryEditor /\u003e\n    \u003cDiaryList diaryList={dummyList} /\u003e\n\u003c/div\u003e;\n```\n\n-   `dummyList 배열`에 데이터(id, 작성자, 내용, 감정점수, 생성시간)를 `객체타입`으로 담아 만들기\n-   `DiaryList` 컴포넌트에 diaryList 속성으로 dummyList 배열 props로 보내기\n\n\u003cbr\u003e\n\n```jsx\n// DiaryList.js\n\nconst DiaryList = ({ diaryList }) =\u003e {\n    return (\n        \u003cdiv className=\"DiaryList\"\u003e\n            \u003ch2\u003e일기 리스트\u003c/h2\u003e\n            \u003ch4\u003e{diaryList.length}개의 일기가 있습니다.\u003c/h4\u003e\n            \u003cdiv\u003e\n                {diaryList.map((it, idx) =\u003e (\n                    \u003cdiv key={it.id}\u003e\n                        \u003cdiv\u003e작성자 : {it.author}\u003c/div\u003e\n                        \u003cdiv\u003e일기 : {it.content}\u003c/div\u003e\n                        \u003cdiv\u003e감정 : {it.emotion}\u003c/div\u003e\n                        \u003cdiv\u003e작성 시간(ms) : {it.created_date}\u003c/div\u003e\n                    \u003c/div\u003e\n                ))}\n            \u003c/div\u003e\n        \u003c/div\u003e\n    );\n};\n\nDiaryList.defaultProps = {\n    diaryList: [],\n};\n\nexport default DiaryList;\n```\n\n-   DiaryList 컴포넌트에서 diaryList를 props로 받기\n-   `map 메서드`를 사용하여 배열 생성\n-   map 메서드의 콜백함수로 배열 순회하여 객체 데이터 각각의 \u003cdiv\u003e 태그를 생성하고 최상위 \u003cdiv\u003e 태그에 담기\n-   이 경우, 생성된 배열 간에 키가 지정되어있지 않아 콘솔 에러가 발생\n-   따라서 최상위 \u003cdiv\u003e 요소에 `key 속성`에 각각의 배열을 구분할 수 있는 `id`를 넣어 에러 해결\n    -   콜백함수의 `두 번째 인자인 인덱스(idx)`를 받아 id 대신 key 속성에 넣어 사용할 수 있음\n    -   하지만 추후 순서가 변경될 경우, 수정 및 삭제의 동작 시 문제가 발생할 수 있어 id가 객체에 있다면 id를 사용하는 것을 지향함\n-   map 메서드 안의 요소는 계속 반복되는 요소로 독립적인 컴포넌트로 관리할 수 있음\n\n\u003cbr\u003e\n\n### 3-3. 리스트의 아이템을 관리할 컴포넌트 생성\n\n```jsx\n// DiaryList.js\n\nimport DiaryItem from \"./DiaryItem\";\n\nconst DiaryList = ({ diaryList }) =\u003e {\n    return (\n        \u003cdiv className=\"DiaryList\"\u003e\n            \u003ch2\u003e일기 리스트\u003c/h2\u003e\n            \u003ch4\u003e{diaryList.length}개의 일기가 있습니다.\u003c/h4\u003e\n            \u003cdiv\u003e\n                {diaryList.map((it, idx) =\u003e (\n                    \u003cDiaryItem key={it.id} {...it} /\u003e\n                ))}\n            \u003c/div\u003e\n        \u003c/div\u003e\n    );\n};\n```\n\n-   DiaryItem 컴포넌트를 받아 콜백함수에서 사용\n-   동일하게 `key 속성`으로 `id 값`을 사용하고, 나머지 데이터를 `Spread 연산자(...)`를 사용하여 `props`로 보내기\n\n\u003cbr\u003e\n\n```jsx\n// DiaryItem.js\n\nconst DiaryItem = ({ author, content, created_date, emotion, id }) =\u003e {\n    return (\n        \u003cdiv className=\"DiaryItem\"\u003e\n            \u003cdiv className=\"info\"\u003e\n                \u003cspan\u003e\n                    작성자 : {author} | 감정점수 : {emotion}\n                \u003c/span\u003e\n                \u003cbr /\u003e\n                \u003cspan className=\"date\"\u003e{new Date(created_date).toLocaleString()}\u003c/span\u003e\n            \u003c/div\u003e\n            \u003cdiv className=\"content\"\u003e{content}\u003c/div\u003e\n        \u003c/div\u003e\n    );\n};\n\nexport default DiaryItem;\n```\n\n-   `DiaryItem` 컴포넌트 생성\n-   props로 받은 데이터를 요소에 담아 출력\n\n\u003cbr\u003e\n\n![리스트 렌더링](README_img/리스트_데이터_렌더링.png)\n\n\u003c리스트 데이터 렌더링 예시 결과\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 4. React에서 리스트 사용하기2 - 데이터 추가\n\n### 4-1. 학습목표\n\n- 배열을 이용한 React의 List에 아이템을 동적으로 추가해보기\n\n\u003cbr\u003e\n\n### 4-2. 컴포넌트 \u0026 데이터 구조 생각해보기\n\n![state 끌어올리기1](README_img/State_lifting_01.png)\n\n- DiaryEditor 컴포넌트에서 작성된 일기 내용을 DiaryList 컴포넌트에서 렌더링을 하길 원함\n- 하지만, 리액트에서는 `동등한 레벨`에선 `데이터 전달이 불가능`\n\n\u003cbr\u003e\n\n![state 끌어올리기2](README_img/State_lifting_02.png)\n\n- 리액트는 `단방향`으로 `데이터`가 흐르는 특성을 지님\n\n\u003cbr\u003e\n\n![state 끌어올리기3](README_img/State_lifting_03.png)\n\n- 이를 해결하기 위해 부모인 `App 컴포넌트`에 `[data, setData]의 State`를 만듦\n- `DiaryEditor` 컴포넌트에는 상태 함수인 `setData`를, `DiaryList` 컴포넌트에는 업데이트되는 `data`를 `props`로 각각 전달\n\n\u003cbr\u003e\n\n![state 끌어올리기4](README_img/State_lifting_04.png)\n\n- 이렇게 되면 일기가 작성될 때, DiaryEditor는 상태 함수인 setData를 호출\n- 새롭게 작성된 일기 데이터는 state의 data로 전달됨\n- 이 업데이트 된 data를 DiaryList에서 받아 리렌더링하게 됨\n\n\u003cbr\u003e\n\n![state 끌어올리기5](README_img/State_lifting_05.png)\n\n- 즉, `데이터`는 부모에서 자식, 즉, `위에서 아래`로 전달\n- 자식에서는 부모로 상태 함수를 호출하기에 `이벤트`는 `아래에서 위`로 전달\n\n\u003cbr\u003e\n\n### 4-3. State 만들기\n\n### - App 컴포넌트 state 생성\n\n```jsx\n// App.js\n\nconst [data, setData] = useState([]);\n\nconst dataId = useRef(0);\n\nconst onCreate = (author, content, emotion) =\u003e {\n    const created_date = new Date().getTime();\n    const newItem = {\n        author,\n        content,\n        emotion,\n        created_date,\n        id: dataId.current,\n    };\n    dataId.current += 1;\n    setData([newItem, ...data]);\n};\n```\n\n- 이벤트 onCreate 생성\n- useRef()에 초기값으로 0 지정 =\u003e \u003c변수명\u003e.current로 값을 확인 할 수 있음\n- 이 후 \u003c변수명\u003e.current를 1씩 증가시켜 id를 1씩 증가시킴\n- 새로 작성된 일기내용을 기존 내용들의 앞에 위치시킴\n\n\u003cbr\u003e\n\n### - props로 이벤트와 데이터 전달\n\n```jsx\n// App.js\n\n\u003cdiv className=\"App\"\u003e\n    \u003cDiaryEditor onCreate={onCreate} /\u003e\n    \u003cDiaryList diaryList={data} /\u003e\n\u003c/div\u003e\n```\n\n- DiaryEditor로 onCreate 함수 전달\n- DiaryList로 data 전달\n\n\u003cbr\u003e\n\n### - 이벤트 받기\n\n```jsx\n// DiaryEditor.js\n\nconst DiaryEditor = ({ onCreate }) =\u003e {...}\n```\n\n- props로 전달된 onCreate 받기\n\n\u003cbr\u003e\n\n### - 일기를 제출하면 이벤트 호출\n\n```jsx\n// DiaryEditor.js\n\nconst handleSubmit = () =\u003e {\n    if (state.author.length \u003c 1) {\n        authorInput.current.focus();\n        return;\n    }\n\n    if (state.content.length \u003c 5) {\n        contentInput.current.focus();\n        return;\n    }\n    onCreate(state.author, state.content, state.emotion);\n    alert(\"저장 성공\");\n    setState({\n        author: \"\",\n        content: \"\",\n        emotion: 1,\n    });\n};\n```\n\n- 제출(handleSubmit) 시, onCreate()함수를 호출함\n- 인자로 state의 author, content, emotion을 전달\n- 저장 후, 입력 칸 비우고, 감정 1로 초기화\n\n\u003cbr\u003e\n\n![state끌어올리기 결과](README_img/state끌어올리기_데이터_렌더링.gif)\n\n\u003cstate를 활용한 리스트 데이터 렌더링 예시 결과\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 5. React에서 리스트 사용하기3 - 데이터 삭제\n\n### 5-1. 학습목표\n\n- 삭제버튼을 각 일기 리스트 목록마다 생성\n- 삭제버튼을 클릭할 경우, 확인 메시지가 전달되고 확인을 누르면 해당 일기 아이템이 삭제되고 리렌더링 됨\n\n\u003cbr\u003e\n\n### 5-2. onDelete 함수 생성\n\n```jsx\n// App.js\n\n// onDelete 함수\nconst onDelete = (targetId) =\u003e {\n    console.log(`${targetId}가 삭제되었습니다`);\n    const newDiaryList = data.filter((it) =\u003e it.id !== targetId);\n    setData(newDiaryList);\n};\n\nreturn (\n    \u003cdiv className=\"App\"\u003e\n        \u003cDiaryEditor onCreate={onCreate} /\u003e\n        // DiaryList로 props로 onDelete 전달\n        \u003cDiaryList onDelete={onDelete} diaryList={data} /\u003e\n    \u003c/div\u003e\n);\n```\n\n- onDelete 함수는 삭제버튼과 함께 해당 일기 아이템의 id값을 targetId인자로 받음\n- 필터를 사용하여 data에서 id가 인자로 받은 targetId와 같지 않은 나머지 아이템들을 모음\n- 새롭게 생성된 일기 리스트들을 setData() 상태 함수에 넣어 상태 바꾸기\n\n\u003cbr\u003e\n\n### 5-3. DiaryList로 전달된 onDelete 함수를 DiaryItem 컴포넌트로 전달\n\n```jsx\n// DiaryList.js\n\nconst DiaryList = ({ onDelete, diaryList }) =\u003e {\n    return (\n        \u003cdiv className=\"DiaryList\"\u003e\n            \u003ch2\u003e일기 리스트\u003c/h2\u003e\n            \u003ch4\u003e{diaryList.length}개의 일기가 있습니다.\u003c/h4\u003e\n            \u003cdiv\u003e\n                {diaryList.map((it, idx) =\u003e (\n                    // DiaryItem으로 onDelete 전달\n                    \u003cDiaryItem key={it.id} {...it} onDelete={onDelete} /\u003e\n                ))}\n            \u003c/div\u003e\n        \u003c/div\u003e\n    );\n};\n```\n\n- onDelete 함수를 DiaryItem 컴포넌트로 props로 전달\n\n\u003cbr\u003e\n\n### 5-4. 삭제 시, onDelete 함수 호출하고 해당 아이템의 id값 전달\n\n```jsx\n// DiaryItem.js\n\n// onDelete 함수 전달 받음\nconst DiaryItem = ({\n                       onDelete,\n                       author,\n                       content,\n                       created_date,\n                       emotion,\n                       id,\n                   }) =\u003e {\n    return (\n        \u003cdiv className=\"DiaryItem\"\u003e\n            // ...\n            \u003cbutton\n                onClick={() =\u003e {\n                    console.log(id);\n                    if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {\n                        onDelete(id);\n                    }\n                }}\n            \u003e\n                삭제하기\n            \u003c/button\u003e\n        \u003c/div\u003e\n    );\n};\n```\n\n- onClick의 콜백함수를 지정\n- `window.confirm()` : 확인 메시지 띄우기\n  - 확인 클릭(true)일 경우, `onDelete` 함수 호출하여 파라미터로 해당 일기 아이템의 id 전달\n  - 취소 클릭(false)일 경우, 아무 일도 발생하지 않음\n\n\u003cbr\u003e\n\n![리스트 삭제 결과](README_img/리스트_데이터_삭제하기.gif)\n\n\u003c리스트 데이터 삭제 예시 결과\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 6. React에서 리스트 사용하기4 - 데이터 수정\n\n### 6-1. 수정하기 기능에서 필요한 것\n\n- 수정하기 버튼\n- 수정하기 버튼을 누르면 기존 내용이 수정폼으로 변환\n- 특정 글자 수를 충족 시, 수정 완료되고 그렇지 않을 경우, 수정폼에 포커스 되도록 하기\n\n\u003cbr\u003e\n\n### 6-2. isEdit의 상태에 따른 버튼과 일기내용 변환\n\n### - 수정하기 버튼 만들기\n\n```jsx\n// DiaryItem.js\n\n\u003cbutton onClick={handleRemove}\u003e삭제하기\u003c/button\u003e\n\u003cbutton onClick={toggleIsEdit}\u003e수정하기\u003c/button\u003e\n```\n\n- 기존 삭제하기 버튼과 함께 수정하기 버튼 만들기\n- 수정하기 버튼 클릭 시, toggleIsEdit 함수 실행\n\n\u003cbr\u003e\n\n### - state와 toggleIsEdit\n\n```jsx\n// DiaryItem.js\n\nconst [isEdit, setIsEdit] = useState(false);\nconst toggleIsEdit = () =\u003e setIsEdit(!isEdit);\n```\n\n- `isEdit`이란 변수를 설정하고 state로 기본 값을 `false`로 지정\n- toggleIsEdit은 상태 함수 setIsEdit의 인자로 `!isEdit`을 두어 isEdit이 false면 true로, true면 false로 `반전`되도록 함(스위치)\n- true는 수정상태, false는 비 수정상태\n- 수정하기 버튼의 onClick에 toggleIsEdit 지정\n\n\u003cbr\u003e\n\n### - 삼항 연산자를 이용한 일기내용, 수정폼, 버튼 변환\n\n```jsx\n// DiaryItem.js\n\nconst [localContent, setLocalContent] = useState(content);\n\n...\n\n\u003cdiv className=\"content\"\u003e\n    {isEdit ? (\n        \u003ctextarea\n            value={localContent}\n            onChange={(e) =\u003e {\n                setLocalContent(e.target.value);\n            }}\n        /\u003e\n    ) : (\n        \u003c\u003e{content}\u003c/\u003e\n    )}\n\u003c/div\u003e\n\n{isEdit ? (\n    \u003c\u003e\n        \u003cbutton onClick={handleQuitEdit}\u003e수정 취소\u003c/button\u003e\n        \u003cbutton onClick={handleEdit}\u003e수정 완료\u003c/button\u003e\n    \u003c/\u003e\n) : (\n    \u003c\u003e\n        \u003cbutton onClick={handleRemove}\u003e삭제하기\u003c/button\u003e\n        \u003cbutton onClick={toggleIsEdit}\u003e수정하기\u003c/button\u003e\n    \u003c/\u003e\n)}\n```\n\n- 삼항 연산자를 사용하여 isEdit이 true, 즉 수정상태이면, textarea 요소가 생김\n- 수정폼 textarea의 내용은 변수 localContent로 지정하고 state를 사용하여 `초기 값`을 기존에 작성되어있던 `일기 내용인 content`로 설정\n- 버튼의 경우에도 삼항 연산자를 사용하여 isEdit이 true, 즉 수정상태이면 `수정 취소`, `수정 완료` 버튼을 보여주고, false로 수정상태가 아니면 기존의 `삭제하기` 및 `수정하기` 버튼이 보이도록 함\n\n\u003cbr\u003e\n\n### 6-3. 버튼의 onClick 설정 및 데이터 이벤트 함수 자식으로 전달\n\n### - onClick 설정\n\n```jsx\n// DiaryItem.js\n\n// textarea의 DOM 접근 할 수 있도록 localContentInput 생성\nconst localContentInput = useRef();\n\n// 수정 완료 버튼 클릭 시, 실행되는 hadleEdit 함수\nconst handleEdit = () =\u003e {\n    if (localContent.length \u003c 5) {\n        localContentInput.current.focus();\n        return;\n    }\n    if (window.confirm(`${id}번째 일기를 수정하시겠습니까?`)) {\n        onEdit(id, localContent);\n        toggleIsEdit();\n    }\n};\n\n\n// 수정 취소 버튼 클릭 시, 실행되는 handleQuitEdit 함수\nconst handleQuitEdit = () =\u003e {\n    setIsEdit(false);\n    setLocalContent(content);\n};\n\n...\n\n// 내용 수정 폼 textarea\n\u003ctextarea ref={localContentInput}... /\u003e\n\n// 수정 취소, 수정 완료 버튼\n\u003c\u003e\n    \u003cbutton onClick={handleQuitEdit}\u003e수정 취소\u003c/button\u003e\n    \u003cbutton onClick={handleEdit}\u003e수정 완료\u003c/button\u003e\n\u003c/\u003e\n```\n\n- 먼저 수정 취소 버튼의 `handleQuitEdit`을 보면, `setIsEdit(false);`을 통해 수정하지 않음으로 변경\n- 만약, 수정 중이였다면 수정 취소를 누르고 다시 수정하기 버튼을 클릭할 경우, 앞선 수정 내역이 그대로 남아있기에 취소 시, 이를 `초기화`하기 위해 `setLocalContent(content);`으로 기존 일기내용 넣기\n- 수정 완료를 클릭하면 `handleEdit` 함수가 실행 됨\n- textarea에 `useRef()`인 `localContentInput` 지정하여 DOM 접근 할 수 있도록 하기\n- handleEdit 함수는 기존 일기내용의 규칙처럼 5글자 미만으로 작성된 경우, `focus()` 주기\n- 통과된 경우, 확인 창을 띄우고 `onEdit()` 함수에 id와 localContent를 보내 데이터 수정\n- toggleIsEdit() 함수로 다시 수정 활성화 상태 닫기\n\n\u003cbr\u003e\n\n### - App 컴포넌트에서 onEdit 전달하기\n\n```jsx\n// App.js\n\nconst onEdit = (targetId, newContent) =\u003e {\n    setData(\n        data.map((it) =\u003e\n            it.id === targetId ? { ...it, content: newContent } : it,\n        ),\n    );\n};\n\n...\n\n\u003cDiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} /\u003e\n```\n\n```jsx\n// DiaryList.js\n\nconst DiaryList = ({ onEdit, onRemove, diaryList }) =\u003e {\n    ...\n    \u003cDiaryItem key={it.id} {...it} onRemove={onRemove} onEdit={onEdit} /\u003e\n};\n```\n\n- onEdit 함수는 데이터의 `id`와 `수정된 내용`을 전달받음\n- data들을 `map`으로 순회하여 인자로 전달받은 id와 같은 데이터를 찾아 content만 수정된 newContent로 `덮어씌움`\n- App 컴포넌트에서 생성된 onEdit 함수를 DiaryList 컴포넌트로 전달하고 다시 DiaryItem 컴포넌트로 전달함\n\n\u003cbr\u003e\n\n![리스트 수정 결과](README_img/리스트_데이터_수정하기.gif)\n\n\u003c리스트 데이터 수정 예시 결과\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 7. React Lifecycle 제어하기 - useEffect\n\n### 7-1. Lifecycle\n\n- 생애주기로 일반적으로 `시간의 흐름`에 따라 탄생부터 죽음까지 이르는 `단계적` 과정\n- React의 컴포넌트 역시 생명주기(Lifecycle)을 가짐\n- \n\u003cbr\u003e\n\n![리액트 라이프사이클](README_img/lifecycle.png)\n\n- React의 Lifecycle은 크게 3가지의 단계로 나뉨\n    - `탄생`(Mount) : 화면에 나타나는 것\n    - `변화`(Update) : 업데이트(리렌더)\n    - `죽음`(Unmount) : 화면에서 사라짐\n- 각각의 단계마다 `특정한 작업`을 수행하도록 할 수 있음\n\n\u003cbr\u003e\n\n### - Lifecycle 각각의 단계에서 사용하는 메서드\n\n- 지금까지는 화살표 함수를 이용한 `함수형 컴포넌트`만 이용해왔음\n- 하지만, 이 메서드들은 `클래스형 컴포넌트`에서만 사용 가능 하다.\n- `ref`, `state`의 경우도 함수형 컴포넌트에서는 사용이 불가능하고 `클래스형 컴포넌트`에서만 사용이 가능하다.\n\n\u003cbr\u003e\n\n1. Mount 단계 : ComponentDidMount()\n2. Update 단계 : ComponentDidUpdate()\n3. Unmount 단계 : ComponentWillUnmount()\n\n\u003cbr\u003e\n\n### 7-2. React Hooks\n\n- 2019년 6월 정식 출시된 기능\n- 앞선 Lifecycle에서 사용하는 메서드, state, ref를 함수형 컴포넌트를 사용하는 React에서 `사용하기 어려움`이 있었음\n- 따라서 이러한 문제점을 해결하고자, `use` 키워드를 앞에 붙여 `클래스형 컴포넌트`가 사용하는 이 기능들을 `함수형 컴포넌트`에서 사용할 수 있도록 Hooking(낚음)한 것\n- ex) useState, useEffect, useRef, ...\n\n\u003cbr\u003e\n\n### - 애초에 React에서 클래스형 컴포넌트를 사용하지 않은 이유는?\n\n- 클래스형 컴포넌트의 코드가 매우 길어질 수 있고 복잡해질 수 있음\n- 중복 코드, 가독성 문제 등 여러 문제점을 해결하기 위해서 함수형 컴포넌트를 사용\n\n\u003cbr\u003e\n\n### 7-3. useEffect\n\n- 앞선 각 `Lifecycle의 단계에서 사용하는 메서드`들을 함수형 컴포넌트에서 사용할 수 있게 해줌\n- 2개의 파라미터인 `콜백함수`, `의존성 배열`을 받음\n\n```jsx\n// useEffect 사용\n\nimport React, { useEffect } from \"react\";\n\nuseEffect(() =\u003e {\n    // 콜백함수 작성\n}, []); // '[]'는 Dependency Array(의존성 배열) : 이 배열 내에 들어있는 값이 변화하면 콜백함수가 수행됨\n```\n\n\u003cbr\u003e\n\n### - Mount 단계에서 작업수행\n\n```jsx\n// 새로 만든 Lifecycle.js\n\n// Mount 단계에서 작업수행\n\nimport React, { useEffect, useState } from \"react\";\n\nconst Lifecycle = () =\u003e {\n// Mount 단계에 실행됨 -\u003e dependency array에 빈배열\n    useEffect(() =\u003e {\n        console.log(\"Mount!\");\n    }, []);\n}\n```\n\n- dependency 배열에 아무 값도 넣지 않으면(`빈배열`) Mount 단계에서 콜백함수가 수행됨\n\n\u003cbr\u003e\n\n### - Update 단계에서 작업수행\n\n```jsx\n// Lifecycle.js\n\nimport React, { useEffect, useState } from \"react\";\n\nconst Lifecycle = () =\u003e {\n    const [count, setCount] = useState(0);\n    const [text, setText] = useState(\"\");\n\n    // Update 단계에 실행됨 -\u003e dependency array 없애기: 어느 요소라도 업데이트 되면 콜백함수 수행\n    useEffect(() =\u003e {\n      console.log(\"Update!\");\n    });\n    \n    // 특정 요소가 업데이트 되면 콜백함수 수행\n    // count의 state가 변하는 순간 콜백함수 수행\n    useEffect(() =\u003e {\n      console.log(`count is update : ${count}`);\n      if (count \u003e 5) {\n        alert(\"count가 5를 넘었습니다. 따라서 1로 초기화 합니다.\");\n        setCount(1);\n      }\n    }, [count]);\n    \n    // text의 state가 변하는 순간 콜백함수 수행\n    useEffect(() =\u003e {\n      console.log(`text is update : ${text}`);\n    }, [text]);\n}\n```\n\n- `dependency 배열을 자체를 없애면` 어느 요소라도 업데이트 되면 콜백함수 수행\n- `dependency 배열에 값을 넣으면`, 해당 변수 값이 업데이트 되면 콜백함수 수행\n\n\u003cbr\u003e\n\n![update](README_img/useEffect_update.gif)\n\n\u003cUpdate시 콘솔출력 예시\u003e\n\n\u003cbr\u003e\n\n### - Unmount 단계에서 작업수행\n\n```jsx\n// Lifecycle.js\n\nimport React, { useEffect, useState } from \"react\";\n\nconst UnmountTest = () =\u003e {\n    // Unmount 단계에서 수행되는 작업을 만들기 위해서는 콜백함수 안에서\n    // 함수를 리턴하게 하면, Unmount되는 경우, 리턴된 함수가 수행됨\n    useEffect(() =\u003e {\n        console.log(\"Mount!\");\n        return () =\u003e {\n            // Unmount 경우, 수행\n            console.log(\"Unmount!\");\n        };\n    }, []);\n    return \u003cdiv\u003eUnmount Testing Component\u003c/div\u003e;\n};\n```\n\n- Unmount 단계에서 수행되는 작업을 만들기 위해서는 useEffect의 콜백함수에 `리턴 함수를 정의`하면 해당 함수가 Unmount 단계에서 수행됨\n\n\u003cbr\u003e\n\n![mount, Unmount](README_img/useEffect_mount_unmount.gif)\n\n\u003cMount와 Unmount시 콘솔출력 예시\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 8. React에서 API 호출하기\n\n### 8-1. 학습목표\n\n- useEffect를 사용하여 Mount 시점에 API를 호출하고 해당 API 결과 값을 일기 데이터의 초기 값으로 이용하기\n\n\u003cbr\u003e\n\n### 8-2. fetch로 API 받아오기\n\n```javascript\n// App.js\n\nconst getData = async () =\u003e {\n    const res = await fetch(\n        \"https://jsonplaceholder.typicode.com/comments\",\n    ).then((res) =\u003e res.json());\n    const initData = res.slice(0, 20).map((it) =\u003e {\n        return {\n            author: it.email,\n            content: it.body,\n            emotion: Math.floor(Math.random() * 5) + 1,\n            created_date: new Date().getTime(),\n            id: dataId.current++,\n        };\n    });\n\n    setData(initData);\n};\n```\n\n- jsonplaceholder(더미 json을 응답하는 API)를 통해 fetch로 API 받기\n- .then()을 사용하여 resolve되면, 해당 응답 데이터를 가져옴\n- 하지만 해당 데이터는 헤더만 있기에 `.json()`로 사용가능한 `Promise 상태`로 변환\n- 이중 20개를 slice하고, map을 통해 각각의 객체를 담은 배열을 리턴한다.\n- 이 배열을 `setData`에 담아 data를 업데이트한다.\n\n\u003cbr\u003e\n\n### 8-3. useEffect로 Mount시 데이터 호출\n\n```javascript\n// App.js\n\nuseEffect(() =\u003e {\n    getData();\n}, []);\n```\n\n- useEffect를 사용하고 dependency 배열을 `빈 배열`로 설정하여 `Mount 단계`에서 해당 블록을 수행\n- 앞선 API를 호출하는 getData() 함수를 호출시킨다.\n\n\u003cbr\u003e\n\n![API 호출](README_img/API사용.gif)\n\n\u003c화면 렌더링 시, API를 통해 가져온 데이터를 사용\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 9. 최적화1 연산 결과 재사용 - useMemo\n\n### 9-1. 학습목표\n\n- 현재 일기 데이터를 분석하는 함수제작\n- 일기 데이터의 길이가 변화하지 않을 때, 함수가 값을 다시 계산하지 않도록 하기\n- Memoization 개념 이해하기\n\n\u003cbr\u003e\n\n### 9-2. Memoization\n\n- 프로그래밍 기법 중 하나\n- 이미 계산해본 `연산 결과를 기억`해두고 동일한 계산을 수행할 경우, 연산을 `재수행하지 않고`, `기억해둔 데이터`를 바로 반환\n\n\u003cbr\u003e\n\n### 9-3. App 컴포넌트에서 기분 좋은 일기, 기분 나쁜 일기 계산\n\n- 필요한 데이터 3가지\n  - 기분 나쁜 일기(1~2점) 개수\n  - 기분 좋은 일기(3~5점) 개수\n  - 기분 좋은 일기의 비율\n\n\u003cbr\u003e\n\n### - 필요한 데이터 분석 함수 생성\n\n```javascript\n// App.js\n\nconst getDiaryAnalysis = () =\u003e {\n    console.log(\"일기 분석 시작\");\n\n    const goodCount = data.filter((it) =\u003e it.emotion \u003e= 3).length;\n    const badCount = data.length - goodCount;\n    const goodRatio = (goodCount / data.length) * 100;\n    return { goodCount, badCount, goodRatio };\n};\n\nconst { goodCount, badCount, goodRatio } = getDiaryAnalysis();\n```\n\n- getDiaryAnalysis 함수 생성하고, 함수가 호출될 때마다 콘솔에 \"일기 분석 시작\"을 출력한다.\n- goodCount는 data 배열에서 객체의 emotion 값이 3이상인 것만 필터로 모아 length로 개수를 지정\n- badCount는 전체 일기 개수 data.length에서 goodCount를 뺀 나머지 개수\n- goodRatio는 전체 일기 개수에서 좋은 일기 개수를 나누고 100을 곱한 비율\n- 최종적으로 getDiaryAnalysis 함수는 3개의 상수를 리턴함\n- 비구조화 할당을 통해 함수 호출 시, 각각의 상수를 사용할 수 있게 됨\n\n\u003cbr\u003e\n\n### - 데이터 출력\n\n```javascript\n// App.js\n\n\u003cdiv className=\"App\"\u003e\n    ...\n    \u003cdiv\u003e전체 일기 : {data.length}\u003c/div\u003e\n    \u003cdiv\u003e기분 좋은 일기 개수 : {goodCount}\u003c/div\u003e\n    \u003cdiv\u003e기분 나쁜 일기 개수 : {badCount}\u003c/div\u003e\n    \u003cdiv\u003e기분 좋은 일기 비율 : {goodRatio}\u003c/div\u003e\n    ...\n\u003c/div\u003e\n```\n\n- 각각의 데이터를 출력한다.\n- 콘솔을 보면 \"일기 분석 시작\"을 2번 출력하는데 이 이유는 처음에 data에 아무 것도 없는 상태에서 1번 호출되고, API로 데이터를 받아 리렌더 되어 2번째 호출일 발생하기에 2번 출력되는 것을 알 수 있음\n\n\u003cbr\u003e\n\n### - 문제점\n\n- 일기의 `내용을 수정`하는 것은 현재 우리가 출력하는 `기분 좋은 일기 개수`, `기분 나쁜 일기 개수`, `기분 좋은 일기 비율`에 어떠한 영향도 미치지 않는다.\n- 그럼에도 불구하고 `내용을 수정`하면 `리렌더`가 발생하고 \"일기 분석 시작\"이 콘솔에 출력되며 `일기 분석 함수가 또 호출`된 것을 알 수 있다.\n- 현재는 함수 하나가 호출되지만, 일기의 개수가 많아지거나, 더 많은 함수가 리렌더 시, 호출된다면 프로그램의 사용성에 영향을 줄 수 있다.\n- 따라서 리렌더 될 때마다 함수를 호출하는 것이 아닌, `특정 값이 변화할 때만` 해당 함수를 수행하도록 할 수 있다.\n\n\u003cbr\u003e\n\n### 9-4. useMemo\n\n- 위의 문제를 useMemo를 사용하여 해결할 수 있다.\n- useMemo는 `콜백함수`, `dependency 배열`을 인자로 받음, (useEffect와 유사)\n\n```javascript\n// App.js\n\nconst getDiaryAnalysis = useMemo(() =\u003e {\n    console.log(\"일기 분석 시작\");\n\n    const goodCount = data.filter((it) =\u003e it.emotion \u003e= 3).length;\n    const badCount = data.length - goodCount;\n    const goodRatio = (goodCount / data.length) * 100;\n    return { goodCount, badCount, goodRatio };\n}, [data.length]);\n```\n\n- 콜백함수가 실행되고 해당 리턴 값을 계속 기억해둔다.\n- 그리고 dependency 배열에 담긴 값이 변화하지 않으면 리턴 값을 그대로 사용한다.\n- 하지만 dependency 배열에 담긴 값이 변화하면 새로 업데이트된 리턴 값을 다시 사용하게 된다.\n- 따라서 불필요한 연산을 막을 수 있다.\n\n\u003cbr\u003e\n\n### - useMemo의 타입 (실수 주의)\n\n- useMemo를 사용할 경우, 콜백함수가 리턴하는 값을 받기 때문에 getDiaryAnalysis는 함수가 아닌 상수가 됨\n\n```javascript\n// App.js\n\nconst { goodCount, badCount, goodRatio } = getDiaryAnalysis;\n```\n\n- 따라서 `getDiaryAnalysis();`와 같이 함수로 호출했었지만 useMemo를 사용하면 `getDiaryAnalysis`로 가져와야 함\n- 함수 호출로 가져올 경우, 에러 발생\n\n\u003cbr\u003e\n\n![useMemo](README_img/useMemo.gif)\n\n\u003cuseMemo를 사용하여 불필요한 호출, 연산 막기 예시\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 10. 최적화2 컴포넌트 재사용 - React.memo\n\n### 10-1. 불필요한 리렌더\n\n- count와 text라는 상태를 가진 App에서 count 값이 변화하면 어떻게 될까?\n\n![불필요한 리렌더](README_img/React_memo_background.png)\n\n- `state가 변화`하면 해당 컴포넌트와 자식 컴포넌트들은 `모두 리렌더가 발생`\n- 불필요한 리렌더를 발생시키기에 컴포넌트의 양이 많을 경우, `성능 비효율`이 발생할 수 있다.\n\n\u003cbr\u003e\n\n### 10-2. 해결방안\n\n- 자식 컴포넌트에 `조건을 지정`한다.\n- 해당 조건이 `충족`될 경우에만 `렌더링을 수행`하도록 함\n\n![해결방안](README_img/React_memo_solution.png)\n\n\u003cbr\u003e\n\n### 10-3. React.memo\n\n- 함수형 컴포넌트에서 `동일한 props`을 `부모 컴포넌트`로부터 받아서 `동일한 렌더링`이 발생할 경우, 다시 렌더링하지 않고 마지막으로 렌더링된 결과를 재사용하는 방법\n- props에 의해서만 렌더링을 재사용하는 것이며, state등의 변화 시에는 리렌더링 발생함\n\n\u003cbr\u003e\n\n### - React.memo 테스트\n\n- 부모 컴포넌트인 OptimizeTest를 생성\n- `count`와 `text`의 두 개의 상태를 생성\n- 자식 컴포넌트인 CountView와 TextView에 각각 props 전달\n\n```javascript\n// OptimizeTest.js\n\nconst OptimizeTest = () =\u003e {\n    const [count, setCount] = useState(1);\n    const [text, setText] = useState(\"\");\n\n    return (\n        \u003cdiv style={{ padding: 50 }}\u003e\n            \u003cdiv\u003e\n                \u003ch2\u003ecount\u003c/h2\u003e\n                \u003cCountView count={count} /\u003e\n                \u003cbutton onClick={() =\u003e setCount(count + 1)}\u003e+\u003c/button\u003e\n            \u003c/div\u003e\n            \u003cdiv\u003e\n                \u003ch2\u003etext\u003c/h2\u003e\n                \u003cTextView text={text} /\u003e\n                \u003cinput\n                    value={text}\n                    onChange={(e) =\u003e {\n                        setText(e.target.value);\n                    }}\n                /\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    );\n};\n```\n\n\u003cbr\u003e\n\n\u003cReact.memo 사용 전\u003e\n\n```javascript\n// Optimize.js\n\nimport { useState, useEffect } from \"react\";\n\nconst TextView = ({ text }) =\u003e {\n  useEffect(() =\u003e {\n    console.log(`Update :: text : ${text}`);\n  });\n  return \u003cdiv\u003e{text}\u003c/div\u003e;\n};\n\nconst CountView = ({ count }) =\u003e {\n  useEffect(() =\u003e {\n    console.log(`Update :: count : ${count}`);\n  });\n  return \u003cdiv\u003e{count}\u003c/div\u003e;\n};\n```\n\n- `CountView`와 `TextView` 두 개의 자식 컴포넌트를 생성\n- 각각 prop으로 count와 text를 받는다.\n- useEffect를 사용하고 dependency 배열을 받지 않게하여 어떤 요소라도 Update(리렌더)되면 콘솔을 출력하도록 설정\n\n\u003cbr\u003e\n\n![React.memo 적용 전](README_img/React_memo_non.png)\n\n\u003c자식 컴포넌트 동시에 리렌더되어 콘솔 출력\u003e\n\n\u003cbr\u003e\n\n\u003cReact.memo 사용 후\u003e\n\n```javascript\n// Optimize.js\n\nimport React, { useState, useEffect } from \"react\";\n\nconst TextView = React.memo(({ text }) =\u003e {\n  useEffect(() =\u003e {\n    console.log(`Update :: text : ${text}`);\n  });\n  return \u003cdiv\u003e{text}\u003c/div\u003e;\n});\n\nconst CountView = React.memo(({ count }) =\u003e {\n  useEffect(() =\u003e {\n    console.log(`Update :: count : ${count}`);\n  });\n  return \u003cdiv\u003e{count}\u003c/div\u003e;\n});\n```\n\n- React를 import하기\n- props를 받는 자식의 함수형 컴포넌트 전체를 `React.memo()`로 감싸기\n- 이렇게 하게 되면 props의 값이 바뀌는 경우에만 해당 자식 컴포넌트가 리렌더하게 된다.\n\n\u003cbr\u003e\n\n![React.memo 적용 후](README_img/React_memo_using.png)\n\n\u003ccount 값만 변화하여 CountView만 리렌더되어 콘솔 출력\u003e\n\n\u003cbr\u003e\n\n### - props로 객체를 받는 경우 : 문제점\n\n```javascript\n// OptimizeTest.js\n\nconst OptimizeTest = () =\u003e {\n    const [count, setCount] = useState(1);\n    const [obj, setObj] = useState({\n        count: 1,\n    });\n\n    return (\n        \u003cdiv style={{ padding: 50 }}\u003e\n            \u003cdiv\u003e\n                \u003ch2\u003eCounter A\u003c/h2\u003e\n                \u003cCounterA count={count} /\u003e\n                \u003cbutton\n                    onClick={() =\u003e {\n                        setCount(count);\n                    }}\n                \u003e\n                    A button\n                \u003c/button\u003e\n            \u003c/div\u003e\n            \u003cdiv\u003e\n                \u003ch2\u003eCounter B\u003c/h2\u003e\n                \u003cCounterB obj={obj} /\u003e\n                \u003cbutton\n                    onClick={() =\u003e {\n                        setObj({ count: obj.count });\n                    }}\n                \u003e\n                    B button\n                \u003c/button\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    );\n};\n```\n\n- state로 count, obj 데이터 설정\n- `setCount(count)`, `setObj({ count: obj.count })`로 기존 데이터와 같은 값을 state로 전달\n\n\u003cbr\u003e\n\n```javascript\n//OptimizeTest.js\n\nimport React, { useState, useEffect } from \"react\";\n\nconst CounterA = React.memo(({ count }) =\u003e {\n  useEffect(() =\u003e {\n    console.log(`CounterA Update - count : ${count}`);\n  });\n  return \u003cdiv\u003e{count}\u003c/div\u003e;\n});\n\nconst CounterB = React.memo(({ obj }) =\u003e {\n  useEffect(() =\u003e {\n    console.log(`CounterB Update - count : ${obj.count}`);\n  });\n  return \u003cdiv\u003e{obj.count}\u003c/div\u003e;\n});\n```\n\n- useMemo를 사용하여 부모 컴포넌트 OptimizeTest로부터 받은 props인 count와 obj가 변경되면 useEffect에 의해서 콘솔에 업데이트 결과가 출력되도록 설정하였다.\n- CounterA 컴포넌트의 경우, `count` 값이 `setCount(count)`에 의해 동일한 값을 보내기에 `리렌더되지 않음`\n- 반면 CounterB 컴포넌트의 경우, `obj` 값이 `setObj({ count: obj.count })`로 값의 변화가 없지만 `리렌더가 발생`한다.\n\n\u003cbr\u003e\n\n\u003c객체를 비교하는 방법\u003e\n\n```javascript\nconst a = {count: 1};\nconst b = {count: 1};\n```\n\n- 위의 경우, `a === b`는 true일까?\n  - 정답은 false\n- 객체를 비교하는 경우, 객체의 `주소(해시)`를 비교하게 되는데 이 주소가 다르기에 다른 객체로 인식하게 된다. (`얕은 비교`)\n- 따라서 동일한 props를 받은 것같지만 다른 객체로 판단하기에 React.memo에서 리렌더링이 발생한다.\n\n\u003cbr\u003e\n\n```javascript\nconst a = {count: 1};\nconst b = a;\n```\n\n- 이 경우는 b에서 a 자체를 참조하여 같은 객체 주소를 가리키기에 `a === b`가 `true`이다.\n\n\u003cbr\u003e\n\n### - props로 객체를 받는 경우 : 해결 - areEqual() 함수\n\n```javascript\n// OptimizeTest.js\n\nconst CounterB = ({ obj }) =\u003e {\n    useEffect(() =\u003e {\n        console.log(`CounterB Update - count : ${obj.count}`);\n    });\n    return \u003cdiv\u003e{obj.count}\u003c/div\u003e;\n};\n```\n\n- 기존 CounterB 컴포넌트에서 React.memo 제거\n\n\u003cbr\u003e\n\n```javascript\n// OptimizeTest.js\n\nconst areEqual = (prevProps, nextProps) =\u003e {\n    if (prevProps.obj.count === nextProps.obj.count) {\n        return true; // 이전 props와 현재 props가 같다 -\u003e 리렌더 발생 안 함\n    }\n    return false; // 이전 props와 현재 props가 다르다 -\u003e 리렌더 발생\n};\n\n// -------------------------------------------------\n\n// 축약된 코드\n\nconst areEqual = (prevProps, nextProps) =\u003e {\n    return prevProps.obj.count === nextProps.obj.count;\n};\n```\n\n- 두 개의 객체 인자를 받아 값이 같은지 비교하여 `같으면 true`, `다르면 false`를 리턴하는 함수 `areEqual`을 생성\n\n\u003cbr\u003e\n\n```javascript\n// OptimizeTest.js\n\nconst MemoizedCounterB = React.memo(CounterB, areEqual);\n\n...\n\u003cdiv\u003e\n    \u003ch2\u003eCounter B\u003c/h2\u003e\n    \u003cMemoizedCounterB obj={obj} /\u003e\n    \u003cbutton\n        onClick={() =\u003e {\n            setObj({ count: obj.count });\n        }}\n    \u003e\n        B button\n    \u003c/button\u003e\n\u003c/div\u003e\n...\n```\n\n- React.memo에 기존의 컴포넌트 함수 `CounterB`와 값을 비교하는 함수 `areEqual`을 인자로 주면 새로운 함수형 컴포넌트를 생성함\n- 이 함수를 부모 컴포넌트에 사용하면 props로 받은 객체의 얕은 비교를 하지않고, 두 번째 인자의 areEqual이 리턴하는 true/false에 따라 리렌더를 결정하게 된다.\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 11. 최적화3 컴포넌트 \u0026 함수 재사용 - useCallback\n\n### 11-1. 최적화할 요소 찾기\n\n### - React Developer Tools 활용\n\n- 크롬 브라우저의 확장 앱으로 메타(Meta)에서 개발함\n- 브라우저 상의 페이지가 React 기반으로 제작되었는지 확인할 수 있음\n- 옵션에서 `Highlight updates when components render`를 활성화하면 현재 `리렌더`링되는 컴포넌트를 `하이라이트` 해줌\n\n\u003cbr\u003e\n\n![리액트 개발 도구 하이라이트 기능](README_img/React_highlight.gif)\n\n\u003c일기 내용이 업데이트될 때마다 DiaryEditor.js 영역이 노란색으로 하이라이트됨\u003e\n\n\u003cbr\u003e\n\n### - 리렌더링이 일어나는 경우\n\n- 본인이 가진 state가 변경될 경우\n- 부모 컴포넌트가 리렌더링되는 경우\n- 자신이 받은 props가 변경되는 경우\n\n\u003cbr\u003e\n\n### 11-2. useCallback\n\n- ReactHooks 중 하나\n- useCallback은 useMemo와 동일하게 구성되나 `useMemo`는 동일한 `연산의 결과 값`을 리턴하는 반면, `useCallback`은 `콜백함수`를 리턴한다.\n- dependency 배열의 값이 변화하지 않으면 동일한 콜백함수를 사용하는 것을 도움\n\n```javascript\n// useCallback 기본구성(콜백함수, dependency 배열)\n\nconst memoizationCallback = useCallback(\n    () =\u003e {\n        doSometing(a, b);\n    }, [a, b]\n);\n```\n\n\u003cbr\u003e\n\n### 11-3. useCallback 사용\n\n- DiaryList에서 변화가 있을 경우, data 변수가 업데이트되고 data를 state로 가지는 App.js(부모 컴포넌트)는 리렌더링 된다.\n- 부모 컴포넌트에서 리렌더링이 일어나기에 자식 컴포넌트인 `DiaryList`와 `DiaryEditor` 모두 리렌더링 된다.\n- 하지만 data가 변화하더라도 `DiaryEditor`는 `리렌더링 될 필요가 없다`.\n- DiaryEditor는 부모 App 컴포넌트로부터 `onCreate` 함수를 props로 받고 있다.\n- 따라서 동일한 onCreate 함수를 props로 받아 리렌더링이 일어나지 않도록 `onCreate` 함수에 `useCallback`을 사용하고 `DiaryEditor`는 동일한 props를 받으면 리렌더링되지 않도록 `React.memo`를 사용한다. \n\n```javascript\n// DiaryEditor.js\n\nexport default React.memo(DiaryEditor);\n```\n\n```javascript\n// App.js\n\nconst onCreate = useCallback((author, content, emotion) =\u003e {\n    const created_date = new Date().getTime();\n    const newItem = {\n        author,\n        content,\n        emotion,\n        created_date,\n        id: dataId.current,\n    };\n    dataId.current += 1;\n    setData([newItem, ...data]);\n}, []);\n```\n\n\u003cbr\u003e\n\n### 11-4. 최적화의 딜레마\n\n- useCallback을 적용한 현재 DiaryEditor에서 일기를 작성하면, onCreate가 수행되는데 useCallback의 `dependency`에 `빈배열`을 담고 있기 때문에 최초의 mount될 때의 `data(빈배열)`만 기억하여 `작성된 일기를 빈배열에 계속 넣게 된다`.\n- 밑빠진 독에 물붓기와 같은 현상 발생\n- `dependency 배열`에 `data`를 넣게 되면, data를 `참조`받아 기존 일기에 새로 작성된 일기를 추가하게 되지만 `data가 변화`할 때마다 `DiaryEditor가 리렌더`되어 useCallback을 사용한 `이점이 사라진다`.\n\n\u003cbr\u003e\n\n### 11-5. 함수형 업데이트\n\n- setState 함수에 함수를 전달하는 것\n\n```javascript\n// App.js\n\nconst onCreate = useCallback((author, content, emotion) =\u003e {\n    const created_date = new Date().getTime();\n    const newItem = {\n        author,\n        content,\n        emotion,\n        created_date,\n        id: dataId.current,\n    };\n    dataId.current += 1;\n    setData((data) =\u003e [newItem, ...data]);\n}, []);\n```\n\n- `setData((data) =\u003e [newItem, ...data])`를 보면 setData 함수 내에 함수를 사용하고 data를 인자로 받아 업데이트된 배열을 리턴한다.\n- 이렇게 하면 dependency 배열은 data를 가지지 않아 data가 수정되더라도 onCreate는 동일한 콜백함수를 리턴하여 리렌더가 발생하지 않음\n- 대신 setData에서 콜백함수 `인자`로 `data`를 참조하기에 data가 `빈배열로 초기화되지 않는다`.\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 12. 최적화4 - React.memo + useCallback\n\n### 12-1. 최적화 필요 부분\n\n- DiaryItem에서 `일기 하나`가 삭제될 경우, `모든 일기들이 리렌더링`되는 것을 알 수 있음\n- 일기의 개수가 많이 있을 경우, 성능에 좋지 않음\n\n\u003cbr\u003e\n\n### 12-2. React.memo와 useCallback 적용\n\n```javascript\n// DiaryItem.js\n\nconst DiaryItem = ({onEdit, onRemove, author, content, created_date, emotion, id}) =\u003e {\n    ...\n}\n\nexport default React.memo(DiaryItem);\n```\n\n- `React.memo`로 `동일한 props`를 받을 경우, `리렌더 발생 방지`\n- 현재 DiaryItem 컴포넌트는 onEdit, onRemove, author, content, created_date, emotion, id를 props로 받고 있음\n- author, content, created_date, emotion, id는 동일한 값의 props를 받는다는 것을 React.memo를 통해 인식할 수 있음\n- 하지만, onEdit, onRemove의 경우, 함수이기 때문에 얕은 비교로 동일한 함수인지 아닌지 React.memo에서는 판별이 어려움\n- 따라서 `onEdit`과 `onRemove`에 `useCallback`으로 동일한 콜백함수를 보내는 처리를 해주어야 함\n\n\u003cbr\u003e\n\n```javascript\n// App.js\n\nconst onRemove = useCallback((targetId) =\u003e {\n    setData((data) =\u003e data.filter((it) =\u003e it.id !== targetId));\n}, []);\n\nconst onEdit = useCallback((targetId, newContent) =\u003e {\n    setData((data) =\u003e\n        data.map((it) =\u003e\n            it.id === targetId ? { ...it, content: newContent } : it,\n        ),\n    );\n}, []);\n```\n\n- App 컴포넌트에서 onRemove 함수와 onEdit 함수에 useCallback 처리를 하고 dependency 배열은 빈배열로 처리\n- useCallback의 딜레마에 빠지지 않고 setState에서 최신의 데이터를 받게하기 위해 data를 인자로 받는 화살표 함수를 사용하여 함수형 업데이트를 진행\n- 이렇게 하면 DiaryItem 하나를 업데이트하여도 다른 DiaryItem들이 리렌더 되지 않음\n\n\u003cbr\u003e\n\n![React.memo + useCallback](README_img/React_memo_useCallback.gif)\n\n\u003cReact.memo와 useCallback을 활용하여 업데이트되는 DiaryItem만 리렌더\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 13. 복잡한 상태 관리 로직 분리하기 - useReducer\n\n### 13-1. App 컴포넌트의 복잡함\n\n![App의 상태변화함수](README_img/React_setStates.png)\n\n- 현재 App 컴포넌트에는 useState로 인하여 `setState`인 상태 변화 처리함수가 `코드 상에 많이 포함`되어있음\n- 이 상태 변화 함수들은 모두 `data를 사용`하고 있기 때문에 App 컴포넌트에서 작성되었음\n- App 컴포넌트 코드가 복잡하고 길어짐\n- 상태 변화 함수가 많아질수록 더 복잡해짐\n\n\u003cbr\u003e\n\n### 13-2. useReducer\n\n- React Hooks 중 하나\n- 컴포넌트에서 `상태 변화 로직`을 `분리`하여 따로 관리\n\n\u003cbr\u003e\n\n\u003cuseReducer를 사용하지 않은 예시\u003e\n\n```jsx\nconst Counter = () =\u003e {\n    const [count, setCount] = useState(0);\n    \n    const add1 = () =\u003e {\n        setCount(count + 1);\n    };\n    \n    const add10 = () =\u003e {\n        setCount(count + 10);\n    };\n    \n    const add100 = () =\u003e {\n        setCount(count + 100);\n    };\n    \n    return (\n        \u003cdiv\u003e\n            {count}\n            \u003cbutton onClick={add1}\u003eadd 1\u003c/button\u003e\n            \u003cbutton onClick={add10}\u003eadd 10\u003c/button\u003e\n            \u003cbutton onClick={add100}\u003eadd 100\u003c/button\u003e\n        \u003c/div\u003e\n    )\n};\n```\n\n\u003cbr\u003e\n\n\u003cuseReducer를 사용하여 상태 변화 로직 분리 예시\u003e\n\n```jsx\n// 상태 관리 로직 분리\n\nconst reducer = (state, action) =\u003e {\n    switch (action.type) {\n        case 1:\n            return state + 1;\n        case 10:\n            return state + 10;\n        case 100:\n            return state + 100;\n        default:\n            return state;\n    }\n}\n```\n\n- 상태 변화 로직을 따로 분리하여 `switch-case 문법`처럼 활용\n- `state` : 현재 가장 최신의 state\n- `action` : dispatch 호출 시 전달한 인자인 `action 객체`\n\n\u003cbr\u003e\n\n```jsx\n// useReducer 적용\n\nconst Counter = () =\u003e {\n    const [count, dispatch] = useReducer(reducer, 1);\n    \n    return (\n        \u003cdiv\u003e\n            {count}\n            \u003cbutton onClick={() =\u003e dispatch({ type: 1})}\u003eadd 1\u003c/button\u003e\n            \u003cbutton onClick={() =\u003e dispatch({ type: 10})}\u003eadd 10\u003c/button\u003e\n            \u003cbutton onClick={() =\u003e dispatch({ type: 100})}\u003eadd 100\u003c/button\u003e\n        \u003c/div\u003e\n    )\n};\n```\n\n- useState와 같이 `비구조화 할당` 사용\n- `count` : 0번 인덱스로 상태(state)\n- `dispatch` : 1번 인덱스로 상태 변화를 일으키는(`raise`) 함수\n- `useReducer()` : 상태 변화 관리 React Hook\n  - `reducer` : 0번 인덱스로 dispatch에서 일어난 `상태변화 action을 처리`해주는 함수\n  - `1` : 상태인 count의 `초기 값`\n- `dispatch({ type: 1 })` : dispatch 호출 시, 전달하는 {type: 1}과 같은 객체를 `action 객체`라고 함\n\n\u003cbr\u003e\n\n### 13-3. App 컴포넌트에 useReducer 적용하기\n\n```jsx\n// App.js\n\n// reducer 함수 따로 생성\n\nconst reducer = (state, action) =\u003e {\n    switch (action.type) {\n        // 처음 Mount 될 때,\n        case \"INIT\": {\n            return action.data; // action에 담긴 data 그대로 리턴\n        }\n        // 생성할 때,\n        case \"CREATE\": {\n            const created_date = new Date().getTime(); // 생성일자 따로 처리\n            const newItem = {\n                ...action.data,\n                created_date,\n            }; // 받은 action의 data에 생성일자 추가한 새로운 일기 newItem\n            return [newItem, ...state]; // 기존 일기 state에 newItem 추가하여 리턴\n        }\n        // 삭제할 때,\n        case \"REMOVE\": {\n            // 해당 id가 아닌 일기만 모아서 리턴\n            return state.filter((it) =\u003e it.id !== action.targetId);\n        }\n        // 수정할 때,\n        case \"EDIT\": {\n            // 해당 id인 일기를 찾아서 content만 action으로 받은 newContent로 수정하여 리턴\n            return state.map((it) =\u003e\n                it.id === action.targetId ? { ...it, content: action.newContent } : it,\n            );\n        }\n        default:\n            return state;\n    }\n};\n```\n\n```jsx\n// App.js\n\n// useState 대신 useReducer 사용\nconst [data, dispatch] = useReducer(reducer, []);\n\n// 기존의 setData를 이용하는 모든 상태 변화 함수 수정 -\u003e dispatch 적용\nconst getData = async () =\u003e {\n    const res = await fetch(\n        \"https://jsonplaceholder.typicode.com/comments\",\n    ).then((res) =\u003e res.json());\n    const initData = res.slice(0, 20).map((it) =\u003e {\n        return {\n            author: it.email,\n            content: it.body,\n            emotion: Math.floor(Math.random() * 5) + 1,\n            created_date: new Date().getTime(),\n            id: dataId.current++,\n        };\n    });\n\n    dispatch({ type: \"INIT\", data: initData });\n};\n\nconst onCreate = useCallback((author, content, emotion) =\u003e {\n    dispatch({\n        type: \"CREATE\",\n        data: { author, content, emotion, id: dataId.current },\n    });\n    dataId.current += 1;\n}, []);\n\nconst onRemove = useCallback((targetId) =\u003e {\n    dispatch({ type: \"REMOVE\", targetId });\n}, []);\n\nconst onEdit = useCallback((targetId, newContent) =\u003e {\n    dispatch({ type: \"EDIT\", targetId, newContent });\n}, []);\n```\n\n- `dispatch`는 기존의 데이터를 어떠한 처리없이 받는 것이 가능하기에 기존의 useCallback 딜레마에서 함수형 업데이트 처리와 같은 `별도의 조치를 하지 않아도 괜찮음`\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n## 14. 컴포넌트 트리에 데이터 공급하기 - Context\n\n### 14-1. 전달되는 props와 컴포넌트에서 활용되는 props\n\n![props 구분](README_img/React_Context.png)\n\n- App 컴포넌트에서 생성된 `onEdit` 함수와 `onRemove` 함수는 `DiaryList` 컴포넌트에는 `전달`만 이루어지고 `DiaryItem` 컴포넌트에서 실질적으로 `사용`됨 (비효율적)\n- 전달되는 props가 많아지게 되면 중간에 이름을 수정하는 등의 작업을 할 때, 모두 수정해주는 반복 작업을 해야함\n- 이를 `Props 드릴링`이라고 함\n\n\u003cbr\u003e\n\n### 14-2. Provider, Context\n\n\u003cProvider : 공급자 컴포넌트\u003e\n\n![Provider](README_img/React_Provider.png)\n\n- `Provider 컴포넌트`에게 App 컴포넌트의 `모든 데이터`를 전달받음\n- 모든 컴포넌트가 Provider로부터 필요한 props를 `직접 전달` 받음\n- 이처럼 모든 컴포넌트가 Provider의 자식 컴포넌트가 되어 props를 직접 전달받을 수 있는 영역을 `Context`, 문맥이라고 함\n- 같은 Context에 속해있지 않으면 prop을 provider로부터 직접 전달 받는 것이 불가능\n\n\u003cbr\u003e\n\n### - Context 생성\n\n```jsx\nconst MyContext = React.createContext(defaultValue);\n```\n\n- Context를 쉽게 작성하도록 React의 `Context API`를 활용할 수 있음\n- `createContext` : Context 생성 함수\n\n\u003cbr\u003e\n\n### - Context Provider를 통한 데이터 공급\n\n```jsx\n\u003cMyContext.Provider value={전역으로 전달하고자하는 값}\u003e\n    {/*이 context안에 위치할 자식 컴포넌트들*/}\n\u003c/MyContext.Provider\u003e\n```\n\n- `Childern Props` : 컴포넌트를 컴포넌트로 감싸 컴포넌트를 props로 전달\n- 자식 컴포넌트 수의 제한은 없음\n\n\u003cbr\u003e\n\n### 14-3. 코드에 적용\n\n### - data를 관리할 Context 생성\n\n```jsx\n// App.js\n\nexport const DiaryStateContext = React.createContext();\n```\n\n- 다른 컴포넌트에서 import할 수 있도록 `export` 해주어야 함\n\n\u003cbr\u003e\n\n### - data Context의 Provider 컴포넌트로 자식 컴포넌트 감싸기\n\n```jsx\n// App.js\n\n\u003cDiaryStateContext.Provider value={data}\u003e\n    \u003cdiv className=\"App\"\u003e\n        \u003cDiaryEditor onCreate={onCreate} /\u003e\n        \u003cdiv\u003e전체 일기 : {data.length}\u003c/div\u003e\n        \u003cdiv\u003e기분 좋은 일기 개수 : {goodCount}\u003c/div\u003e\n        \u003cdiv\u003e기분 나쁜 일기 개수 : {badCount}\u003c/div\u003e\n        \u003cdiv\u003e기분 좋은 일기 비율 : {goodRatio}\u003c/div\u003e\n        \u003cDiaryList onEdit={onEdit} onRemove={onRmove} /\u003e\n    \u003c/div\u003e\n\u003c/DiaryStateContext.Provider\u003e\n```\n\n- DiaryStateContext의 Provider 컴포넌트에 value를 data로 지정\n\n\u003cbr\u003e\n\n### - DiaryList 컴포넌트에서 해당 props 받기\n\n```jsx\n// DiaryList.js\n\nconst DiaryList = () =\u003e {\n    const diaryList = useContext(DiaryStateContext);\n};\n```\n\n- 기존의 props는 제거하고 React Hooks의 하나인 `useContext`를 통해 Context에서 diaryList 데이터를 가져옴\n\n\u003cbr\u003e\n\n### - 상태 변화 함수도 관리할 Context 생성 \n\n```jsx\nexport const DiaryDispatchContext = React.createContext();\n```\n\n- `data`와 `상태 변화 함수`를 `하나의 Context`에서 관리할 경우, 기존의 React.memo와 useCallback으로 리렌더를 방지한 `최적화가 풀려버리고` data가 업데이트 될때마다 다시 `리렌더가 발생함`\n- 따라서 다른 Context에서 관리해야함\n\n\u003cbr\u003e\n\n### - 상태 변화 함수들 하나의 변수에 담기\n\n```jsx\n// App.js\n\nconst memoizedDispatches = useMemo(() =\u003e {\n    return { onCreate, onRemove, onEdit };\n}, []);\n\n// ----------------------------------------------\n\n// 이렇게 묶으면 안 됨\nconst memoizedDispatches = { onCreate, onRemove, onEdit };\n```\n\n- 단순 객체로 묶어서 Context의 value로 전달하면 App 컴포넌트가 업데이트될 때, 상태 변화 함수들도 재생성됨\n- 따라서 함수의 연산 결과 값을 기억하는 useMemo를 사용해야 함\n\n\u003cbr\u003e\n\n### - 상태 변화 함수 Provider 컴포넌트 적용\n\n```jsx\n// App.js\n\n\u003cDiaryStateContext.Provider value={data}\u003e\n    \u003cDiaryDispatchContext.Provider value={memoizedDispatches}\u003e\n        \u003cdiv className=\"App\"\u003e\n            \u003cDiaryEditor /\u003e\n            \u003cdiv\u003e전체 일기 : {data.length}\u003c/div\u003e\n            \u003cdiv\u003e기분 좋은 일기 개수 : {goodCount}\u003c/div\u003e\n            \u003cdiv\u003e기분 나쁜 일기 개수 : {badCount}\u003c/div\u003e\n            \u003cdiv\u003e기분 좋은 일기 비율 : {goodRatio}\u003c/div\u003e\n            \u003cDiaryList /\u003e\n        \u003c/div\u003e\n    \u003c/DiaryDispatchContext.Provider\u003e\n\u003c/DiaryStateContext.Provider\u003e\n```\n\n- 해당 Context의 Provider 컴포넌트를 중첩하여 props를 전달\n- value로 앞서 생성한 `memoizedDispatches`를 지정\n- `onCreate={onCreate}`와 같은 상태 변화 함수 props들 제거\n\n\u003cbr\u003e\n\n### - Provider에서 props 받기\n\n```jsx\n// DiaryEditor.js\nconst DiaryEditor = () =\u003e {\n    const { onCreate } = useContext(DiaryDispatchContext);\n};\n\n```\n\n- 기존 props 삭제\n- useContext로 DiaryDispatchContext에서 onCreate 가져오기\n\n\u003cbr\u003e\n\n```jsx\n// DiaryItem.js\n\nconst DiaryItem = ({ id, author, content, emotion, created_date }) =\u003e {\n    const {onEdit, onRemove} = useContext(DiaryDispatchContext);\n}\n```\n\n- 기존 props에서 상태 변화 함수 props 삭제\n- useContext로 DiaryDispatchesContext에서 onEdit, onRemove 가져오기","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeonggoncho%2Freact-practice-diary","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeonggoncho%2Freact-practice-diary","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeonggoncho%2Freact-practice-diary/lists"}