한입 크기로 잘라먹는 리액트 Section 6

yiseonline·2023년 5월 27일
0

udemy study

목록 보기
6/7
post-thumbnail

6.0 프로젝트 소개

1) 사용자 입력 및 배열 리스트 처리하기
2) React Lifecycle과 API
3) React App 프로처럼 성능 최적화하기 with 도구 사용
4) React 컴포넌트 트리에 전역 데이터 공급하기


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

한시간 째 나는 시작도 못하고 애만 먹고 있다..

진짜 이거 왜이러는거냐? 다 맞게 했는데 ㅠㅠ
강의 빨리 듣고 실습하고 싶은데 하지도 못하고.. 억울하다

해결은 안됐지만 난 시간이 없으니까
무시하고 그냥 한다 !!

const DiaryEditor=()=>{
    return <div className="DiaryEditor"></div>;
};
export default DiaryEditor;

ㄴDiaryEditor.js 파일 생성

(DiaryEditor.js)

import { useState } from "react";

const DiaryEditor=()=>{

    const [state,setState]=useState({
        author:"",
        content:"",
    });

    return(
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
        name="author"
        value={state.author} 
        onChange={(e)=> { //값이 바뀌었을 때 수행하는 event
          setState({
            author:e.target.value, //author만 바꾸고
            content:state.content, //content는 유지한다
          });
        }}
        />
      </div>
      <div>
        <textarea value={state.content} //여러줄을 입력받을 수 있음
          onChange={(e)=>{
            setState({
                content:e.target.value, //content만 바꾸고
                author:state.content, //author는 유지
              });
          }}
        /> 
      </div>
    </div>
    );
};
export default DiaryEditor;
import { useState } from "react";

const DiaryEditor=()=>{

    const [state,setState]=useState({
        author:"",
        content:"",
        emotion:1, //감정 설정값 = 1
    });

const handleChangeState =(e)=>{
    setState({
        ...state,
        [e.target.name]: e.target.value,
    });
};

    return(
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
        name="author"
        value={state.author} 
        onChange={handleChangeState}
        />
      </div>
      <div>
        <textarea name="content"
         value={state.content} //여러줄을 입력받을 수 있음
         onChange={handleChangeState}
        /> 
      </div>
      <div> //감정점수 설정
        <select name="emotion" value={state.emotion} onChange={handleChangeState}>
            <option value={1}>1</option>
            <option value={2}>2</option>
            <option value={3}>3</option>
            <option value={4}>4</option>
            <option value={5}>5</option>
        </select>
      </div>
    </div>
    );
};
export default DiaryEditor;

(App.css)

.DiaryEditor{
  border: 1px solid gray;
  text-align: center;
  padding: 20px;
}

.DiaryEditor input, textarea{
  margin-bottom: 20px;
  width: 500px;
  padding: 10px;
}

.DiaryEditor textarea{
  height: 150px;
}

.DiaryEditor select{
  width: 300px;
  padding: 10px;
  margin-bottom: 20px;
}

.DiaryEditor button{
  width: 500px;
  padding: 10px;
  cursor: pointer;
}

ㄴ 스타일링 해주기

난 해주면 뭐함? 보이지가 않는데.. ㅠㅠ


6.2 React에서 DOM 조작하기 -useRef

const handleSubmit=()=>{
    if(state.author.length <1){ 
          alert("작성자는 최소 1글자 이상을 입력해주세요");
          return; //아니면 중단
    }

    if(state.content.length <5){
        alert("작성자는 최소 5글자 이상을 입력해주세요");
        return;
  }

    alert("저장 성공");
}

handleSubmit 함수를 고쳤는데 alert는 잘 안쓰니까 focus로 써보면 !

-last code-

import React, { useRef, useState } from "react";

const DiaryEditor=()=>{

    const authorInput=useRef(); //ref=dom에 접근할 수 있음
    const contentInput =useRef();

    const [state,setState]=useState({
        author:"",
        content:"",
        emotion:1,
    });

const handleChangeState =(e)=>{
    setState({
        ...state,
        [e.target.name]: e.target.value,
    });
};

const handleSubmit=()=>{
    if(state.author.length <1){ 
        authorInput.current.focus(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능 
        return; //아니면 중단
    }

    if(state.content.length <5){
        contentInput.current.focus();
        return;
  }

    alert("저장 성공");
}

    return(
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
        ref={authorInput}
        name="author"
        value={state.author} 
        onChange={handleChangeState}
        />
      </div>
      <div>
        <textarea name="content"
        ref={contentInput}
         value={state.content} //여러줄을 입력받을 수 있음
         onChange={handleChangeState}
        /> 
      </div>
      <div>
        <select name="emotion" value={state.emotion} onChange={handleChangeState}>
            <option value={1}>1</option>
            <option value={2}>2</option>
            <option value={3}>3</option>
            <option value={4}>4</option>
            <option value={5}>5</option>
        </select>
      </div>
    <div>
        <button onClick={handleSubmit}>일기 저장하기</button>
    </div>
    </div>
    );
};
export default DiaryEditor;

6.3 React에서 배열 사용하기 1- 리스트 렌더링(조회)


오류를 드디어 해결함

근데 왜

아무것도 안뜰까?
울고싶다 ....


미친 드디어 ㅠㅠㅠㅠ

-last code-
(App.js)

import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';

const dummyList = [
  {
    id:1,
    author:"일서연",
    content:"하이 1",
    emotion:1,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  },
  {
    id:2,
    author:"이서연",
    content:"하이 2",
    emotion:2,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  },
  {
    id:3,
    author:"삼서연",
    content:"하이 3",
    emotion:3,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  }
];

const App =() => {
  return (
    <div className="App">
      <DiaryEditor/>
      <DiaryList diarList ={dummyList}/>
    </div>
  );
};

export default App;

(DiaryEditor.js)

import React, { useRef, useState } from "react";

const DiaryEditor=()=>{

    const authorInput=useRef(); //ref=dom에 접근할 수 있음
    const contentInput =useRef();

    const [state,setState]=useState({
        author:"",
        content:"",
        emotion:1,
    });

const handleChangeState =(e)=>{
    setState({
        ...state,
        [e.target.name]: e.target.value,
    });
};

const handleSubmit=()=>{
    if(state.author.length <1){ 
        authorInput.current.focus(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능 
        return; //아니면 중단
    }

    if(state.content.length <5){
        contentInput.current.focus();
        return;
  }

    alert("저장 성공");
}

    return(
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
        ref={authorInput}
        name="author"
        value={state.author} 
        onChange={handleChangeState}
        />
      </div>
      <div>
        <textarea name="content"
        ref={contentInput}
         value={state.content} //여러줄을 입력받을 수 있음
         onChange={handleChangeState}
        /> 
      </div>
      <div>
        <select name="emotion" value={state.emotion} onChange={handleChangeState}>
            <option value={1}>1</option>
            <option value={2}>2</option>
            <option value={3}>3</option>
            <option value={4}>4</option>
            <option value={5}>5</option>
        </select>
      </div>
    <div>
        <button onClick={handleSubmit}>일기 저장하기</button>
    </div>
    </div>
    );
};
export default DiaryEditor;

(DairyList.js)

import DiaryItem from "./DiaryItem.js";

const DiaryList=({diaryList})=>{
    return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.lenght}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((it)=> (
          <DiaryItem key={it.id} {...it}/>
        ))}
      </div>
    </div>
    );
};

