이미지 불러오기

이미지 파일은 import구문을 통해 불러오고, 불러온 이미지 주소를 src 속성으로 사용하면 된다.

import diceImg from './assets/dice.png';

function Dice() {
  return <img src={diceImg} alt="주사위 이미지" />;
}

export default App;

인라인 스타일

리액트에서 인라인 스타일은 문자열이 아닌 객체형으로 사용한다. 프로퍼티 이름은 CSS 속성 이름으로, 프로퍼티 값은 CSS 속성 값으로 쓰는데, 이때 프로퍼티 이름은 아래의 boarderRadius 처럼 대시 기호 없이 카멜 케이스로 써야 한다.

import diceImg from './assets/dice.png';

const style = {
  borderRadius: '50%',
  width: '120px',
  height: '120px',
};

function Dice() {
  return <img style={style} src={diceImg} alt="주사위 이미지" />;
}

export default App;

CSS 파일 불러오기

import 구문으로 파일을 불러올 수 있는데 from 키워드 없이 사용하면 된다.

import diceImg from './assets/dice.png';
import './Dice.css';

function Dice() {
  return <img src={diceImg} alt="주사위 이미지" />;
}

export default App;

클래스네임 사용하기

CSS 파일에 정의된 클래스명을 className prop에 문자열로 넣어주면 된다. 이 때 재사용성을 위해 clasName prop을 부모 컴포넌트에서 받으면 더 좋다.

import diceImg from './assets/dice.png';
import './Dice.css';

function Dice({ className = '' }) {
  const classNames = `Dice ${className}`;
  return <img className={classNames} src={diceImg} alt="주사위 이미지" />;
}

export default App;

🍯더 편리하게 클래스네임 사용하기

  • 위에서 여러 className을 템플릿 문자열로 합쳐서 사용해봤는데, 몇 개 없을 때는 상관 없지만 개수가 늘어 날수록 아래처럼 알아보기 힘들어진다는 문제점이 있다.

1. 템플릿 문자열을 사용

function Button({ isPending, color, size, invert, children }) {
  const classNames = `Button ${isPending ? 'pending' : ''} ${color} ${size} ${invert ? 'invert' : ''}`;
  return <button className={classNames}>{children}</button>;
}

export default Button;

2. 배열을 사용

function Button({ isPending, color, size, invert, children }) {
  const classNames = [
    'Button',
    isPending ? 'pending' : '',
    color,
    size,
    invert ? 'invert' : '',
  ].join(' ');
  return <button className={classNames}>{children}</button>;
}

export default Button;

위 예시 코드처럼 지저분하게 느껴지고, 매번 반복되는 코드를 작성한다는 번거로움이 있다. 개발자들은 이럴 때 라이브러리라는 걸 사용하는데, 다른 개발자가 미리 만들어 놓은 코드를 이용해서 편하게 개발하는 것이다.

클래스네임의 경우에도 편리하게 사용할 수 있는 라이브러리가 많이 있다. 그중에서도 이번에 사용할 라이브러리는 바로 classnames라는 라이브러리이다. 아래 예시 코드를 보시면 알겠지만, 클래스네임에만 집중할 수 있어 훨씬 읽기 편하다. 이렇게 적절한 라이브러리를 쓰면 개발 생산성이 엄청 좋아진다.

3. classnames 라이브러리를 사용

import classNames from 'classnames';

function Button({ isPending, color, size, invert, children }) {
  return (
    <button
      className={classNames(
        'Button',
        isPending && 'pending',
        color,
        size,
        invert && 'invert',
      )}>
     { children }
   </button >
  );
}

export default Button;

classnames 은 NPM이라는 프로그램을 통해 설치할 수 있다. 터미널에서 npm install classnames 을 입력하고 설치한 다음에, 위 예시처럼 import 로 불러와서 사용하면 된다. NPM 저장소 사이트로 들어가면 사용 방법과 설명이 나와있으니, 아래 링크를 한 번 살펴보고 사용해보는 것도 좋다.

NPM classnames 패키지

