React.js Fragments, Portals & Refs

강정우·2022년 12월 31일
0

react.js

목록 보기
14/45
post-thumbnail
post-custom-banner

문제

  • JSX는 요소가 한 요소에 의해 wrapping 되어있어야 한다.
    왜? JS라는 언어의 태생적 한계 때문이다.
    즉, "root" JSX 요소에는 1개만 return하거나 저장되어야하며, 다만 여러 children 요소를 가질 수 있기에 하나에 담아서 보내는 것이다.

  • 하지만 이를 배열에 담아서 return하는 것은 괜찮다. 배열을 1개의 객체로 보기때문에
    다만 또 한가지 애로사항이 있는데 바로 여기서도 봤던 key exception이다.

[단순히 배열을 return했을 때는 react입장에서는 다 똑같은 요소이기 때문에 어디에 추가해야될지 몰라 일단 제일 뒤에 추가하고 다시 코드에 맞게 재배열했다는 내용이었는데 그만큼 비효율적으로 되어버린 다는 뜻이다.]

  • 그래서 일일이 key값을 다 부여해야하는데 귀찮아서 통상 이렇게 처리하지 않는다. 그래서 기존에 하던것 처럼 처리하는데 이때 발생하는 문제가 있다.

div "soup"

  • 디브 수프 즉, div 태그가 물처럼 많다 라는 뜻이다.
  • 실제 DOM으로 렌더링될 때 리액트 컴포넌트가 많이 중첩될 수 있다.
    왜냐면 JSX의 요구 사항 또는 제한 사항 때문에 그것들을 error가 안 나게 하려면 수많은 div들이 필요할 것이다.
  • 그렇다면 사용자가 보는 최종 Html페이지는 불필요한 무수히 많은 div가 있을 것이다.

    즉, semantic하지 않게되는 것이다!
    또한 궁극적으로 web application이 매우 느려질 것이다!

Fragments

  • 조각, 파편이라는 뜻으로 단순히 div로 감싸는 것이 아닌 Wrapper 사용자 지정 컴포넌트를 작성한다.
    아래는 Fragments 태그를 Wrapper라는 이름으로 구현해보았다.

  • 보면 코드가 매우 단순하다. 그냥 Wrapper 여닫는 태그 안에있는 내용물들만 반환해주는 것이다.
    이렇게 되면 JSX를 배열로 처리하는 것 처럼 귀찮은 요소도 없고, 무수히 많은 div로 처리하는 것 처럼 수많은 div가 생성되지도 않는다!
  • 그런데 사실 이 Wrapper component를 우리가 만들 필요는 없다! 왜? 이미 Ract.Fragment가 제공하기 때문이다.
  • 위 사진과 같이 2중 하나를 선택하여 쓸 수 있는데 이는 프로젝트 설정에 따라 다르다.
    build workflow가 이를 지원해야하기 때문이다.
  • 왼쪽 방법은 항상 작동한다. 마치 우리가 Wrapper를 만들어서 쓰는 것 처럼
    즉, 요 2개의 코드는 HTML요소를 DOM에 랜더링하지 않는다. 즉, div soup가 발생하지 않는다.
  • 하지만 본인이 봤을 때 가시적이지 않다고 느낀다면 <React.Fragment>를 사용해도 무방하다.

Portals

  • Fragment와 비슷한 일을 수행한다.
    다만 우선 코드를 봐보자
  • 위 코드는 기술적으로는 아무런 문제가 없다. 하지만 의미적인 관점이나 semantic한 Html구조인지 보았을 때는 아니라는 것이다.
    즉, 기본적으로 modal창을 비록한 side drawer나 dialog 같은 오버레이 요소들은 모든요소 위에 overlay되기 때문에 스타일링이나 접근성의 관점에서는 문제가 된다.
    위 코드를 보면 알겠다 싶이 modal이라고 적혀있음에도 그냥 하나의 Html구조인것처럼 보이기 때문이다.

  • 현재 문제점이다. 모달창이 어떠한 요소사이게 낑겨서 출력되고있다. 그렇다면 만약에 그 어떠한 요소의 위치가 바뀌어버리면 모달창 또한 바뀔수 있다.
    그래서 가장 좋은 위치는 <Body><div id=root> 밑이 가장 좋겠다. 그런데 이 일을 portal이 한다는 것이다.

