본문 바로가기
React

리액트를 클린하게 작성하는 법

by 윤-찬미 2021. 11. 18.

Components

이름짓기

// 컴포넌트의 이름은 꼭 지정하고, 이름을 정확하고 알기 쉽게 지정해주세요

// BAD😡
export default () => <div>...</div>

// GOOD🤩
export default function Card() { return <div>...</div> )

일관되게 작성하기

컴포넌트를 작성할때 일관된 스타일을 유지하는게 좋습니다.

예를들어 컴포넌트를 작성할 때 function(일반함수)을 쓰던가, 아니면 화살표 함수로만 쓰는 등의 일관성을 지키는게 중요하고 이는 여러분의 코드에 더 나은 생산성을 가져다 줄 것 이라 생각합니다!

코드분리하기

리액트 컴포넌트를 코드를 작성할 때 200줄 미만으로 작성하는 편이 좋습니다.

200줄 이라는 말의 속뜻은 컴포넌트를 작은 구성요소로 나누라는 말입니다.

예를 들어 유틸 코드나 constants 등의 상수 요소들을 분리 하는 것을 의미합니다.

// BEFORE😡
// Form.tsx
const Form = () => {
  const [state, setState] = useState({
    key1: 'value',
    key2: [
      {
        anotherKey: 'value'
      }
    ]
    // ... some more complex state
  })

  const complexHelper = (value) => {
    // ... Some fat amount of logic
  }
}

// GOOD🤩
// Form.tsx
const Form = () => {
  const [state, setState] = useState(defaultState);
}

// Form.constants.ts
export const defaultState = {
  // .. form objects
}

// Form.utils.ts

export const complexHelper = (value) => {
  // ... Helper function body
}

Destructure props

컴포넌트의 props로 넘기는 값들은 props를 반복적으로 사용하지 않도록 Destructure를 통해 개별키로 나누는게 더 좋습니다.

// BEFORE😡
const Input = (props) => {
  return <input value={props.value} onChange={props.onChange}/>
}

// GOOD🤩
const Input = ({value, onChange}) => (
  <input value={value} onChange={onChange} />
)

너무 많은 props를 전달하지 않도록 합니다.

너무 많은 props가 전달 된 컴포넌트는 유지관리가 어려울 뿐더러 코드 파악도 어려워 질 수 있습니다.

보통은 6개 이상의 props가 전달되는 경우는 더 작은 요소들로 세분화해서 나눌 수 있을 것 입니다!

하지만 일부 많은 props 값이 필요한 경우도 있긴 합니다.

그런 특정한 로직의 경우를 제외하고는 너무 많은 props를 전달하지 않도록 하는 것이 좋습니다!

렌더링 함수 중첩 피하기

컴포넌트 내에서 마크업과 로직을 분리할때 컴포넌트 내부에 함수를 생성해서 만드는 경우가 있는데, 이는 좋지 않은 코드 스타일 입니다. 코드를 읽기어렵게 만드는 원인이 되기도 합니다.

// BEFORE😡
const Navbar = () => {

  const NavbarButton = () => {
    return <button>...</button>
  }

  return <div>{NavbarButton()}</div>
}

// GOOD🤩
import NavbarButton from '../components/NavbarButton'

const Navbar = () => {
  return <div> <NavbarButton/> </div>
}

조건부 렌더링을 통해서 깔끔하게 코드 작성하기

const Counter = ({count}) => {
  return <div>
    {!!count && <h1>Count: {count}</h1>}
  </div>
}

props의 기본값을 설정할 때 defaultProps보다, 키가 정의된 위치에서 바로 지정하는 게 좋습니다!

함수형 컴포넌트에서 defaultProps는 deprecate되었다고 합니다!!!

// BEFORE😡
const Component = ({ title, subtitle, text}) => {
  return <div>..</div>
}

Component.defaultProps = {
  title: 'Default Title',
  subtitle: 'Generic Subtitle',
  text: ''
}

// GOOD🤩
const Component = ({
  title: 'Default Title',
  subtitle: 'Generic Subtitle',
  text: '',
}) => {
  return <div>...</div>
}

props로 구성요소를 각각 넘기는 것 보다 오브젝트를 넘기는 것도 좋은 방법입니다.

위에서 너무 많은 props를 넘기지 말라고 이야기 했었죠?

이럴때 오브젝트로 묶어서 보내는 것도 좋은 방법이라 생각합니다. 당연히 그룹화 할 수 있는 것들로요!

// BEFORE😡
<UserAccount 
  name={user.name}
  email={user.email}
  id={user.id}
/>

// GOOD🤩
<UserAccount user={user} />

Boolean Props

가능하면 isHungry={true} 보다 isHungry 만 전달 될 수 있도록 하는게 더 깔끔할 수 있습니다.

// BEFORE😡
import React from 'react'

const HungryMessage = ({ isHungry }) => (
  <span>{isHungry ? 'I am hungry' : 'I am full'}</span>
)

export const BooleanPropBad = () => (
  <div>
    <span>
      <b>This person is hungry: </b>
    </span>
    <HungryMessage isHungry={true} />
    <br />
    <span>
      <b>This person is full: </b>
    </span>
    <HungryMessage isHungry={false} />
  </div>
)

// GOOD🤩
import React from 'react'

const HungryMessage = ({ isHungry }) => (
  <span>{isHungry ? 'I am hungry' : 'I am full'}</span>
)

