React

 

 

인프런 : 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.프로젝트 소개

 

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103522?tab=curriculum

 

 

 

 

 

 

 

 

 

36.React에서 사용자 입력 처리하기

 

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103523?tab=curriculum

 

 

강의 진행 중 사용된 소스코드 입니다.

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 - 리스트 렌더링 (조회)

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103525?tab=curriculum

 

 

 

 

 

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 - 데이터 추가하기

 

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103526?category=questionDetail&tab=curriculum

 

 

 

 

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 - 데이터 삭제하기

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103527?category=questionDetail&tab=curriculum

 

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 - 데이터 수정하기

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103528?category=questionDetail&tab=curriculum

 

 

 

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

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103529?category=questionDetail&tab=curriculum

 

 

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 호출하기

 

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103530?category=questionDetail

 

 

 

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

 

소스 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103531?category=questionDetail&tab=curriculum

 

 

 

react developer tools 다운로드 링크입니다.

https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ko

 

 

 

 

 

 

 

 

 

 

 

45.최적화 1 - useMemo

 

소스 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103532?category=questionDetail&tab=curriculum

 

 

 

 

 

데이터  변하지 않으면    기존  메모값을  그대로 사용한다.

여기서는 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://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103533?category=questionDetail&tab=curriculum

 

 

강의 진행 중 사용된 소스코드 입니다.

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

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103534?tab=curriculum

 

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 - 최적화 완성

 

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103535?tab=curriculum

 

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

 

강의  :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103536?tab=curriculum&q=614226&category=questionDetail

 

 

 

 

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

 

강의 :

https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/103537?tab=curriculum&q=614226&category=questionDetail

 

 

 

 

 

 

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);
            });

~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

마음의 즐거움은 육체의 즐거움과 달리 즐기면 즐길수록 더욱 즐겁고 힘을 얻는다.

댓글 ( 4)

댓글 남기기

작성