사용법

  • portal은 2가지가 필요하다. 1. 장소 2. 장소로 간다는 알림
  1. 장소
  • 위치는 index.html에 가장의 div를 만들어 미리 들어갈 장소를 마련해둔다.
  1. 장소로 간다는 알림
  • 해당 모달 컴포넌트가 있는 .js파일에 장소를 잡아주는 컴포넌트를 또 생성할 텐데 괜찮다. 왜냐면
    이 web application에서 장소를 잡아주는 컴포넌트는 항상 모달 컴포넌트와 같이 보내지기 때문이다.
  • 다만 장소를 잡아주는 컴포넌트다른 컴포넌트와 함께 쓰려면 따로 생성해두어도 된다.

  • 그래서 위 코드를 보면 Backdrop으로 재사용이 가능한 그림자(?)를 만들었고 ModalOverlay로 모달창 안에 들어갈 내용을 만든 후
    ReacDOM.createPortal method를 통해 보낸 것을 확인할 수 있다.

    즉, 포탈은 렝더링된 HTML내용을 다른 곳으로 옮기는 것이다.

Refs

  • reference의 준말로 다른 DOM요소에 접근하여 그것들로 작업할 수 있게 해주는 것이다.
    무슨뜻이냐 하면 렌더링된 DOM요소에 직접적으로 접근하여 수정할수 있다는 말이다.
  • 일단 우리는 매번 사용자가 키를 누를 때마다 state를 업데이트 하고있다.
    (onChange 프로퍼티에 event로 값을 가져오기 때문에.)
    하지만 폼을 제출할 때만 필요한데 onChange로 바뀌는 매 순간마다 state를 검증 로직을 거친후 업데이트 하니 이는 약간 느려질 수 있다. 이때 사용하는 것이 ref이다.

사용법

  • 마지막에 렌더링되는 HTML 요소들과 다른 JS코드의 연결을 ref를 사용하여 연결할 수 있다.
  • useRef 훅을 import 해주고 여타 다른 훅과 마찬가지로 함수형 컴포넌트 안에서만 사용가능하다.
import React, {useState, useRef} from "react";
...

