Developer/React

React Clean Code 를 위한 팁

jaddong 2022. 9. 4. 17:02
320x100

1. The imports statements : React Import Structure(RIS)

import를 사용하여 파일과 모듈을 가져올 수 있습니다 . 대부분의 경우, 특히 대규모 프로젝트로 작업할 때 더 나은 가독성과 리팩토링을 위해 절대 가져오기 를 설정하고 사용하는 것이 좋습니다.

Bad example

// Note: A not-so-good example for unorganized & relative imports

import React from 'react';
import Alert from '@material-ui/lab/Alert';
import GoogleMap from '../../../components/map';
import { getHistories } from '../../../utils/mains';
import { putResponses, getResponses } from '../../../middleware/actions/response';
import '../../common/style.scss';
import { getTimezones, getTimezoneName, getDay } from '../../../utils/date';
import HistorySection from '../../../components/history-section';

Good example

// Note: A better example for an organized & absolute imports

import React from 'react';

import Alert from '@material-ui/lab/Alert';

import GoogleMap from 'components/map';
import HistorySection from 'components/history-section';

import { getHistories } from 'utils/mains';
import { getTimezones, getTimezoneName, getDay } from 'utils/date';
import { putResponses, getResponses } from 'middleware/actions/response';

import './style.scss';

 

2.  단일 조건인 조건부 렌더링에는 && 를 쓰자.

만약 어떤 값이 true 일 때 렌더링하고 false일 때는 렌더하지 않는 식으로, 조건에 따라 렌더링을 하거나 하지 않는 경우가 있습니다. 이때는 삼항연산자를 사용하지 말고 대신 &&연산자를 활용하는 것이 좋습니다.

JavaScript에서 truthy, falsy가 모호하기 때문에 && 키워드를 쓸 때 !!를 쓰는 것이 좋습니다.

Bad example

{/* 삼항연산자를 사용하면 null 같이 불필요한 코드가 늘어납니다. */}
{!!showConditionalText ? <p>The condition must be true!</p> : null}

Good example

{/* 삼항연산자를 활용하면 조건별 렌더링을 더 가시적으로 표현할 수 있습니다. */}
{!!showConditionalText && <p>The condition must be true!</p>}

 

3.  문자열 값을 Props로 넘길 때는 쌍따옴표를 이용하자.

문자열 Prop값은 별도의 중괄호({}, curly brace)나 백틱(``, backticks)없이 그저 쌍따옴표만을 통해서도 전달할 수 있습니다.

Bad example

{/* 문자열 prop값을 중괄호, 백틱, 쌍따옴표, 홑따옴표로 감싸 전달한 사례 */}
<Greeting personName={"John"} />

Good example

{/* 문자열 prop값은 그저 쌍따옴표만으로도 충분히 전달할 수 있습니다. */}
<Greeting personName="John" />

 

4.  인자가 Event 객체 뿐인 이벤트 핸들러 함수는 함수명만 입력하자.

만약 이벤트 핸들러가 오직 Event 객체 하나만을 인자로 받는다면, 그냥 이벤트 핸들러로 함수명만을 입력하면 됩니다. 즉, onChange={e => handleChange(e)} 이라고 쓸 필요없이 그냥 onChange={handleChange}라고 쓰면 된다는 뜻입니다.

Bad example

{/* Event 객체 하나만 인자로 받는데 인자를 전달하는 함수형태로 작성 */}
<input id="name" value={inputValue} onChange={e => handleChange(e)} />

Good example

const handleChange = e => {
  setInputValue(e.target.value)
}

{/* 그저 Event 객체 하나만 인자로 받는 함수는 함수명만을 입력 */}
<input id="name" value={inputValue} onChange={handleChange} />

 

5.  Undefined Props 대비책을 걱정하지 말자.

undefined props는 제외됩니다. 만약 어떤 props가 undefined로 제공되어도 컴포넌트 작동에 문제가 없다면, props값으로 undefined를 전달하는 것에 대한 대비책을 걱정할 필요는 없습니다.

Bad example

const ButtonOne = ({ handleClick }) => (
  <button onClick={handleClick || undefined}>Click me</button>
)

