구름톤 유니브☁️ 4기 FE 스터디

서론

이번 포스팅에서는 리액트에서Composition과 Inheritance를 비교하여 알아보고자 합니다.


본론

1. Composition

📖 Composition란?

여러 개의 컴포넌트를 조합해 새로운 컴포넌트를 만드는 기법으로
리액트는 컴포넌트를 가볍고 독립적인 단위로 보고, 이들을 조합해 UI를 구성하도록 설계되어 있다.

 

✅ Composition 방법

📌 Containment

// children prop을 사용한 FancyBorder 컴포넌트
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
// WelcomeDialog 컴포넌트
function WelcomeDialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        어서오세요
      </h1>
      <p className="Dialog-message">
        우리 사이트에 방문하신 것을 환영합니다!
      </p>
    </FancyBorder>
  );
}

하위 컴포넌트를 children 프로퍼티로 포함하는 가장 기본적인 합성 방법이다.

FancyBorder안의 컴포넌트들이 children이라는 props로 전달되는 것이다.

// SplitPane 컴포넌트
function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

// App 컴포넌트에서 SplitPane 사용 예시
function App(props) {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      }
    />
  );
}

만약 여러 개의 children 집합이 필요한 경우라면, 위 코드 처럼 left와 right라는 두 개의 props를 정의하여

각각 분리해서 렌더링하게 된다.

 

📌 Specialization

// Dialog 컴포넌트
function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

// Dialog를 특수화한 WelcomeDialog 컴포넌트
function WelcomeDialog(props) {
  return (
    <Dialog
      title="어서 오세요"
      message="우리 사이트에 방문하신 것을 환영합니다!"
    />
  );
}

범용 컴포넌트(개념)을 구별이 되도록 구체화하는 방법이다.

위 예시의 경우 WelcomeDialog(구체화된 개념)은 Dialog(범용적인 개념)의 특별한 케이스다.

기존 객체지향 개념에서는 상속을 사용하나 리액트에서는 Composition을 사용하여 Specialization을 한다.

 

✅ Containment와 Specialization 같이 사용하기

// 범용 Dialog 컴포넌트 (children을 사용하도록 확장)
function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}
// Dialog를 사용한 SignUpDialog 컴포넌트
function SignUpDialog(props) {
  const [nickname, setNickname] = useState('');

  const handleChange = (event) => {
    setNickname(event.target.value);
  };

  const handleSignUp = () => {
    alert(`어서 오세요, ${nickname}님!`);
  };

  return (
    <Dialog
      title="환성 탐사 프로그램"
      message="닉네임을 입력해 주세요."
    >
      <input
        value={nickname}
        onChange={handleChange}
      />
      <button onClick={handleSignUp}>
        가입하기
      </button>
    </Dialog>
  );
}

✅ Inheritance

📖 Inheritance(상속)이란?
상속, 다른 컴포넌트로부터 상속을 받아서 새로운 컴포넌트를 만드는 것

 

복잡한 컴포넌트를 쪼개서 여러개의 컴포넌트로 만들고 만든 컴포넌트를 조합해서 새로운 컴포넌트를 만들자!

라는 개념을 이해하고만 가자

 

📌 Composition vs Inheritance

상속은 객체지향 프로그래밍에서 클래스 기반 상속을 통해 기능을 확장하는 방식인데,

이는 리액트에서는 권장되지 않는 방식이고 리액트에서는 합성 방식을 권장한다.

왜 그럴까?

 

리액트에서 상속을 잘 사용하지 않는 이유

  • 복잡도 증가: 컴포넌트 간 의존성이 높아져 유지보수 어려움
  • 재사용성 증가: 자식 클래스에 의존적인 API 설계 필요
  • 컨텍스트 공유 어려움: props·state 조합이 복잡
class BaseButton extends React.Component {
  render() {
    return <button {...this.props} className="base-button" />;
  }
}

class IconButton extends BaseButton {
  render() {
    return (
      <BaseButton onClick={this.props.onClick}>
        <Icon name={this.props.icon} />
        {this.props.children}
      </BaseButton>
    );
  }
}