DiaryList.defaultProps={
  diaryList:[],
};

export default DiaryList;

(DiaryItem.js)

const DiaryItem=({author, content, created_date,emotion,id})=>{
    return <div className="DiaryItem">
        <div className="info">
            <span>작성자 : {author} | 감정점수 : {emotion}
            </span>
            <br/>
            <span className="date">{new Date(created_date).toLocaleString()}</span>
        </div>
        <div className="content">{content}</div>
    </div>
}

export default DiaryItem;

(App.css)

.DiaryEditor{
  border: 1px solid gray;
  text-align: center;
  padding: 20px;
}

.DiaryEditor input, textarea{
  margin-bottom: 20px;
  width: 500px;
  padding: 10px;
}

.DiaryEditor textarea{
  height: 150px;
}

.DiaryEditor select{
  width: 300px;
  padding: 10px;
  margin-bottom: 20px;
}

.DiaryEditor button{
  width: 500px;
  padding: 10px;
  cursor: pointer;
}

/* List */

.DiaryList{
  border:1px solid gray;
  padding: 20px;
  margin-top: 20px;
}

.DiaryList h2{
  text-align: center;
}

/* Item */

.DairyItem{
  background-color: rgb(240,240,240);
  margin-bottom: 10px;
  padding: 20px;
}

.DairyItem .info{
  border-bottom: 1px solid gray;
  padding-bottom: 10px;
  margin-bottom: 10px;
}

.DairyItem.date{
  color: gray;
}

.DairyItem .content{
  font-weight: bold;
  margin-bottom: 30px;
  margin-top: 30px;
}

ㄴ 스타일링을 줘봤다
근데 CSS 는 왜 코드 블럭 앞에 css라고 써도 색깔이 안바꾸ㅣ지?
스터디 시간에 물어봐야겠음 ㅡㅡ

3번째 강의는 약간 받아쓰기..하는 느낌이었다 아무래도 한번 더 봐야지 필기를 하던 이해를 하던 뭘 할 수 있을 것 같다 힝

-결과

이렇게 쓰고 저장하기 누르면

우왕


6.4 React에서 배열 사용하기 2- 데이터 추가하기


이렇게 추가해줘야 하지만
리액트에선 같은 레벨끼리는 데이터를 주고받을 수 없다
(리액트는 단방향으로만 데이터가 흐른다 !)


이럴땐 app 컴포넌트에 state로 아래의 레벨들에게 데이터를 전달한다 !

setData가 새로운 아이템을 추가해서 app component에서 추가된 data를 DiaryList로 내려감
=> 단방향 데이터 흐름을 사용해도 데이터를 주고받게할 수 있따 !!

문제가 생겨따

왜 난 저장이 안된는 것임?

-last code-
(DiaryEditor.js)

import React, { useRef, useState } from "react";

const DiaryEditor=({onCreate})=>{
    const authorInput=useRef();
    const contentInput=useRef();

    const [state,setState]=useState({
        author:"",
        content:"",
        emotion:1,
    });

    const handleChangeState =(e)=>{
        setState({
            ...state,
            [e.target.name]: e.target.value,
        });
    };

    const handleSubmit=()=>{
        if(state.author.length <1){ 
            authorInput.current.focus(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능 
            return; //아니면 중단
        }

        if(state.content.length <5){
            contentInput.current.focus();
            return;
        }
        onCreate(state.author,state.content,state.emotion);
        alert("저장 성공");
        setState({
            author:"",
            content:"",
            emotion:1,
        });
    };

    return(
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
        ref={authorInput}
        name="author"
        value={state.author} 
        onChange={handleChangeState}
        placeholder="작성자"
        type="text"
        />
      </div>
      <div>
        <textarea name="content"
        ref={contentInput}
        value={state.content} //여러줄을 입력받을 수 있음
        onChange={handleChangeState}
        placeholder="일기"
        type="text"
        /> 
      </div>
      <div>
        <span>오늘의 감정 점수 : </span>
        <select 
        name="emotion" 
        value={state.emotion} 
        onChange={handleChangeState}
        >
            <option value={1}>1</option>
            <option value={2}>2</option>
            <option value={3}>3</option>
            <option value={4}>4</option>
            <option value={5}>5</option>
        </select>
      </div>
      <div>
        <button onClick={handleSubmit}>일기 저장하기</button>
      </div>
    </div>
    );
};
export default DiaryEditor;

(DiaryList.js)

import DiaryItem from "./DiaryItem";

const DiaryList=({diaryList})=>{
    return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((it)=> (
          <DiaryItem key={`diaryitem_${it.id}`} {...it}/>
        ))}
      </div>
    </div>
    );
};

DiaryList.defaultProps={
  diaryList:[],
};

export default DiaryList;

(index.js)

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals

(DiaryItem.js)

const DiaryItem=({author, content, created_date,emotion,id})=>{
    return(
    <div className="DiaryItem">
        <div className="info">
            <span>작성자 : {author} | 감정점수 : {emotion} |
            </span>
            <br/>
            <span className="date">{new Date(created_date).toLocaleString()}</span>
        </div>
        <div className="content">{content}</div>
    </div>
    );
};

export default DiaryItem;

(App.js)

import { useRef, useState } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';

/*const dummyList = [
  {
    id:1,
    author:"일서연",
    content:"하이 1",
    emotion:1,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  },
  {
    id:2,
    author:"이서연",
    content:"하이 2",
    emotion:2,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  },
  {
    id:3,
    author:"삼서연",
    content:"하이 3",
    emotion:3,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  }
];*/