'코린이 개념잡기 > React' 카테고리의 다른 글

브라우저가 리액트를 알아듣는 원리  (0) 2025.01.04
리액트 렌더링  (0) 2025.01.03
State  (0) 2025.01.03
Props와 Children  (1) 2025.01.03
컴포넌트 문법  (0) 2025.01.03
  • 변수의 범위(scope)가 코드의 구조에 따라 결정되는 방식
  • 즉, 함수가 정의된 위치에 따라 어떤 변수에 접근할 수 있는지를 결정한다.
  • JavaScript에서는 렉시컬 스코프를 따른다.

특징

1. 함수가 정의된 위치에 따라 그 함수가 접근할 수 있는 변수의 범위가 결정된다. (함수가 호출되는 위치와는 무관)

2. 함수 안에 다른 함수를 정의할 수 있다. 내부 함수는 외부 함수의 변수에 접근할 수 있지만 반대는 성립되지 않는다.

function outer() {
    const outerVar = 'I am outer';

    function inner() {
        console.log(outerVar); // 'I am outer' 출력
    }

    inner();
}

outer();

3. 전역에서 정의된 변수는 모든 내부 함수에서 접근할 수 있지만, local에서 정의된 변수는 그 지역의 함수 내에서만 접근할 수 있다.

const globalVar = 'I am global';

function myFunction() {
    const localVar = 'I am local';

    console.log(globalVar); // 'I am global' 출력
    console.log(localVar); // 'I am local' 출력
}

myFunction();
console.log(localVar); // ReferenceError 발생

렉시컬 스코프와 클로저(Closure)

  • 렉시컬 스코프와 관련된 중요한 개념 중 하나는 클로저(closure)이다.
  • 클로저는 함수 안에 있는 함수로, 외부 함수의 변수를 기억하고 사용할 수 있는 내부함수이다.
function myHouse() {
  let toy = "축구공";

  function playGround() {
    console.log(toy); // '축구공'을 출력
  }

  return playGround;
}

let play = myHouse();
play(); // '축구공'을 출력
  • myHouse 함수는 toy라는 장난감을 가지고 있다.
  • playGround 함수는 toy를 기억하고 있고,
  • 나중에 playGround 함수를 실행할 때 toy를 사용할 수 있다.

'코린이 개념잡기 > JavaScript' 카테고리의 다른 글

자바스크립트 this  (0) 2025.01.03
웹 브라우저의 동작 원리  (1) 2024.12.28
var, let, const  (0) 2024.12.28
얕고(Shallow), 깊은(Deep) 복사(Copy)  (1) 2024.12.20
==와 ===의 차이  (0) 2024.12.20
  • this가 가리키는 값은 호출되는 방식에 따라 달라진다.

1. 전역 컨텍스트(코드가 전역에서 실행될때의 환경): 전역에서 함수가 호출될 때, this는 전역 객체(브라우저에서는 window)를 가리킨다. 일반적으로 브라우저에서의 전역객체는 window이며 서버(node.js)에서의 전역객체는 global이다.

console.log(this); //window
console.log(this === window); //true

2. 객체 메소드 : 객체의 메소드로 호출될 때 this는 그 메소드를 호출한 객체를 가리킨다. 메소드를 호출하면 기본적으로 this는 해당 메소드를 가지고 있는 객체에 바인딩(binding, 어떤 코드에서 함수를 호출할 때 그 해당 함수가 위치한 메모리 주소로 연결)된다.

const obj = {
    name: 'Elsa',
    greet: function() {
        console.log(this.name);
    }
};
obj.greet(); // 'Elsa' 출력

3. 생성자 함수 : 생성자 함수에서 this는 새로 생성된 객체를 가리킨다.

function Person(name) {
    this.name = name;
}
const person = new Person('Arony');
console.log(person.name); // 'Arony' 출력

4. 화살표 함수 : 화살표 함수에서는 this가 상위 스코프의 this를 그대로 사용한다. 즉, 화살표 함수 내에서 this는 자신이 정의된 위치의 this를 참조한다.