const ButtonTwo = ({ handleClick }) => {
  const noop = () => {}
  return <button onClick={handleClick || noop}>Click me</button>
}

export const UndefinedPropsBad = () => (
  <div>
    <ButtonOne />
    <ButtonOne handleClick={() => alert('Clicked!')} />
    <ButtonTwo />
    <ButtonTwo handleClick={() => alert('Clicked!')} />
  </div>
)

Good example

const ButtonOne = ({ handleClick }) => (
  <button onClick={handleClick}>Click me</button>
)

export const UndefinedPropsGood = () => (
  <div>
    <ButtonOne />
    <ButtonOne handleClick={() => alert('Clicked!')} />
  </div>
)

 

6.  이전 상태에 의존하는 상태를 갱신할 때는 updater 함수를 전달하자.

만약 새로운 상태가 이전의 상태값에 의존한다면, 이전 state값을 이용한 함수(updater 함수)를 전달해야 합니다. React 상태 갱신은 일괄적으로 이뤄지므로 이런 식으로 갱신하지 않으면 예상치 못한 결과가 나올 수 있습니다.

Bad example

export const PreviousStateBad = () => {
  const [isDisabled, setIsDisabled] = useState(false)
  
  // (역주) 이전 값에 의존하는 상태갱신 함수에 갱신결과값만을 전달하면
  const toggleButton = () => setIsDisabled(!isDisabled)
  
  // (역주) 이 함수의 결과가 정상적으로 작동하지 않음을 알 수 있습니다.
  const toggleButton2Times = () => {
    for (let i = 0; i < 2; i++) {
      toggleButton()
    }
  }

  return (
    <div>
      <button disabled={isDisabled}>
        I'm {isDisabled ? 'disabled' : 'enabled'}
      </button>
      <button onClick={toggleButton}>Toggle button state</button>
      <button onClick={toggleButton2Times}>Toggle button state 2 times</button>
    </div>
  )
}

Good example

export const PreviousStateGood = () => {
  const [isDisabled, setIsDisabled] = useState(false)

  // (역주) 상태갱신 함수의 인자로 이전 값을 인자로 하는 updater 함수를 전달하면
  const toggleButton = () => setIsDisabled(isDisabled => !isDisabled)

  // (역주) 아래 함수가 의도한대로 동작함을 알 수 있습니다.
  const toggleButton2Times = () => {
    for (let i = 0; i < 2; i++) {
      toggleButton()
    }
  }

  return (
    <div>
      <button disabled={isDisabled}>
        I'm {isDisabled ? 'disabled' : 'enabled'}
      </button>
      <button onClick={toggleButton}>Toggle button state</button>
      <button onClick={toggleButton2Times}>Toggle button state 2 times</button>
    </div>
  )
}

 

7.  관련 없는 코드를 별도의 구성 요소로 이동하자.

코드를 별도의 React 구성요소로 추상화 하는 것이 더 깔끔한 React 코드를 작성하는 가장 쉬운 방법입니다.

Bad example

export default function App() {
  const posts = [
    {
      id: 1,
      title: "How to Build YouTube with React"
    },
    {
      id: 2,
      title: "How to Write Your First React Hook"
    }
  ];

  return (
    <main>
      <Navbar title="My Special App" />
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            {post.title}
          </li>4. 공유 기능을 React 훅으로 이동
        ))}
      </ul>
    </main>
  );
}

function Navbar({ title }) {
  return (
    <div>
      <h1>{title}</h1>
    </div>
  );
}

Good example

export default function App() {
 return (
    <main>
      <Navbar title="My Special App" />
      <FeaturedPosts />
    </main>
  );
}

function Navbar({ title }) {
  return (
    <div>
      <h1>{title}</h1>
    </div>
  );
}