위 코드처럼 상속을 사용하면, IconButton이 BaseButton 구현에 종속적이어서 변화에 약하다는 문제점이 존재한다.


마무리

이번 글에서는 리액트에서 컴포넌트를 조합하는 기법인 Composition과 Inheritance에 대해 다뤄보았는데

전체적인 내용을 요약하자면

  Composition Inheritance
유연성 매우 높음 (런타임에 조합 가능) 낮음 (컴파일/설계 시 결정)
재사용성 범용 컴포넌트 + 슬롯 구조 클래스 계층 설계 필요
유지보수성 낮은 결합도 → 용이 높은 결합도 → 어려움
결론 ✅ 권장 ❌ 비권장

 

오늘도 읽어주셔서 감사합니다 🫡

'TIL > React' 카테고리의 다른 글

[React] Handling Events & Conditional Rendering & List and Keys  (1) 2025.05.06
[React] useMemo(), useCallback(), useRef()  (0) 2025.04.14
[React] JSX & Rendering Elements  (0) 2025.04.08
[React] React.js란?  (0) 2025.04.08

구름톤 유니브☁️ 4기 FE 스터디

서론

이번 포스팅에서는 리액트에서 이벤트를 처리하는 방법과 조건부 렌더링 그리고 리스트와 키에 대해 알아보고자 합니다.


본론

1. Handling Events

📖 Event란?

우선 이벤트의 개념에 대해 정의하자면, 이벤트는 컴퓨터 프로그램에서 어떠한 특정 사건을 의미한다.

예를들면 사용자가 버튼을 클릭하는 것도 이벤트라고 볼 수 있다.

웹 사이트가 정상적으로 돌아가기 위해서는 다양한 이벤트를 잘 처리해야할 필요가 있다.

 

✅ DOM과 리액트에서의 Event

📌 DOM의 Event

<button onclick="activate()">
	Activate
</button>

DOM에서는 click이라는 이벤트를 처리할 함수를 onclick 으로 정의하게 된다.

 

📌 리액트의 Event

<button onClick={activate}>
	Activate
</button>

리액트에서는 onClick 이벤트의 이름이 카멜표기법으로 표기된다.

DOM에서는 함수를 문자열로 전달하지만 리액트에서는 함수 그대로 전달한다.

📖 Camel Case(카멜 표기법)란?

사막에있는 낙타의 등 모양처럼 첫글자는 소문자, 중간에나오는 새로운 단어는 대문자로 시작하는 표기법이다.

흔하게 사용하기 때문에 알아두자

 

✅ Event Handler (Event Listener)

해당 이벤트를 처리하는 함수를 이벤트 핸들러라고 한다.

이는 어떤 사건이 발생하면, 사건을 처리하는 역할을 수행한다.

 

📌 클래스 컴포넌트의 경우

class Toggle extends React.Component {
  constructor(props) {
    super(props);

    this.state = { isToggleOn: true };

    // callback에서 `this`를 사용하기 위해서는 바인딩을 필수적으로 해줘야 합니다
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? '켜짐' : '꺼짐'}
      </button>
    );
  }
}

handleClick 함수를 정의하는 것은 일반적인 함수 정의와 동일하게 한다.

이 때, 이러한 handleClick 함수에서 this를 사용하려면 constructor에서 바인딩을 필수로 해야한다.

다만, 클래스 컴포넌트를 최근 잘 쓰지 않기 때문에 간단히 읽고만 넘어가자

 

📌 함수 컴포넌트의 경우

function Toggle(props) {
  const [isToggleOn, setIsToggleOn] = useState(true);

  // 방법 1. 함수 안에 함수로 정의
  function handleClick() {
    setIsToggleOn((isToggleOn) => !isToggleOn);
  }

  // 방법 2. arrow function을 사용하여 정의
  const handleClick = () => {
    setIsToggleOn((isToggleOn) => !isToggleOn);
  }

  return (
    <button onClick={handleClick}>
      {isToggleOn ? "켜짐" : "꺼짐"}
    </button>
  );
}