const App =() =>{
  const [data,setData]=useState([]);

  const dataId=useRef(0);

  const onCreate=(author,content,emotion)=>{
    const created_date=new Date().getTime();
    const newItem={
      author,
      content,
      emotion,
      created_date,
      id:dataId.current
    };
    dataId.current+=1;
    setData([newItem,...data]); //newItem추가 하고 원래 배열에 있던걸 하나하나 나열
  };

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate}/>
      <DiaryList diarList ={data}/>
    </div>
  );
};

export default App;

코드샌드박스에서 다 비교해도 저장이 안된다..

오ㅐㅈㅣ??????


6.5 React에서 배열 사용하기 3 - 데이터 삭제하기

App.js에다가 삭제 함수를 넣어주었다


  const onDelete=(targetId)=>{
    console.log(`${targetId}가 삭제되었습니다`);
    const newDiaryList=data.filter((it)=>it.id!==targetId);
    console.log(newDiaryList);
    setData(newDiaryList);
  };

DiaryList.js에다가도

const DiaryList=({onDelete, diaryList})=>{
    return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((it)=> (
          <DiaryItem key={`diaryitem_${it.id}`} {...it} onDelete={onDelete}/>
        ))}
      </div>
    </div>
    );
};

삭제가 들어가게 해주었ㄷㅏ


6.6 React에서 배열 사용하기 -4 데이터 수정하기

<button>수정하기</button>

수정하기 버튼 만들구

<button onClick={handleRemove}>삭제하기</button>

onClick에 있던 if문을 없애고

const handleRemove=()=>{
        if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)){
            onRemove(id);
        }
    }

함수에 넣어주어따

const [isEdit,setIsEdit]=useState(false);

isEdit=> 수정할 수 있는지 없는지를 알려주는 boolean형
이게 true이면 수정중으로 간주해서 수정하면 되고
false이면 그냥 수정,삭제 버튼으로 랜드 하면 됨

    const toggleIsEdit=()=> setIsEdit(!isEdit);

toggleIsEdit이 호출이 되면 setIsEdit이 호출이 되면서 not연산을 통해서 isEdit이 true였다면 false로, false였다면 true로 바뀌는 반전 연산을 해줌

<button onClick={handleRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>

밑에다가는 요렇게 넣어주었다

 const handlQuitEdit=()=>{
        setIsEdit(false);
        setLocalContent(content);
    }

handlQuitEdit함수 설정하고

<button onClick={handlQuitEdit}>수정 취소</button>

에다가 넣어서 수정해준당

-last code-
(DiaryItem.js)

import { useRef, useState } from "react";

const DiaryItem=({
    onEdit,
    onRemove,
    author, 
    content, 
    created_date,
    emotion,
    id
})=>{

    const [isEdit,setIsEdit]=useState(false);
    const toggleIsEdit=()=> setIsEdit(!isEdit);
    
    const localContentInput=useRef();
    const [localContent,setLocalContent]=useState("");
    
    const handleRemove=()=>{
        if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)){
            onRemove(id);
        }
    };

    const handlQuitEdit=()=>{
        setIsEdit(false);
        setLocalContent(content);
    };

    const handleEdit =()=>{
        if(localContent.length<5)
        {
            localContentInput.current.focus();
            return;
        }

        if(window.confirm(`${id}번 째 일기를 수정하시겠습니까?`))
        {
        onEdit(id,localContent);
        toggleIsEdit();
        }
    }

    return(
    <div className="DiaryItem">
        <div className="info">
            <span>작성자 : {author} | 감정점수 : {emotion}
            </span>
            <br/>
            <span className="date">
                {new Date(created_date).toLocaleString()}
            </span>
        </div>
        <div className="content">
            {isEdit?(
            <>
              <textarea 
              ref={localContentInput}
              value={localContent} 
              onChange={(e)=>setLocalContent(e.target.value)}/>
            </>
            ):(
            <>{content}</>
            )}
            </div>
            {isEdit?(
            <>
                <button onClick={handlQuitEdit}>수정 취소</button>
                <button onClick={handleEdit}>수정 완료</button>
        </>
        ):(
        <>
        <button onClick={handleRemove}>삭제하기</button>
        <button onClick={toggleIsEdit}>수정하기</button>
        </>
        )}
    </div>
    );
};

export default DiaryItem;

(App.js)

import { useRef, useState } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';

/*const dummyList = [
  {
    id:1,
    author:"일서연",
    content:"하이 1",
    emotion:1,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  },
  {
    id:2,
    author:"이서연",
    content:"하이 2",
    emotion:2,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  },
  {
    id:3,
    author:"삼서연",
    content:"하이 3",
    emotion:3,
    created_date: new Date().getTime() //date객체를 ms로 변환해서 저장
  }
];*/

function App(){
  const [data,setData]=useState([]);

  const dataId=useRef(0);

  const onCreate=(author,content,emotion)=>{
    const created_date=new Date().getTime();
    const newItem={
      author,
      content,
      emotion,
      created_date,
      id:dataId.current
    };
    dataId.current+=1;
    setData([newItem,...data]); //newItem추가 하고 원래 배열에 있던걸 하나하나 나열
  };

  const onRemove=(targetId)=>{
    console.log(`${targetId}가 삭제되었습니다`);
    const newDiaryList=data.filter((it)=>it.id!==targetId);
    console.log(newDiaryList);
    setData(newDiaryList);
  };

  const onEdit=(targetId,newContent)=>{
    setData(
      data.map((it)=>
        it.id===targetId?{...it, content:newContent}:it)
    );
  };

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate}/>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
    </div>
  );
};
export default App;

(DiaryEditor.js)

import React, { useRef, useState } from "react";