‼️단, 메소드가 화살표 함수로 작성되었을 경우 화살표 함수의 this는 상위 컨텍스트의 this를 계승받기 때문에 this가 전역객체에 바인딩된다.
const obj = {
    name: 'Charlie Puth',
    greet: function() {
        const arrow = () => console.log(this.name);
        arrow();
    }
};
obj.greet(); // 'Charlie Puth' 출력

-----

const zoo = {
  tiger: function() {
    console.log('tiger this:', this);
  },
  rabbit() {
    console.log('rabbit this:', this);
  },
  pig: () => {
    console.log('pig this:', this);
  }
}

zoo.tiger(); //object
zoo.rabbit(); //object
zoo.pig(); //window

5.이벤트 핸들러 : DOM 이벤트 핸들러에서 this는 이벤트가 발생한 요소를 가리킨다.

const button = document.querySelector('button');
button.addEventListener('click', function() {
    console.log(this); // 버튼 요소를 출력
});

'코린이 개념잡기 > JavaScript' 카테고리의 다른 글

렉시컬 스코프(Lexical scope)  (3) 2025.01.03
웹 브라우저의 동작 원리  (1) 2024.12.28
var, let, const  (0) 2024.12.28
얕고(Shallow), 깊은(Deep) 복사(Copy)  (1) 2024.12.20
==와 ===의 차이  (0) 2024.12.20

state는 리액트에서 변수 같은것인데, State를 바꾸면 리액트가 알아서 화면을 새로 렌더링 해준다.

리액트에서 state를 만들고, state를 바꾸기 위해서는 일단 useState라는 함수를 활용해야 한다.

//App.js
import { useState } from 'react';

// ...

  const [num, setNum] = useState(1);

// ...

보통 이렇게 Destructuring 문법으로 작성하는데 useState 함수가 초기값을 아규먼트로 받고 그에 따른 실행 결과로 요소 2개를 가진 배열의 형태로 리턴을 하기 때문이다.

이때 첫 번째 요소가 바로 state이고, 두 번째 요소가 이 state를 바꾸는 setter 함수인데, 참고로 위 코드에서도 볼 수 있듯 첫 번째 변수는 원하는 state의 이름(num)을 지어주고, 두 번째 변수에는 state 이름 앞에 set을 붙인 다음 카멜 케이스로 이름을 지어주는 것(setNum)이 일반적이다.

state는 변수에 새로운 값을 할당하는 방식으로 변경하는 것이 아니라 반드시 setter 함수를 활용해야 하는데 setter 함수는 호출할 때 전달하는 아규먼트 값으로 state 값을 변경해 준다. (그래서 const 키워드로 state를 만든다.)

그래서 아래 코드처럼 setter 함수를 활용해서 이벤트 핸들러를 등록해두면, 이벤트가 발생할 때마다 상태가 변하면서 화면이 새로 그려지게 된다.

//App.js
import { useState } from 'react';
import Button from './Button';
import Dice from './Dice';

function App() {
  const [num, setNum] = useState(1);

  const handleRollClick = () => {
    setNum(3); // num state를 3으로 변경!
  };

  const handleClearClick = () => {
    setNum(1); // num state를 1로 변경!
  };

  return (
    <div>
      <Button onClick={handleRollClick}>던지기</Button>
      <Button onClick={handleClearClick}>처음부터</Button>
      <Dice color="red" num={num} />
    </div>
  );
}

export default App;
//Button.js
import { children } from "react";

function Button({ children, onClick }) {
  return <button onClick={onClick}>{children}</button>;
}

export default Button;
//App.js
import { useState } from "react";
import Button from "./Button";
import Dice from "./Dice";

function random(n) { //파라미터 n으로 숫자값을 전달받아서
  return Math.ceil(Math.random() * n); //1부터 n까지의 랜덤한 정수를 반환하는 함수
}

