JiSoo's Devlog

[Udemy React 완벽 가이드] Section 8 본문

Frontend/React

[Udemy React 완벽 가이드] Section 8

지숭숭숭 2024. 1. 4. 11:42

JSX 제한 사항 및 해결 방법

  • 루트 수준에서 JSX 효소들이 인접해 있으면 오류 발생
  • 일반적으로 JSX에서 루트 JSX 요소는 1개여야 한다
  • 값을 반환하거나 값을 변수 또는 속성에 저장하려면 그 값은 반드시 JSX 요소 1개여야 한다
  • 인접한 요소들을 div나 사용자가 정의한 컴포넌트로 감싸면 된다
  • div 사용하는 대신 네이티브 자바스크립트 배열을 사용할 수도 있다 -> 키 지정
  • 키 프롭은 우리가 만든 값으로 추가할 수도 있다
  • 실제 DOM으로 렌더링 될 때 리액트 컴포넌트가 많이 중첩될 수 있는데 그 모든 컴포넌트들은 여러 이유로 div로 감싸거나 다른 감싸는 컴포넌트가 필요하다
  • 너무 많은 html 요소를 렌더링 하면 궁극적으로 애플리케이션이 느려질 것이다

 

 

컴포넌트 감싸기(wrapper) 만들기

  • 칠드런 프롭은 사용자 정의 컴포넌트에서 여는 태그와 닫는 태그 사이에 넣어준 모든 내용을 담고 있다

Wrapper.js

const Wrapper = (props) => {
  return props.children;
};

export default Wrapper;
 

 

 

리액트 조각

  • div 삭제하고 빈 태그만 사용해도 되는데 유효하지 않은 JSX가 된다
  • 모든 프로젝트에서 유효한 JSX 일 필요는 없다
<React.Fragment>
      <AddUser onAddUser={addUserHandler} />
      <UsersList users={usersList} />
</React.Fragment>
 
  • import React, {useState, Fragment} from 'react'; 해주면 <Fragment>만 써줘도 된다

 

 

리액트 포털 소개

  • 기본적으로 모달은 페이지 위에 표시되는 오버레이이기 때문에 당연히 다른 모든 것 위에 있다
  • 따라서 이것이 만약 다른 HTML 코드 안에 중첩되어 있다면 기술적으로는 스타일링 덕분에 작동할지 몰라도 좋은 코드는 아니다
  • 모달 HTML 내용을 일반적인 곳이 아니라 다른 곳에서 렌더링 하면 되는데 이렇게 하려면 리액트 포털을 사용하면 된다
return (
  <React.Fragment>
    <MyModal />
    <MyInputForm />
  </React.Fragment>
);
 

↓ ↓ ↓ ↓ ↓

<div class="my-modal">
  <h2>A Modal Title!</h2>
</div>
<section>
  <h2>Some other content ... </h2>
  <form>
    <label>Username</label>
    <input type="text"/>
  </form>
</section>
 

 

 

포털 작업하기

  • 포털을 사용하려면 컴포넌트를 이동시킬 장소와 컴포넌트에서 그곳에 포털을 가져가야 한다고 알려줄 필요가 있다
  • index.html에 div의 id를 지정해 나중에 이 장소를 찾아오는데 사용한다 -> id는 modal-root 혹은 backdrop-root
  • backdrop-root 밑에 overlay-root를 쓰면 모든 종류의 오버레이나 모달, 사이드 드로어 등을 가져올 수 있다
  • 새로운 컴포넌트를 만드는 데 동일한 파일에 추가한다. 이유는 이 앱에서 Backdrop 컴포넌트는 모달과 함께 사용하기만 할 것이기 때문
  • 리액트 DOM은 브라우저에 대한 리액트용 어댑터의 일종이라 할 수 있다
  • 포털의 핵심은 렌더링 된 HTML 내용을 다른 곳으로 옮기는 것
  • 일반적으로 컴포넌트를 사용하는 곳에서는 createPortal을 사용하여 해당 컴포넌트의 HTML 내용을 다른 곳으로 포털, 즉 이동시킬 수 있다. 다만 렌더링 되는 실제 DOM 안에서만 가능

 

ErrorModal.js

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;
 

 

 

"ref"로 작업하기

  • 참조를 뜻하는데 다른 DOM 요소에 접근해서 그것들로 작업할 수 있게 해주는 것
  • ref를 사용해 연결을 설정할 수 있다
  • ref 값은 항상 객체로 항상 current 프롭을 갖고 있다
  • current 프롭은 그 ref가 연결된 실제 값을 갖는다
  • ref는 코드가 조금 더 적지만 DOM을 조작한다는 예외적인 일을 해야 하고 state는 더 깔끔하지만 코드를 조금 더 많이 쓴다

 

AddUser.js

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

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

const AddUser = (props) => {
  const nameInputRef = useRef();
  const ageInputRef = useRef();

  const [error, setError] = useState();

  const addUserHandler = (event) => {
    event.preventDefault();
    const enteredName = nameInputRef.current.value;
    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} />
          <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;
 

 

 

제어되는 컴포넌트와 제어되지 않는 컴포넌트

  • 입력 컴포넌트는 제어되지 않는 컴포넌트가 된다.
  • input id="username" type="text" ref={nameInputRef} 이것들은 내부 state이기 때문에 이것들 안으로 반영되는 값은 리액트에 의해 제어되지 않는다
  • 입력 컴포넌트는 일반적으로 폼 컴포넌트를 말하는데 브라우저에 의해 내부 state를 갖는 경향이 있는데 사용자 입력을 받아 저장하고 반영하는 인풋 요소가 구성되어 있다
  • 리액트 앱에서 해당 컴포넌트로 작업할 때 리액트 state를 입력 컴포넌트에 연결하고 싶기에 리액트에서 인풋 컴포넌트로 작업할 때 제어/제어되지 않음에 대해 이야기한다
728x90