• 티스토리 홈
  • 프로필사진
    redpome
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
redpome
  • 프로필사진
    redpome
    • 분류 전체보기 (50)
      • 내일배움캠프 (23)
      • 웹개발 지식 (2)
      • 프로그래머스 (8)
      • React (7)
      • 코딩테스트 (1)
      • UI-UX (1)
      • 타입스크립트 (2)
      • Next.js (3)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
      등록된 댓글이 없습니다.
    • 최근 공지
        등록된 공지가 없습니다.
      # Home
      # 공지사항
      #
      # 태그
      # 검색결과
      # 방명록
      • TIL - 트러블 슈팅
        2025년 01월 23일
        • redpome
        • 작성자
        • 2025.01.23.:54

        올림픽에서 국가별 메달을 입력하고 저장,로드가 가능한 CRUD를 만들었다. 제작하면서 CSS를 화려하게 넣고싶었지만 디자인을 못하니 레퍼런스를 뒤져가며 찾아볼 수 밖에 없었다. 기능적인 구현도 마찬가지이지만 컴포넌트별 분리시에도 꽤나 고생했었다.


        1. 컴포넌트 분리

        기존에 해왔던 분리하고하면 함수 단위로 파일을 나눈 정도가 다이다. Props를 부모와 자식간에 넘겨주는 방식조차도 하지 못했고 심지어 이번 과제때는 자식간 Props를 넘겨주는 과정에서 분리를 하려다보니 정말 최악의 리팩토링 결과만을 보고말았다.

        지난 포스팅에서 최악의 Props를 넘기는 방법을 보고나서 완전한 분리는 하지않더라도 최대한 해보고자 나누었다.

        UI별로 먼저 나누고 기능별로 나누고자 했지만 동적으로 생성되는 '삭제' 버튼은 자식 컴포넌트간 Props를 넘겨주어야한다.

        국가명, 금메달,은메달,동메달...이렇게 많은 항목이 생성되는데 삭제 버튼을 따로 만들어 넘겨주는게 엄청 복잡해졌다.
        오늘 아침은 어제 못한 분리를 진행하면서 결국은 다음과 같이 App.jsx를 만들게 되었다.

         

        import { useState } from "react";
        import "./App.css";
        import MedalList from "./components/MedalList.jsx";
        import MedalForm from "./components/MedalForm.jsx";
        import MedalSort from "./components/MedalSort.jsx";
        import MedalSaveLoad from "./components/MedalSaveLoad.jsx";
        const App = () => {
          const [rows, setRows] = useState([]);
        
          const deleteMedalInfo = (id) => {
            setRows(rows.filter((element) => element.id !== id));
          };
        
          return (
            <div className="contentContainer">
              <div className="headerContainer">
                <h1>2024 올림픽 메달 집계</h1>
                <MedalSaveLoad rows={rows} setRows={setRows} />
              </div>
              <MedalForm rows={rows} setRows={setRows} />
              <MedalSort rows={rows} setRows={setRows} />
              {rows.length === 0 ? (
                <p className="emptyMessage">현재 기록된 데이터가 없습니다</p>
              ) : (
                <MedalList rows={rows} deleteMedalInfo={deleteMedalInfo} />
              )}
            </div>
          );
        };
        
        export default App;

         

        이것도 선녀다. 삭제 버튼과 데이터를 부모 컴포넌트에서 넘겨주기로 결정했다. 참고로 데이터가 rows인 이유는 Material UI에서 테이블을 쓰려고했는데 거기에서 데이터를 rows라는 중첩 구조로 넘겨주어서 그냥 그대로 쓰게된 것이다.

        결국 App.jsx를 부모 컴포넌트로 하면서 MedalForm,MedalList,MedalSaveLoad로 어떻게든 나누게 되었다.

         


         

        2. 로컬 스토리지

        로컬 스토리지는 Web API의 사용이라 별 건 없지만 코딩 테스트때도 그렇고 원본의 불변성을 잃지않으면서 값을 바꿀때 습관적으로 반환하지 않는 상황을 여러 번 겪었다. 코딩 테스트에서 짧게 쓰는 코드들을 보고 나도 그렇게 쓰고자했던 버릇이 들면서 생긴 습관같다.

          const saveData = () => {
            localStorage.setItem("Saved", JSON.stringify(rows));
        
            console.log("Saved!");
          };
        
          const loadData = () => {
            const loadData = JSON.parse(localStorage.getItem("Saved"));
            setRows([...loadData]);
          };


        저장과 로드를 하는 코드 일부이다. setRows를 통해 데이터를 덮어쓰기하였다.

         


        3. Prettier 자동완성

        Prettier는 코드를 깔끔하게 지켜준다. 

        const MedalSort = ({ rows, setRows }) => {
          const sortByValue = (e) => {
            const selectOption = e.target.value;
            switch (selectOption) {
              case "id":
                console.log("기본 정렬");
                // sorted.sort((a, b) => a.id - b.id);
                setRows([...rows].sort((a, b) => a.id - b.id));
                break;
              case "totalMedal":
                console.log("총 메달 수");
                setRows(
                  [...rows].sort(
                    (a, b) =>
                      b.gold + b.silver + b.copper - (a.gold + a.silver + a.copper)
                  )
                );
                break;
              case "goldMedal":
                console.log("금메달 순");
                setRows([...rows].sort((a, b) => b.gold - a.gold));
                break;
            }
          };


        select 태그를 통해 option의 value에 따라 구분하도록 설계했다. Case 연습도하고 괜찮았는데 저장할 때 이상한 일이 벌어진다.

         

              case "totalMedal":
                console.log("총 메달 수");
                setRows(
                  [...rows].sort(
                    (a, b) =>
                      b.gold + b.silver + b.copper - (a.gold + a.silver + a.copper)
                  )
                );
                break;

         

        총 메달 수로 정렬하는 건데 이상하게 b의 괄호가 없어진다. 눈으로 봤을때 이상한 값이 나올 것 같은데, 이건 .prettierrc라는 설정 파일에서 따로 설정을 해줘야한단다.
        이상없이 코드가 동작하는 이유는 덧셈 연산자가 우선순위로 수행되기 때문에 위에서는 빼기 연산자가 마지막에 수행되기 때문이다.

        그룹화 () 괄호
        승산계열 * / % 곱하기, 나누기, 나머지
        덧셈계열 + - 더하기, 빼기
        비교계열 < <= > >= 비교
        동등성 == != === !== 동등, 동일성 비교
        논리 연산 `&&   ` 논리 AND, OR
        할당/증감 = += -= *= 등 할당, 증감, 감소
        쉼표 연산 , 연산을 나열


        위의 표는 연산자 연산순위에 대한 표이다. 결국 내 코드에서는 괄호안의 덧셈이 먼저 수행되고 그 다음 b의 속성 값에 대한 덧셈, 마지막으로 뺄셈이 수행이 된다.

         


        4. e

                  <label>국가명</label>
                  <input
                    placeholder="국가를 입력하세요"
                    type="text"
                    onChange={(e) => setCountry(e.target.value)}
                  ></input>

         

        useState에서 country라는 변수명을 선언했고 해당 상태를 통해 사용자가 입력한 값을 추적한다.

        근데 위에는 value={country}가 없었는데도 잘만 작동했다.

          const addMedalInfo = (e) => {
            e.preventDefault();
            const checkCountry = rows.some((row) => row.country === country);
            if (!country) {
              alert("국가를 입력하세요");
              return;
            }
            if (checkCountry) {
              alert("이미 존재하는 국가입니다");
              return;
            }
            const newRow = {
              id: Date.now(),
              country,
              gold: goldMedal,
              silver: silverMedal,
              copper: copperMedal,
            };
            setRows([...rows, newRow]);
            setCountry("");
            setGoldMedal(0);
            setSilverMedal(0);
            setCopperMedal(0);
          };

         

        위는 사용자 입력을 받아서 추가하는 함수이다. e를 인자로 받고있다. 즉 상태 추적이 아니라 현재 Input에 있는 값만 받아들이기에 useState의 상태가 있으나마나했던 것이다.
        입력이 끝나고나서 인풋 필드를 비우려해도 적용되지않았던 이유도 추적되지 않은 상태를 비우고 있던것이었다.

        '내일배움캠프' 카테고리의 다른 글

        TIL - UseContext 사용기, 트러블 슈팅  (1) 2025.02.06
        포켓몬 도감 만들기 -(1)  (0) 2025.02.05
        실행 컨텍스트 (3) - 콜 스택, 마이크로 태스크  (0) 2025.01.17
        TIL - Scope, this 바인딩, 클래스  (2) 2025.01.15
        개인과제 - 영화 정보 사이트  (0) 2025.01.14
        다음글
        다음 글이 없습니다.
        이전글
        이전 글이 없습니다.
        댓글
      조회된 결과가 없습니다.
      스킨 업데이트 안내
      현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
      ("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
      목차
      표시할 목차가 없습니다.
        • 안녕하세요
        • 감사해요
        • 잘있어요

        티스토리툴바