function App() {
  const [num, setNum] = useState(1);

  const handleRollClick = () => {
    const nextNum = random(6); //random6을 할당해준 다음 이 값으로 setNum 함수를 호출하면
    setNum(nextNum); //던지기 버튼을 누를 때마다 랜덤한 숫자로 num State가 변경
  };

  const handleClearClick = () => {
    setNum(1);
  };

  return (
    <div>
      <div>
        <Button onClick={handleRollClick}>던지기</Button>
        <Button onClick={handleClearClick}>처음부터</Button>
      </div>
      <Dice color="red" num={num} />
    </div>
  );
}

export default App;

참조형 State

자바스크립트의 자료형은 크게 기본형(Primitive type)과 참조형(Reference type)로 나눌 수 있다. 특히 참조형 값들은 조금 독특한 특성을 가지고 있어서 변수로 다룰 때도 조금 주의해야 할 부분들이 있었는데 state를 활용할 때도 마찬가지이다.

// ... 

  const [gameHistory, setGameHistory] = useState([]);

  const handleRollClick = () => {
    const nextNum = random(6);
    gameHistory.push(nextNum);
    setGameHistory(gameHistory); // state가 제대로 변경되지 않는다!
  };

// ...

위 코드에서 볼 수 있듯 배열 값을 가진 gameHistory에 push 메소드를 이용해서 배열의 값을 변경한 다음, 변경된 배열을 setter 함수로 state를 변경하려고 하면 코드가 제대로 동작하지 않는다.

gameHistory state는 배열 값 자체를 가지고 있는 게 아니라 그 배열의 주소값을 참조하고 있기 때문에 push 메소드로 배열 안에 요소를 변경했다고 하더라도 결과적으로 참조하는 배열의 주소값은 변경된 것이 아니게 된다.

리액트 입장에서도 state 값이 바뀌어야 새롭게 화면을 렌더하는데 아무리 새로운 요소가 추가된 배열을 setter 함수에 담았다고 하더라도 요소가 추가되기 전의 배열과 추가된 이후의 배열의 주소값은 계속해서 같기 때문에 state가 변경되었다고 판단하지 않는 것이다.

그래서 배열이나 객체같은 참조형 타입 state를 변경할 때는 아예 전체를 새로 만든다고 생각하는게 좋다. 가장 간단한 방법은 Spread 문법(...)을 활용하는 것이다.

import { useState } from "react";
import Button from "./Button";
import Dice from "./Dice";

function random(n) {
  return Math.ceil(Math.random() * n);
}

function App() {
  const [num, setNum] = useState(1);
  const [sum, setSum] = useState(0);
  const [gameHistory, setGameHistory] = useState([]); //빈 배열열

  const handleRollClick = () => {
    const nextNum = random(6);
    setNum(nextNum);
    setSum(sum + nextNum); //던지기 버튼을 누를때마다 새로운 주사위 값이 sum State에 더해지게 된다.
    // gameHistory.push(nextNum); //gameHistory가 배열이니까 push 메소드로 nextNum을 추가한 다음
    // setGameHistory(gameHistory); //setter 함수로 새 값이 추가된 gameHistory State를 전달
    setGameHistory([...gameHistory, nextNum]);
  };

  const handleClearClick = () => {
    setNum(1);
    setSum(0);
    setGameHistory([]);
  };

  return (
    <div>
      <div>
        <Button onClick={handleRollClick}>던지기</Button>
        <Button onClick={handleClearClick}>처음부터</Button>
      </div>
      <div>
        <h2>나</h2>
        <Dice color="blue" num={num} />
        <h2>총점</h2>
        <p>{sum}</p>
        <h2>기록</h2>
        <p>{gameHistory.join(", ")}</p>
      </div>
    </div>
  );
}
//join메소드는 아규먼트로 전달한 값을 배열의 각 요소들 사이사이에 넣어서 결과적으로 하나의 문자열로 만들어주는 메소드이다.

export default App;

이 참조형 state의 특성을 이해하지 못하면, 간혹 state가 제대로 변경되지 않는 버그가 발생했을 때 원인을 제대로 찾지 못하는 경우가 발생할 수도 있기 때문에 참조형 state를 활용할 땐 반드시 새로운 값을 만들어서 state를 변경해야 한다.

