Portals & Refs

ZeroJun·2022년 6월 27일
0

React

목록 보기
8/13

JSX제한 사항 및 해결 방법

jsx코드는 리액트 createElement로 반환된다. 자바스크립트에서는 단 1개의 createElement호출만 반환되어야 한다.

이런 문제를 해결하기 위한 방법은 다음과 같다.

이런식으로 병렬 요소들을 div로 감싸면 마치 배열에 값을 담아 반환하는 것 처럼 div아래 중첩된 jsx요소를 반환한다.

  1. 네이티브 자바스크립트 배열을 사용한다.
  • 리액트의 배열 규칙처럼 key를 넣어줘야 한다.

보통 1번 방법의 코드 양이 적어서 1번 방법을 사용한다.

그러나 이렇게 div로 감싸주는 경우 실제 dom으로 렌더링 될 때, 아래와 같은 문제가 발생할 가능성이 높다.

이런것을 div Soup라고 한다. 이렇게 되면 스타일링을 망칠 수 있다. (중첩된 css선택자 등) 또한 불필요하고, 너무 많은 html요소가 렌더링 되면 웹앱이 느려질 것이다.

즉, 이런 해결 방식은 간단하지만 이상적인 해결법은 아니다.

컴포넌트 Wrapper만들기

const Wrapper = (props) => {
  return props.children;
  // 여는 태그와 닫는 태그 사이에 넣어준 모든 내용을 담고 있다.
};

export default Wrapper;

<Wrapper> 
  <Card>
        ... 중략
  </Card>
</Wrapper>
// 이렇게 div대신 Wrapper로 감싸준다.

여기서 Wrapper은 실제 브라우저에서 렌더링 되지 않는다.

리액트 조각

사실 리액트에서는 위에서 언급한 Wrapper과 같은 기능을 자체 지원한다. 프로젝트 설정에 따라 빈 태그만 삽입하거나, React.Fragment를 사용하면 된다.


<>
  <AddUser onAddUser={addUserHandler} />
  <UsersList users={usersList} />
</>

<React.Fregment>
  <AddUser onAddUser={addUserHandler} />
  <UsersList users={usersList} />
</React.Fragment>

리액트 포털 소개

웹에서 모든 요소의 가장 위에 떠야하는 모달창 같은 오버레이가 DOM의 깊숙한 곳에 들어있는 경우, 동작은 하나 좋은 구현은 아니다. 포탈은 이런 오버레이를 바깥으로 빼낼 수 있는 기능이다.

const ErrorModal = (props) => {
  return (
    <React.Fragment>
      <div className={classes.backdrop} onClick={props.onConfirm} />
      <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>
    </React.Fragment>
  );
};

포탈을 쓰지 않은 경우 모달창(ErrorModal)이 다른 요소의 옆에서 랜더링 된다.

포털 작업하기

  1. html에 포털시킬 위치에 아래와 같이 추가한다.
  <body>
    <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>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
import ReactDOM from "react-dom";

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( /* createPortal(요소, 도착 realDom) */
        <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>
  );
};

모달창 요소가 body바로 아래 위치한 것을 확인할 수 있다.

포탈의 핵심은 랜더된 html요소를 다른 곳으로 옮기는 것이다. 이것은 실제 랜더링 되는 real dom에서 일어나는 작업이기 때문에 기존의 jsx를 통한 컴포넌트 작업은 그대로 유지될 수 있다.

'ref'로 작업하기

ref는 real dom을 조작하기 위한 훅이다. 입력값을 제출할 때 무언가 입력할때마다 state를 계속 변경시키는 것이 별로일 때 ref를 활용할 수 있다. 혹은 아래와 같은 상황에서 사용한다.

focus
text selection
media playback
애니메이션 적용
d3.js, greensock 등 DOM 기반 라이브러리 활용

const 주소값을_담는_그릇 = useRef(참조자료형)
// 이제 주소값을_담는_그릇 변수에 어떤 주소값이든 담을 수 있습니다.
return (
    <div>
      <input ref={주소값을_담는_그릇} type="text" />
        {/* React에서 사용 가능한 ref라는 속성에 주소값을_담는_그릇을 값으로 할당하면*/}
        {/* 주소값을_담는_그릇 변수에는 input DOM 엘리먼트의 주소가 담깁니다. */}
        {/* 향후 다른 컴포넌트에서 input DOM 엘리먼트를 활용할 수 있습니다. */}
    </div>);


function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>);
}

위의 상황을 제외하고 대부분의 경우 기본 React 문법을 벗어나 useRef 를 남용하는 것은 부적절하고, React의 특징이자 장점인 선언형 프로그래밍 원칙과 배치되기 때문에, 조심해서 사용해야한다. 하지만 일반적으로 입력값을 읽거나 입력값을 초기화 시킬때는 사용해도 무방하다.

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

ref속성이 붙은 요소는 제어되지 않는 컴포넌트다. ref를 통해 돔에 접근하면 그 뒤에 돔을 조작하는 것은 원래의 브라우저 돔 조작 api지 리액트가 아니기 때문이다. 기존에 수행하던 방식인 state를 통해 관리했던 input은 제어되는 컴포넌트다. 내부 state가 리액트에 의해 제어되기 때문이다.

이런 용어와 개념은 자주 쓰이기 때문에 기억해두자.

0개의 댓글