리액트로 데이터 다루기

배열 렌더링하기

배열을 렌더링할 땐 key를 기억

  • map을 통해 컴포넌트를 만들 때는 고유한 값을 가진 id를 지정해야 한다.
  • 배열의 idx는 key로 사용할 수 없음
function ReviewList({ items, onDelete }) {
  return (
    <ul>
      {items.map((item) => {
        return (
          **<li key={item.id}>**
            <ReviewListItem
              item={item}
              onDelete={onDelete}
            />
            <input></input>
          </li>
        );
      })}
    </ul>
  );
}

key를 써야 하는 이유

  • 고유하지 않은 값으로 key를 설정하지 않는 경우 렌더링이 잘못 일어날 수 있다.
  • 요소마다 고유한 값을 지정하고 나면 결과만 보고도 배열이 어떻게 변화했는 지 이해할 수 있다.
    • 그래서 고유한 값으로 key를 지정하는 것!

데이터 가져오기

fetch 사용하기

<aside> 📄 **Axios와 fetch의 차이 ?

  • axios는 사용하기 좀 더 편리하다
  • fetch에는 존재하지 않는 기능이 좀 더 많다
  • promise based
  • axio는 json을 자동으로 적용해서 response 객체를 반환
  • axios는 data를 바로 전달
  • 요청을 중도 Cancel, 응답시간 초과 설정 등의 기능이 있다.
  • fetch는 import가 필요없다.
  • 업데이트가 잦은 경우에도 fetch는 걱정 없다.
  • request aborting에 대해서 표준적인 방법을 제공해 주지 못한다.
  • response timeout API 제공이 되지 않는다.
  • error handling 관련 문**

</aside>

