[Project-심플다이어리] 리스트 렌더/추가/수정/삭제

배열에 직접 추가하고 삭제하는 것이 아님
=> 기존 data에 새 데이터를 합치거나 삭제 데이터를 빼준 데이터를 새로운 변수에 담아서 setData를 통해 다시 셋팅(업데이트)함

알게된 것

  • props 넘기고 사용하기 (+한단계 더 안에있는 컴포넌트까지 props전달)
  • useRef() 활용
  • filter 활용
  • state를 toggle(반전연산)함수로 만들어 사용

  • 일기를 작성해주는 에디터컴포넌트와 리스트를 보여주는 리스트컴포넌트로 나뉜다.
  • App컴포넌트에서 일기data를 관리한다

컴포넌트&데이터 구조

  • 초기값 data = [item1]
  • <DiaryEditor props={setData}/> setData를 전달하여 DiaryEditor에서 새로운 일기 작성을 하면 App컴포넌트의 data에 추가가 된다. => data = [item1, item2]
  • 추가된 data를 <DiaryList props={data} />로 전달하여 리스트에 data를 적용


1. props전달

DiaryList 컴포넌트 생성 후 props로 리스트를 넘겨서 사용할 수 있다.

//DiaryList.js
const DiaryList = ({ diaryList }) => {
    return (
        <div className="DiaryList">
            <h2>일기 리스트</h2>
            <h4>{diaryList.length}개의 일기가 있습니다.</h4>
            <div>
                {diaryList.map((item) => (
                    <div key={item.id}>
                        <div>작성자 : {item.author}</div>
                        <div>일기 : {item.content}</div>
                        <div>감정 : {item.emotion}</div>
                        <div>작성 시간 : {item.created_date}</div>
                    </div>
                ))}
            </div>
        </div>
    )
}

DiaryList.defaultProps = {
	diaryList: [],
}
  • diaryList.length 로 사용하고있는데 리스트가 없다면?
    => DiaryList가 undefined 라면 .defaultProps 를 사용하여 별도의 처리로 빈 배열을 갖도록 하여 버그가 생기지않도록 해준다

  • map사용 시 key값을 넘겨줘야 하는데 콜백함수에서 각 아이템의 index로 넘겨줄 수 있지만 나중에 수정, 삭제 시에 문제가 생길 수 있음
    => 데이터에 id값이 있다면 id를 사용할것을 추천!


item컴포넌트 분할

데이터 리스트를 수정, 삭제를 DiaryList 컴포넌트 안에서 전부 구현하는게 아닌 컴포넌트를 분할해 주는게 좋다.

//DiaryList.js
const DiaryList = ({ diaryList }) => {
    return (
        <div className="DiaryList">
            <h2>일기 리스트</h2>
            <h4>{diaryList.length}개의 일기가 있습니다.</h4>
            <div>
                {diaryList.map((item) => (
                    <DiaryItem  key={item.id} {...item}/>
                ))}
            </div>
        </div>
    )
}
  • DiaryItem 컴포넌트로 key와 item의 요소들을 props로 전달
  • 전개연산자를 사용하여 요소들을 풀어서 가져갈 수 있음
//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>
    )
}
  • new Date().toLocaleStriong() : 가져온 created_date(ms로 계산된 시간)을 'yyyy.mm.dd.time'로 나타내줌


2. 아이템 추가

  • onCreate함수를 DiaryEditor컴포넌트의 props로 전달
//App.js
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])
  }
  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate} />
      <DiaryList diaryList={data} />
    </div>
  );
}
  • 일기 리스트를 담고있을 data, 일기 리스트를 셋팅해줄 setData
  • dataId = useRef(0) : useRef를 사용하여 dataId에 초기값 0인 변경가능한 값을 담아줌
  • DiaryEditor에서 작성한 author, content, emotion을 인수로 전달 받아서 onCreate작업을 처리함
  • dataId.current : useRef의 .current는 변경 가능한 값을 담고있음
    => 컴포넌트가 한번 업데이트 될 때 마다 담고있는 값에 +1을 해줌
  • setData([newItem, ...data]) : 최신작성글을 앞으로 놓기 위해 newItem을 0인덱스에 놓고 전개연산자로 기존 data를 풀어줌
  • 그렇게 셋팅된 data를 diaryList컴포넌트로 넘겨준다


3. 아이템 삭제

  • DiaryItem 컴포넌트에서 삭제버튼 생성
  • 아이템 추가처럼 삭제함수(onRemove)를 DiaryList를 통해 DiaryList 컴포넌트로 전달
//App.js
const onRemove = (targetId) => {
    const newDiaryList = data.filter((item) => item.id !== targetId)
    setData(newDiaryList)
}

return (
    <div className="App">
      <DiaryEditor onCreate={onCreate} />
      <DiaryList onRemove={onRemove} diaryList={data}/>
    </div>
  );
//DiaryItem.js
const handleRemove = () => {
	if(window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
		onRemove(id)
	}
}

<button onClick={() => {handleRemove}}>삭제하기</button>
  • window.confirm() : 예(true), 아니오(false)를 반환해주는 컨펌 창을 띄워줌
  • true면 1번에서 props로 받아온 id(해당 아이템의 id) onRemove의 인수로 넘겨줌
  • data.filter() : item의 id가 해당 아이템의 id가 아니면 newDiaryList로 새로운 배열을 만들어 줌
    => 클릭한 요소의 id인 아이템만 제외하고 새로운 배열로 생성
  • setData() : 새로운 배열을 업데이트 시켜줌


4. 아이템 수정

  • DiaryItem 컴포넌트에 수정버튼 생성
  • App.js에서 수정함수(onEdit)를 DiaryList를 통해 DiaryItem 컴포넌트로 전달
  • 수정하고있을 때(isEdit상태) 보여지는 버튼도 다르게 변경
//App.js
const onEdit = (targetId, newContent) => {
    setData(
        data.map((item) =>
            item.id === targetId ? {...item, content: newContent} : item
        )
    )
  }
return (
    <div className="App">
      <DiaryEditor onCreate={onCreate} />
      <DiaryList onRemove={onRemove} diaryList={data} onEdit={onEdit}/>
    </div>
  );
//DiaryItem.js
const [isEdit, setIsEdit] = useState(false);
const toggleIsEdit = () => setIsEdit(!isEdit)

const [localContent, setLocalContent] = useState(content);
const localContentInput = useRef()

const handleQuitEdit = () => {
	setIsEdit(false)
    setLocalContent(content)
}

const handleEdit = () => {
	if(localContent.length < 5) {
		return localContentInput.current.focus();
	}
	if(window.confirm(`${id}번째 일기를 수정하시겠습니까?`)) {
		onEdit(id, localContent)
		toggleIsEdit()
	}
}

//...생략
  • setIsEdit(!isEdit) : toggleIsEdit을 실행할때마다 isEdit을 반대로 업데이트 함
  • localContent로 수정할 일기 내용을 불러오고 저장함
  • handleQuitEdit() : 수정중일 때 수정을 취소하면서 !isEdit상태로 바꾸며, localContent에 수정중이던 text를 기존 content로 다시 넣어줌
  • onEdit() : 컨펌창 예(true) 클릭 시 id와 localContent(수정된 텍스트)를 전달해줌


공부하며 정리&기록하는 ._. 씅로그

좋은 웹페이지 즐겨찾기