Javascript

2024 React 리액트 치트 시트

드리프트2 2024. 3. 4. 22:43

안녕하세요?

 

오늘은 리액트 초보자가 볼 만한 치트 시트 개념으로 모던 리액트에서 다루는 내용을 빠르게 훑어 보도록 하겠습니다.

 

** 목 차 **


React 요소 (Elements)

리액트 요소는 일반적인 HTML 요소와 구문을 공유합니다.

 

유효한 HTML 요소는 모두 리액트로 표현할 수 있습니다.

<h1>My Header</h1>
<p>My paragraph</p>
<button>My button</button>

 

이러한 리액트 요소는 JSX를 사용하여 구성되며, 이는 HTML 구문과 JavaScript 함수를 혼합한 리액트의 독특한 기능입니다.

 

그러나 JSX는 HTML이 아닌 JavaScript 함수로 작동하기 때문에 몇 가지 구문적 차이가 있습니다.

 

특히 img와 같은 단일 태그 요소는 자체 닫기를 해야 하며 슬래시(/)로 종료해야 합니다:

<img src="my-image.png" />
<br />
<hr />

리액트 요소 속성 (Element Attributes)

JSX는 JavaScript의 camelCase 규칙과 일치하도록 속성의 수정된 구문을 도입합니다.

 

예를 들어, HTML의 class 속성은 JSX에서 className으로 표현됩니다.

<div className="container"></div>

리액트 요소 스타일 (Element Styles)

JSX에서 인라인 스타일을 적용하려면 큰 따옴표 대신 중괄호를 두 번 사용합니다.

 

스타일은 일반 문자열이 아닌 객체 내의 속성으로 표현됩니다:

<h1 style={{ fontSize: 24, margin: '0 auto', textAlign: 'center' }}>My header</h1>

React 프래그먼트 (Fragments)

리액트는 모든 요소를 단일 부모 컴포넌트 내에서 반환해야 하는 요구 사항을 해결하기 위해 특별한 요소인 프래그먼트를 제공합니다.

 

이는 리액트가 반환된 요소에 대해 단일 "부모"를 필요로 하기 때문에 중요합니다.

 

div와 같은 컨테이너 요소를 사용하지 않고도 프래그먼트를 사용할 수 있습니다:

// 유효한 구문
function MyComponent() {
  return (
    <>
      <h1>My header</h1>
      <p>My paragraph</p>
    </>
  );
}

 

프래그먼트는 일반적인 구문 또는 축약 구문으로 작성할 수 있습니다: <React.Fragment></React.Fragment> 또는 <></>입니다.


리액트 컴포넌트

리액트 useState 훅의 영역에서 우리는 리액트 컴포넌트라고 부르는 요소 클러스터를 구성할 수 있는 능력을 갖고 있습니다.

 

기본 함수 컴포넌트는 표준 JavaScript 함수와 매우 유사한 방식으로 작성되며 몇 가지 주의 사항이 있습니다.

 

먼저, 컴포넌트 이름은 대문자로 시작해야 합니다.

 

즉, myComponent가 아닌 MyComponent와 같은 이름을 사용해야 합니다.

 

또한 일반 JavaScript 함수와 달리 컴포넌트는 JSX를 정상적으로 반환해야 합니다.

 

리액트 함수 컴포넌트의 기본 구문은 다음과 같습니다:

function App() {
  return (
     <div>Hello world!</div>
  );
}

React Props

리액트 세계에서 컴포넌트는 props라고 부르는 데이터를 받아올 수 있는 능력을 갖고 있습니다.

 

이러한 props는 부모 컴포넌트에서 자식 컴포넌트로 전달됩니다.

 

예를 들어, App 컴포넌트에서 User 컴포넌트로 "name"이라는 prop을 전달하는 경우를 살펴보겠습니다:

function App() {
  return <User name="John Doe" />
}

function User(props) {
  return <h1>Hello, {props.name}</h1>; // Hello, John Doe!
}

 

props는 사실상 객체이므로 User 컴포넌트 내에서 "name" prop의 값을 리액트 훅을 통해 추출할 수 있습니다.

 