'코린이 개념잡기 > React' 카테고리의 다른 글

리액트 렌더링  (0) 2025.01.03
리액트 디자인 적용하는 방법 +🍯  (1) 2025.01.03
Props와 Children  (1) 2025.01.03
컴포넌트 문법  (0) 2025.01.03
JSX 문법  (0) 2025.01.03

Props

JSX 문법에서 컴포넌트를 작성할 때 컴포넌트에도 속성을 지정할 수 있다. 리액트에서 이렇게 컴포넌트에 지정한 속성들을 Props라고 부른다.

Props는 Properties의 약자인데 컴포넌트에 속성을 지정해주면 각 속성이 하나의 객체로 모여서 컴포넌트를 정의한 함수의 첫 번째 파라미터로 전달한다.

//App.js
import Dice from './Dice';

function App() {
  return (
    <div>
      <Dice color="blue" />
    </div>
  );
}

export default App;
//Dice.js
import diceBlue01 from './assets/dice-blue-1.svg';

function Dice(props) {
  console.log(props)
  return <img src={diceBlue01} alt="주사위" />;
}

export default Dice;

위 코드들 처럼 App 함수에서 사용하는 Dice 컴포넌트에 color라는 속성을 blue로 지정해주고, Dice 함수 내부에서 props라는 파라미터를 하나 만들어 출력해보면 브라우저 콘솔에는 다음과 같은 출력 결과가 나타난다.

{ color: "blue" }

그래서 컴포넌트를 활용할 때 속성값을 다양하게 전달하고 이 props 값을 활용하면, 똑같은 컴포넌트라도 전달된 속성값에 따라 서로 다른 모습을 그려낼 수도 있게 된다.

//App.js
import Dice from './Dice';

function App() {
  return (
    <div>
      <Dice color="red" num={2} />
    </div>
  );
}

export default App;
//Dice.js
import diceBlue01 from './assets/dice-blue-1.svg';
import diceBlue02 from './assets/dice-blue-2.svg';
// ...
import diceRed01 from './assets/dice-red-1.svg';
import diceRed02 from './assets/dice-red-2.svg';
// ...

const DICE_IMAGES = {
  blue: [diceBlue01, diceBlue02],
  red: [diceRed01, diceRed02],
};

function Dice(props) {
  const src = DICE_IMAGES[props.color][props.num - 1];
  const alt = `${props.color} ${props.num}`;
  return <img src={src} alt={alt} />;
}

export default Dice;

props가 객체 형태를 띠고 있으니 Destructuring 문법을 활용해서 좀 더 간결하게 코드를 작성할 수도 있다.

import diceBlue01 from './assets/dice-blue-1.svg';
import diceBlue02 from './assets/dice-blue-2.svg';
// ...
import diceRed01 from './assets/dice-red-1.svg';
import diceRed02 from './assets/dice-red-2.svg';
// ...

const DICE_IMAGES = {
  blue: [diceBlue01, diceBlue02],
  red: [diceRed01, diceRed02],
};

function Dice({ color = 'blue', num = 1 }) {
  const src = DICE_IMAGES[color][num - 1];
  const alt = `${color} ${num}`;
  return <img src={src} alt={alt} />;
}

export default Dice;

결과


Children

props에는 children이라는 조금 특별한 프로퍼티(prop, 프롭)가 있다.

JSX 문법으로 컴포넌트를 작성할 때 컴포넌트를 단일 태그가 아니라 여는 태그와 닫는 태그의 형태로 작성하면, 그 안에 작성된 코드가 바로 이 children 값에 담기게 된다.

//Button.js
import { children } from "react";

function Button({ children }) {
  return <button>{children}</button>;
}

export default Button;
//App.js
import Button from './Button';
import Dice from './Dice';

function App() {
  return (
    <div>
      <div>
        <Button>던지기</Button>
        <Button>처음부터</Button>
      </div>
      <Dice color="red" num={2} />
    </div>
  );
}