const DiaryEditor=({onCreate})=>{
    const authorInput=useRef();
    const contentInput=useRef();

    const [state,setState]=useState({
        author:"",
        content:"",
        emotion:1,
    });

    const handleChangeState =(e)=>{
        setState({
            ...state,
            [e.target.name]: e.target.value,
        });
    };

    const handleSubmit=()=>{
        if(state.author.length <1){ 
            authorInput.current.focus(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능 
            return; //아니면 중단
        }

        if(state.content.length <5){
            contentInput.current.focus();
            return;
        }
        onCreate(state.author,state.content,state.emotion);
        alert("저장 성공");
        setState({
            author:"",
            content:"",
            emotion:1,
        });
    };

    return(
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
        ref={authorInput}
        name="author"
        value={state.author} 
        onChange={handleChangeState}
        placeholder="작성자"
        type="text"
        />
      </div>
      <div>
        <textarea name="content"
        ref={contentInput}
        value={state.content} //여러줄을 입력받을 수 있음
        onChange={handleChangeState}
        placeholder="일기"
        type="text"
        /> 
      </div>
      <div>
        <span>오늘의 감정 점수 : </span>
        <select 
        name="emotion" 
        value={state.emotion} 
        onChange={handleChangeState}
        >
            <option value={1}>1</option>
            <option value={2}>2</option>
            <option value={3}>3</option>
            <option value={4}>4</option>
            <option value={5}>5</option>
        </select>
      </div>
      <div>
        <button onClick={handleSubmit}>일기 저장하기</button>
      </div>
    </div>
    );
};
export default DiaryEditor;

(DiaryList.js)

import DiaryItem from "./DiaryItem";

const DiaryList=({onEdit, onRemove, diaryList})=>{
    return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((it)=> (
          <DiaryItem key={it.id} {...it} onEdit={onEdit} onRemove={onRemove}/>
        ))}
      </div>
    </div>
    );
};

DiaryList.defaultProps={
  diaryList:[],
};

export default DiaryList;

대박 어렵다..
그리고 아직도 내 오류는 안고쳐짐
왤까? 진짜 완벽히 똑같은데 .. 나도 화면으로 바뀌는거 보고싶은데
안보여서 재미가 없다 ㅠㅡㅜ


6.7 React Lifecycle 제어하기 -useEffect

Lifecycle이란?
-생애주기
React component도 생애주기를 가짐

-탄생-변화-죽음-
탄생 - 화면에 나타나는거 mount
변화 - 업데이트(리렌더) update
죽음 - 화면에서 사라짐 unmount

use키워드를 앞에 붙여서 클래스형이 갖고있떤 거를 함수가 채가는거


use함수 - 매개변수로 첫번째는 callback함수를, 두번째는 의존성배열을 씀

(Lifecycle.js)를 만들고 코드를 넣어준다

import React,{useEffect,useState} from 'react';

const Lifecycle=()=>{

    const [count,setCount]=useState(0);
    const[text,setText]=useState("");

    useEffect(()=>{
        console.log("Mount!");
    },[]);

    useEffect(()=>{
        console.log("Update!");
    });

    return (
      <div style={{padding:20}}>
        <div>
            {count}
            <button onClick={()=>setCount(count+1)}>+</button>
        </div>
        <div>
            <input value={text} onChange={(e)=>setText(e.target.value)}/>
        </div>
      </div>
    );
};

export default Lifecycle;

이렇게 하면

맨 위에 +버튼 누르면 console에 update라고 뜬다

useEffect(()=>{
        console.log(`count is update : ',$count`);
    },[count])

    useEffect(()=>{
        console.log(`text is update : ${text}`);
    },[text]);

함수 추가 했떠니

업데이트 대박 잘됨

count 5 넘기면 경고 메시지 띄우게 해봄

useEffect(()=>{
        console.log(`count is update : ',${count}`);
        if(count>5){
            alert("count가 5를 넘었습니다. 따라서 1로 초기화합니다");
            setCount(1);
        }
    },[count])


ㄴ 옹 ㅋ

(Lifecycle.js)

import React,{useEffect,useState} from 'react';

const UnmounTest=()=>{
    return <div>Unmount Testing Componetent</div>
}

const Lifecycle=()=>{
    const [isVisible, setIsVisible]=useState(false);
    const toggle=()=>setIsVisible(!isVisible);

    return (
      <div style={{padding:20}}>
        <button onClick={toggle}>ON/OFF</button>
        {isVisible && <UnmounTest/>} 
      </div>
    );
};

export default Lifecycle;

이렇게 싹 수정해줌
여기에서
{isVisible && <UnmounTest/>}
-> isVisible값이 true가 되면 단락회로평가를 못해서 뒤에꺼가 값이 true가 된다. false이면 단락회로평가를 하게 돼서 unmounttest도 못하게 된다

한번 누르면

한번 더 누르면 사라짐

누르면 mount나오게 하기

const UnmounTest=()=>{
    useEffect(()=>{
        console.log("Mount!");
    },[])
    return <div>Unmount Testing Componetent</div>
}

unmount도 해보았음

const UnmounTest=()=>{
    useEffect(()=>{
        console.log("Mount!");

        return ()=>{
            // unmount시점에 실행되게 됨
            console.log("unmount!");
        }
    },[]);

    return <div>Unmount Testing Componetent</div>
}

6.8 React에서 API 호출하기

(App.js)

function App(){
  const [data,setData]=useState([]);

  const dataId=useRef(0);

  const getData =async()=>{ //promise를 반환하는 비동기함수
    const res = await fetch('https://jsonplaceholder.typicode.com/comments'
    ).then((res)=>res.json());
    console.log(res);
  };

  useEffect(()=>{
    getData();
  },[])

API 추가함
그리고 나서 보니까

아까 500개 json에 있던것들을 다 불러옴

(APp.js)

const getData =async()=>{ //promise를 반환하는 비동기함수
    const res = await fetch('https://jsonplaceholder.typicode.com/comments'
    ).then((res)=>res.json());
    const initData = res.slice(0,20).map((it)=>{//20개 추리기
      return{
      author : it.email,
      content : it.body,
      emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
      created_date:new Date().getTime(),
      id:dataId.current++
      }
    })
    setData(initData);
  };

getData 함수를 만들어서 내장객체 fetch와 async, await를 이요해서 일기 데이터의 초기값을 정해봄


6.9 React Developer Tools

리액트 개발시에 효율성을 높여주는 개발자 도구 !

  • 크롬의 확장도구
    크롬 웹스토어에서 깔고 검사 들어가서 해주면

    이렇게 새로운 항목ㅇㅣ 뜸 위 사진은 component에 들어간 것

두번째

highlight눌러주면

글자 쓰면 색 하이라이트가 나온다는데 왜 난 안나오냐??


6.10 최적화 1 -useMemo

Memorization
-> 이미 계산해 본 연산결과를 기억해두었다가
똑같은 계산을 시키면 다시 연산을 안하고 기억해 두었던 데이터를 반환시키게 하는 방법

기분이 좋은 건 몇갠지, 안좋은건 몇갠지, 기분이 좋은거의 비율은 어케 되는지 -> 함수 구현

(App.js)에다가 구현

  const getDiaryAnalysis=()=>{
    console.log("일기 분석 시작");

    const goodCount = data.filter((it)=>it.emotion>=3).length;
    const badCount = data.length-goodCount;
    const goodRatio = (goodCount/data.length)*100;
    return {goodCount,badCount,goodRatio};
  }

  const {goodCount,badCount,goodRatio}=getDiaryAnalysis();

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate}/>
      <div>전체 일기 : {data.length}</div>
      <div>기분 좋은 일기 개수 : {goodCount}</div>
      <div>기분 나쁜 일기 개수 : {badCount}</div>
      <div>기분 좋은 일기 비율 : {goodRatio}</div>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
    </div>
  );