더 깔끔한 코드 접근 방식을 위해, 특히 "name"과 같은 단일 prop을 다룰 때 객체 비구조화를 사용할 수 있습니다:

function App() {
  return <User name="John Doe" />
}

function User({ name }) {
   return <h1>Hello, {name}!</h1>; // Hello, John Doe!
}

 

또한 다른 요소와 컴포넌트를 포함한 모든 JavaScript 값이 prop으로 전달될 수 있다는 점을 유의해야 합니다.


React Children Props

Props는 컴포넌트의 여는 태그와 닫는 태그 사이에 데이터를 넣어 전달할 수도 있습니다.

 

이러한 방식으로 전달된 props는 children 속성 내에 있습니다.

 

예를 들어, User 컴포넌트의 태그 사이에 내용을 전달하는 경우:

function App() {
  return (
    <User>
      <h1>Hello, John Doe!</h1>
    </User>
  );
}

function User({ children }) {
  return children; // Hello, John Doe!
}

React 조건문 (Conditionals)

리액트 컴포넌트와 요소는 조건에 따라 표시될 수 있습니다.

 

하나의 접근 방법은 if 문과 별도의 return 문을 사용하는 것입니다.

 

function App() {
  const isAuthUser = useAuth();
  if (isAuthUser) {
    // 사용자가 인증되었다면 앱을 사용할 수 있게 해줍니다.
    return <AuthApp />;
  }
  // 사용자가 인증되지 않았다면 다른 화면을 표시합니다.
  return <UnAuthApp />;
}

 

만약 return 문 내에서 조건을 생성하고 싶다면, 값을 반환하는 조건을 사용해야 합니다.

 

삼항 연산자를 활용하려면 전체 조건을 중괄호로 감싸세요.

function App() {
  const isAuthUser = useAuth();
  return (
    <>
      <h1>내 앱</h1>
      {isAuthUser ? <AuthApp /> : <UnAuthApp />}
    </>
  );
}

리액트 리스트 (Lists)

리액트 컴포넌트의 리스트는 .map() 함수를 사용하여 데이터 배열을 반복하고 JSX를 생성할 수 있습니다.

 

SoccerPlayer 컴포넌트를 사용하여 축구 선수 목록을 출력하는 예를 살펴보겠습니다:

function SoccerPlayers() {
  const players = ["Messi", "Ronaldo", "Laspada"];
  return (
    <div>
      {players.map((playerName) => (
        <SoccerPlayer key={playerName} name={playerName} />
      ))}
    </div>
  );
}

 

데이터 배열을 순환할 때 key prop을 포함시키는 것이 중요하며, 이 key는 단순히 요소 인덱스가 아닌 고유한 값으로 할당되어야 합니다.

 

위의 예시에서는 고유한 값인 playerName이 key로 사용됩니다.


리액트 컨텍스트 (React Context)

리액트 컨텍스트는 컴포넌트 트리 전체를 통해 데이터를 원활하게 전달하는 메커니즘으로, 오로지 props에만 의존하지 않아도 됩니다.

 

props의 문제점은 때때로 중간 컴포넌트를 통해 불필요하게 전달해야 하는 경우가 있으며, 이를 props drilling이라고 일반적으로 말합니다.

 

다음과 같이 props가 불필요하게 'Body' 컴포넌트를 통해 전달되는 단순화된 시나리오를 고려해 보겠습니다:

function App() {
  return (
    <Body name="John Doe" />
  );
} 

function Body({ name }) {
  return (
    <Greeting name={name} />
  );
} 

function Greeting({ name }) {
  return <h1>Welcome, {name}</h1>;
}

 

Context를 적용하기 전에 컴포넌트가 불필요한 세그먼트를 통해 props를 전달하는 관행을 피할 수 있는지 여부를 고려하는 것이 좋습니다.

 

Context를 구현할 때는 리액트에서 제공하는 createContext 함수를 사용합니다.

 

이 함수는 컨텍스트의 시작점이 되는 초기 값을 인자로 받아옵니다.

 

결과적으로 생성된 컨텍스트에는 Provider와 Consumer 속성이 포함되어 있으며, 둘 다 컴포넌트로 작동합니다.

 