useEffect로 초기 데이터 가져오기

  • useEffect
    • 함수 실행을 useEffect로 대체해서 초기에 단 한 번 함수를 호출하도록 사용할 수 있다.
    • 무한 루프를 방지하기 위함이다.
  • 처음 한 번만 실행하기
  • useEffect(() => { // 실행할 코드 }, []);
  • 값이 바뀔 때마다 실행하기
  • useEffect(() => { // 실행할 코드 }, [dep1, dep2, dep3, ...]);

페이지네이션

  • 오프셋 기반 페이지네이션
    • = 상쇄하다
    • = 지금까지 받아온 데잍를 상쇄
    • ex) 20개를 받았으니까 10개 더 보내줘;
    • 개수 기반으로 데이터를 나누는 것.
  • 커서 기반 페이지 네이션
    • =데이터를 가리키는 값
    • =지금까지 받은 데이터를 표시한 책갈피
    • 다음 커서 값을 함께 보내 커서 값을 통해 서버에서 페이지를 호출한다.
    ⇒ 오프셋이랑 달리 데이터 중복 없이 호출할 수 있다.

네트워크 로딩 처리하기

  • 네트워크를 처리하는 동안 특정 기능을 막아 사용자 경험을 증가시킬 수 있다.
  • const handleLoad = async (options) => { let result; try { setIsLoading(true); result = await getReviews(options); } catch (error) { console.error(error); return; } finally { setIsLoading(false); } const { paging, reviews } = result; if (options.offset === 0) { setItems(reviews); } else { setItems((prevItems) => [...prevItems, ...reviews]); } setOffset(options.offset + options.limit); setHasNext(paging.hasNext); };

네트워크 에러 처리하기

const handleLoad = async (options) => {
    let result;
    try {
      setIsLoading(true);
      result = await getReviews(options);
    } catch (error) {
      **setLoadingError(error);**
      return;
    } finally {
      setIsLoading(false);
    }

    const { paging, reviews } = result;
    if (options.offset === 0) {
      setItems(reviews);
    } else {
      setItems((prevItems) => [...prevItems, ...reviews]);
    }
    setOffset(options.offset + options.limit);
    setHasNext(paging.hasNext);
  };

입력 폼 다루기

리액트에서 입력 폼 만들기

<aside> 📄 - JS 에서의 onChange는 사용자가 input을 변경한 후 focus out이 발생할 때 발생

  • 리액트에서의 onChange = JS의 onInput과 동일하게 사용(Input값이 변경 시마다)

</aside>

하나의 state로 폼 구현하기

  • 여러 가지 state를 객체화해서 하나의 state, 함수로 관리할 수 있다.
const [values, setValues] = useState({ title: "", rating: 0, content: "" });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues((prevValues) => ({
      ...prevValues,
      [name]: value,
    }));
  };
  • prevValues를 사용하는 이유는 객체 안의 속성이 여러가지일 때 모든 객체가 함께 전달되기 위함이다!

제어 컴포넌트와 비제어 컴포넌트

  • 제어 컴포넌트
    • Controlled Component
    • 인풋의 value값을 리액트에서 지정한다.
    • 리액트에서 사용하는 값과 실제 input 값이 항상 일치해서 주로 권장되는 방법이다.
  • 비제어 컴포넌트
    • Uncontrolled Component
    • 인풋의 value값을 리엑트에서 지정하지 않는다.
    → 리액트에서 사용하는 값과 실제 input 값이 다르다.

파일 인풋

  • 파일 인풋은 비제어로 만들어져야 한다.

ref로 DOM 노드 가져오기

import { useRef } from "react";

function FileInput({ name, value, onChange }) {
  const inputRef = useRef();

  const handleChange = (e) => {
    const nextValue = e.target.files[0];
    onChange(name, nextValue);
  };

  return (
    <input
      type='file'
      onChange={handleChange}
      ref={inputRef}
    />
  );
}

export default FileInput;
  • useRef는 리액트 훅의 한 종류로 특정 DOM 요소에 접근이 가능하다.

사이드 이펙트 정리하기

  • useEffect는 리액트 컴포넌트 함수 안에서 사이드 이팩트를 실행하고 싶을 때 사용하는 함수
  • useEffect는 사이드 이펙트 생성 후 정리하는 방법도 제공한다.
  • 정리 함수를 함께 작성
  useEffect(() => {
    if (!value) return;
    const nextPreview = URL.createObjectURL(value);
    setPreview(nextPreview);

    return () => {
      setPreview();
      URL.revokeObjectURL(nextPreview);
    };
  }, [value]);

데이터 보내기

리액트 Hook

  • 어딘가에 걸 수 있는 고리
  • 리액트에서 use로 시작하는 메소드들을 hook이라고 한다.
  • 내가 작성한 코드를 다른 프로그램에 연결해서 그 값이나 기능을 사용하는 것

리액트 Hook의 규칙

  • 리액트 컴포넌트 혹은 커스텀 함수 안에서만 호출될 수 있다.
  • 리액트 훅은 반복문 안에서 사용될 수 없다
  • 리액트 훅은 렌더링 시 같은 순서대로 사용되어야 한다.

useCallback

  • 함수가 매번 바뀌는 문제를 해결하기 위해 사용
    • 함수 생성 X 리액트에서 함수를 기억할 수 있다
    • useCallback의 디펜던시 리스트 값이 바뀔 때만 함수를 새로 만든

전역 데이터 다루기

  • Context?
    • 프로그램이 커지면 Props와 state만으로 관리하기 힘들다.
  • React Context
    • props를 거치지 않고 쉽게 상태를 전달할 수 있다.
import { createContext, useContext, useState } from "react";

const LocaleContext = createContext();

export function LocaleProvider({ defaultValue = "ko", children }) {
  const [locale, setLocale] = useState(defaultValue);

  return (
    <LocaleContext.Provider value={{ locale, setLocale }}>
      {children}
    </LocaleContext.Provider>
  );
}

export function useLocale() {
  const context = useContext(LocaleContext);

  if (!context) {
    throw new Error("반드시 LocaleProvider 안에서 사용해야 합니다");
  }

  const { locale } = context;

  return locale;
}

export function useSetLocale() {
  const context = useContext(LocaleContext);

  if (!context) {
    throw new Error("반드시 LocaleProvider 안에서 사용해야 합니다");
  }

  const { setLocale } = context;

  return setLocale;
}

상태 관리

  • 데이터를 언제 어디서 바뀐 건지도 파악하기 위해 쉬운 상태 관리 구조 필요
  • ⇒ 데이터의 변경을 한 곳에서 관리 : Flux
  • Flux
  • Redux
  • React Query
  • SWR
  • Recoil

'TIL' 카테고리의 다른 글

웹 프로토콜  (0) 2024.08.15
함수형 프로그래밍 (Function Programming)  (0) 2024.08.14
http와 https의 통신 방법 차이  (0) 2024.08.13
브라우저의 렌더링 과정  (0) 2024.08.12
[PB] 7주차 스터디 정리  (0) 2024.08.06

SurveyMate 프로젝트 회고 내용

2024로 넘어가는 겨울방학 동안 인생 첫 프로젝트를 진행했다. 한 학기 동안 배운 프론트 지식을 확실히 함도 있었고, PM과 디자이너까지 포함하는 프로젝트의 기회는 정말 귀하다고 생각되어 즐겁게 참여할 수 있었다.

 

UMC 5기 웹으로 참여하면서 기본적인 웹사이트를 만드는 데 필요한 언어들을 배웠기에 지식을 잘 써보자는 마음가짐으로 참여했다. 실제로 두 번의 지원 중 두 번째로 참여했던 프로젝트에 바로 참여할 수 있었다. 프로젝트에 참여하면서 중요하게 여긴 포인트는 세 가지이다. 웹앱 구현. 반응형. API. 결론적으로는 잘 배웠고 해낸 것 같다. 정말 감사하게도 프론트는 모두 첫 프로젝트였던 것에 반해 백 분들이 다들 경험이 있어 잘 이끌어주셨다. Swagger도 제대로 사용 못 해 당황한 프론트들을 많이 이끌어주셨다. 첫 프로젝트여서 그런가 많이 배웠다. 협업하는 법도, 연결하는 법도, 코드 짜는 법도. Git 쓰는 법도 제대로 배운 것 같다.

 

이 프로젝트는 PM 1명, 디자이너 1명, 프론트 4명과 백 3명으로 진행되었다.

 


 

 

1 .이메일 템플릿 제작

 

이번 프로젝트를 하면서 가장 노력한 점은 일단 시도해 보자는 마음가짐이었다. 혹여나 할 일이 하나 떨어지면 일단 해 보자는 마음으로 제가 할게요를 외쳤다. 이메일 템플릿이 그 첫 번째 도전이었다.

 

이 프로젝트의 회원가입은 학생 사용자와 일반 사용자를 구분하기 위해 학교용 계정을 구분하는 기능이 필요했다. 그러기 위해 단순 정규식 구분이 아닌 보안까지 신경 쓰기 위한 이메일을 통행 인증번호를 받는 형식으로 사용했다. 이메일로 딸랑 인증번호 6자를 보낼 수는 없으니 최소한의 형식을 갖춘 이메일 템플릿을 만들기로 했다. 평소에도 뉴스레터에 관심이 많은 터라 도움이 될 것 같아 내가 만든다고 해 봤다.

 

근데 놀랍게도 생각보다 정보가 없었다... 결론적으로는 html만을 사용해서 템플릿을 제작하고 백앤드에 넘겨야 했다. 

 

 

html 만을 사용해서 코드를 작성하는 것은 꽤 어려웠기 때문에 mjml 코드를 사용했다. mjml은 mjml 코드를 작성해 템플릿을 완성하면 자동으로 html 코드로 치환해 준다. 코드를 작성하기 위한 코드를 배워야 하는 것이므로 상당히 어이없고 귀찮아 보이지만... mjml은 다행히 사용법을 30분 이내로 마스터할 정도로 굉장히 쉬웠다. 아래의 라이브 코드에서 mjml로 원하는 템플릿을 만들고 html 코드로 변환해 그대로 가져다가 사용하면 된다. 

 

 

 

 

 

 

2. 처음으로 만든 반응형 페이지

 

반응형을 의식하고 페이지를 만든다는 것이 생각보다 힘든 일이었다. 그래도 애매하개 알고 있던 미디어 쿼리, 뷰포트 사용법에 대해 알아갈 수 있었다.

 

1) 상단바 기본 컴포넌트를 만들면서 애매했던 부분이 있었다. 공통 마진을 적용하기 위해 전역 스타일을 지정해 놨는데 상단바만큼은 기본 마진에서 제외되야 했기 때문이다. 이 부분을 해결하면서 보기만 했었던 calc 개념을 적극적으로 사용할 수 있었다. 

margin-left: calc(-50vw + 50%);

 

2) input type = file 의 어려움. 프로필 사진의 오른쪽 아래에 있는 보라색 버튼을 클릭하면 갤러리에서 사진을 선택할 수 있다. 문제는 input 태그의 타입을 지정하는 것만으로도 갤러리에 접근하는 것은 가능하지만 기본 라벨을 꾸미기 위해서는 input의 기본 css를 없앨 수 없기 때문에 아예 display: none으로 바꾼 후 label 태그를 추가해 input의 label을 따로 지정해야 한다. 

