[230508] Fragment | Component | props 파라미터 | useState | 상태 끌어올리기

윤지수·2023년 5월 8일
0
post-thumbnail

⚛️ Fragment

JSX 컴포넌트는 최상위 부모요소를 가져야 하기 때문에 여러 개의 엘리먼트들을 감싸주기 위해서 보통 <div> 태그로 다른 태그들을 감싸주었다. 하지만 이렇게 코드를 작성하면 의미 없는 <div> 태그를 자주 사용하게 되고 이는 시멘틱한 마크업을 방해한다.

Fragment를 사용하면 <div>와 같은 별도의 노드를 추가하지 않고 여러 개의 자식을 감싸줄 수 있다.

// react 모듈 추가
import React from 'react';

function App() {
  return (
	<React.Fragment>
	  <p>안녕, 라이캣 1!</p>
	  <p>안녕, 라이캣 2!</p>
	</React.Fragment>
  );
}
// react 모듈 안에 있는 Fragment 메서드 추가
import React, { Fragment } from "react";

function App() {
  return (
	<Fragment>
	  <p>안녕, 라이캣 1!</p>
	  <p>안녕, 라이캣 2!</p>
	</Fragment>
  );
}

import 구문에 {Fragment}를 추가하면 React를 붙이지 않아도 된다.
Fragment라는 키워드 대신 <> </>(단축 문법)을 사용할 수도 있다. 이때는 Fragment를 사용하기 위해 React 모듈을 따로 추가할 필요가 없다.

Fragment도 React 엘리먼트이기 때문에 일반적인 React 엘리먼트와 마찬가지로 props를 전달할 수 있다.
그러나 Fragment는 화면에 렌더링되는 요소가 아니기 때문에 스타일과 관련된 속성을 사용하는 것은 무의미하며 보통 리스트 아이템의 key 값을 설정할 때 많이 사용한다.
💡 단축 문법으로 사용할 때는 props를 사용할 수 없다.

⚛️ Component

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

<Licat /><Time />과 같은 사용자 정의 요소가 컴포넌트
App()도 하나의 컴포넌트

💡 컴포넌트의 이름을 지을 때는 첫 글자를 대문자로 작성해야 컴포넌트로 해석된다.
💡 컴포넌트가 쓰이는 목적에 따라 컴포넌트 이름을 정한다.

컴포넌트 파일 분리
컴포넌트는 재사용성을 극대화한다.

src 폴더에 components 폴더 만들고 각 컴포넌트 파일을 만든다.
파일 가장 아랫줄에 export 구문을 입력하여 밖으로 빼낼 모듈을 설정한다.
App.js에서 각 컴포넌트들을 import 구문으로 가지고 온다.

⚛️ props 파라미터

props
컴포넌트를 만들 때 넣어줄 수 있는 속성의 집합

컴포넌트는 props라는 임의의 입력을 받아 리액트 엘리먼트들을 화면에 어떻게 표시할지 설정할 수 있다.
props를 통해 입력받은 속성을 사용하면 된다.
컴포넌트를 동적으로 사용할 수 있게 된다.

props로 넘길수 있는 데이터는 다양하다. 문자열, 숫자형 등등 심지어 JSX까지 넘길 수 있다.

function HelloProps(props) {
  return (
    <div>
      <p>my name is {props.name} and age is {props.age}</p>
      <strong>you are {props.someFunc()}</strong>
      <p>this is someArr {[...props.someArr]}</p>
      <p>this is someObj {props.someObj.one}</p>
      {props.someJSX}
    </div>
  )
}

function App() {
  return (
	<HelloProps 
      name="mallang"
      age={23} 
	  someFunc={() => 'awesome!!!'}
	  someArr={[1, 2, 3]} 
	  someObj={{ one: 1 }} 
	  someJSX={<img src="https://picsum.photos/id/237/200/300" />}
	/>
  );
}

💡 구조분해할당을 통해 props.를 생략할 수 있다.

function HelloProps({name, age, someFunc, someArr, someObj, someJSX}) {
  return (
    <div>
      <p>my name is {name} and age is {age}</p>
      <strong>you are {someFunc()}</strong>
      <p>this is someArr {[...someArr]}</p>
      <p>this is someObj {someObj.one}</p>
      {someJSX}
    </div>
  )
}

function App() {
  return (
	<HelloProps 
      name="mallang"
      age={23} 
	  someFunc={() => 'awesome!!!'}
	  someArr={[1, 2, 3]} 
	  someObj={{ one: 1 }} 
	  someJSX={<img src="https://picsum.photos/id/237/200/300" />}
	/>
  );
}

defaultProps로 기본값 설정
컴포넌트에 props를 지정하지 않았을 때 기본적으로 사용할 값을 설정하고 싶다면 컴포넌트에 defaultProps라는 값을 설정하면 된다.

function Hello({ color, name }) {
  return <div style={{ color }}>안녕하세요 {name}</div>
}

Hello.defaultProps = {
  name: '이름없음'
}

