[React] 5. JSX, props, state 그리고 불변성

해롱그·2023년 11월 2일

react

목록 보기
6/14

JSX?

JavaScript의 확장 문법으로 자스의 모든 기능이 포함되어 있으며, React Element를 생성하기 위한 문법
*element: 단순히 화면에 그려지는 HTML적 요소

JSX는 템플릿 언어와 비슷해 보이지만, JavaScript의 강력한 기능들을 모두 사용할 수 있다.
JSX는 React.createElement() 의 호출을 통해 일반 JavaScript 객체인 "React element"로 컴파일 된다.

React DOM은 HTML 어트리뷰트 이름 대신 camelCase를 네이밍 컨벤션으로 사용한다.
예를 들어, tabindex는 tabIndex로 작성한다. class 어트리뷰트는 JavaScript의 예약어이므로 className으로 작성한다.

<h1 className="hello">My name is Clementine!</h1>

엘리먼트

React 엘리먼트는 React 애플리케이션을 구성하는 블록이다.
엘리먼트는 컴포넌트라는 널리 알려진 개념과 혼동되기 쉽다. 엘리먼트는 화면에 보이는 것들을 기술하며, React 엘리먼트는 변경되지 않는다.

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

일반적으로 엘리먼트는 직접 사용되지 않고 컴포넌트로부터 반환된다.

컴포넌트

React 컴포넌트는 페이지에 렌더링할 React 엘리먼트를 반환하는 작고 재사용 가능한 코드 조각이다.
가장 간단한 React 컴포넌트는 React 엘리먼트를 반환하는 일반 JavaScript 함수이다.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;

함수 컴포넌트 뿐만 아니라 ES6 class로도 작성할 수 있다.

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

컴포넌트는 기능별로 나눌 수 있으며 다른 컴포넌트 안에서 사용할 수 있다.
컴포넌트는 다른 컴포넌트, 배열, 문자열 그리고 숫자를 반환할 수 있다. 화면을 구성하는 데 자주 사용되는 UI(Button, Panel, Avatar), 혹은 복잡한 UI(App, FeedStory, Comment) 컴포넌트는 재사용 가능한 컴포넌트가 될 수 있다. 컴포넌트의 이름은 항상 대문자로 시작해야 한다.

props

컴포넌트의 입력값으로 props는 부모 컴포넌트로부터 자식 컴포넌트로 전달된 데이터이다.
props는 읽기 전용이라는 것을 잊지 말자! props는 어떤 방식으로든 수정해서는 안된다.

// 틀린 예
props.number = 42;

사용자의 입력 또는 네트워크 응답에 반응하여 어떤 값을 수정해야 한다면 state를 사용하자!

props.children

모든 컴포넌트에서 props.children을 사용할 수 있다.
props.children은 컴포넌트의 여는 태그와 닫는 태그 사이의 내용을 포함한다. 예를 들어,

<Welcome>Hello world!</Welcome>

Hello world! 문자열은 Welcome 컴포넌트의 props.children으로 사용할 수 있다.

function Welcome(props) {
  return <p>{props.children}</p>;
}

Class로 정의된 컴포넌트에서는 this.props.children을 사용한다.

class Welcome extends React.Component {
  render() {
    return <p>{this.props.children}</p>;
  }
}

state

컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. 왜 바뀌어야 할까? 바로 UI(element)로의 반영을 위해서다!
ex) const name = "홍부인"; 이라고 만들었는데, 만약 name이라는 값이 바뀌어야만 하는 정보였어야 했다면 state로 생성한다!!

컴포넌트와 관련된 일부 데이터가 시간에 따라 변경될 경우 state가 필요하다.

state와 props의 가장 중요한 차이점은 props는 부모 컴포넌트로부터 전달받지만, state는 컴포넌트에서 관리된다는 것이다. 컴포넌트는 props를 변경할 수 없지만, state는 변경할 수 있다.

데이터가 변경되는 각 특정한 부분에 대해, 해당 상태를 "소유"하는 컴포넌트는 하나만 존재해야 한다. 서로 다른 두 컴포넌트의 상태를 동기화하려고 하지 말 것!
대신, 공통 상태를 두 컴포넌트의 공통 조상으로 끌어올리고 해당 데이터를 두 컴포넌트에 props로 전달해라.

state 만들기

useState() 를 사용한다.