export default App;

그래서 JSX 문법으로 컴포넌트를 작성할 때 어떤 정보를 전달할 때는 일반적인 props의 속성값을 주로 활용하고, 화면에 보여질 모습을 조금 더 직관적인 코드로 작성하고자 할 때 children 값을 활용할 수 있다.

이 children을 활용하면 단순히 텍스트만 작성하는 걸 넘어서 컴포넌트 안에 컴포넌트를 작성할 수도 있고, 컴포넌트 안에 복잡한 태그들을 더 작성할 수도 있으니 이 값을 어떻게 활용하면 좋을지 한번 고민해 보면 좋을 것 같다.

 

'코린이 개념잡기 > React' 카테고리의 다른 글

리액트 디자인 적용하는 방법 +🍯  (1) 2025.01.03
State  (0) 2025.01.03
컴포넌트 문법  (0) 2025.01.03
JSX 문법  (0) 2025.01.03
리액트 입문하기  (0) 2025.01.02

리액트 엘리먼트(React Element)

JSX문법으로 작성한 요소는 결과적으로 자바스크립트 객체가 된다.

import ReactDOM from "react-dom/client";

const element = <h1>안녕 리액트!</h1>;
console.log(element);

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(element);
{$$typeof: Symbol(react.element), type: "h1", key: null, ref: null, props: {…}, …}

이런 객체를 리액트 엘리먼트라고 부르는데 이 리액트 엘리먼트를 ReactDOM.render 함수의 아규먼트로 전달하게 되면, 리액트가 객체 형태의 값을 해석해서 HTML 형태로 브라우저에 띄워주는 것이다.

리액트 엘리먼트는 리액트로 화면을 그려내는데 가장 기본적인 요소이다.

리액트 컴포넌트(React Component)

리액트 컴포넌트는 리액트 엘리먼트를 조금 더 자유롭게 다루기 위한 하나의 문법이다.

컴포넌트를 만드는 가장 간단한 방법은 자바스크립트의 함수를 활용하는 건데, 아래 코드에서 JSX 문법으로 작성된 하나의 요소를 리턴하는 Hello 함수가 바로 하나의 컴포넌트이다.

이렇게 요소를 컴포넌트로 작성하게 되면 다양한 장점들이 있다.

더보기

1. 재사용성

  • 모듈화: 컴포넌트는 독립적인 코드 블록으로, 여러 곳에서 재사용할 수 있다. 동일한 UI 요소를 여러 번 사용해야 할 때, 컴포넌트를 작성하면 코드 중복을 줄일 수 있다.

2. 유지보수성

  • 분리된 책임: 각 컴포넌트는 특정 기능이나 UI를 담당합니다. 이를 통해 코드가 더 명확해지고, 특정 컴포넌트에서 발생하는 버그를 쉽게 찾고 수정할 수 있다.
  • 독립적인 개발: 팀원들이 서로 다른 컴포넌트를 독립적으로 개발할 수 있어 협업이 용이해집니다.

3. 가독성

  • 명확한 구조: 컴포넌트를 사용하면 애플리케이션의 구조가 명확해져 코드의 가독성이 향상된고, 각 컴포넌트가 어떤 역할을 하는지 쉽게 이해할 수 있다.
  • 단순화된 UI: 복잡한 UI를 여러 개의 작은 컴포넌트로 나누면, 각 컴포넌트가 단순해지므로 전체적인 이해가 쉬워진다.

4. 상태 관리

  • 로컬 상태 관리: 각 컴포넌트는 자신의 상태를 관리할 수 있어, 상태의 변화를 더 쉽게 처리할 수 있다. 이는 UI의 동적인 부분을 구현하는 데 유리하다.
  • 상태 분리: 서로 다른 컴포넌트가 독립적으로 상태를 관리할 수 있으므로, 상태 변경이 다른 컴포넌트에 미치는 영향을 최소화할 수 있다.