export const BooleanPropGood = () => (
  <div>
    <span>
      <b>This person is hungry: </b>
    </span>
    <HungryMessage isHungry />
    <br />
    <span>
      <b>This person is full: </b>
    </span>
    <HungryMessage isHungry={false} />
  </div>
)

컴포넌트를 props로 넘기고자 한다면

굳이 랩핑해서 전달할 필요 없습니다.

// BEFORE😡
import React from 'react'

const CircleIcon = () => (
  <svg height="100" width="100">
    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
  </svg>
)

const ComponentThatAcceptsAnIcon = ({ IconComponent }) => (
  <div>
    <p>Below is the icon component prop I was given:</p>
    <IconComponent />
  </div>
)

export const UnnecessaryAnonymousFunctionComponentsBad = () => (
  <ComponentThatAcceptsAnIcon IconComponent={() => <CircleIcon />} />
)

// GOOD🤩
import React from 'react'

const CircleIcon = () => (
  <svg height="100" width="100">
    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
  </svg>
)

const ComponentThatAcceptsAnIcon = ({ IconComponent }) => (
  <div>
    <p>Below is the icon component prop I was given:</p>
    <IconComponent />
  </div>
)

export const UnnecessaryAnonymousFunctionComponentsGood = () => (
  <ComponentThatAcceptsAnIcon IconComponent={CircleIcon} />
)

setState를 할때는 이전값을 기반으로 바꾸도록 코드를 작성하세요!

여러 사이드 이펙트를 줄여 줍니다!

// BEFORE😡
const toggleButton = () => setIsDisabled(!isDisabled)

// GOOD🤩
const toggleButton = () => setIsDisabled(isDisabled => !isDisabled)

불필요한 컨텍스트를 추가하지 마세요!

아래 처럼 그룹핑한 오브젝트 변수 이름을 user로 지었을 경우 userId, userEmail등으로 굳이 작성할 필요 없습니다.

// BEFORE😡
const user = {
  userId: "296e2589-7b33-400a-b762-007b730c8e6d",
  userEmail: "JDoe@example.com",
  userFirstName: "John",
  userLastName: "Doe",
  userAge: 23,
};

user.userId;

// GOOD🤩
const user = {
  id: "296e2589-7b33-400a-b762-007b730c8e6d",
  email: "JDoe@example.com",
  firstName: "John",
  lastName: "Doe",
  age: 23,
};

user.id;

불필요한 조건문을 상세하게 달필요 없습니다

예를 들어 firstName !== "" && firstName !== null && firstName !== undefined 는

모두 if문에선 동일한 boolean 값을 리턴 합니다! 따라서 !!firstName 로도 충분히 표현될 수 있습니다!

// BEFORE😡
if (isActive === true) {
  // ...
}

if (firstName !== "" && firstName !== null && firstName !== undefined) {
  // ...
}

const isUserEligible = user.isVerified() && user.didSubscribe() ? true : false;

// GOOD🤩
if (isActive) {
  // ...
}

if (!!firstName) {
  // ...
}

const isUserEligible = user.isVerified() && user.didSubscribe();

switch 문 보다 객체 또는 맵으로 표현하는 것이 더 나을 수도 있습니다!

// BEFORE😡
const getColorByStatus = (status) => {
  switch (status) {
    case "success":
      return "green";
    case "failure":
      return "red";
    case "warning":
      return "yellow";
    case "loading":
    default:
      return "blue";
  }
};

// GOOD🤩
const statusColors = {
  success: "green",
  failure: "red",
  warning: "yellow",
  loading: "blue",
};

const getColorByStatus = (status) => statusColors[status] || "blue";

비동기 코드를 사용하고자 할때 async await 적극 활용!!

콜백은 당연히 피하는 게 좋고 더 나은 방법은 promise를 활용하는 방법인데, 이것보다 제일 베스트는 ES6의 async await 입니다!

// GOOD🤩
getUser()
  .then(getProfile)
  .then(getAccount)
  .then(getReports)
  .then(sendStatistics)
  .catch((err) => console.error(err));

// VERY GOOD🤩🤩
async function sendUserStatistics() {
  try {
    const user = await getUser();
    const profile = await getProfile(user);
    const account = await getAccount(profile);
    const reports = await getReports(account);
    return sendStatistics(reports);
  } catch (e) {
    console.error(err);
  }
}

오류는 구체적으로! 정해 놓고 핸들링 하는게 더 좋습니다.

이렇게 작성하면 코드에 예상되는 오류가 어떤 것이며, 어떻게 처리 했는지 파악이 쉽고, 나중에 버그 찾기도 수월 합니다!

// BEFORE😡
try {
  // Possible erronous code
} catch (e) {
  console.log(e);
}

// GOOD🤩
try {
  // Possible erronous code
} catch (e) {
  console.error(e);
  alertUserOfError(e);
  reportErrorToServer(e);
  throw new CustomError(e);
}

유틸함수에 jsDoc 적극 활용하기

코드를 문서화 해놓으면 코드의 품질을 높이고, 다른 사람들이 코드 파악하는 데 아주 많은 도움을 줍니다!

/**  
 * Returns x raised to the n-th power.  
 *  
 * @param {number} x The number to raise.  
 * @param {number} n The power, should be a natural number.  
 * @return {number} x raised to the n-th power.  
 */ 
function pow(x, n) {   
    // ...
}