const [ value, setValue ] = useState( 초기값 );

불변성

메모리에 있는 값을 변경할 수 없는 것.
자바스크립트의 데이터 형태중에 원시데이터는 불변성이 있고, 원시데이터가 아닌 객체, 배열, 함수는 불변성이 없다.

(1) 변수를 저장하면 메모리에 어떻게 저장이 될까?

let num = 1 이라고 선언을 하면, 메모리에는 1 이라는 값이 저장된다. 그리고 num 이라는 변수는 메모리에 있는 1을 참조한다. 그리고 이어서 let secondNum = 1 이라고 다른 변수를 선언한다면 이때도 자바스크립트는 이미 메모리에 생성되어 있는 1이라는 값을 참조한다.
즉, num과 secondNum은 변수의 이름은 다르지만, 같은 메모리의 값을 바라보고 있는 것이다. 그래서 우리가 콘솔에 num===secondNum 을 하면 true가 보인다.

하지만 원시데이터가 아닌 값(객체, 배열, 함수)는 이렇지 않다.
let obj_1 = {name: 'kim'} 이라는 값을 선언하면 메모리에 obj_1이 저장된다. 그리고 이어서 let obj_2 = {name: 'kim'}; 이라고 같은 값을 선언하면 obj_2라는 메모리 공간에 새롭게 저장된다.
고로 obj_1===obj_2false가 된다.

(2) 데이터를 수정하면 어떻게 될까?

다시 원시데이터로 돌아와서 기존에 1이던 num에 num=2 라는 새로운 값을 할당하면 메모리에서는 어떻게 될까?
원시데이터는 불변성이 있다. 즉, 기존 메모리에 저장되어 있는 1이라는 값이 변하지 않고, 새로운 메모리 저장공간에 2가 생기고 num은 새로운 메모리 공간에 저장된 2를 참조하게 된다.
그래서 secondNum을 콘솔에 찍으면 여전히 1로 보인다. num과 secondNum은 각각 다른 메모리 저장공간을 참조하고 있기 때문이다.

이젠 obj_1을 수정해보자.
obj_1.name = 'park' 이라고 새로운 값을 할당하면 어떻게 될까? 객체는 불변성이 없다. 그래서 기존 메모리 저장공간에 있는 {name: 'kim'} 이라는 값이 {name: 'park'}으로 바뀌어 버린다!

원시데이터는 수정을 했을 때 메모리에 저장된 값 자체는 바꿀 수 없고, 새로운 메모리 저장공간에 새로운 값을 저장한다. 원시데이터가 아닌 데이터는 수정했을 때 기존에 저장되어 있던 메모리 저장공간의 값 자체를 바꿔버린다.

(3) 왜 리액트에서는 원시데이터가 아닌 데이터의 불변성을 지켜주는 것을 중요시할까!?

리액트에서는 화면을 re-rendering 할지 말지 결정할 때 state의 변화를 확인한다. state가 변했으면 re-rendering 하는 것이고, state가 변하지 않았으면 re-rendering 하지 않는다.
이 때, state가 변했는지 변하지 않았는지 확인하는 방법은 state 변화 전, 후의 메모리 주소 비교이다.
그래서 만약 리액트에서 원시데이터가 아닌 데이터를 수정할 때 불변성을 지켜주지 않고, 직접 수정을 가하면 값은 바뀌지만 메모리 주소는 변함이 없게 되는 것이다. 그래서 즉, 개발자가 값은 바꿨지만 리액트는 state가 변했다고 인지하지 못하게 되기 때문에 일어나야 할 re-rendering이 일어나지 않게 된다.

(4) 리액트 불변성 지키기!

배열을 setState 할 때 불변성을 지켜주기 위해 직접 수정을 가하지 않고 전개 연산자를 사용해서 기존의 값을 복사하고, 그 이후에 값을 수정하는 식으로 구현한다.

import React, { useState } from "react";

function App() {
  const [dogs, setDogs] = useState(["말티즈"]);

  const onClickHandler = () => {
    // spread operator를 이용해서 dogs를 복사한다.
    // 그리고 나서 항목을 추가한다.
    setDogs([...dogs, "시고르자브르종"]);
  }

  console.log(dogs);
  return (
    <div>
      <button onClick={onClickHandler}>버튼</button>
    </div>
  );
}

export default App;
profile
사랑아 컴퓨터해 ~

0개의 댓글