7일차 폴더구조와 props 및 컴포넌트 재사용성 (setState, 목록 보여주기/삭제하기)

osdsoonhyun·2023년 2월 14일
0

코드캠프

목록 보기
5/22
post-thumbnail
  1. setState의 원리
  2. 최신 데이터 다시 가져와 -> refetch
  3. 왜 목록에서 삭제가 안되지? -> key/index

포트폴리오 리뷰

컴포넌트 안에서 router 사용시 주의점

  • [boardId] 폴더 안에서 index.js 파일 안에서는 router.query.boardId의 값이 나오는 것은 알겠으나 과연 src/componeents/units/board 폴더 안에 <BoardDetail> 에서도 boardId를 알 수 있을까?

  • boardId 라는 값이 없어도 하나로 합쳐지는 파일이 index.js로 합쳐지므로(import 되므로) 실행될 때에는 합쳐지고 실행되기 때문에 boardId 값을 그대로 따라간다. 그래서 그 하위 파일(컴포넌트)에서도 boardId 값을 불러 사용할 수 있다.

  • 위에서 [boardId] 폴더 안에서는 detail 컴포넌트에서 boardId 값이 사용될 수 있다는 것을 확인했다.

  • 그렇다면 detail 컴포넌트가 new 폴더 안에서 사용될 때 boardId 값을 출력한다면 어떻게 될까?

  • 결론적으론, undefied 값이 출력된다.

따라서 컴포넌트가 어디에 import 되었는지를 잘 확인하고 router 부분을 사용해야 한다.
router는 주소 관련이라 결국 컴포넌트가 페이지에 합쳐졌을때 실행되는 것이기 때문에 최종 브라우저에 따라 router가 작동할지 여부를 가린다.

emotion 파일에 props 넘겨주기

emotion 파일에는 자바스크립트 코드가 없으므로 함수를 만들어주어 사용해야 한다. 템플릿 리터럴 사용

export const BlueButton = styled.button`
	font-size: ${(props)=> {
    	return props.rrr;
    }};
	background-color: ${(props)=> {
		return props.qqq;
    }};
`
  • 이때 화살표 함수에서 중괄호와 return 사이에 아무것도 없다면 중괄호와 return이 소괄호로 바뀌고 또한 소괄호가 특별한 의미가 없다면 생략이 가능하다.
  • 의미가 있다면 생략 불가(예를들어 객체가 들어왔을때 소괄호를 생략하면 객체의 중괄호가 함수의 중괄호로 의미가 변환되므로 생략 불가)
export const BlueButton = styled.button`
	font-size: ${props=> props.rrr};
	background-color: ${props=> props.qqq};
`

자바스크립트 변수에 true를 넘기고 싶다면 중괄호 안에 true를 써줘야 한다

  • container에서 mycolor를 state로 담아 <BoardWriteUi>에 props로 mycolor를 넘겨주고 다시 BoardWrite.presenter에서 props로 넘겨주어 BoardWrite.styles에서 받아 함수로 props 값을 삼항연사자로 나타낸다.

QUIZ) 작성자, 제목, 내용을 모두 입력하면 버튼이 노란색으로 바꾸기


export default function BoardWrite() {
  const [mycolor, setMycolor] = useState(false);

  const [나의함수] = useMutation(CREATE_BOARD);
  const [writer, setWriter] = useState('');
  const [title, setTitle] = useState('');
  const [contents, setContents] = useState('');

  ...

  const onChangeWriter = (event) => {
    setWriter(event.target.value);
    if (writer && title && contents) {
      setMycolor(true);
    }
  };

  const onChangeTitle = (event) => {
    setTitle(event.target.value);
    if (writer && title && contents) {
      setMycolor(true);
    }
  };

  const onChangeContents = (event) => {
    setContents(event.target.value);
    if (writer && title && contents) {
      setMycolor(true);
    }
  };

  return (
    <BoardWriteUI
      ...
      mycolor={mycolor}
    />
  );
}
  • 이렇게 하면 writer, title, contents를 채우면 버튼이 false->true로 바뀐다.
  • 그리고 만약에 지우게 된다면 다시 props를 업데이트해서 넘겨줘야하는데 이것은 어떻게 처리해야 하는지
  • 여기서 하나씩만 입력하면 변하지 않는데 이것은 setState의 동작원리를 파악해야 한다.