결과값은 잘 나온다

ㅅㅐ로 고침함녀

두번 나온다

한번 호출하고 분석하고 한번 더 호출하기 때문
->낭비 -> useMemo를 쓰자

useMemo=> 첫번째 인자로 콜백함수를 받아서 함수가 리턴하는 연산을 최적화 할 수 있도록 도와줌 & 두번째로 배열을 전달해야함

(App.js에 useMemo쓴거)

const getDiaryAnalysis=useMemo(
    ()=>{
    console.log("일기 분석 시작");

    const goodCount = data.filter((it)=>it.emotion>=3).length;
    const badCount = data.length-goodCount;
    const goodRatio = (goodCount/data.length)*100;
    return {goodCount,badCount,goodRatio};
    },[data.length]);

  const {goodCount,badCount,goodRatio}=getDiaryAnalysis; //꼭 함수 말고 값으로 해주자

6.11 최적화 2 - React.memo

setCount 하려고 하면
먼저 app componentcount값이 바뀌고 state가 업데이트 되어서 app component가 리랜드 됨 -> 부모 component가 리랜더 되면 자식 component도 리랜더 되기 때문에 countviewtextview도 리랜더 된다
=> 여기서 연산의 낭비 발생 !!

그러니까 업데이트 조건을 걸어주면 낭비가 줄어든다 !
=> 업데이트 조건 걸어주는게 React.memo

https://ko.legacy.reactjs.org/docs/getting-started.html -> 리액트 나무위키 급의 페이지

리액트는 고차 컴포넌트다 !
ㄴ 근데 고차 컴포넌트란?

낡은걸 주면 새걸 주는 좋은 친구인듯

똑같은 props를 받으면 똑같은 props를 내보냄=리랜더링 하지 않겠다

OptimizeTest.js 생성함

const OptimizeTest = ()=>{
    return <div style={{padding:50}}></div>
}

export default OptimizeTest;

App.js에 <OptimizeTest/>해주고 Crtl S 하면

상단에 줄이 생김 !!

import React, { useState,useEffect} from "react";

const Textview=({text})=>{
    return <div>{text}</div>
}

const CountView = ({count})=>{
    return <div>{count}</div>
}

const OptimizeTest = ()=>{

    const [count,setCount]=useState(1);
    const [text,setText]=useState("");

    return <div style={{padding:50}}>
      <div>
          <h2>count</h2>
          <CountView count={count}/>
          <button onClick={()=>setCount(count+1)}>+</button>
      </div>
      <div>
        <h2>text</h2>
        <Textview text={text}/>
        <input value={text} onChange={(e)=>setText(e.target.value)}/>
      </div>
    </div>
}

export default OptimizeTest;

OptimizeTest.js를 이렇게 바꿔줌 그랬더니

이렇게 count랑 text가 생겨났다

const Textview=({text})=>{
    useEffect(()=>{
        console.log(`Update :: Text : ${text}`);
    });
    return <div>{text}</div>;
};

const CountView = ({count})=>{
    useEffect(()=>{
        console.log(`Update :: Count : ${count}`);
    });
    return <div>{count}</div>;
};

console을 넣어줌

잘 나오는군 ㅎㅎ

메모 사용

const Textview=React.memo(({text})=>{
    useEffect(()=>{
        console.log(`Update :: Text : ${text}`);
    });
    return <div>{text}</div>;
});

prop인 text가 바뀌지 않으면 절대 랜더링이 안일어난다

카운트에도 사용했떠니

const Textview=React.memo(({text})=>{
    useEffect(()=>{
        console.log(`Update :: Text : ${text}`);
    });
    return <div>{text}</div>;
});

const CountView = React.memo(({count})=>{
    useEffect(()=>{
        console.log(`Update :: Count : ${count}`);
    });
    return <div>{count}</div>;
});


솔직히 뭐가 다른건지 잘 모르겠지만 콘솔은 잘 나온다

import React, { useState,useEffect} from "react";

const CounterA = React.memo(({count})=>{
    useEffect(()=>{
        console.log(`CounterA Update - count : ${count}`)
    })
    
    return <div>{count}</div>
});

const CounterB = React.memo(({obj})=>{
    useEffect(()=>{
        console.log(`CounterB Update - count : ${obj.count}`)
    })
    
    return <div>{obj.count}</div>
});

const OptimizeTest = ()=>{

const [count,setCount]=useState(1);
const [obj,setObj]=useState({
    count : 1,
});

    return (
    <div style={{padding:50}}>
      <div>
        <h2>Counter A</h2>
        <CounterA count = {count}/>
        <button onClick={()=> setCount(count)}>A button</button>
      </div>
      <div>
        <h2>Counter B</h2>
        <CounterB obj = {obj}/>
        <button onClick={()=> setObj({
            count : obj.count
            })}>B button</button>
      </div>
    </div>
    );
};

export default OptimizeTest;

OptimizeTest.js를 갈아엎음
CounterA랑 B에 React.memo잘 넣어주고 실행했는데

CounterA 누르면 아무것도 출력도 변화도 없다
CounterB 누르면 console에 찍히긴 함
-> props가 객체여서 (객체는 얕은비교여서 그럼)

값에 의한 비교가 아닌 주소에 의한 비교 !!

-last code-
(OptimizeTest.js)

import React, { useState,useEffect} from "react";

const CounterA = React.memo(({count})=>{
    useEffect(()=>{
        console.log(`CounterA Update - count : ${count}`)
    })
    
    return <div>{count}</div>
});

const CounterB = ({obj})=>{
    useEffect(()=>{
        console.log(`CounterB Update - count : ${obj.count}`)
    })
    
    return <div>{obj.count}</div>
};

const areEqual = (prevProps,nextProps)=>{
    if(prevProps.obj.count === nextProps.obj.count){
        return true; //이전 props와 현재 props가 같다 = 리랜더링을 일으키지 마라
    }
    return false; //이전 props와 현재 props가 다르다 = 리랜더링을 일으켜라
}

const MemoizedCounterB = React.memo(CounterB,areEqual); //리랜더링을 할지 말지 결정