함수 컴포넌트에서는 함수 안에 또 다른 함수로 정의하거나 arrow function을 이용하여 정의한다.

함수 컴포넌트에서는 onClick 이벤트리스터에 this를 사용하지않고 바로 함수를 넣어준다.

 

Arguments 전달하기

Arguments는 함수에 주장할 내용으로 이해하자

즉, 함수에 전달할 데이터 = 이벤트 핸들러에 전달할 데이터 = 파라미터

function MyButton(props) {
  const handleDelete = (id, event) => {
    console.log(id, event.target);
  };

  return (
    <button onClick={(event) => handleDelete(1, event)}>
      삭제하기
    </button>
  );
}

파라미터를 전달하는 방법은 위 코드와 같이 수행한다. (함수 컴포넌트의 경우)


2. Conditional Rendering

Condition은 조건을 의미한다.

즉, 조건부 렌더링은 어떠한 조건에 따라서 렌더링의 결과가 달라지는 것을 의미한다.

예를들어 조건부의 값이 True면 버튼을 보여주고, False이면 버튼을 가린다.

function UserGreeting(props) {
  return <h1>다시 오셨군요!</h1>;
}

function GuestGreeting(props) {
  return <h1>회원가입을 해주세요.</h1>;
}

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;

  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

위 코드의 경우 isLoggedIn의 값에 따라 다른 렌더링 결과를 나타낸다.

📖 JS의 Truthy와 Falsy란?

보통의 언어에서는 참과 거짓을 분류할때 boolean 자료형을 사용하고 True와 False로 분류한다.

JS에서는 True는 아니지만, True로 여겨지는 값이 존재하는데 이를 Truthy라고 한다.

마찬가지로, False는 아니지만, False로 여겨지는 값역시 Falsy라고 한다.

// truthy
true
{}          // empty object
[]          // empty array
42          // number, not zero
"0", "false" // string, not empty

// falsy
false
0, -0       // zero, minus zero
0n          // BigInt zero
'', "", ``  // empty string (single, double, template)
null
undefined
NaN         // not a number

위는 자바스크립트의 대표적인 Truthy와 Falsy를 나타내는 경우들이다.

 

Element Variables

조건부 렌더링을하다보면 렌더링해야할 것을 변수로 다뤄야하는 경우가 존재한다.

아래는 리액트 element를 변수로 다루는 방법이다.

function LoginControl(props) {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleLoginClick = () => {
    setIsLoggedIn(true);
  };

  const handleLogoutClick = () => {
    setIsLoggedIn(false);
  };

  let button;
  if (isLoggedIn) {
    button = <LogoutButton onClick={handleLogoutClick} />;
  } else {
    button = <LoginButton onClick={handleLoginClick} />;
  }

  return (
    <div>
      <Greeting isLoggedIn={isLoggedIn} />
      {button}
    </div>
  );
}

컴포넌트라고 하지만, button은 컴포넌트로부터 생성된 리액트 element인 것

이런식으로 element를 변수처럼 저장해서 사용한다.

 

Inline Conditions

Inline Conditions는 조건문을 코드 안에 집어넣는 것을 의미한다.

 

📌 Inline if

if문과 동일하게 사용하기 위해 && 논리 연산자를 사용한다.

 

inline if는 이러한 && 연산자를 jsx 코드안에서 중괄호를 사용해 직접 넣는 방법

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;

  return (
    <div>
      <h1>안녕하세요!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          현재 {unreadMessages.length}개의 읽지 않은 메시지가 있습니다.
        </h2>
      }
    </div>
  );
}

function Counter(props) {
  const count = 0;

  return (
    <div>
      {count && <h1>현재 카운트: {count}</h1>}
    </div>
  );
}

이 때, && 논리 연산자 결과가 False 라고 하더라도 아무것도 안나오는게 아니라 0 값이 렌더링되어 나온다.

 

📌 Inline if-else

Inline if-else 문은 ? 삼항연산자를 사용한다.