5. 생명주기 관리

  • 컴포넌트 생명주기: 각 컴포넌트는 자신의 생명주기를 갖고 있어, 마운트, 업데이트, 언마운트 시에 필요한 작업을 쉽게 처리할 수 있어 리소스 관리와 최적화에 유리하다.

6. 테스트 용이성

  • 독립적인 테스트: 각 컴포넌트를 독립적으로 테스트할 수 있어, 유닛 테스트와 통합 테스트가 더 수월해진다. 이는 코드의 신뢰성을 높이는 데 도움이 된다.

7. 성능 최적화

  • React.memo: 리액트는 컴포넌트가 업데이트될 때, 필요한 경우에만 리렌더링을 수행한다. 이를 통해 성능을 최적화하고 불필요한 렌더링을 줄일 수 있다.

8. CSS 및 스타일링

  • 스타일 캡슐화: 각 컴포넌트에 스타일을 캡슐화하여 특정 컴포넌트의 스타일이 다른 컴포넌트에 영향을 미치지 않도록 할 수 있다. CSS 모듈, styled-components 등의 기법을 활용할 수 있다.
import ReactDOM from "react-dom/client";

function Hello() {
  return <h1>안녕 리액트</h1>;
}

const element = (
  <>
    <Hello />
    <Hello />
    <Hello />
  </>
);

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(element);

그리고 이렇게 컴포넌트를 작성하면, 위 코드에서 element 변수 안의 JSX 코드에서 볼 수 있듯 컴포넌트 함수 이름을 통해 하나의 태그처럼 활용할 수가 있다.

이런 특성을 모듈 문법으로 활용하면 훨씬 더 독립적으로 컴포넌트 특성에 집중해서 코드를 작성할 수가 있다.

//Dice.js
import diceBlue01 from "./assets/dice-blue-1.svg";

function Dice() {
  return <img src={diceBlue01} alt="주사위" />;
}

export default Dice;
//App.js
import Dice from "./Dice";

function App() {
  return (
    <div>
      <Dice />
    </div>
  );
}

export default App;

한 가지 주의해야 할 부분은, 리액트 컴포넌트의 이름은 반드시 첫 글자를 대문자로 작성해야 한다는 것이다. 컴포넌트 이름의 첫 글자가 소문자라면 오류가 발생하니 꼭 주의하도록 하자.

'코린이 개념잡기 > React' 카테고리의 다른 글

리액트 디자인 적용하는 방법 +🍯  (1) 2025.01.03
State  (0) 2025.01.03
Props와 Children  (1) 2025.01.03
JSX 문법  (0) 2025.01.03
리액트 입문하기  (0) 2025.01.02

JSX란?

JSX는 자바스크립트의 확장 문법이다. 리액트로 코드를 작성할 때 HTML 문법과 비슷한 이 JSX 문법을 활용하면 훨씬 더 편리하게 화면에 나타낼 코드를 작성할 수 있다.

import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>안녕 리액트! 나는 코린이야!!!</h1>);

JSX 문법

JSX는 자바스크립트로 HTML과 같은 문법을 사용할 수 있도록 만들어주는 편리한 문법이지만, 그만큼 꼭 지켜야 할 규칙들도 있다.

HTML과 다른 속성명

1. 속성명은 카멜 케이스로 작성하기!

JSX 문법에서도 태그에 속성을 지정해 줄 수 있다. 단, 여러 단어가 조합된 몇몇 속성들을 사용할 때는 반드시 카멜 케이스(Camel Case)로 작성해야 한다. 여러 단어가 조합된 HTML 속성들이 많진 않지만, 예를 들면 `onclick`, `onblur`, `onfocus` 등과 같은 이벤트 속성이나, `tabindex` 같은 속성들이 있다. 이런 속성들은 모두 `onClick`, `onBlur`, `onFocus`, `onMouseDown`, `onMouseOver`, `tabIndex` 처럼 작성해야한다.

import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <button onClick= ... >클릭!</button>
);

단, 예외적으로 HTML에서 비표준 속성을 다룰 때 활용하는 `data-*` 속성은 카멜 케이스(Camel Case)가 아니라 기존의 HTML 문법 그대로 작성하셔야 한다.