const .js이름 = props => {
	const HTML과관련된변수명 = useRef();

  • 리액트가 이 코드에 처음 도달해서 이 코드를 렌더링할 때 nameInputRef에 저장된 값을 이 인풋을 기반으로 렌더링된 네이티브 DOM 요소에 설정한다. 그럼 결과값은 무엇인가?

  • ref의 함수로 나온 결과값은 항상 객체이며 항상 current라는 property를 갖고있다. 그리고 값은 렌더링된 DOM노드의 값을 갖고있다.
    즉, 코드가 실행되자마자 ref property덕에 useRef와 연결되는 것이다.

  • 그래서 실제 DOM의 값과 연결된것이기 때문에 여러가지 작업을 할 수 있다. 하지만 사실 조작하지 않는 것이 좋다. 왜? DOM은 React에 의해서만 조작되어야하기 때문에 하지만 우리는 단지 값을 읽기위해 쓰는 것이기 때문에 괜찮다!

    그래서 이것을 제어되지 않는 컴포넌트(Uncontrolled component)라고 하기도 한다.
    왜? react의 state에 의해서 제어되지 않기 때문에

  • 그럼이제 값을 가지고오기 위해 사용했던 state는 필요가 없는 것일까?
    그건 아니다 입력 후 입력창을 비워버리고 싶을 때 초기화 구문으로 사용해도 된다.
  • 그런데 이때 ref속성으로 직접적으로 조작해도 된다. 사실 앞에서 사용하면 안된다고 했지만 권장사항이고 절대! 남용하는 것이 아닌 input창 초기화에만 사용할 것이다.
  • 하지만 공식홈페이지에는 state를 사용하여 관리할 수 있다면 그것을 권하고 있다.
  • 값을 빠르게 읽고싶다 + 화면에 바뀔 요소가 없다 + 코드를 조금 치고싶다 : Ref
    정석대로 가고싶다 + 안전하고 깔끔하게 코드를 치고싶다 : state

uncontrolled component

  • 앞서 언급했던 react의 state에 의해 제어되지 않는 컴포넌트들을 뜻한다.
    이게 왜? 중요할까?

  • 가장 일반적으로 입력 컴포넌트에 대해 이야기할 때는 <form> 컴포넌트를 사용한다.
    왜냐하면 <form> 컴포넌트는 기본적으로 브라우저에 의해 내부 state를 갖는 경향이 있기 때문이다.
    즉, 사용자 입력을 받아 저장하고 반영하는 인풋 요소가 구성되어 있다. 그리고 리액트 앱에서 해당 컴포넌트로 작업할 때 리액트 state를 그것에 연결하여 사용한다.

  • 하지만 ref를 사용하면 react 작동 원리를 거스르기 때문에 리액트에서 <input> 컴포넌트로 작업할 때
    제어/제어되지 않음에 대해 논하는 것이다

forward refs

  • forward refs는 input 컴포넌트와 명령형으로 상호 작용할 수 있게 해준다.

  • 즉, 예를 들어 어떤 state를 전달해서 그 컴포넌트에서 무언가를 변경하는 방식이 아니라
    컴포넌트 내부에서 함수를 호출하는 방식으로 변경한다.

  • 하지만 이 방법은 자주 해서 안 됩니다 왜냐하면 일반적인 리액트 패턴이 아니기 때문이다.

  • forward refs의 반환값은 react 컴포넌트이다.

사용법

import React, {useImperativeHandle} from "react";

const 사용자정의컴포넌트 = React.forwardRer((props, ref) => {
	~~useRef()~~
})

useImperativeHadle(ref, () => {
	return {};
})

useImperativeHadle(ref, 객체를 반환하는 함수) 이다.
이때 이 객체는 외부에서 사용할 수 있는 모든 데이터를 포함한다.
즉, 외부에서 객체의 key값으로 접근한다는 뜻이다. 그리고 이때 value값을 함수를 point해도 좋다.

예제

  • 로그인할 때 form이 유효하지 않는다면 유효하지 않는 input을 찾아 focus하시오.
  1. 변수 선언 및 로그인이 들어가있는 해당 컴포넌트 함수를 찾아서 로직을 구현다.

  1. Ref훅을 가져와 순수 HTML로 짜여있는 input 컴포넌트 함수로 들어간다. 이때 ref는 앞서 배운것처럼 uncontrolled이기 때문에 별도로 예약어인 ref를 사용해주어야한다. 또한 useImperativeHandle도 함께 import 해주어야한다.
    마찬가지로 ref를 정의하고
    focus할 수 있는 함수를 구현한다. 또한
    해당 HTML에 내장 기능인 ref prop을 추가한다.

  2. 다시 로직이 구현되어있는 js로 돌아와 컴포넌트가 사용된 tag에 props를 추가해준다.

useImperativeHandle 및 forwardRef를 사용하면
리액트 컴포넌트에서 온 기능을 노출하여
부모 컴포넌트에 연결한 다음, 부모 컴포넌트 안에서
참조를 통해 그 컴포넌트를 사용하고
기능을 트리거할 수 있다

profile
智(지)! 德(덕)! 體(체)!
post-custom-banner

0개의 댓글