function UserStatus(props) {
  return (
    <div>
      이 사용자는 현재 <b>{props.isLoggedIn ? '로그인' : '로그인하지 않은'}</b> 상태입니다.
    </div>
  );
}
function LoginControl(props) {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleLoginClick = () => {
    setIsLoggedIn(true);
  };

  const handleLogoutClick = () => {
    setIsLoggedIn(false);
  };

  return (
    <div>
      <Greeting isLoggedIn={isLoggedIn} />
      {isLoggedIn
        ? <LogoutButton onClick={handleLogoutClick} />
        : <LoginButton  onClick={handleLoginClick} />
      }
    </div>
  );
}

이 때, 문자열이 아닌 element를 넣어서 사용할 수도 있다.

 

📖 만약 컴포넌트를 렌더링하고 싶지 않을때는?
function MainPage(props) {
  const [showWarning, setShowWarning] = useState(false);

  const handleToggleClick = () => {
    setShowWarning(prevShowWarning => !prevShowWarning);
  }

  return (
    <div>
      <WarningBanner warning={showWarning} />
      <button onClick={handleToggleClick}>
        {showWarning ? '감추기' : '보이기'}
      </button>
    </div>
  );
}

null을 리턴하면 렌더링되지 않는다.


3. List and Keys

list는 목록으로 같은 아이템을 순서대로 모아놓은 것이다.

리스트를 위해 사용하는 자료구조가 배열이다. JS의 변수나 객체들을 하나의 변수로 묶어놓은 것

key는 열쇠와 유사한데 각 열쇠마다 고유한 모양을 가지고 있는 것처럼

key는 리스트에서 각 객체나 아이템을 구분할 수 있는 고유한 값을 의미한다.

 

여러 개의 Component 렌더링하기

JS의 map() 함수를 사용하여 여러 개의 컴포넌트를 렌더링할 수 있다.

이는 배열에 들어있는 값을 어떤 처리를한 뒤 리턴을 하는 것이다.

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

배열의 첫번째 아이템부터 순서대로 처리하여 리턴하여 총 5개의 <li> 컴포넌트가 생성된다.

이를 render 함수를 통해 렌더링하게 된다.