html에서는 for을 쓰면 된다고 한 것 같은데 리액트에서는 htmlFor을 써야한다고 해서... 뭔가 복잡했다.

+ accept 속성으로 파일 형식 지정이 UX에 더 좋다고 한다.

 

3) 처음에 개념을 빠르게 건너뛰면서 공부를해서 flexbox나 position에 대한 개념이 추상적이었는데 이번에 제대로 공부했다. 기본 프로필 사진과 변경버튼의 위치를 고정한 상태로 반응형이 되게 위치하는 게 헷갈렸는데 wrapper 안에 두 컴포넌트를 position으로 고정한 이후에 위치를 조정함으로 해결했다.

 

 

3. 회원가입 

지금 보면 웃긴데 API 연결을 스스로 처음 해 봐서 신나서 캡처했다.. 여기서 공부한 건 1) Axios 문법 2) Axios에 담거나 받는 파일의 형식들 분류와 방법 3) 두 페이지에 걸친 API 연결 시 인자를 넘기는 방법 등 ; <navigate / >, <link /> 태그에 보내고 싶은 state을 담아서 보낼 수 있다는 게 신기했다.

 

 

4. 바텀시트 만들기

 

 

나름대로 어려웠던 하나만 클릭되는 버튼 만들기... 여기서 중요한 하나를 클릭할 시 이미 클릭돼있던 컴포넌트는 클릭이 해제되야 한다는 것. 