function FeaturedPosts() {
  const posts = [
    {
      id: 1,
      title: "How to Build YouTube with React"
    },
    {
      id: 2,
      title: "How to Write Your First React Hook"
    }
  ];

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

 

8.  공유되는 기능을 React 훅으로 이동하자.

hook 폴더에 공유되는 기능의 훅을 생성하면 해당 구성요소는 재사용할 수 있습니다.

Before

// src/components/FeaturedPosts.js

export default function FeaturedPosts() {
  const [posts, setPosts] = useState([]);  	
    
  useEffect(() => {
    fetch('<https://jsonplaceholder.typicode.com/posts>')
      .then(res => res.json())
      .then(data => setPosts(data));
  }, []);

  return (
  	{posts.map((post) => (
		{post.title}
	))}
  );
}

After

// src/hooks/useFetchPosts.js

export default function useFetchPosts() {
  const [posts, setPosts] = React.useState([]);  	
    
  React.useEffect(() => {
    fetch('<https://jsonplaceholder.typicode.com/posts>')
      .then(res => res.json())
      .then(data => setPosts(data));
  }, []);

  return posts;
}

// src/components/FeaturedPosts.js

import useFetchPosts from '../hooks/useFetchPosts.js';

export default function FeaturedPosts() {
  const posts = useFetchPosts()

  return (
      {posts.map((post) => (
        {post.title}
       ))}
  );
}

 

9.  JSX에서 최대한 많은 JavaScript를 제거하자.

인라인 함수가 포함되면 컴포넌트와 관련 기능의 목적이 모호해져 JSX가 읽기 더 어려워지는 것 볼 수 있습니다.

Bad example

export default function FeaturedPosts() {
  const posts = useFetchPosts()

  return (
    <ul>
      {posts.map((post) => (
        <li onClick={event => {
          console.log(event.target, 'clicked!');
        }} key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Good example

export default function FeaturedPosts() {
  const posts = useFetchPosts()
  
  function handlePostClick(event) {
    console.log(event.target, 'clicked!');   
  }

  return (
    <ul>
      {posts.map((post) => (
        <li onClick={handlePostClick} key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

 

10.  JSX 스타일을 관심 분리하자.

JSX에 인라인 스타일을 작성하게 되면 코드를 읽기 어렵게 만들고 추가 JSX를 작성하기 더 어렵게 만듭니다.

Bad example

function Navbar({ title }) {
  return (
    <div style={{ marginTop: '20px' }}>
      <h1 style={{ fontWeight: 'bold' }}>{title}</h1>
    </div>
  )
}

Good example

function Navbar({ title }) {
  const styles = {
    div: { marginTop: "20px" },
    h1: { fontWeight: "bold" }
  };

  return (
    <div style={styles.div}>
      <h1 style={styles.h1}>{title}</h1>
    </div>
  );
}

 

11.  React Context로 반복되는 props drilling 줄이자.

컴포넌트 전체에서 재사용하고 싶은 props를 작성하는 경우, React에 내장된 Context를 사용해 props를 전달할 수 있습니다.

Before

import React from "react";

export default function App() {
  const user = { name: "Reed" };

  return (
      <main>
        <Navbar title="My Special App" user={user}/>
        <FeaturedPosts user={user}/>
      </main>
  );
}
// src/components/Navbar.js
import React from "react";

function Navbar({ title , user}) {
  return (
    <div>
      <h1>{title}</h1>
      {user && <a href="/logout">Logout</a>}
    </div>
  );
}
// src/components/FeaturedPosts.js
import React from "react";

function FeaturedPosts({ user }) {
  const posts = useFetchPosts();

  if (user) return null;

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

After

import React, { createContext } from "react";

const UserContext = createContext();

export default function App() {
  const user = { name: "Reed" };

  return (
    <UserContext.Provider value={user}>
      <main>
        <Navbar title="My Special App" />
        <FeaturedPosts />
      </main>
    </UserContext.Provider>
  );
}
// src/components/Navbar.js
import React, { useContext } from "react";

function Navbar({ title }) {
  const user = useContext(UserContext);

  return (
    <div>
      <h1>{title}</h1>
      {user && <a href="/logout">Logout</a>}
    </div>
  );
}
// src/components/FeaturedPosts.js
import React, { useContext } from "react";

function FeaturedPosts() {
  const posts = useFetchPosts();
  const user = useContext(UserContext);

  if (user) return null;

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

👉 출처

반응형