const OptimizeTest = ()=>{

const [count,setCount]=useState(1);
const [obj,setObj]=useState({
    count : 1,
});

    return (
    <div style={{padding:50}}>
      <div>
        <h2>Counter A</h2>
        <CounterA count = {count}/>
        <button onClick={()=> setCount(count)}>A button</button>
      </div>
      <div>
        <h2>Counter B</h2>
        <MemoizedCounterB obj = {obj}/>
        <button onClick={()=> setObj({
            count : obj.count
            })}>B button</button>
      </div>
    </div>
    );
};

export default OptimizeTest;

ㄴ 이렇게 코드를 짰을 때

CounterB 버튼 눌러도 아무것도 변화 X 리랜더링 안하는것


6.12 최적화 3 - useCallback


(중요!)useCallback은 콜백의 메모이제이션된 버전을 반환할 것입니다.

-last code-
(App.js)

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';

const App=()=>{
  const [data,setData]=useState([]);

  const dataId=useRef(0);

  const getData =async()=>{ //promise를 반환하는 비동기함수
    const res = await fetch('https://jsonplaceholder.typicode.com/comments'
    ).then((res)=>res.json());

    const initData = res.slice(0,20).map((it)=>{//20개 추리기
      return{
      author : it.email,
      content : it.body,
      emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
      created_date:new Date().getTime(),
      id:dataId.current++
      };
    });
    setData(initData);
  };

  useEffect(()=>{
    getData();
  },[]);

  const onCreate=useCallback((author,content,emotion)=>{
    const created_date=new Date().getTime();
    const newItem={
      author,
      content,
      emotion,
      created_date,
      id:dataId.current
    };
    dataId.current+=1;
    setData((data)=>[newItem,...data]); //newItem추가 하고 원래 배열에 있던걸 하나하나 나열
  },[]);

  const onRemove=(targetId)=>{
    console.log(`${targetId}가 삭제되었습니다`);
    const newDiaryList=data.filter((it)=>it.id!==targetId);
    setData(newDiaryList);
  };

  const onEdit=(targetId,newContent)=>{
    setData(
      data.map((it)=>
        it.id===targetId?{...it, content:newContent}:it)
    );
  };

  const getDiaryAnalysis=useMemo(
    ()=>{
    const goodCount = data.filter((it)=>it.emotion>=3).length;
    const badCount = data.length-goodCount;
    const goodRatio = (goodCount/data.length)*100;
    return {goodCount,badCount,goodRatio};
    },[data.length]);

  const {goodCount,badCount,goodRatio}=getDiaryAnalysis;

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate}/>
      <div>전체 일기 : {data.length}</div>
      <div>기분 좋은 일기 개수 : {goodCount}</div>
      <div>기분 나쁜 일기 개수 : {badCount}</div>
      <div>기분 좋은 일기 비율 : {goodRatio}</div>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
    </div>
  );
};
export default App;

6.13 최적화 4 - 최적화 완성

나머지 최적화 하는 작업을 하였다 !!
onEdit과 onRemove를 최적화 시킴

(App.js)

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';

const App=()=>{
  const [data,setData]=useState([]);

  const dataId=useRef(0);

  const getData =async()=>{ //promise를 반환하는 비동기함수
    const res = await fetch('https://jsonplaceholder.typicode.com/comments'
    ).then((res)=>res.json());

    const initData = res.slice(0,20).map((it)=>{//20개 추리기
      return{
      author : it.email,
      content : it.body,
      emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
      created_date:new Date().getTime(),
      id:dataId.current++
      };
    });
    setData(initData);
  };

  useEffect(()=>{
    getData();
  },[]);

  const onCreate=useCallback((author,content,emotion)=>{
    const created_date=new Date().getTime();
    const newItem={
      author,
      content,
      emotion,
      created_date,
      id:dataId.current
    };
    dataId.current+=1;
    setData((data)=>[newItem,...data]); //newItem추가 하고 원래 배열에 있던걸 하나하나 나열
  },[]);

  const onRemove=useCallback((targetId)=>{
    setData(data=>data.filter((it)=>it.id!==targetId));
  },[]);

  const onEdit=useCallback((targetId,newContent)=>{
    setData((data)=>
      data.map((it)=>
        it.id===targetId?{...it, content:newContent}:it)
    );
  },[]);

  const getDiaryAnalysis=useMemo(
    ()=>{
    const goodCount = data.filter((it)=>it.emotion>=3).length;
    const badCount = data.length-goodCount;
    const goodRatio = (goodCount/data.length)*100;
    return {goodCount,badCount,goodRatio};
    },[data.length]);

  const {goodCount,badCount,goodRatio}=getDiaryAnalysis;

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate}/>
      <div>전체 일기 : {data.length}</div>
      <div>기분 좋은 일기 개수 : {goodCount}</div>
      <div>기분 나쁜 일기 개수 : {badCount}</div>
      <div>기분 좋은 일기 비율 : {goodRatio}</div>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
    </div>
  );
};
export default App;

(DiaryItem.js)

import React,{ useEffect, useRef, useState } from "react";

const DiaryItem=({
    onEdit,
    onRemove,
    author, 
    content, 
    created_date,
    emotion,
    id
})=>{
    useEffect(()=>{console.log(`${id}번 째 아이템 렌더 !`);
    });
    
    const [isEdit,setIsEdit]=useState(false);
    const toggleIsEdit=()=> setIsEdit(!isEdit);
    
    const localContentInput=useRef();
    const [localContent,setLocalContent]=useState("");
    
    const handleRemove=()=>{
        if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)){
            onRemove(id);
        }
    };

    const handlQuitEdit=()=>{
        setIsEdit(false);
        setLocalContent(content);
    };

    const handleEdit =()=>{
        if(localContent.length<5)
        {
            localContentInput.current.focus();
            return;
        }

        if(window.confirm(`${id}번 째 일기를 수정하시겠습니까?`))
        {
        onEdit(id,localContent);
        toggleIsEdit();
        }
    }

    return(
    <div className="DiaryItem">
        <div className="info">
            <span>작성자 : {author} | 감정점수 : {emotion}
            </span>
            <br/>
            <span className="date">
                {new Date(created_date).toLocaleString()}
            </span>
        </div>
        <div className="content">
            {isEdit?(
            <>
              <textarea 
              ref={localContentInput}
              value={localContent} 
              onChange={(e)=>setLocalContent(e.target.value)}/>
            </>
            ):(
            <>{content}</>
            )}
            </div>
            {isEdit?(
            <>
                <button onClick={handlQuitEdit}>수정 취소</button>
                <button onClick={handleEdit}>수정 완료</button>
        </>
        ):(
        <>
        <button onClick={handleRemove}>삭제하기</button>
        <button onClick={toggleIsEdit}>수정하기</button>
        </>
        )}
    </div>
    );
};