setState의 동작 원리 3단계

  • 카운트 올리기 버튼을 누르면 onClickCountUp이 실행이 되고 setCount가 실행되는데 그렇다고 count 값이 100으로 바뀌는 것이 아니다.
  • 그렇지 않고 setCount 값이 실행되면 기존의 count의 값(지금은 초기값 0)이랑 비교를 하고 값이 같으면 변경 없고 다르면 변경이 된다. 바뀐 다음에는 화면이 리렌더링이 된다.

처음에 그려지면 렌더링되고
state가 바뀌면 hook을 제외한 함수, return 등 자바스크립트 코드들이 다시 실행된다. 다시 바뀐 state로 실행되는데 이것을 리렌더링이라 한다.

  • 카운트 올리기 버튼을 누르면 초기값 0에서 setCount에 의해 100으로 바뀌는 것이 아니라 count 값이 100인 값으로 리렌더링 되어서 다시 보여지게 되는 것이다.
    -> 그럼 리렌더링 되었을때 화면이 다시 보여지게 되면 백엔드에서 데이터를 불러올때 그 데이터들도 새로 보여지게되는데 이렇게 불필요하게 재생성되는 것을 뒤에서 배울 memoization을 통해 해결할 수 있다.
import React, { useState } from 'react';

export default function CounterStatePage() {
  const [count, setCount] = useState(0);
  function onClickCountUp() {
    setCount(1);
    setCount(2);
    setCount(3);
    setCount(4);
    setCount(5);
  }
  return (
    <div>
      <div id='count'>{count}</div>
      <button onClick={onClickCountUp}>카운트 올리기!!</button>
    </div>
  );
}
  • 만약 setCount를 여러 개 넣으면 비효율적으로 작동하는데 이것을 막기 위해 react에서는 임시공간을 활용한다.
  • onClickCountUp 버튼이 실행되면 첫 번째 count 값인 1이 임시공간에 들어가고 2,3,4,5까지 들어간다.
    5번의 리렌더링이 일어나지 않게끔 바로 count 값을 바꾸지 않고 임시공간에 count 값이 들어가고 마지막 5가 들어갔을때 함수가 끝나면 state가 0->5로 바뀌었으니 리렌더링이 한 번만 실행된다.
  • setCount(1) ~ setCount(4)까지는 무시된다.

  • 위 코드와 같이 writer, title, contents에 모두 q가 들어가 있으나 아직 contents에 q는 임시공간에 들어가 있어서 조건이 false라 setMycolor 함수가 실행이 안된 것이다.
  • 두 번째 값을 넣어줘야만 setContents가 리렌더링이 되어 contents 값이 qw가 들어간다.
  • 그리고 비로소 mycolor 값이 true가 된다.

이것을 해결하기 위해 setState 가 위치에서 변경이 되고 리렌더링이 바로 실행되는 것이 아님을 알았다면 state 값이 아닌 event.target.value의 값으로 해줘야 한다.

정리
setState를 한다고 그 즉시 state가 변경되는 것이 아니고, 모아두었다가 한번에 처리한다.
그래서 setState를 여러 번 해줘도 중간 값은 임시저장소에 저장해두고 마지막에 해준 값만 출력이 된다.

map과 HTML 연결

  • aaa 배열 안에 [], 가 사라지고 HTML로 보여진다.

위 결과와 동일한 코드

const aaa = ["철수","영희","훈이"].map((el)=><div>{el}</div>)