import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <div>
    상태 변경: 
    <button className="btn" data-status="대기중">대기중</button>
    <button className="btn" data-status="진행중">진행중</button>
    <button className="btn" data-status="완료">완료</button>
  </div>
);

2. 자바스크립트 예약어와 같은 속성명은 사용할 수 없다.

JSX 문법도 결국은 자바스크립트 문법이기 때문에, `for`나 `class`처럼 자바스크립트의 문법에 해당하는 예약어와 똑같은 이름의 속성명은 사용할 수 없다. 그래서 HTML의 `for`의 경우에는 자바스크립트의 반복문 키워드 `for`와 겹치기 때문에 `htmlFor`로, HTML의 `class` 속성도 자바스크립트의 클래스 키워드 `class`와 겹치기 때문에 `className`으로 작성해 주어야 한다.  [React 공식 문서 - 어트리뷰트의 차이]

import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <form>
    <label htmlFor="name">이름</label>
    <input id="name" className="name-input" type="text" />
  </form>
);

반드시 하나의 요소로 감싸기 - Fragment

JSX 문법을 활용할 때는 반드시 하나의 요소로 감싸주어야 한다. 그래서 아래 코드처럼 여러 개의 요소를 작성하면 오류가 발생하는데

import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <p>안녕</p>
  <p>리액트! 난 코린이야!</p>
);

이럴 때는 아래 코드처럼 여러 태그를 감싸는 부모 태그를 만들어 하나의 요소로 만들어 주어야 한다.

import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <div>
    <p>안녕</p>
    <p>리액트!</p>
  </div>
);

하지만 이렇게 작성한다면 때로는 꼭 필요하지 않은 부모 태그가 작성될 수 있다. 그럴 땐 `Fragment`로 감싸주면 의미 없는 부모 태그를 만들지 않아도 여러 요소를 작성할 수 있다. 참고로 Fragnent는 빈 태그로 감싸는 단축 문법으로도 활용할 수 있다.

import { Fragment } from 'react';
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <> //혹은 <Fragnent>
    <h1>안녕 리액트! 나는 코린이야!!!</h1>
    <p>리액트 너를 산산조각 내기 위해 왔다!</p>
  </> //혹은 </Fragnent>
);

 

자바스크립트 표현식 넣기

JSX 문법에서 중괄호{}를 활용하면 자바스크립트 표현식을 넣을 수 있다. 잘 활용하면, 중괄호 안에서 문자열을 조합할 수도 있고 변수에 이미지 주소를 할당해서 `img` 태그의 `src` 속성값을 전달해 줄 수도 있고, 이벤트 핸들러를 좀 더 편리하게 등록할 수도 있다.

import { Fragment } from 'react';
import ReactDOM from 'react-dom/client';

const product = 'MacBook';
const model = 'Air';
const imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/MacBook_with_Retina_Display.png/500px-MacBook_with_Retina_Display.png'

function handleClick(e) {
  alert('곧 도착합니다!');
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <>
    <h1>{product + ' ' + model} 주문하기</h1>
    <img src={imageUrl} alt="제품 사진" />
    <button onClick={handleClick}>확인</button>
  </>
);

코드 창과 결과화면

단, JSX 문법에서 중괄호는 자바스크립트 표현식을 다룰 때 활용하기 때문에, 중괄호 안에서 for, if문 등의 문장은 다룰 수 없다. 그런데도 만약 JSX 문법을 활용할 때 조건문이 꼭 필요하다면 조건 연산자를, 반복문이 꼭 필요하다면 배열의 반복 메소드를 활용해 볼 수 있다.

'코린이 개념잡기 > React' 카테고리의 다른 글

리액트 디자인 적용하는 방법 +🍯  (1) 2025.01.03
State  (0) 2025.01.03
Props와 Children  (1) 2025.01.03
컴포넌트 문법  (0) 2025.01.03
리액트 입문하기  (0) 2025.01.02

+ Recent posts