export default React.memo(DiaryItem);

6.14 복잡한 상태로 관리 로직 분리하기 -useReducer

App component 속 onCreate, onEdit, onRemove 와 같은 상태 변화 처리 함수들이 코드가 길어져서 복잡해보임 -> 바깥으로 분리하자 ! =useReducer 사용

여기서 {type} 이런 애들은 action 객체라고 부름 = 상태변화를 설명할 객체, 상태변화가 나타나면 이 객체들은

reducer로 보내짐-> switch문에서 나오는 반환값이 새로운 상태가 됨 => count에 state가 업데이트 됨!!

이번 시간에는 reducer 함수를 만들고 원래에 있던 함수 속 내용들을 지우면서 복잡해보이지 않게 해주는 작업을 함

const reducer=(state,action)=>{
  switch(action.type){
    case 'INIT':{
      return action.data //새로운 state
    }
    case 'CREATE':{
      const created_date=new Date().getTime();
      const newItem={
        ...action.data,
        created_date
      }
      return [newItem,...state];
    }
    case 'REMOVE':{
      return state.filter((it)=>it.id!==action.targetId);
    }
    case 'EDIT':{
      return state.map((it)=>it.id===action.targetId?
      {...it,content:action.newContent}:it
      );
    }
    default:
    return state;
  }
}

ㄴreducer 함수

-last code-

(App.js)

import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';

const reducer=(state,action)=>{
  switch(action.type){
    case 'INIT':{
      return action.data //새로운 state
    }
    case 'CREATE':{
      const created_date=new Date().getTime();
      const newItem={
        ...action.data,
        created_date
      }
      return [newItem,...state];
    }
    case 'REMOVE':{
      return state.filter((it)=>it.id!==action.targetId);
    }
    case 'EDIT':{
      return state.map((it)=>it.id===action.targetId?
      {...it,content:action.newContent}:it
      );
    }
    default:
    return state;
  }
}

const App=()=>{
  //const [data,setData]=useState([]);

  const [data,dispatch]=useReducer(reducer, [])
  
  const dataId=useRef(0);

  const getData =async()=>{ //promise를 반환하는 비동기함수
    const res = await fetch('https://jsonplaceholder.typicode.com/comments'
    ).then((res)=>res.json());



    const initData = res.slice(0,20).map((it)=>{//20개 추리기
      return{
      author : it.email,
      content : it.body,
      emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
      created_date:new Date().getTime(),
      id:dataId.current++
      };
    });

    dispatch({type:"INIT",data:initData})
  };

  useEffect(()=>{
    getData();
  },[]);

  const onCreate=useCallback((author,content,emotion)=>{

    dispatch({type:'CREATE',
    data:{author,content,emotion,id:dataId.current}
    });
    dataId.current+=1;
  },[]);

  const onRemove=useCallback((targetId)=>{
    dispatch({type:"REMOVE",targetId})
  },[]);

  const onEdit=useCallback((targetId,newContent)=>{

    dispatch({type:"EDIT",targetId,newContent})

  },[]);

  const getDiaryAnalysis=useMemo(
    ()=>{
    const goodCount = data.filter((it)=>it.emotion>=3).length;
    const badCount = data.length-goodCount;
    const goodRatio = (goodCount/data.length)*100;
    return {goodCount,badCount,goodRatio};
    },[data.length]);

  const {goodCount,badCount,goodRatio}=getDiaryAnalysis;

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate}/>
      <div>전체 일기 : {data.length}</div>
      <div>기분 좋은 일기 개수 : {goodCount}</div>
      <div>기분 나쁜 일기 개수 : {badCount}</div>
      <div>기분 좋은 일기 비율 : {goodRatio}</div>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
    </div>
  );
};
export default App;

6.15 컴포넌트 트리에 데이터 공급하기 - Context

이거를

이거로 바꾸면 좋다

강의에서는

이렇게 Context.Provider가 DiaryEditor과 DiaryList를 묶고 있는데
나는

왜 이러지?

오 나왔당

-last code-

export const DiaryStateContext = React.createContext();
export const DiaryDispatchContext = React.createContext();

추가해줌

return (
    <DiaryStateContext.Provider value={data}>
      <DiaryDispatchContext.Provider>
    <div className="App">
      <DiaryEditor onCreate={onCreate}/>
      <div>전체 일기 : {data.length}</div>
      <div>기분 좋은 일기 개수 : {goodCount}</div>
      <div>기분 나쁜 일기 개수 : {badCount}</div>
      <div>기분 좋은 일기 비율 : {goodRatio}</div>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
    </div>
      </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );

리턴 속에도 바꿔주었당

그랬더니

옹 ㅋ

(App.js)

import React, 
{ useCallback, 
  useEffect, 
  useMemo, 
  useReducer, 
  useRef, 
} from 'react';

import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';

const reducer=(state,action)=>{
  switch(action.type){
    case 'INIT':{
      return action.data //새로운 state
    }
    case 'CREATE':{
      const created_date=new Date().getTime();
      const newItem={
        ...action.data,
        created_date
      }
      return [newItem,...state];
    }
    case 'REMOVE':{
      return state.filter((it)=>it.id!==action.targetId);
    }
    case 'EDIT':{
      return state.map((it)=>it.id===action.targetId?
      {...it,content:action.newContent}:it
      );
    }
    default:
    return state;
  }
};

export const DiaryStateContext = React.createContext();
export const DiaryDispatchContext = React.createContext();