백엔드에서 받아오는 데이터를 게시물 목록 형태로 보여줄 때에는 .map을 이용하여 화면에 뿌려주면 된다.

실무에서 백엔드 데이터 예제

// 백엔드에서 받아온 데이터라고 가정
// 컴포넌트 위에 만든 이유: 컴포넌트 리렌더링 돼도 다시 안만들어짐 => 변하지 않는 상수(emotion, queries..)
const FRUITS = [
  {number: 1,title:"레드향"},
  {number: 2,title:"사과"},
  {number: 3,title:"샤인머스켓"}
]
export default function MapFruitsPage() {


  const aaa = FRUITS.map(el=><div>{el.number} {el.title}</div>)
  return (
    <>
      {aaa}
      {FRUITS.map(el=><div>{el.number} {el.title}</div>)}
    </>
  );
}

map을 사용한 목록 출력 _fetchBoard

데이터 목록 삭제하기

  • mutation deleteBoard 사용


1. playground에서 한 번 해보고 복사해서 가져오기
2. 변경된 값 $변수로 변환
3. 타입 적어줘야할 것 윗줄에 적어주기

삭제를 클릭하면 onClickDelete가 실행된다
함수가 실행되면 함수 안에 이벤트가 들어오고 event.target하면 태그들이 들어온다.
event.target.id를 가져오고 Html을 가져온 것이므로 string -> number로 타입 변환해주고 사용한다.

삭제버튼 클릭 -> 백엔드에 삭제 요청 api -> 삭제하고 응답 받음 -> 정상적으로 삭제됨
그러나 데이터베이스에만 삭제 되었으나 내 화면에서는 삭제가 되지 않음. 새로고침하면 삭제가 됨.

이를 해결하기 위해 삭제한 이후에 다시 FETCH_BOARDS를 요청해야 한다.

refetchQueries

fetch 했던 것을 다시 fetch한다.

삭제 버튼 클릭 -> 백엔드에 삭제 요청 -> DB에서 삭제 -> refetch 안에 있는 것을 새롭게 다시 요청 -> 화면에 새로 보여줌(삭제된 것 제외)

삭제 버튼 누르면 mutation 1번, query 1번 요청이 간다.

하지만 이렇게 하면 체크박스는 사라지지 않는다
이유는 map은 효율성을 위해서 전체를 기억하고 있다 여기서 새롭게 바꾸기 위해서는 key를 주어야 한다.

정리
게시물을 수정/ 삭제 했을 때 변경된 데이터가 반영 된 모습을 보여주기 위해서는 refetchQueries를 이용한다.
refetchQueries를 사용하면, mutation 요청이 완료된 후 바로 데이터를 다시 요청해서 refresh된 데이터를 받아올 수 있다.

map key

key는 map에 고유한 값을 가지고 있어야 한다.

  • 주의사항
    - map은 index 값을 가지고 있어서 key에 고유한 값으로 map의 index를 주게 되면 삭제 버튼을 누르고 다시 fetch가 되면 아래 것이 위로 올라오면서 삭제버튼의 인덱스가 그대로 살아있으므로 삭제가 안된줄 안다.(에러 발생)

key 값으로는 index 값이 아닌 중복되지 않은 고유한 값을 사용해야 한다.

소소한 꿀팁

  • 이렇게 되어있다면 app.js는 aaa 폴더 안에 index.js 파일이라는 것과 결과는 동일하다.

  • map을 활용하여 보여줄 때 fragment <>...</> 을 사용했는데 빈 fragment에는 key와 같은 속성을 줄 수 없으므로 이때에는 <Fragment key={el.number}>...</Fragment>를 사용하면 된다.

map의 key 값은 문자열로 주는 것이 좋다.

어떠한 문제가 발생했을 때 어떠한 것의 작동원리를 떠올리면 해결방법이 떠오른다.

0개의 댓글