📖 리스트에 Key를 지정하지 않는다면?
function NumberList(props) {
  const { numbers } = props;

  const listItems = numbers.map((number) =>
    <li>{number}</li>
  );

  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

위와 같이 오류가 발생한다. 따라서 리스트의 각 값은 고유한 key를 가지고 있어야 한다.

 

List의 Key

리액트에서 키는 아이템을 구분하기 위한 고유한 문자열이다. (ex. 학번, 주민번호…)

리액트에서 키값은 같은 리스트에 있는 elements 사이에서만 고유한 값이면 된다.

두 대학교 사이에는 학번이 동일하여도 문제가 없는 것 처럼

두 리스트 사이에서는 Key가 같아도 상관 없다.

 

📌 Key로 id를 사용하는 경우

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

📌 Key로 index를 사용하는 경우

const todoItems = todos.map((todo, index) =>
  // 아이템들의 고유한 ID가 없을 경우에만 사용해야 함
  <li key={index}>
    {todo.text}
  </li>
);

이 때, 배열 내 아이템의 순서가 바뀔 수 있는 경우 index로 키값을 사용하는 걸 권장하지 않는다.

성능에 부정적인 영향을 미칠 수 있기 때문이다.

또한, 키를 넣어주지 않으면 기본적으로 인덱스값을 키로 사용한다.


 

마무리

이번 글에서는 리액트에서 이벤트를 처리하는 방법과 조건부 렌더링 그리고 리스트와 키에 대해 다뤄보았는데

전체적인 내용을 요약하자면

 

1. Handling Events

  • React는 <button onClick={handler}> 식으로 카멜케이스 이벤트에 함수 레퍼런스를 전달
  • 함수 컴포넌트에선 useState + 내부 함수(또는 화살표 함수)로 간단히 핸들러 정의
  • 인자 전달 시 onClick={(e) => handleDelete(id, e)} 형태 사용

2. Conditional Rendering

  • if–return 으로 분기하거나
  • Inline if (&&) / if–else (?:)를 JSX 안에 바로 삽입
  • 렌더링 안 할 땐 return null 또는 condition && <Component/>

3. Lists and Keys

  • array.map(item => <li>{...}</li>) 으로 여러 엘리먼트 생성
  • 각 요소에 고유한 key(가능하면 id, 불가피할 땐 index) 지정
  • 올바른 키 사용이 React의 효율적인 업데이트를 돕습니다.

 

오늘도 읽어주셔서 감사합니다 🫡

'TIL > React' 카테고리의 다른 글

[React] Composition vs Inheritance  (0) 2025.05.13
[React] useMemo(), useCallback(), useRef()  (0) 2025.04.14
[React] JSX & Rendering Elements  (0) 2025.04.08
[React] React.js란?  (0) 2025.04.08

구름톤 유니브☁️ 4기 FE 스터디

서론

이번 포스팅에서는 리액트 Hooks 중 useMemo(), useCallback(), useRef() Hook에 대해 알아보고자 합니다.


본론

1. useMemo()

useMemo는 memoized value, 즉 “기억된 값”을 리턴해주는 Hook이다.

📖 Memoization이란?

최적화를 위해 사용하는 개념으로

비용이 높은 함수의 호출 결과를 저장해두었다가 같은 입력이 들어오면 다시 계산하지 않고 이전에 저장했던 결과를 반환한다.

따라서 불필요한 중복 연산을 줄여 리소스를 적게 사용하는 최적화 기법이다.

const memoizedValue = useMemo(
	() => {
    	// 연산량이 높은 작업을 수행하여 결과를 반환
        return computeExpensiveValue(의존성 변수, 의존성 변수2);
    },
    [의존성 변수1, 의존성 변수2]
);

 

✅ useMemo()의 동작 방식

  • 첫 번째 파라미터: 계산할 함수 (memoized value를 생성하는 함수)
  • 두 번째 파라미터: 의존성 배열

📌 의존성 배열의 값이 바뀔 때만 내부 함수가 실행되고, 그 외에는 이전 계산 값을 그대로 반환한다. => 빠른 렌더링 속도

📌 이 때, useMemo()로 전달되는 함수는 렌더링이 일어나는 동안에만 실행된다.
      즉, sideEffect와 같은 렌더링이 일어나는 동안에 일어나면 안될 작업을 useMemo()에서 수행해선 안된다.

 

의존성 배열이 없다면?
const memoizedValue = useMemo(
	() => computeExpensiveValue(a, b)
);

위 코드와 같이 useMemo()를 사용할 때 의존성 배열이 없을 경우 매 렌더링마다 함수가 실행된다.

이는 useMemo()를 사용하는 의미가 없는 행위이므로, 의존성 배열을 명확히 넣어주도록 하자

의존성 배열이 빈 배열이라면?
const memoizedValue = useMemo(
	() => {
    	return computeExpensiveValue(a, b);
    },
    []
);

위 코드와 같이 의존성 배열이 빈 배열일 경우에는 컴포넌트가 처음 마운트될 때 함수가 실행된다.

따라서 마운트 시점에만 필요한 연산(초기화 등)에 주로 사용된다.


2. useCallback()

useCallback은 useMemo와 거의 똑같이 생겼지만, 값이 아닌 “함수”를 기억하고 반환한다는 점에서 다르다.

useCallback()은 주로 props로 자식 컴포넌트에 함수를 넘겨줄 때, 불필요한 리렌더링을 방지하고자 할 때 사용한다.

const memoizedCallback = useCallback(
	() => {
    	doSomething(의존성 변수1, 의존성 변수2);
    },
    [의존성 변수1, 의존성 변수2]
);

 

✅ useCallback()의 동작 방식

  • 첫 번째 파라미터: 계산할 함수 (memoized value를 생성하는 함수) 
  • 두 번째 파라미터: 의존성 배열 

계산할 함수는 콜백이라고 부르고, 의존성 배열의 변수가 변경될 경우 memoization된 콜백함수를 반환한다.

import { useState, useCallback } from 'react';

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

  // 컴포넌트가 마운트 될 때만 함수가 정의됨
  const handleClick = useCallback((event) => {
    // 클릭 이벤트 처리
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        {count}
      </button>
      <ChildComponent handleClick={handleClick} />
    </div>
  );
}

export default ParentComponent;

위와 같이 hadleClick 함수를 useCallback을 사용하여 정의하게 되었고 의존성 배열을 빈 배열로 두었으므로,

컴포넌트가 마운트 될 때만 함수가 정의된다. 따라서 함수의 불필요한 리렌더링(정의)를 줄일 수 있게 된다.


3. useRef()

useRef()는 컴포넌트 내에서 어떤 값을 기억하거나 특정 DOM 요소를 직접 참조하고 싶을 때 사용한다.

참조(Reference)란 특정 컴포넌트에 접근할 수 있는 객체를 의미한다.

const inputRef = useRef();

<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>

 

 

📖 useRef()

  • useRef()는 파라미터로 초기값을 넣으면 초기화된 Reference 객체를 반환한다.
  • Reference 객체는 컴포넌트 생명주기 전체에 걸쳐 유지된다.
  • useRef()는 변경가능한 current라는 속성을 가진 하나의 상자라고 생각하면 된다.
function TextInputWithFocusButton(props) {
  const inputElem = useRef(null);

  const onButtonClick = () => {
    // current는 마운트된 input element를 가리킴
    inputElem.current.focus();
  };

  return (
    <>
      <input ref={inputElem} type="text" />
      <button onClick={onButtonClick}>
        Focus the input
      </button>
    </>
  );
}

 

useRef()는 내부의 데이터가 변경되었을 때 별도로 알리지 않는다는 점을 기억하자

이는 속성 변경이 리렌더링으로 이어지지 않는다는 것을 의미한다.

 

📖 Callback Ref
function MeasureExample(props) {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback((node) => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <>
      <h1 ref={measuredRef}>안녕, 리액트</h1>
      <h2>위 헤더의 높이는 {Math.round(height)} 입니다.</h2>
    </>
  );
}

callback ref방식을 사용하면 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있고 이를 통해 다른 정보들을 업데이트 할 수 있다.

<h1>태그가 마운트 언마운트 될때만 업데이트가 일어나는 것

 

 


마무리

이번 글에서는 useMemo(), useCallback(), useRef() 훅을 다뤄보았는데 전체적인 내용을 요약하자면

Hook 역할 반환 값 주 용도
useMemo() 연산 결과를 기억 무거운 계산 최적화
useCallback() 함수 자체를 기억 함수 자식 컴포넌트 리렌더링 방지 등
useRef() DOM 참조 / 값 저장 ref 객체 DOM 제어, 렌더링 영향 없는 변수 저장

 

오늘도 읽어주셔서 감사합니다 🫡

'TIL > React' 카테고리의 다른 글

[React] Composition vs Inheritance  (0) 2025.05.13
[React] Handling Events & Conditional Rendering & List and Keys  (1) 2025.05.06
[React] JSX & Rendering Elements  (0) 2025.04.08
[React] React.js란?  (0) 2025.04.08

구름톤 유니브☁️ 4기 FE 스터디

서론

이번 포스팅에서는 JSX란 무엇이고 리액트에서 화면에 요소를 표시하는 렌더링에 대해 간단히 알아보고자 합니다.


본론

1. JSX란?

JSX는 JavaScript + XML/HTML의 줄임말로,
JS 코드 안에서 마치 HTML처럼 태그를 쓸 수 있게 해주는 JS 문법 확장이다.

const element = <h1>Hello, world!</h1>;

위 JSX 코드는 내부적으로 다음과 같은 JS 코드로 변환된다.

const element = React.createElement(
  'h1',
  { className: null },
  'Hello, world!'
);

 

JSX의 장점

 

  • 📦 간결한 코드: 보기 쉽고 쓰기 편함
  • 👁️ 가독성 향상: HTML 구조처럼 보여서 코드 흐름 파악이 쉬움
  • 🐞 버그 찾기 쉬움: 구조가 명확해 디버깅에 유리
  • 🛡️ 보안성 강화: Injection 공격 방지 등 내부적으로 보안에도 강점

리액트에서 JSX를 사용하는 것이 필수는 아니나, 가독성과 코드 복잡성 면에서 매우 편리하여 많은 개발자가 JSX를 사용하고 있다.

 


2. Rendering Elements (렌더링 요소)

Elements는 어떤 물체를 구성하는 성분이라고 정의할 수 있다.

이는 리액트에서는 리액트 앱을 구성하는 요소라고 볼 수 있다.

즉, Element는 리액트 앱을 구성하는 가장 작은 블록을 의미한다.

const element = <h1>Hello</h1>;

 

이건 React Element라고도 불리며, HTML 요소와 비슷하지만 JS 객체 형태이다.

이 Element는 실제로는 이런 JS 객체라고 보면 된다.

{
  type: 'h1',
  props: {
    children: 'Hello'
  }
}

 

 

React Elements = Virtual DOM

 

React Element(왼쪽)는 Virtual DOM을 구성하고, render를 통해 실제 브라우저 DOM(오른쪽) 바뀐다.

리액트에서 <h1>Hello</h1> 같은 JSX는 결국 React Element라는 자바스크립트 객체로 변환된다.
그리고 이 React Element 들이 모여 만들어진 것이 바로 Virtual DOM이다.

 

Elements의 특징

  • Immutable (불변성)

Component = 붕어빵 틀 / Element = 붕어빵

 

React Element는 불변성을 지니므로 한 번 만든 Element는 바꿀 수 없고, 변경되면 새로 만들어서 갈아끼운다.

즉, 변경해야할 사항이 있다면 새로운 Element를 만들어서 이를 re-rendering하게 된다고 보면된다.

 

Element Rendering

const element = <h1>안녕, 리액트!</h1>
ReactDOM.render(element, document.getElementById('root'));

리액트 렌더링은 Virtual DOM에서 실제 DOM으로 이동하는 과정

function tick(){
  const element = (
    <div>
    	<h1> 안녕, 리액트!</h1>
    	<h2> 현재 시간 : {new Date().toLocalTimeString()}</h2>
	</div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);

setInterval() 함수에 의해 1초마다 tick 함수가 실행될 때 마다 새로운 Element가 갱신되게 된다.

이는 기존 Element를 변경한다기보다는 매 초마다 새로운 Element를 생성하고 이를 re-rendering하는 개념으로 봐야한다.


마무리

이번 글에서는 JSX와 Rendering Element의 개념을 다뤄보았는데 전체적인 내용을 요약하자면

  • JSX는 XML/HTML이 더해진 JS 문법 확장이고
  • Element는 리액트 앱을 구성하는 가장 작은 단위
  • React에서는 Virtual DOM과 비교 후, 필요한 부분만 실제 DOM에 반영하는 방식으로 빠르게 렌더링

 

읽어주셔서 감사합니다 🫡

구름톤 유니브☁️ 4기 FE 스터디

서론

프론트엔드 개발을 시작하게 된다면 꼭 들어보고 사용하게 될텐데

정작 리액트가 뭐냐고 물어봤을 때 대답하기가 어렵곤 했습니다 (ㄱㅡ)...

따라서 이번 포스팅에서는 React에 대한 간단한 소개를 하고자 합니다!


본론

React.js

1. React란?

리액트는 JavaScript UI 라이브러리. 쉽게 말해, 사용자 인터페이스(UI)를 효율적이고 빠르게 만들 수 있게 도와주는 도구라고 볼 수 있다.

 

리액트 외에도 Angular JS, Vue JS와 같은 Java Script UI 프레임워크가 존재한다.

프레임워크? 라이브러리?

사실 리액트를 사용하면서도 은근 자주 헷갈려했던 부분인데, 리액트는 프레임워크가 아닌 라이브러리라는 점이었다.

그럼 프레임워크와 라이브러리는 어떻게 다른걸까? 이는 App의 흐름의 제어권한이 누구에게 있냐의 차이로 볼 수 있다.

  • 프레임워크: 흐름의 제어권한을 프레임워크가 가지고 있다.
  • 라이브러리: 흐름의 제어권한이 개발자에게 있다. 즉, 개발자가 라이브러리의 코드를 호출하여 사용한다고 볼 수 있음.

2. React의 장점

- Virtual Dom을 통한 빠른 렌더링

DOM? Virtual DOM?

DOM(Document Object Model)은 HTML 문서를 계층 형태로 표현한 인터페이스이다.

즉, DOM은 웹사이트의 정보를 모두 담고있는 객체이고, 화면이 업데이트되는 것은 곧 DOM이 수정됨을 의미한다.

 

그런데 문제점은 이러한 브라우저 DOM을 수정하는 작업은 비용이 많이 드는 무거운 작업이라는 것이다.

 

따라서 리액트에서는 Virtual DOM을 사용함으로써 State가 변경되었을 때 이 변경된 부분에 대해서만 업데이트를 수행하기 때문에 빠른 렌더링이 가능해지는 것이다.

 

- Components-Based (컴포넌트 기반)

리액트의 또 다른 장점은 컴포넌트 기반으로 개발이 진행된다는 점이다.

마치 레고 블럭을 조립하듯 리액트에서는 각 컴포넌트들을 조합하여 웹 사이트를 개발하는 것이다.

 

예를 들어, 하나의 웹페이지를 '헤더', '네비게이션 바', '게시글 목록', '댓글창'처럼 각각의 독립된 컴포넌트로 나눌 수 있고, 이걸 조립하듯이 붙여서 하나의 화면을 만든다.

이러한 컴포넌트 기반 방식이 가지는 장점은 다음과 같다.

  • 재사용성: 각각의 독립된 컴포넌트들을 여러 곳에서 재사용할 수 있다.
  • 유지보수 용이: 변경 사항이 생기더라도 해당 컴포넌트만 수정하면 되므로, 유지보수가 용이하다.

- 활발한 커뮤니티와 생태

리액트는 Meta에서 진행 중인 오픈소스 프로젝트인만큼, 지속적인 업데이트와 풍부한 생태계를 갖고 있다.

  • 문서화가 잘 되어있으며
  • StackOverFlow나 Github에서 활발한 지식 공유가 이루어지고 있으며
  • React Native를 통해 Java Script만으로 웹과 모바일 서비스를 모두 개발할 수도 있다.

- 단점 역시 존재하는데...

  • 배워야 할 개념이 많음 (JSX, Props, State, Hook, Routing, 상태관리 등등...)
  • 상태 관리를 하려면 Recoil, Redux 같은 추가 도구가 필요함
  • 업데이트가 잦은 만큼 학습량이 늘어나는 경향이 있다

하지만 이건 프론트엔드 개발 분야가 워낙 트렌드에 민감한 분야라 어쩔 수 없는 운명이기도 하다...🥲


3. 리액트 시작하기

- 직접 리액트를 연동하는 방법

1. 기존 HTML 코드에 DOM Container (root DOM node)를 추가한다. 이는 Virtual DOM의 시작점이라고 이해

<div id="root"></div>

2. 리액트 JS 파일들을 가져온다.

    <!-- 리액트 가져오기 -->
    <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>

    <!-- 리액트 컴포넌트 가져오기 -->
    <script src="MyButton.js"></script>

 

- create-react-app

1. Node.js와 npm 환경 설정

  • Node.js v14.0.0 이상
  • npm v6.14.0 이상
  • VS Code 설치

2. npx 명령어를 이용해 실행

npx create-react-app <프로젝트 이름>

패키지를 정해진 위치에 설치하고 찾아서 사용하는 같은 번거로운 작업을 편리하게 수행해준다.


마무리

리액트를 처음 접하면 생소하고 어려운 개념들이 많을 수 있지만, 그만큼 매력도 많은 라이브러리입니다.

이번 글에서는 React의 개요에 대해 다뤘고, 다음 포스팅에서는 JSX나 Props, State 같은 더 구체적인 리액트 개념을 하나씩 알아볼 예정입니다!

 

읽어주셔서 감사합니다 🫡

+ Recent posts