const App=()=>{
  const [data,dispatch]=useReducer(reducer, []);
  
  const dataId=useRef(0);

  const getData =async()=>{ //promise를 반환하는 비동기함수
    const res = await fetch('https://jsonplaceholder.typicode.com/comments'
    ).then((res)=>res.json());

    const initData = res.slice(0,20).map((it)=>{//20개 추리기
      return{
      author : it.email,
      content : it.body,
      emotion:Math.floor(Math.random()*5)+1, //1~5중 난수 발생
      created_date:new Date().getTime(),
      id:dataId.current++
      };
    });

    dispatch({type:"INIT",data:initData})
  };

  useEffect(()=>{
    getData();
  },[]);

  const onCreate=useCallback((author,content,emotion)=>{
    dispatch({type:'CREATE',
    data:{author,content,emotion,id:dataId.current}
    });
    dataId.current+=1;
  },[]);

  const onRemove=useCallback((targetId)=>{
    dispatch({type:"REMOVE",targetId})
  },[]);

  const onEdit=useCallback((targetId,newContent)=>{
    dispatch({type:"EDIT",targetId,newContent})
  },[]);

  const memoizedDispatches = useMemo(()=>{
    return {onCreate,onRemove,onEdit}
  },[]);

  const getDiaryAnalysis=useMemo(
    ()=>{
    const goodCount = data.filter((it)=>it.emotion>=3).length;
    const badCount = data.length-goodCount;
    const goodRatio = (goodCount/data.length)*100;
    return {goodCount,badCount,goodRatio};
    },[data.length]);

  const {goodCount,badCount,goodRatio}=getDiaryAnalysis;

  return (
    <DiaryStateContext.Provider value={data}>
      <DiaryDispatchContext.Provider value={memoizedDispatches}>
    <div className="App">
      <DiaryEditor onCreate={onCreate}/>
      <div>전체 일기 : {data.length}</div>
      <div>기분 좋은 일기 개수 : {goodCount}</div>
      <div>기분 나쁜 일기 개수 : {badCount}</div>
      <div>기분 좋은 일기 비율 : {goodRatio}</div>
      <DiaryList onEdit={onEdit} onRemove={onRemove} diarList ={data}/>
    </div>
      </DiaryDispatchContext.Provider>
    </DiaryStateContext.Provider>
  );
};
export default App;

(DiaryEditor.js)

import React, { useContext, useRef, useState } from "react";
import { DiaryDispatchContext } from "./App";

const DiaryEditor=()=>{

  const {onCreate}=useContext(DiaryDispatchContext)

    const authorInput=useRef();
    const contentInput=useRef();

    const [state,setState]=useState({
        author:"",
        content:"",
        emotion:1,
    });

    const handleChangeState =(e)=>{
        setState({
            ...state,
            [e.target.name]: e.target.value,
        });
    };

    const handleSubmit=()=>{
        if(state.author.length <1){ 
            authorInput.current.focus(); //ref객체는 현재 가리키는 값을 property로 불러와서 사용가능 
            return; //아니면 중단
        }

        if(state.content.length <5){
            contentInput.current.focus();
            return;
        }
        onCreate(state.author,state.content,state.emotion);
        alert("저장 성공");
        setState({
            author:"",
            content:"",
            emotion:1,
        });
    };

    return(
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
        ref={authorInput}
        name="author"
        value={state.author} 
        onChange={handleChangeState}
        placeholder="작성자"
        type="text"
        />
      </div>
      <div>
        <textarea name="content"
        ref={contentInput}
        value={state.content} //여러줄을 입력받을 수 있음
        onChange={handleChangeState}
        placeholder="일기"
        type="text"
        /> 
      </div>
      <div>
        <span>오늘의 감정 점수 : </span>
        <select 
        name="emotion" 
        value={state.emotion} 
        onChange={handleChangeState}
        >
            <option value={1}>1</option>
            <option value={2}>2</option>
            <option value={3}>3</option>
            <option value={4}>4</option>
            <option value={5}>5</option>
        </select>
      </div>
      <div>
        <button onClick={handleSubmit}>일기 저장하기</button>
      </div>
    </div>
    );
};
export default React.memo(DiaryEditor);

(DiaryList.js)

import { useContext } from "react";
import DiaryItem from "./DiaryItem";
import { DiaryStateContext } from "./App";

const DiaryList=()=>{
  const diaryList=useContext(DiaryStateContext);
    return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((it)=> (
          <DiaryItem key={it.id} {...it} />
        ))}
      </div>
    </div>
    );
};

DiaryList.defaultProps={
  diaryList:[],
};

export default DiaryList;

(DiaryItem.js)

import React,{ useContext, useRef, useState } from "react";
import { DiaryDispatchContext } from "./App";

const DiaryItem=({
    author, 
    content, 
    created_date,
    emotion,
    id
})=>{
    const {onRemove,onEdit}=useContext(DiaryDispatchContext)
    const [isEdit,setIsEdit]=useState(false);
    const toggleIsEdit=()=> setIsEdit(!isEdit);
    
    const localContentInput=useRef();
    const [localContent,setLocalContent]=useState("");
    
    const handleRemove=()=>{
        if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)){
            onRemove(id);
        }
    };

    const handlQuitEdit=()=>{
        setIsEdit(false);
        setLocalContent(content);
    };

    const handleEdit =()=>{
        if(localContent.length<5)
        {
            localContentInput.current.focus();
            return;
        }

        if(window.confirm(`${id}번 째 일기를 수정하시겠습니까?`))
        {
        onEdit(id,localContent);
        toggleIsEdit();
        }
    }

    return(
    <div className="DiaryItem">
        <div className="info">
            <span>작성자 : {author} | 감정점수 : {emotion}
            </span>
            <br/>
            <span className="date">
                {new Date(created_date).toLocaleString()}
            </span>
        </div>
        <div className="content">
            {isEdit?(
            <>
              <textarea 
              ref={localContentInput}
              value={localContent} 
              onChange={(e)=>setLocalContent(e.target.value)}/>
            </>
            ):(
            <>{content}</>
            )}
            </div>
            {isEdit?(
            <>
                <button onClick={handlQuitEdit}>수정 취소</button>
                <button onClick={handleEdit}>수정 완료</button>
        </>
        ):(
        <>
        <button onClick={handleRemove}>삭제하기</button>
        <button onClick={toggleIsEdit}>수정하기</button>
        </>
        )}
    </div>
    );
};

export default React.memo(DiaryItem);

10개의 댓글

comment-user-thumbnail
2023년 6월 3일

리액트 나무위키ㅋㅋㅋ 잘 복습하고 갑니당
이제 css 돌아가죠...?

1개의 답글
comment-user-thumbnail
2023년 6월 4일

Chilern과 Dariy를 기억하겠습니다.

2개의 답글
comment-user-thumbnail
2023년 6월 4일

사진이 있어서 구조 구성한 것을 보기에 편하네요. 감사합니다.

1개의 답글

일서연, 이서연, 삼서연 마음에 들어욯ㅎㅎㅎ 보니까 하면서도 오류가 많았네요... 오류 해결한다고 시간도 엄청 오래 걸렸을 거 같은데 수고 많았어여ㅜㅜ

1개의 답글