본문 바로가기
Study/Udemy-React-완벽 가이드

9. 프래그먼트 작업, portar & Ref (feat. 유데미 React 완벽 가이드)

by NFAP0221S 2022. 5. 30.

https://www.udemy.com/course/best-react/

 

【한글자막】 React 완벽 가이드 with Redux, Next.js, TypeScript

Javascript부터 웹 어플리케이션 배포까지, React와 프론트엔드 최신 기술을 가장 쉽고 확실하게 배우는 법

www.udemy.com

이 게시물은 유데미 React 완벽 가이드 강의를 보고 메모를 남기는 게시물 입니다.


프래그먼트 작업

리액트는 요소여러개가 부모 요소에 감싸져 있어야만 return이 가능하다.

컴포넌트 내부는 하나의 DOM 트리 구조로 이루어져야 한다는 규칙이 있기 때문이다.

만약 그렇지 않다면 에러가 발생할 것이다.

그래서 보통 div 태그로 모든 요소들을 감싸주는데, div태그 대신 프래그먼트로 div를 대체할 수 있다.

 

div태그

지금까지는 div 태그로 감싸는 방법을 했었다.

  return (
    <div>
      <AddUser onAddUser={addUserHandler} />
      <UsersList users={usersList} />
    </div>
  );

 

빈 태그

빈 태그로 감싸는 방법도 가능하다.

  return (
    <>
      <AddUser onAddUser={addUserHandler} />
      <UsersList users={usersList} />
    </>
  );

프라그먼트

빈 태그는 저에게는 가독성이 좀 떨어져 보여 프라그먼트를 사용 해 보았다.

  return (
    <React.Fragment>
      <AddUser onAddUser={addUserHandler} />
      <UsersList users={usersList} />
    </React.Fragment>
  );

프래그먼트에 React를 입력하고 싶지 않으면, fragment를 import 해주면 된다.

import React, { useState, Fragment } from "react";

  return (
    <Fragment>
      <AddUser onAddUser={addUserHandler} />
      <UsersList users={usersList} />
    </Fragment>
  );

React Potals

리액트 공식 홈페이지에서 설명하는 리액트 포탈이란? 

Portal은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공합니다.

 

보통 컴포넌트 렌더링 메서드에서 엘리먼트를 반환할 때 그 엘리먼트는 부모 노드에서 가장 가까운 자식으로 DOM에 마운트되는데 포탈을 사용하면 DOM의 다른 위치에 자식을 마운트 하여 효율적으로 렌더링을 할 수있다.

즉, 렌더링 HTML 내용을 다른 곳으로 옮기는 것이다.


포탈의 전형적인 케이스는 overflow: hidden이나 z-index 가 사용된 경우인데, 시각적으로 자식을 “튀어나오도록” 보여야 하는 경우이다. 

포탈 사용하기

index.html 에서 기본적으로 id가 root인 태그만 있는데 여기서 2개의 div 태그를 더 생성한다.

    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="backdrop-root"></div>
    <div id="overlay-root"></div>
    <div id="root"></div>

ReactDOM 을 import하고 사용해보자. ReactDOM.createPortal(child, container) 

import React from "react";
import ReactDOM from "react-dom";

import Card from "./Card";
import Button from "./Button";
import classes from "./ErrorModal.module.css";

const Backdrop = (props) => {
  return <div className={classes.backdrop} onClick={props.onConfirm} />;
};

const ModalOverlay = (props) => {
  return (
    <Card className={classes.modal}>
      <header className={classes.header}>
        <h2>{props.title}</h2>
      </header>
      <div className={classes.content}>
        <p>{props.message}</p>
      </div>
      <footer className={classes.actions}>
        <Button onClick={props.onConfirm}>Okay</Button>
      </footer>
    </Card>
  );
};

const ErrorModal = (props) => {
  return (
    <React.Fragment>
      {ReactDOM.createPortal(  // 포탈 사용
        <Backdrop onConfirm={props.onConfirm} />, // 첫번째 인자
        document.getElementById("backdrop-root")  // 두번째 인자
      )}
      {ReactDOM.createPortal( // 포탈 사용
        <ModalOverlay         // 첫번째 인자
          title={props.title}
          message={props.message}
          onConfirm={props.onConfirm}
        />,
        document.getElementById("overlay-root")  // 두번째 인자
      )}
    </React.Fragment>
  );
};

export default ErrorModal;

  
  # ErrorModal.js

 

Modal 호출되기 전

Modal 호출됨

포탈을 사용해서 원하는 DOM요소에 마운트 되어 렌더링 해보았다.

 


Ref

ref란  reference의 줄임말이며 React에서 DOM 요소에 접근할 수 있는 방법이다.

기존 코드는 state를 이용해 값을 읽어드렸는데 사실 값만 읽어야하는 경우에는 state보다는 ref를 사용하는 것이 코드가 훨씬 줄어든다.

Ref 를 사용하는 방법

  1.  리액트 훅으로 useRef로 import 한다.
  2. 상수를 선언하여 useRef() 를 할당한다.
  3. 선언한 상수를 DOM요소에 접근하고 싶은 태그에 ref속성을 생성하여 중괄호를 사용하여 상수를 할당한다.
  4. 3번작업을 마치면 상수가 해당 DOM 요소에 접근가능 해진다.

useRef를 사용하면 아래의 코드처럼 사용하면 된다.

import React, { useState, useRef } from "react"; // 리액트 훅으로 useRef 임포트

import Card from "../UI/Card";
import Button from "../UI/Button";
import ErrorModal from "../UI/ErrorModal";
import Wrapper from "../Helpers/Wrapper";
import classes from "./AddUser.module.css";

const AddUser = (props) => {
  const nameInputRef = useRef(); // ref 선언 후 DOM요소에 접근하고 싶은 태그에 ref속성으로 연결
  const ageInputRef = useRef(); // ref 선언

  const [error, setError] = useState();

  const addUserHandler = (event) => {
    event.preventDefault();
    const enteredName = nameInputRef.current.value;    // ref 의 현재 값
    const enteredUserAge = ageInputRef.current.value;

    if (enteredName.trim().length === 0 || enteredUserAge.trim().length === 0) {
      setError({
        title: "Invalid input",
        message: "Please enter a valid name and age (non-empty values).",
      });
      return;
    }
    if (+enteredUserAge < 1) {
      setError({
        title: "Invalid age",
        message: "Please enter a valid age (> 0).",
      });
      return;
    }
    props.onAddUser(enteredName, enteredUserAge);
    nameInputRef.current.value = "";
    ageInputRef.current.value = "";
  };

  const errorHandler = () => {
    setError(null);
  };

  return (
    <Wrapper>
      {error && (
        <ErrorModal
          title={error.title}
          message={error.message}
          onConfirm={errorHandler}
        />
      )}
      <Card className={classes.input}>
        <form onSubmit={addUserHandler}>
          <label htmlFor="username">Username</label>
          <input id="username" type="text" ref={nameInputRef} /> // ref속성을 사용하여 ref 와 연결
          <label htmlFor="age">Age (Years)</label>
          <input id="age" type="number" ref={ageInputRef} />
          <Button type="submit">Add User</Button>
        </form>
      </Card>
    </Wrapper>
  );
};

export default AddUser;