ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [프리온보딩코스 세션 회고 #5] 리액트에서 좋은 코드와 세 번째 과제 리뷰 (2)
    🏄‍♀️ 원티드X위코드 프리온보딩코스 2021. 8. 12. 01:45

    8/9 (월) [리액트에서 좋은 코드란?]

    세션 내용

    • 리액트에서 좋은 코드란?
    • React에 적용할 수 있는 Clean Code
    • 확장성을 고려하고, 재사용이 가능한 컴포넌트 설계

    0. 블로그 작성 Tips

    • 글의 대제목/소제목이 잘 분리되어 정리되어 있고, 문단이 깔끔하고, 코드의 syntax highlighting이 잘 되어 있어야 한다.
    • 명확한 주제가 있는 글이어야 한다.
      - 나의 경험을 통해 다른 사람에게 새로운 정보를 전달하는가?
      - 특정 주제를 깔끔하고 알기 쉽게 정리하여 다른 사람들로 하여금 그 개념을 쉽게 이해할 수 있게 작성되었는가?
      - 인사 담당자가 읽었을 때 이 사람을 뽑고 싶게 만드는 글인가? (긍정적/협업능력/셀프피드백/회고)
    • css, jsx, js, bash 등 언어에 맞는 syntax highlighting 기능을 꼭 활용해야 한다.
    • 적당히 재밌는 글을 좋지만 전체적으로 일기 쓰듯이 "~했다", "~그랬다 ㅋㅋ" 등은 자제해야 한다.

    1.  기업 과제 Review (2)

    1.1 권한 관리

    대부분 Private, Public Route로 분리하고 해당 컴포넌트에서 권한 확인을 하는데, 권한 확인은 컴포넌트로 나누지 않고 로직으로 해야 한다. 즉, 권한에 맞는 컴포넌트가 매번 생성되는 것이 아니라 컴포넌트에 들어가서 이 사람의 권한이 이 메뉴에 접근할 수 있는 권한인가 아닌가의 여부를 파악하는 식으로 해야 한다. 그렇지 않으면 권한이 늘어나면 늘어날수록 프론트 코드가 점점 길어질 수밖에 없다. 어떤 기능에 대해 수정 사항이 생겼을 때, 프론트 코드에도 추가적인 작업을 해야하는 식의 로직은 작성하지 말아야 한다!

    1.2 권한 정보

    데이터에 대한 구조 설계도 중요! 일반적으로 권한에 따라 달라지는 접근 메뉴들은 하나하나 각각 주지 않는다. 해당 유저의 권한만 전달받고, 그 권한에 따른 접근 가능 메뉴 데이터를 또 따로 관리하여 그 정보를 활용하여 처리할 수 있게끔 한다. 프론트 단은 접근 가능한 메뉴를 상수로 잘 관리하면 된다!

    1.3 이벤트 핸들러는 JSX 내부에 로직 구현을 하지 말고 밖으로 빼기!

    <CheckBox
      key={`brand${idx}`}
      value={name}
      checked={filter?.includes(name)}
      onChange={(e) => {
        if (e.target.checked) {
          onChange([...filter, name]);
        } else {
          filter.length === brand.length
            ? onChange([name])
            : onChange(filter.filter((opt) => opt !== name));
        }
      }}
    />

    onChange 핸들러 안의 로직은 바깥에 함수를 따로 만들어 빼주자! JSX 내부에는 정말 눈에 딱 보이는, 마크업 적인 요소, 즉 페이지의 구조만 파악할 수 있는 내용만 두는 것이 좋다! 하지만 setState, useState 등 한 줄 정도의 코드를 작성하는 것은 괜찮다! 다만 딱 읽었을 때 흐름을 방해하는 정도의 로직은 바깥으로 빼자! (하지만 이것은 회사 바이 회사인데, 한줄이더라도 무조건 바깥으로 빼는 회사도 더러 있다!)

    1.4 css에서 할 수 있는 작업은 css에서 처리하자!

    const titleName = title.length > 25 ? title.substring(0, 25).concat("...") : title;

    위의 코드의 경우 js로 25자씩 잘라주고 있는데, 글자마다 크기가 다를 뿐더러, 영어와 한글, 숫자가 혼재되어 있는 경우 25자를 잘랐을 때 내가 원했던 모양대로 딱 잘리지가 않는다. 또한 반응형의 경우 퍼센테이지로 값을 주기 때문에 이렇게 주게 되는 경우 글이 넘쳐 흐르는 문제가 생길 수도 있다. 그렇기 때문에 눈으로 보이는 UI 부분은 css로 해결해보자!

    2. 리액트에서의 좋은 코드

    2.1 자바스크립트에서의 Clean Code

    🔗 자바스크립트 Clean Code

    그중 예리 멘토님이 중요하다고 생각하신 부분을 정리해보자면,

    • 의미있고 발음하기 쉬운 변수 이름을 사용하자!
    • 동일한 유형의 변수에는 동일한 어휘를 사용하자!
    • 함수 인자는 2개 이하가 이상적이다! → 많은 경우에는 비구조화 할당을 활용!
    • 함수는 하나의 행동만 해야 한다! → 함수가 1개 이상의 행동을 한다면 작성, 테스트, 이해 모두가 어려워진다!
    • 함수는 단일 행동을 추상화 해야 한다!
      💡 함수 추상화란?
      공통의 속성이나 기능을 묶어 이름을 붙이는 것, 객체 지향 관점에 클래스를 정의하는 것을 추상화 라고 할 수 있다. 불필요한 부분을 생략하고 객체의 속석 중 가장 중요한 것에만 중점을 두어 개략화 하는 것 즉 모델화 하는 것으로 데이터의 공통된 성질을 추출하여 슈퍼 클래스를 선정하는 개념이다. 🔗 개념 이해에 도움을 준 글
    • 매개변수로 플래그를 사용하지 말자! → 플래그를 사용하는 것 자체가 함수가 한 가지 이상의 역할을 한다는 뜻..!
      💡 플래그란?
      boolean형으로 표현되는 isActive, isNew와 같이 여부를 확인하는 인수라고 할 수 있다.
    • 전역 함수를 사용하지 말자! → 전역 환경을 사용하게 된다면 다른 라이브러리들과 충돌이 일어날 수 있다.
    • 주석없이 코드로 설명하는 것이 가장 좋다! → 회사 프로젝트에만 해당되며, 나 혼자 보고 공부하는 코드에는 많이 달아도 괜찮다!
    • 클린코드를 위해 지금 코드를 다 뒤집는 것은 안된다! 지저분한 코드여도 이해하고 깔끔하게 리팩토링를 해야지 새롭게 다시 작성한다는 생각은 금물!
    • 리팩토링은 추가로 시간을 들여 하는 작업이 아니다! 매일매일 보일 때마다 하는 것이다!

    2.2 React로 사고하기

    • 컴포넌트 분리 → 단일 책임 원칙! 하나의 컴포넌트는 한 가지 일을 하는 것이 이상적이라는 원칙이다!
    • state → state는 오직 상태관리, 상호작용을 위해 사용하는 것! 정적 데이터나 상수등을 위해 사용하지는 말자!

    2.3 컴포넌트 내 변수 위치

    • 컴포넌트 내부 순서 예시를 보고 통일성 있게 맞추자!
      → 가독성을 위해서! 팀프로젝트 때 컴포넌트 import 순서까지 맞춘다. 내 코드인지 다른 팀원의 코드인지에 대해 절대 못알아채게끔 쓰는 코드가 아주 좋은 코드이다.
    1. import 순서도 경로(or연관)에 따라 묶어주자 ex) 멀리 있는 것 부터 차례대로
    : import도 술술 읽힐 수 있도록 import 분류별로 적어놓는다.
    가독성을 위해 import 분류 별로 한 줄씩 띄우기!
    예시) node_modules > utils > 멀리 있는 컴포넌트 > 근처 컴포넌트 > style 관련

    2. propTyp
    : TypeScript를 쓰지 않는 경우, propType이 있으면 좋다!
    → 타입오류를 막아줘서도 좋지만, propType만 보고도 컴포넌트 내에서 관리하고 있는 값의 형식을 바로 알 수 있다.

    3. 컴포넌트 정의
    : 클래스형, 함수형 컴포넌트가 정의되는 것을 의미함

    4. Styled Component
    : 정의된 컴포넌트 위보다 아래에 styled components가 위치하는 것이 좋다.
    어찌됐건 중요한 것은 스타일링이 아닌 로직이기 때문에 더 중요한 것을 위에 쓰는 것은 당연한 것!

    5. 간단한 상수 설정
    : 해당 컴포넌트에서만 사용되는 상수랑 데이터는 해당 컴포넌트 하단에 작성하자!

    // 6. 해당 컴포넌트에서만 사용할 함수
    : 5번과 같이 해당 컴포너트 내에서만 사용되는 함수는 해당 컴포넌트 하단에 작성하자!

    2.4 조건부 렌더링은 패턴이 아니라 가독성을 높이기 위한 팁

    a. 삼항 연산자를 남발하지 말자!

    → 삼항 연산자 중첩은 한눈에 파악하기 힘들다.
    → 요소의 길이가 길어질 때가 많아 흐름을 깨는 경우가 많다. && 연산자로 해결할 수 있는지도 고민해보자!

    const UserData = ({ userData }) => {
      return (
        <>
          {userData
            ? userData.map((data) => (
                <tr key={data.id}>
                  <td>{data.id}</td>
                  <td>{data.name}</td>
                  <td>{data.age}</td>
                  <td>{data.address}</td>
                  <td>{data.cardNumber}</td>
                  <td>{data.userType}</td>
                </tr>
              ))
            : null}
        </>
      );
    };
    1. userData가 있으면 map을 돌리고, 없으면 null인데 이 경우는 && 연산자로 해결할 수 있다.

    2. userData가 배열로 온다는 보장이 없다! 객체가 올 수도, string이 올 수도 있다. map은 배열 메소드이기 때문에 에러가 발생할 수 있다. 그렇기 때문에 userData를 props로 받을 때 값이 없으면 빈 배열을 보내주는 식으로 처리한다.

    3. 더 나아가, userData를 빈 배열로 받아오게 되면, 자식 컴포넌트는 자동으로 렌더된다. 근데 렌더는 최소화하면 할수록 좋기 때문에 자식 컴포넌트가 아닌 부모컴포넌트에서 length가 0보다 크면 userData를 호출하고, 그렇지 않으면 호출하지 않도록 처리해서 넘겨주는 것이 훨씬 더 좋은 코드이다!
    → {userData.length > 0 && <UserData userData={userData} />

     

    b. 가독성을 높이자!

    Before

    <ul
      style={{
        backgroundColor:
          type === ProgressBarType.Head
            ? 'rgba(0, 0, 0, 0.05)'
            : inProgressIndex !== -1
            ? lightColor
            : 'rgba(0, 0, 0, 0.05)',
      }}
    >

    After

    <ul
      className="progress-bar"
      style={{ backgroundColor: (inProgressIndex !== -1) && lightColor}}
    >
    .progress-bar {
    	background-color: rgba(0, 0, 0, 0.05);
    }

    조건에 맞게 스타일링을 다르게 하고 싶은 경우의 코드인데, 이런 경우는 className을 부여하고 css를 별도 작성하여 css 적용 우선순위를 활용해 덮어씌우는 식으로 처리하는 것이 조건문을 쓴 코드보다 훨씬 더 가독성이 좋다!

    export default function UserRows() {
      const { filterInfo } = useContext(FilterInfoContext)
    
      return (
        <tbody>
          {filterInfo.length
            ? filterInfo.map((userInfo) => (
                <UserRow key={userInfo.id} userInfo={userInfo} />
              ))
            : getNullDataTemplate().map((userInfo, index) => (
                <UserRow key={index} userInfo={userInfo} />
              ))}
        </tbody>
      )
    }
    
    const getNullDataTemplate = () =>
      Array(5).fill({
        id: '',
        email: '',
        password: '',
        name: '',
        age: '',
        postcode: '',
        address: '',
        address_detail: '',
        card_number: '',
        auth: '',
      })

    위 코드는 조건마다 다른 배열을 활용하는 식으로 코드를 작성하였는데, 결국에 return하는 UserRow 컴포넌트는 동일하다! 미리 filterInfo의 length를 확인하여 상위에서 filter를 돌려서 배열을 만든 후, jsx에서는 그 값을 받아서 map할 수 있게만 해주면 된다!

    내용을 정리하자면,

    • 복잡해진다? 싶으면 따로 빼기!
    • 함수가 두가지 역할한다 싶으면 하나의 기능만 할 수 있도록 분리하기!
    • 반복되면 무조건 컴포넌트로 만들기!
    • 조건문도 if else를 생략할 수 있다면 최대한 생략하기!

    +) 🔗 실무에서 바로 쓰는 Frontend Clean Code

    완벽하게 이해될 때까지 볼 것!