ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 개발공부 35일차 [유데미 리엑트 강의]
    개인공부 2023. 3. 3. 22:20

    리엑트에서 DOM조작하기

    focus()

    input태그를 마우스로 클릭하여 입력상태로 만든것을 포커스를 얻었다고 한다.

    그리고 입력상태를 떠난것을 포커스가 벗어났다고 한다.

    import { useState, useRef } from "react";
    
    const DiaryEditor = () => {
    
      const authorInput = useRef()
      const contentInput = useRef()
    
      const [state, setState] = useState({
        author: "",
        content: "",
        emotion: 1,
      })
    
      const handleChangeState = (e) => {
        setState({
          ...state,
          //author:state.author
          //content:state.content
          [e.target.name]: e.target.value
          //author:author자리에 입력된 바뀐 값
          //content:content자리에 입력된 바뀐 값
        })
      }
    
      const handleSubmit = () => {
        if (state.author.length < 1) {
          authorInput.current.focus();
          //authorInput.current -> authorInput을 가리킴
          return;
        }
        if (state.content.length < 5) {
          contentInput.current.focus();
          return;
        }
        alert("저장 성공!")
        //1번째 조건으로 author의 값의 길이가 1을 넘지 못하면 authorinput태그가 focus되고 
        //2번째 조건으로 content의 값의 길이가 5을 넘지 못하면 contentinput태그가 focus되고
        //둘다 조건이 true일때 "저장 성공!"팝업창이 뜬다.
      };
    
      return (
        <div className="DiaryEditor">
          <h2>오늘의 일기</h2>
          <div>
            <input
              ref={authorInput}
              name="author"
              value={state.author}
              onChange={handleChangeState}
            />
          </div>
          <div>
            <textarea
              ref={contentInput}
              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>
            <button onClick={handleSubmit}>저장하기</button>
          </div>
        </div>
      )
    }
    export default DiaryEditor;

    리엑트에서 리스트 렌더링 하기.

    new Date().getTime()

    → 현재시간을 밀리초로 만든다.

    1. 예제 리스트를 App.js에 만들고 props로 DiaryList.js에 보낸다.
    //App.js
    import './App.css';
    import DiaryEditor from './DiaryEditor';
    import DiaryList from './DiaryList';
    
    const dummyList = [
      {
        id: 1,
        author: "이건1",
        content: "예시다1",
        emotion: 1,
        created_date: new Date().getTime()
      },
      {
        id: 2,
        author: "이건2",
        content: "예시다2",
        emotion: 2,
        created_date: new Date().getTime()
      },
      {
        id: 3,
        author: "이건3",
        content: "예시다3",
        emotion: 3,
        created_date: new Date().getTime()
      },
    ]
    
    function App() {
      return (
        <div className="App">
          <DiaryEditor />
          <DiaryList diaryList={dummyList} />
        </div>
      );
    }
    
    export default App;
    //DiaryList.js
    const DiaryList = ({ diaryList }) => {
      console.log(diaryList.length)
      return (
        <div className="DiaryList">
          <h2>일기 리스트</h2>
          <h4>{diaryList.length}개의 일기가 있습니다.</h4>
        </div>
      )
    }
    
    export default DiaryList

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

    1. App.js가 DiaryList,DiaryEditor가 사용할 일기데이터를 가지고 있고 useState로 초기값을 빈배열로 시작하게 한다.
    const [data, setData] = useState([])

      2. DiaryList에게는 현재 data를 넘겨주기만 하면된다.

    <DiaryList diaryList={data} />

       3. DiaryEditor이 변경이 되어 저장을 하면 DiaryList에 반영이 되도록 하려면 data의 값이 현재의 값으로 바뀌면되고 그렇게 되면 DiaryList의 data의 값도 바뀌게 된다.

    const [data, setData] = useState([])

       4. onCreate함수로 새로운 일기를 추가 할 수 있도록 한다.

           - DiaryEditor의 onCreate함수를 App.js에서 props로 받는다.

    const DiaryEditor = ({ onCreate }) => {}

          - andleSubmit 함수가 실행되어 저장이 이루어졌을때 onCreate함수를 호출을 한다.

    const handleSubmit = () => {
        if (state.author.length < 1) {
          authorInput.current.focus();
          //authorInput.current -> authorInput을 가리킴
          return;
        }
        if (state.content.length < 5) {
          contentInput.current.focus();
          return;
        }
    
        onCreate(state.author, state.content, state.emotion)
        alert("저장 성공!")

          - 함수가 호출이 될때 author,content,emotion 현재의 값을 받아서 시간, ID값을 추가해서 newItem을 만든다.

    const onCreate = (author, content, emotion) => {
        //author,content,emotion를 파라미터로 받아온다.
        const created_date = new Date().getTime();
        const newItem = {
          author,
          content,
          emotion,
          created_date,
          id: dataId.current
        }
        dataId.current += 1
        //dataId의 초기값으로 0을 줬고 새로운 Item마다 +1이되어 아이디가 부여된다.
        setData([newItem, ...data])
        //원래 있던 data위로 새로운 newItem이 들어오게된다.

    <전체코드>

    더보기
    //App.js
    
    import { useRef, useState } from 'react';
    import './App.css';
    import DiaryEditor from './DiaryEditor';
    import DiaryList from './DiaryList';
    
    // const dummyList = [
    //   {
    //     id: 1,
    //     author: "이건1",
    //     content: "예시다1",
    //     emotion: 1,
    //     created_date: new Date().getTime()
    //   },
    //   {
    //     id: 2,
    //     author: "이건2",
    //     content: "예시다2",
    //     emotion: 2,
    //     created_date: new Date().getTime()
    //   },
    //   {
    //     id: 3,
    //     author: "이건3",
    //     content: "예시다3",
    //     emotion: 3,
    //     created_date: new Date().getTime()
    //   },
    // ]
    
    function App() {
    
      const [data, setData] = useState([])
      //일기데이터를 저장할꺼기 때문에 배열로 초기값을 만든다.
    
      const dataId = useRef(0)
      //useRef는 매번 렌더링을 할때 동일한 ref 객체를 제공한다.(리렌더링에 관여x)
    
      const onCreate = (author, content, emotion) => {
        //author,content,emotion를 파라미터로 받아온다.
        const created_date = new Date().getTime();
        const newItem = {
          author,
          content,
          emotion,
          created_date,
          id: dataId.current
        }
        dataId.current += 1
        //dataId의 초기값으로 0을 줬고 새로운 Item마다 +1이되어 아이디가 부여된다.
        setData([newItem, ...data])
        //원래 있던 data위로 새로운 newItem이 들어오게된다.
      }
    
      return (
        <div className="App">
          <DiaryEditor onCreate={onCreate} />
          <DiaryList diaryList={data} />
        </div>
      );
    }
    
    export default App;
    //DiaryEditor.js
    
    import { useState, useRef } from "react";
    
    const DiaryEditor = ({ onCreate }) => {
    
      const authorInput = useRef()
      const contentInput = useRef()
    
      const [state, setState] = useState({
        author: "",
        content: "",
        emotion: 1,
      })
    
      const handleChangeState = (e) => {
        setState({
          ...state,
          //author:state.author
          //content:state.content
          [e.target.name]: e.target.value
          //author:author자리에 입력된 바뀐 값
          //content:content자리에 입력된 바뀐 값
        })
      }
    
      const handleSubmit = () => {
        if (state.author.length < 1) {
          authorInput.current.focus();
          //authorInput.current -> authorInput을 가리킴
          return;
        }
        if (state.content.length < 5) {
          contentInput.current.focus();
          return;
        }
    
        onCreate(state.author, state.content, state.emotion)
        alert("저장 성공!")
        //1번째 조건으로 author의 값의 길이가 1을 넘지 못하면 authorinput태그가 focus되고 
        //2번째 조건으로 content의 값의 길이가 5을 넘지 못하면 contentinput태그가 focus되고
        //둘다 조건이 true일때 onCreate함수가 실행이되고 "저장 성공!"팝업창이 뜬다.
        setState({
          author: "",
          content: "",
          emotion: 1,
          //일기 작성 후 author, content는 공백으로 이모션은 1점으로 바뀌게해서 기본값으로 초기화 시킨다.
        })
      };
    
      return (
        <div className="DiaryEditor">
          <h2>오늘의 일기</h2>
          <div>
            <input
              ref={authorInput}
              name="author"
              value={state.author}
              onChange={handleChangeState}
            />
          </div>
          <div>
            <textarea
              ref={contentInput}
              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>
            <button onClick={handleSubmit}>저장하기</button>
          </div>
        </div>
      )
    }
    export default DiaryEditor;

    1. DiaryItem.js에 삭제하기 버튼을 만든다.
    2. 버튼에 이벤트핸들러를 만든다.
    <button onClick={() => {
            console.log(id)
          }}>삭제하기</button>
        </div>
    //삭제하기 눌렀을때 콘솔에 고유아이디가 뜬다.

       3. DiaryItem상수에 onDelete를 넣어주고 onClick이벤트가 실행이 됬을때 onDelete(id)함수가 실행이 된다.

    //DiaryItem.js
    const DiaryItem = ({
      onDelete,
      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>
          <button onClick={() => {
            if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
              //window.confirm은 해당 이벤트를 눌렀을때 팝업창이 뜬다.
              onDelete(id)
            }
    
          }}>삭제하기</button>
        </div>
      )
    }
    
    
    
    export default DiaryItem;

       4. DiaryList.js에서 diaryList의 콜백함수로 DiaryItem에 onDelete함수도 같이 실행 될 수 있도록 한다.

    //DiaryList.js
    import DiaryItem from "./DiaryItem.js";
    
    const DiaryList = ({ onDelete, diaryList }) => {
      console.log(diaryList)
      return (
        <div className="DiaryList">
          <h2>일기 리스트</h2>
          <h4>{diaryList.length}개의 일기가 있습니다.</h4>
          <div>
            {diaryList.map((it) => (
              <DiaryItem key={it.id} {...it} onDelete={onDelete} />
            ))}
          </div>
        </div>
      )
    }
    DiaryList.defaultProps = {
      diaryList: []
      //App.js에서 diaryList를 받아올때 혹시나 undefined로 전달될 경우 에러가 뜨는걸 대비해서 빈배열을 넣어준다. 
    }
    
    export default DiaryList;

       5. App.js에서 onDelete함수를 만든다. 

    const onDelete = (targetId) => {
        console.log(`${targetId}가 삭제되었습니다.`)
        const newDiartList = data.filter((it) => it.id !== targetId)
        //targetId가 아닌것만 필터링해서 배열로 바꾼다.
        setData(newDiartList)
        //필터링된 배열을 setData에 넣어서 data의 상태를 변화시킨다.
      }

    setData로 data의 상태를 변화시키게되면 자동으로 DiaryList의 data도 변화된 data가 된다.


    리스트 데이터 수정하기

    1. 수정하기 버튼을 만든다.
    2. state만들기
    //DiaryItem.js
    const [isEdit, setIsEdit] = useState(false)
      //불리언값으로 수정중인지 수정중이 아닌지 분리해주는 isEdit스테이츠를 만듬.
    	//isEdit의 기본값을 false로 지정한다.
      const toggleIsEdit = () => setIsEdit(!isEdit)
      //toggleIsEdit이 호출이 되면 setIsEdit이 not연산을 통해서 isEdit이 
      //false였다면 true로 바뀌고 true였다면 false로 바뀌는 토글이 일어난다.

      3. 토글연산을 수정하기에 onClick이벤트로 넣어준다.

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

      4. isEdit값이 수정하기를 누르면 true값을 갖는다면 수정중인걸로 인지해서 content값을 수정하게한다.

    <div className="content">
            {isEdit ? (
              <>
                <textarea/>
              </>
            ) : (
              <>{content}</>)}
    </div>
    //isEdit이 true라면 textarea가 생성이되고 false라면 content값이 그대로 보여진다.

      5. 수정폼에 입력하는 데이터도 리엑트에서 state로 핸들링하게 해준다.

    const [localContent, setLocalContent] = useState(content);
      //localContent값의 초기값을 content의 값으로 지정해서 수정하기를 누르면 
      //기존에 content값이 유지된채로 textarea가 열린다.

      6. textarea에 state가 핸들링 할 수 있도록 value값과 onChange 이벤트를 넣어준다.

    <div className="content">
            {isEdit ? (
              <>
                <textarea
                  value={localContent}
                  onChange={(e) => setLocalContent(e.target.value)} />
              </>
            ) : (
              <>{content}</>)}
    </div>
        //value값에 localContent에 기존 localContent값이 들어오고 수정이되면 
        //setLocalContent값에 매핑시켜서 사용할 수 있게 만든다.

      7. 수정하기를 눌렀을때 버튼명이 달라지게 한다.

    {isEdit ? (<>
            <button onClick={toggleIsEdit}>수정 취소</button>
            <button>수정 완료</button>
          </>) : (
            <>
              <button onClick={handleRemove}>삭제하기</button>
              <button onClick={toggleIsEdit}>수정하기</button>
            </>
          )}

      8. 수정하기를 누른 후 수정할 내용을 적고 수정취소를 눌렀다가 다시 수정하기를 눌렀을때
    content값이 수정취소를 눌러도 초기화가 되지않기 때문에 content값을 초기화해주는 함수를 만들어준다.

    const handleQuitEdit = () => {
        setIsEdit(false);
        setLocalContent(content)
        //setIsEdit이 false라면 (수정취소를 누른 상태라면)
        //setLocalContent의 값이 content가 되어서 다시 수정하기를 눌렀을때 
        //content의 값만 뜨게된다.
      }
    <button onClick={handleQuitEdit}>수정 취소</button>

     이 함수는 수정취소버튼을 눌렀을때 실행되게 해서 수정취소를 누르면 content의 값이 초기화가 된다.

     

      9. 수정완료를 눌렀을때 수정된값이 보여지게 하기.DiaryEditor에서 App.js까지 전달하려면 App.js에서 수정하는 함수를 만들어서
          DiaryItem.js로 보내줘야 한다.
          리엑트의 특성상 데이터는 위에서아래로 이벤트는 아래에서 위로올라간다.

    const onEdit = (targetId, newContent) => {
        //수정하는ID, 수정하는 Content를 받는다.
        setData(
          data.map((it) => 
    			it.id === targetId ? { ...it, content: newContent } : it)
        )//map내장함수를 이용해서 모든요소를 순회하면서 새로운 배열을 만들어서 setData에 전달한다.
        //모든요소에 수정대상이라면 (it.id가 선택한 id라면)모든 배열에 
        //content를 newContent로 교체한다. 대상이 아니라면 원래있던 데이터를 리턴해준다.
      }

      10. onEdit함수를 DiaryItem의 부모인 DiaryList에 넣어준다.

    //App.js
    
    return (
        <div className="App">
          <DiaryEditor onCreate={onCreate} />
          <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
        </div>
      );

      11. DiaryList에도 DiaryItem로 전달하기 위해 onEdit을 props로 넣어준다.

    //DiaryList.js
    
    const DiaryList = ({ onEdit, onRemove, diaryList }) => {
      console.log(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>
      )
    }

      12. DiaryItem에서 onEdit 을 props로 받아와서 onEdit 을 호출한다.

    const DiaryItem = ({
      onEdit,
    
    const localContentInput = useRef()
    
    const handleEdit = () => {
        if (localContent.length < 5) {
          localContentInput.current.focus();
          return;
        }
        if (window.confirm(`${id}번 째 일기를 수정하시겠습니까?`)) {
          onEdit(id, localContent)
          toggleIsEdit();
    			//수정후에는 toggleIsEdit함수를 실행시켜서 수정폼을 닫아줘야한다.
        }
      }

    localContentInput는 localContent.length < 5인 조건이 실행이 되면 매핑해줄 textarea에 넣어준다.

    <textarea
      ref={localContentInput}
      value={localContent}
      onChange={(e) => setLocalContent(e.target.value)} />

    <완성코드>

    더보기
    //App.js
    import { useRef, useState } from 'react';
    import './App.css';
    import DiaryEditor from './DiaryEditor';
    import DiaryList from './DiaryList';
    
    // const dummyList = [
    //   {
    //     id: 1,
    //     author: "이건1",
    //     content: "예시다1",
    //     emotion: 1,
    //     created_date: new Date().getTime()
    //   },
    //   {
    //     id: 2,
    //     author: "이건2",
    //     content: "예시다2",
    //     emotion: 2,
    //     created_date: new Date().getTime()
    //   },
    //   {
    //     id: 3,
    //     author: "이건3",
    //     content: "예시다3",
    //     emotion: 3,
    //     created_date: new Date().getTime()
    //   },
    // ]
    
    function App() {
    
      const [data, setData] = useState([])
      //일기데이터를 저장할꺼기 때문에 배열로 초기값을 만든다.
    
      const dataId = useRef(0)
      //useRef는 매번 렌더링을 할때 동일한 ref 객체를 제공한다.(리렌더링에 관여x)
    
      const onEdit = (targetId, newContent) => {
        //수정하는ID, 수정하는 Content를 받는다.
        setData(
          data.map((it) => it.id === targetId ? { ...it, content: newContent } : it)
        )//map내장함수를 이용해서 모든요소를 순회하면서 새로운 배열을 만들어서 setData에 전달한다.
        //모든요소에 수정대상이라면 (it.id가 선택한 id라면)모든 배열에 content를 newContent로 교체한다. 대상이 아니라면 원래있던 데이터를 리턴해준다.
      }
      //DiaryEditor에서 App.js까지 전달하려면 App.js에서 수정하는 함수를 만들어서
      //DiaryItem.js로 보내줘야 한다.
    
      const onCreate = (author, content, emotion) => {
        //author,content,emotion를 파라미터로 받아온다.
        const created_date = new Date().getTime();
        const newItem = {
          author,
          content,
          emotion,
          created_date,
          id: dataId.current
        }
        dataId.current += 1
        //dataId의 초기값으로 0을 줬고 새로운 Item마다 +1이되어 아이디가 부여된다.
        setData([newItem, ...data])
        //원래 있던 data위로 새로운 newItem이 들어오게된다.
      }
      const onRemove = (targetId) => {
        console.log(`${targetId}가 삭제되었습니다.`)
        const newDiartList = data.filter((it) => it.id !== targetId)
        //targetId가 아닌것만 필터링해서 배열로 바꾼다.
        setData(newDiartList)
        //필터링된 배열을 setData에 넣어서 data의 상태를 변화시킨다.
      }
    
      return (
        <div className="App">
          <DiaryEditor onCreate={onCreate} />
          <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
        </div>
      );
    }
    
    export default App;
    //DiaryItem.js
    import { useRef, useState } from "react"
    
    const DiaryItem = ({
      onEdit,
      onRemove,
      author,
      content,
      created_date,
      emotion,
      id }) => {
    
      const [isEdit, setIsEdit] = useState(false)
      //불리언으로 수정중인지 ? 수정중이 아닌지 분리해주는 isEdit스테이츠를 만듬.
      const toggleIsEdit = () => setIsEdit(!isEdit)
      //toggleIsEdit이 호출이 되면 setIsEdit이 not연산을 통해서 isEdit이 
      //false였다면 true로 바뀌고 true였다면 false로 바뀌는 토글이 일어난다.
    
      const [localContent, setLocalContent] = useState(content);
      //localContent값의 초기값을 content의 값으로 지정해서 수정하기를 누르면 
      //기존에 content값이 유지된채로 textarea가 열린다.
    
      const localContentInput = useRef()
    
      const handleRemove = () => {
        if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
          //window.confirm은 해당 이벤트를 눌렀을때 팝업창이 뜬다.
          onRemove(id)
        }
      }
    
      const handleQuitEdit = () => {
        setIsEdit(false);
        setLocalContent(content)
        //setIsEdit이 false라면 (수정취소를 누른 상태라면)
        //setLocalContent의 값이 content가 되어서 다시 수정하기를 눌렀을때 
        //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={handleQuitEdit}>수정 취소</button>
            <button onClick={handleEdit}>수정 완료</button>
          </>) : (
            <>
              <button onClick={handleRemove}>삭제하기</button>
              <button onClick={toggleIsEdit}>수정하기</button>
            </>
          )}
    
        </div>
      )
    }
    
    
    
    export default DiaryItem;

    리엑트 뿌셔 ... 진짜..

    너무 어려워...뿌셔버릴꺼야....

    여기저기 들어가고 경로도 모르겠고 다 모르겠고 알다가도 모르겠도 아 ~~~조정래~~~

     

Designed by Tistory.