Provider는 데이터를 전파해야 하는 컴포넌트 트리 주위에 래핑되고, Consumer는 이 값을 소비해야 하는 컴포넌트 내에 배치됩니다.

import { createContext } from 'react';
const NameContext = createContext('');

function App() {
  return (
    <NameContext.Provider value="John Doe">
      <Body />
    </NameContext.Provider>
  );
} 

function Body() {
  return <Greeting />;
} 

function Greeting() {
  return (
    <NameContext.Consumer>
      {name => <h1>Welcome, {name}</h1>}
    </NameContext.Consumer>
  );
}

 

이 접근 방식을 채택함으로써 컴포넌트의 구조를 개선하고 컴포넌트 트리에서 불필요한 요소를 통해 props를 전달하는 필요성을 회피할 수 있습니다.


리액트 훅 (React Hooks)

리액트 훅은 리액트 버전 16.8에서 등장하여 리액트 함수 컴포넌트에 재사용 가능한 상태 관련 로직을 편리하게 통합하는 방법을 제공합니다.

 

이로써 기존에 클래스 컴포넌트에만 제한되었던 기능을 활용할 수 있습니다.

 

이 훅들은 기존에 클래스 컴포넌트에 예약되어 있던 기능을 활용할 수 있도록 해주며, 우리는 애플리케이션에 특정 기능을 부여하기 위해 직접 커스텀 훅을 만들 수도 있습니다.

 

리액트 라이브러리에는 여러 가지 필수적인 훅이 포함되어 있으며, 이 중에서 여섯 가지를 자세히 살펴보겠습니다:

  1. useState
  2. useEffect
  3. useRef
  4. useContext
  5. useCallback
  6. useMemo

특히 useState 훅은 이름 그대로 함수 컴포넌트 내에서 상태 값을 사용할 수 있게 해줍니다.

 

이 훅은 단순한 변수보다 선호되는 이유는 상태가 변경될 때 컴포넌트가 자동으로 다시 렌더링되어 업데이트된 값을 반영하기 때문입니다.

 

useState를 사용할 때는 컴포넌트 상단에서 초기 값을 전달하여 상태 변수를 초기화합니다.

 

반환된 값에 배열 비구조화를 적용하여 저장된 상태와 해당 상태를 업데이트하는 함수에 모두 접근할 수 있습니다.

import { useState } from 'react';

function MyComponent() {
  const [stateValue, setStateValue] = useState(initialValue);
}

 

간단한 카운터를 예로 들어보겠습니다. 상호 작용할 때마다 카운터가 증가합니다:

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  function updateCount() {
    setCount(count + 1);
  }

  return <button onClick={updateCount}>Count is: {count}</button>;
}

 

이를 통해 useState가 함수 컴포넌트의 상태 관리를 간단하고 직관적으로 처리할 수 있도록 도와주며, 리액트 함수 컴포넌트의 기능을 향상시킵니다.


React useState 훅

리액트의 useState 훅은 간단한 목적을 가지고 있습니다.

 

함수 컴포넌트 내에서 상태 값을 사용할 수 있게 해줍니다.

 

기본 변수에 의존하는 대신, useState를 사용하는 이유는 상태가 변경될 때 컴포넌트가 자동으로 다시 렌더링되어 업데이트된 값을 보여주기 위함입니다.

 

다른 훅들과 같은 패턴을 따라, 우리는 컴포넌트의 시작 부분에서 useState를 호출하여 초기 값을 전달하고 상태 변수를 초기화할 수 있습니다.

 

useState에서 반환된 값에 배열 비구조화를 적용하면 저장된 상태와 해당 상태를 업데이트하는 함수에 모두 접근할 수 있습니다.

import { useState } from 'react';

function MyComponent() {
  const [stateValue, setStateValue] = useState(initialValue);
}

 

실제 예시로 카운터를 증가시키는 간단한 상황을 살펴보겠습니다.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  function updateCount() {
    setCount(count + 1);
  }

  return <button onClick={updateCount}>Count is: {count}</button>;
}

 

