본문 바로가기

React

react portals로 popup 혹은 tooltip툴팁 만들기

반응형

리액트를 시작한지 어언 5개월차.. 매번 쓰던 hook 말고도 다른 기능들을 독학하기로 결심했다.

그중 리액트 포탈이라는 것을 보고 여기 정리해두고자 한다.

 

리액트의 기능 중 하나인 Portal 포탈은 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공.. 한다고 공식 문서에 명시되어 있다.

 

 

Portals – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

쉽게 말하자면 객체를 생성했다가 원하는 DOM 위치에 추가해주는 기능을 가지고 있다.

때문에 팝업이나 툴팁처럼 같은 스타일의 컴포넌트를 여러 페이지에서 돌려막기? 할 때에 유용하게 쓰일 것 같다.

 

우선 마크업은 대충 요렇게 짜봤다.

 

App.js

import './App.css';
import { useState, useRef } from 'react'
import Portals from './Portals';

function App() {

  const data = [{
    key: 1,
    title: 'HTML',
    desc: '마크업 기본'
  }, {
    key: 2,
    title: 'css',
    desc: '스타일 정의'
  }, {
    key: 3,
    title: 'javascript',
    desc: '함수 적용'
  }]

  const popOverRef = useRef([]);
  
  const [currentIdx, setCurrentIdx] = useState()

  return (
    <div className="App">
      {data.map((item, idx) => {
        return (
          <div 
          className="lists"
          key={item.key} 
          ref={el => popOverRef.current[idx] = el}
          onClick={() => setCurrentIdx(idx)}>
            <div>{item.title}</div>
          </div>
        )
      })}
      <Portals target={popOverRef.current[currentIdx]}>
        <p className="tooltip">{data[currentIdx]?.desc}</p>
      </Portals>
    </div>
  );
}

export default App;

여러 데이터를 예시로 (일단 3개..) 넣어 두고 map으로 돌렸다. 여기서 중요한 점은 여러개의 엘리먼트를 ref로 저장한 점과 엘리먼트를 클릭할 때마다 currenIdx라는 state를 변경한 것이다.

 

data[currentIdx] 뒤에 ?를 붙인 이유는, 맨처음 화면이 렌더될때 currentIdx 값이 없으므로 오류를 방지하기 위해 넣었다.

 

createPortal은 'Portals'라는 별도의 컴포넌트로 분리시켜서 불렀다.

 

Portals.js

import { createPortal } from 'react-dom'

const Portals = ({ children, target }) => {
    // console.log(children, target)
    return target ? createPortal(children, target) : null
}

export default Portals

 

createPortal은 2개의 인자를 보낸다. 첫번째는 렌더될 자식 요소로, DOM엘리먼트나 문자열 등등 뭐든 렌더링될 만한 애들은 다 넣을 수 있다. 두번째는 렌더시킬 위치, DOM 엘리먼트를 넣는다.

 

createPortal(렌더할 자식놈들, 렌더될 타겟 위치)

 

앞서 ref를 맵핑된 객체마다 넣어준 이유는 바로 이 target(두번째 인자)으로 넘기기 위함이었다.

클릭 할 때마다 target이 바뀌므로 target 값에 따라 해당 dom이 아닐때는 null값으로 리턴하도록 짰다.

 

children은 Portals 컴포넌트 자식으로 넣어둔 p태그가 넘겨진다. 나는 간단하게 p태그로 짰지만, 팝업처럼 구조도 다양하고 콘텐츠도 많아 지면, 이 부분도 컴포넌트화 해서 따로 만들어 넣어도 좋겠다.

 

초 간단하게 짠거라 별건 없지만 참고차 css도 올린다.

 

App.css

.App {
  max-width: 800px;
  margin: 0 auto;
  text-align: center;
}
.lists {
  position: relative;
  padding: 40px 0;
  border: 1px solid #222;
  border-bottom: 0;
}
.lists:last-child {
  border-bottom: 1px solid #222;
}

.tooltip {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  border: 1px solid #999;
  padding: 5px 8px;
  margin: 0;
  font-size: 12px;
  background-color: #d8f5fd
}
 
 
내가 짠 코드는 대충 아래처럼 나온다. 누를때마다 툴팁이 뿅뿅 뜸. 다른 공간 누를때 툴팁이 아예 사라지는 것까지 구현했으면 완벽했을텐데.. 다음에 시간나면 짜봐야지

portals 한번 써봤는데 꽤 매력있는 거 같다. 라이브러리 안 쓰고 급하게 간단한 용도로 필요할 때 이용하면 좋을거 같다.

반응형