한참을 해매다가 selected 옵션을 통해 클릭한 것을 바로바로 selected로 지정하는 코드를 썼다. daybutton을 하나로 묶기 위해 아래처럼 코드를 묶어서 작성하고 변수에 의해 버튼이 생겨나게 했다! 

 {[1, 2, 3, 4, 5, 6, 7].map((day) => (
          <DayButton
            key={day}
            onClick={() => handleDayButtonClick(day)}
            selected={day === selectedDay}
          >
            <DayButtonText>{day}일</DayButtonText>
          </DayButton>
        ))}

 

 

 

 

바텀시트는 뒷면의 흑백처리되는 부분, 아래의 바텀시트, 그 안의 인자 모두 따로 컴포넌트로 만들었다. 

//설문등록 확인 바텀시트
function PointBottom({ point }) {
  return (
    <>
      <BackgroundBottomSheet>
        <BottomSheetWrapper>
          <BottomSheetInfo>
            <InputLabel>설문등록 확인</InputLabel>
            <ProcessExplain>
              {point}포인트를 사용하여 <br />
              설문을 등록하시겠습니까?
            </ProcessExplain>
            <img
              src={Warning}
              alt='warning'
            />
          </BottomSheetInfo>
          <BottomButtonWrapper>
            <CancelButton>
              <C.ButtonText>취소</C.ButtonText>
            </CancelButton>
            <ConfirmButton>
              <C.ButtonText>등록</C.ButtonText>
            </ConfirmButton>
          </BottomButtonWrapper>
        </BottomSheetWrapper>
      </BackgroundBottomSheet>
    </>
  );
}

//바텀시트 스타일 컴포넌트

const BackgroundBottomSheet = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(0, 0, 0, 0.3);
  display: flex;
  align-items: flex-end;
  z-index: 1;
`;

const BottomSheetWrapper = styled.div`
  background-color: #ffffff;
  width: 100vw;
  margin-left: calc(-50vw + 50%);
  border: none;
  border-radius: 20px 20px 0px 0px;
  align-items: center;
`;

const BottomSheetInfo = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 5%;
  text-align: center;
`;

const BottomButtonWrapper = styled.div`
  display: flex;
  flex-wrap: wrap;
  justify-content: space-evenly;
  padding-bottom: 3vh;
`;

const CancelButton = styled.button`
  border-radius: 10px;
  border: 2px solid #cfc8ff;
  background: var(--white, #fff);
  box-shadow: 0px 2px 11px 0px rgba(0, 0, 0, 0.2);
  color: rgba(207, 200, 255, 1);
  width: 45vw;
  height: 5vh;
  justify-content: center;
  align-items: center;
  position: flex;
  cursor: pointer;
`;

const ConfirmButton = styled.button`
  border-radius: 10px;
  background: #6046ff;
  color: var(--white, #fff);
  width: 45vw;
  height: 5vh;
  justify-content: center;
  align-items: center;
  border: none;
  box-shadow: 0px 2px 11px 0px rgba(0, 0, 0, 0.2);
  cursor: pointer;
`;

 

 

자잘한 기능들을 해 보면서 상태 관리에 집중하면서 리액트를 더 배우고 싶다고 느꼈다. 작업사항은 더 있지만 첫 프로젝트에서 느낀 감상들을 늘어놓고 싶어서 회고를 적게 되었다. 인생 첫 프로젝트라 정말 느리게 조금씩 나아갔지만 많은 도움을 받고 많은 배움을 얻은 것 같다. 프로젝트와 함께 한 학기 내내 스터디를 진행한 UMC도 끝이 났다. 앞으로 첫 프로젝트를 양분 삼아 더 나아갈 수 있으면 좋겠다 

 

회고 끝 !

+ Recent posts