이를 통해 useState 훅이 함수 컴포넌트에서 상태 관리를 간단하고 효율적으로 처리할 수 있도록 도와주며, 이 간단한 카운터 예시에서도 리액트 컴포넌트의 기능을 향상시킨다는 것을 보여줍니다.


리액트 useEffect 훅

리액트의 useEffect 훅은 API 호출과 같은 외부 환경과 상호 작용해야 할 때 사용됩니다.

 

주된 목적은 애플리케이션 외부에서 예측할 수 없는 결과를 초래하는 작업을 포함한 부수 효과를 처리하는 것입니다.

 

useEffect의 기본 구문은 첫 번째 인자로 함수를 제공하고 두 번째 인자로 배열을 제공하는 것입니다.

import { useEffect } from 'react';

function MyComponent() {
   useEffect(() => {
     // 부수 효과를 수행합니다.
   }, []);
}

 

게시물 목록을 가져오고 표시하는 데 useEffect를 사용하는 예시를 살펴보겠습니다:

import { useEffect } from 'react';

function PostList() {
   const [posts, setPosts] = useState([]);

   useEffect(() => {
     fetch('https://jsonplaceholder.typicode.com/posts')
       .then(response => response.json())
       .then(posts => setPosts(posts));
   }, []);

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

 

효과 함수 외부에서 값을 필요로 하는 경우 해당 값을 의존성 배열에 포함시켜야 합니다.

 

그 값이 변경되면 효과 함수가 다시 실행됩니다.

 

예를 들어, 모바일 메뉴가 열려 있는지 닫혀 있는지에 따라 본문 요소에 "overflow-hidden" 클래스를 토글하는 코드를 살펴보겠습니다:

function Mobile({ open }) {
  useEffect(() => {
    const body = document.querySelector("#__next");
    if (open) {
      body.classList.add("overflow-hidden");
    } else {
      body.classList.remove("overflow-hidden");
    }
  }, [open]);
  // ...
}

 

이를 통해 useEffect가 부수 효과를 효율적으로 관리하며 외부 환경과의 상호 작용이 리액트 컴포넌트 라이프사이클에 매끄럽게 통합되도록 합니다.


React useRef

리액트의 useRef 훅은 JSX 요소에 직접적인 접근을 제공하여 prop drilling 없이 해당 요소에 접근할 수 있는 메커니즘을 제공합니다.

 

useRef를 활용하려면 훅을 호출하고 반환된 값을 가져와 원하는 리액트 요소의 ref prop에 할당하면 됩니다.

 

중요한 점은 ref가 컴포넌트의 고유한 속성이 아닌 리액트 요소의 속성이라는 것입니다.

 

useRef의 기본 구문은 다음과 같습니다:

import { useRef } from 'react';

function MyComponent() {
  const ref = useRef();
  return <div ref={ref} />;
}

 

특정 요소에 ref가 연결되면 ref.current에 저장된 값은 해당 요소와 직접 상호 작용하는 데 사용될 수 있습니다.

 

예를 들어, 사용자가 Control + K 키 조합을 사용할 때 검색 입력란에 초점을 맞추고 싶다고 가정해 보겠습니다:

import { useWindowEvent } from "@mantine/hooks";
import { useRef } from "react";

function Header() {
  const inputRef = useRef();

  useWindowEvent("keydown", (event) => {
    if (event.code === "KeyK" && event.ctrlKey) {
      event.preventDefault();
      inputRef.current.focus();
    }
  });

  return <input ref={inputRef} />;
}

 

이 예시에서 inputRef는 입력 요소에 직접적으로 접근하는 데 사용되며, useRef가 리액트 컴포넌트 내에서 특정 요소와 상호 작용하는 능력을 향상시킨다는 것을 보여줍니다.


리액트 useContext

리액트의 useContext 훅은 표준 Context.Consumer 컴포넌트를 사용하는 것보다 컨텍스트를 소비하는 과정을 간소화합니다.

 

구문은 useContext에 소비할 컨텍스트 전체 객체를 전달하는 것으로, 반환된 값은 컨텍스트를 통해 전파된 값과 일치합니다.

import { useContext } from 'react';

function MyComponent() {
  const value = useContext(Context);
  // ...
}

 

useContext 훅을 사용하여 이전 예시를 적용해 보겠습니다:

import { createContext, useContext } from 'react';

const NameContext = createContext('');

function App() {
  return (
    <NameContext.Provider value="John Doe">
      <Body />
    </NameContext.Provider>
  );
} 

function Body() {
  return <Greeting />;
} 

function Greeting() {
  const name = useContext(NameContext);
  return (
    <h1>Welcome, {name}</h1>
  );
}

 

이를 통해 useContext가 컨텍스트 값을 접근하는 과정을 간소화하며 기존의 Context.Consumer 컴포넌트에 비해 더 간결하고 가독성 있는 접근 방식을 제공한다는 것을 보여줍니다.


React useCallback

리액트의 useCallback 훅은 컴포넌트가 다시 렌더링될 때마다 함수를 재생성하는 것을 방지하여 애플리케이션의 성능을 최적화하는 데 유용한 도구입니다.

 

이는 앱의 효율성을 유지하는 데 중요합니다.

 

예를 들어, 이전에 살펴본 PlayerList 예시를 고려해 보겠습니다.

 

배열에 플레이어를 추가하고 플레이어를 제거하는 함수(handleRemovePlayer)를 props로 전달하도록 개선한다면, 이 함수는 매 렌더링마다 재생성되어 성능에 영향을 미칠 수 있습니다.

 

이를 해결하기 위해 우리는 useCallback을 사용하여 콜백 함수를 캡슐화하고 그 유일한 인자인 player를 의존성 배열에 포함시킬 수 있습니다.

 

다음은 간단한 예입니다:

function App() {
  const [player, setPlayer] = React.useState("");
  const [players, setPlayers] = React.useState(["Messi", "Ronaldo", "Laspada"]);

  function handleChangeInput(event) {
    setPlayer(event.target.value);
  }

  function handleAddPlayer() {
    setPlayers(players.concat(player));
  }

  const handleRemovePlayer = useCallback((player) => {
    setPlayers(players.filter((p) => p !== player));
  }, [players]);

  return (
    <>
      <input onChange={handleChangeInput} />
      <button onClick={handleAddPlayer}>Add Player</button>
      <PlayerList players={players} handleRemovePlayer={handleRemovePlayer} />
    </>
  );
}

function PlayerList({ players, handleRemovePlayer }) {
  return (
    <ul>
      {players.map((player) => (
        <li key={player} onClick={() => handleRemovePlayer(player)}>
          {player}
        </li>
      ))}
    </ul>
  );
}

 

이를 통해 useCallback이 컨텍스트 값을 접근하는 과정을 간소화하며 기존의 Context.Consumer 컴포넌트에 비해 더 간결하고 가독성 있는 접근 방식을 제공한다는 것을 보여줍니다.


React useMemo

이제 useMemo에 대해 살펴보겠습니다.

 

이 성능 향상 훅은 비싼 계산의 결과를 기억하여 불필요한 다시 계산을 피할 수 있도록 합니다.

 

useEffectuseCallback과는 달리 useMemo는 값을 반환하는 것을 목적으로 합니다.

 

콜백 함수와 의존성 배열이 필요하며, 콜백은 명시적으로 값을 반환해야 합니다.

 

mdx-bundler 문서에서 실제 예시를 살펴보겠습니다.

 

이 예시는 .mdx 파일을 리액트 컴포넌트로 변환합니다:

import * as React from 'react';
import { getMDXComponent } from 'mdx-bundler/client';

function Post({ code, frontmatter }) {
  const Component = React.useMemo(() => getMDXComponent(code), [code]);

  return (
    <>
      <header>
        <h1>{frontmatter.title}</h1>
        <p>{frontmatter.description}</p>
      </header>
      <main>
        <Component />
      </main>
    </>
  );
}

 

여기서 useMemo를 사용하여 Component 값이 다시 렌더링될 때 불필요하게 재생성되지 않도록 합니다.


이는 성능을 향상시키는 데 기여합니다.

 

끝.