export default Hello;

⚛️ useState

변수의 값이 바뀌는 것으로는 컴포넌트가 업데이트 되지 않는다. 단순히 변수의 값이 바뀌는 것은 React가 크게 고려할 사항이 아니고 모든 변수가 변할때마다 컴포넌트를 업데이트한다면 많은 리소스가 낭비될 것이다.
변수의 변화는 가상돔에 아무런 영향을 미치지 않는다. React는 가상돔을 변화시켜줘야 이전의 가상돔과 비교해서 바뀐 것을 화면에 그려준다. 그래서 특정 변수를 지정하여 그 변수가 변할 때마다 컴포넌트를 업데이트하라는 명령을 내려주어야 한다.

컴포넌트의 변화를 감지하고 변경사항을 화면에 반영하는 것은 많은 자원이 소모되는 작업이다. 때문에 React는 컴포넌트를 업데이트 해야할 때를 결정하는 특별한 메커니즘을 가지고 있다. 이를 재조정(reconciliation)을 거친다고 표현한다.
React의 재조정은 Virtual DOM을 사용하여 React 컴포넌트 트리를 비교하고 최적화하는 프로세스이다. React는 컴포넌트가 업데이트될 때마다 Virtual DOM을 사용하여 이전 버전의 컴포넌트 트리와 새 버전의 컴포넌트 트리를 비교한다.

  • 컴포넌트의 타입(p 태그, div 태그 등)이 같은지 비교
  • 컴포넌트의 속성(prop)이 변경되었는지 비교
  • 컴포넌트의 자식 요소가 변경되었는지 비교

이러한 비교 작업을 통해 React는 변경된 부분만 업데이트하고, 나머지 부분은 그대로 유지한다. 이렇게 하면 React는 DOM 조작을 최소화하고 더 빠르고 효율적인 애플리케이션을 만들 수 있다.

reconciliation 프로세스를 통해 React는 컴포넌트를 업데이트하여 즉각적으로 반응하는 사용자 인터페이스를 만들 수 있다. 이것이 바로 React가 다른 프론트엔드 라이브러리와 차별화되는 중요한 기능 중 하나다.

💡 React의 elements 즉, 가상돔은 자바스크립트 객체 형태이다. 이 elements는 불변하는(immutable) 특징을 가지고 있다. 따라서 엄밀하게 따진다면 업데이트되는 것은 컴포넌트이고 가상돔은 교체된다고 표현하는 것이 맞다.
💡 사용자 입력값을 리액트가 사용할 수 있으려면 사용자에게 입력받는 값도 useState로 관리한다.
굳이? reconciliation + 신뢰 가능한 단일 출처의 원칙 때문에

import React, { useState } from "react";

function Resume(props) {
  const [like, setLike] = useState(0);
  // like: 0
  // setLike: like 값을 변경하는 함수

  function clickLike() {
	// +1 은 기존의 like 값과 1을 더해 새로운 값을 반환하는것이고
    // ++ 변수의 값 자체를 직접적으로 변경하려는 시도
    setLike(like + 1);
  }

  return (
      <button onClick={clickLike}>like <span>{like}</span></button>
  );
}

export default Resume;

import 구문에 {useState}를 추가하여 useState를 React에서 import한다.
useState를 실행하면 state 변수와 state 변수의 상태를 바꿔줄 수 있는 함수(setState)가 반환된다. 그러면 배열 구조분해할당 문법으로 likesetLike에 변수와 함수를 할당하게 된다.
useState의 괄호 안에는 초기값을 넣을 수 있다.

useState를 사용하게 되면 setState를 통해 값이 변경될 때 리액트는 자동으로 해당 컴포넌트를 다시 렌더링해준다.
리액트는 어떤 상태(state)가 변경되면 그 부분을 다시 렌더링하는 특징이 있기 때문에 화면에서 계속 바뀌는 부분은 대부분 state를 사용한다. 또한 다시 렌더링할 필요가 없는 데이터는 useState를 사용하지 않음으로써 자원을 아낄 수 있다.

상태 끌어올리기, props drilling
React는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 props 파라미터로 보내는 것만 가능하다(자식->부모, 형제<->형제 X). 데이터가 흐르는 방향이 부모 컴포넌트에서 자식 컴포넌트인 것이다.
따라서 부모 컴포넌트에 있는 상태를 바꿀 수 있는 메서드를 자식 컴포넌트로 보내는 것이 가능한데,
자식 컴포넌트가 메서드를 실행시켰을 때 부모 컴포넌트의 상태가 바뀌게 하는 것을 '상태 끌어올리기'라고 한다. 마치 자식에서 부모한테 데이터를 보내는 것 같은 효과가 있다.
이때 메서드를 실행시킬 자식 컴포넌트까지 계속해서 메서드를 props 파라미터로 전달하는 것을 props drilling이라 한다.

💻 mood-app 실습
https://github.com/yoonmallang22/React/tree/main/practice/mood-app

0개의 댓글