랜더링 시점

  • 컴포넌트에 예정된 상태 업데이트가 있을 경우.
    • 컴포넌트에서 사용된 커스텀 훅의 예정된 업데이트가 있을 경우도 포함합니다.
  • 부모 컴포넌트가 렌더링 되고 리렌더링에서 제외되는 기준에 충족하지 않을 경우. 제외되는 기준은 다음의 네 가지 조건을 모두 동시에 충족해야 합니다.
    • 컴포넌트가 이전에 렌더링 되었어야 함. 즉, 이미 마운트 되었어야 함.
    • 변경된 props(참조)가 없어야 함.
    • 컴포넌트에서 사용하고 있는 context 값이 변경되지 않아야 함.
    • 컴포넌트에 예정된 상태 업데이트가 없어야 함.

 

"렌더"의 뜻

"리액트가 컴포넌트를 렌더링 한다"

컴포넌트(UI에서 업데이트를 예약할 수 있도록 리액트에 의해 강화된 함수)는 리액트에 의해 호출되는 것입니다. 컴포넌트가 스스로의 상태를 능동적으로 변화시켰든 다른 변화 때문인지든 상관없이 리액트에 의해 호출되는 것입니다.

 

리액트의 핵심 디자인 원칙 중 하나에 따라 리액트는 UI 스케줄링 및 업데이트를 완전히 제어할 수 있습니다. 이는 몇 가지 의미를 가집니다.

 

  • 컴포넌트가 만든 하나의 상태 업데이트가 반드시 하나의 렌더링(리액트에 의해 한 번의 컴포넌트 호출)으로 변환되는 것은 아닙니다. 그 이유는 다음과 같습니다.
    • 리액트는 컴포넌트의 상태에 의미 있는 변화가 없다고 생각할 수 있습니다.(object.is에 의해 결정됩니다)
    • 리액트는 상태 업데이트를 하나의 렌더 패스로 일괄 처리하려 합니다.
      • But, 리액트는 promise가 resolve 되는 타이밍을 제어할 수 없기 때문에 promise에서의 상태 업데이트를 일괄 처리할 수 없습니다. setTimeout, setInterval  requestAnimatonFrame 같이 별도의 이벤트 루프 콜 스택에서 실행되는 네이티브 이벤트 핸들러도 마찬가지입니다.
    • 리액트는 여러 렌더 패스로 작업을 분할할 수도 있습니다.(concurrent 리액트 기능)
  • 리액트는 다양한 이유로 컴포넌트를 렌더링(즉, 함수 호출) 할 수 있기 때문에 컴포넌트를 한 번 렌더링 하는 것이 UI의 시각적 업데이트로 반드시 변환되지는 않습니다.

리액트가 props 변경을 탐지하는 데 사용하는 규칙 변경 방법

위에서 언급했던 것처럼, 기본적으로 리액트는 ===를 사용하여 이전 props와 현재 props를 비교합니다.

다행히도 리액트는 컴포넌트를 PureComponent로 만들어가 React.memo로 감쌀 경우, props 변경을 확인할 다른 방법을 제공합니다. 이런 경우에 리액트는 ===를 사용하여 참조가 변경되었는지 확인하는 대신, props의 모든 property에 얕은 비교를 수행합니다. 개념적으로 Object.keys(prevProps).some(key => prevProps[key] !== nextProps[key])와 유사합니다.

그러나 이러한 최적화는 악용되어서는 안되며, 리액트가 이를 기본 렌더링 동작으로 만들지 않은 데에는 이유가 있습니다. Dan Abramov는 props 비교에 드는 비용을 무시해서는 안 되며 더 나은 대안이 많이 있다고 거듭 언급했습니다.

 

 

두 가지 유형의 렌더링

  • 능동적인 렌더링:
    • 컴포넌트(혹은 컴포넌트에서 사용한 커스텀 훅)가 능동적으로 상태를 변경하기 위한 업데이트를 예약합니다.
    • ReactDOM.render를 직접 호출합니다.
  • 수동적인 렌더링: 부모 컴포넌트(들)이 상태 업데이트를 예약하고 컴포넌트가 렌더링 제외 기준을 충족하지 않습니다.

 

능동적인 렌더링

"능동적인 렌더링"은 제가 만든 단어입니다. "능동적인"이란 컴포넌트 자체(또는 컴포넌트가 사용하는 커스텀 훅)가 업데이트를 예약하기 위해 다음을 통해 능동적으로 상태를 변경하는 것을 의미합니다:

  • 클래스 컴포넌트를 사용하는 경우, Component.prototype.setState(즉, this.setState)
  • 함수형 컴포넌트를 사용하는 경우, 훅에 의해 발생한 dispatchAction:
    • useReducer 훅의 dispatch 함수와 useState 훅의 상태 업데이트 함수는 모두 dispatchAction을 사용합니다.

업데이트를 능동적으로 예약하는 또 다른 방법은, ReactDOM.render를 직접 호출하는 것입니다. 다음은 리액트 공식 문서의 예입니다.

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById("root"));
}
setInterval(tick, 1000);

 

 

수동적인 렌더링

수동적인 렌더링은 리액트가 일부 부모 컴포넌트를 렌더링 하고 컴포넌트가 렌더링 제외 기준을 충족하지 않았을 때 발생합니다.

function Parent() {
  return (
    <div>
      <Child />
    </div>
  );
}

 

위의 예시에서 부모 컴포넌트가 리액트에 의해 렌더링 되면, 자식 컴포넌트는 props에 참조/아이덴디티가 변경된 것(이 내용은 나중에 더 자세히 알아봅시다) 외에 의미 있는 변경사항이 없더라도 렌더링 됩니다.

 

위의 예시에서 부모 컴포넌트가 리액트에 의해 렌더링 되면, 자식 컴포넌트는 props에 참조/아이덴디티가 변경된 것(이 내용은 나중에 더 자세히 알아봅시다) 외에 의미 있는 변경사항이 없더라도 렌더링 됩니다.

렌더 단계에서 리액트는 재귀적으로 컴포넌트 트리를 탐색하여 컴포넌트를 렌더링 합니다. 따라서 만약 자식 컴포넌트에게 또 다른 자식 컴포넌트가 있다면 해당 자식 컴포넌트도 렌더링 됩니다. (계속해서 얘기하지만, 렌더링 제외 기준을 충족하지 않을 경우에 한합니다.)

function Child() {
  return <GrandChild />; // 만약 `Child` 가 렌더링 되면 `GrandChild` 또한 렌더링 됩니다.
}

그러나 컴포넌트 중 하나가 렌더링 제외 기준을 충족한다면 리액트는 해당 컴포넌트를 렌더링 하지 않습니다.

 

모든 자식 컴포넌트가 동일하게 만들어지지 않았을 때

먼저 예를 살펴보겠습니다.

default function App() {
  return (
    <Parent lastChild={<ChildC />}>
      <ChildB />
    </Parent>
  );
}

function Parent({ children, lastChild }) {
  return (
    <div className="parent">
      <ChildA />
      {children}
      {lastChild}
    </div>
  );
}

function ChildA() {
  return <div className="childA"></div>;
}

function ChildB() {
  return <div className="childB"></div>;
}

function ChildC() {
  return <div className="childC"></div>;
}

 

만약 Parent의 업데이트가 예정되어 있다면, 어떤 컴포넌트가 리렌더링 될까요?

ChildA는 리렌더링 되었습니다. 이는 부모 컴포넌트가 업데이트를 예약하고 리렌더링 되었다는 것을 알고 있기 때문에 놀라운 일은 아닙니다.

그러나 ChildA와 달리 ChildB와 ChildC는 리렌더링 되지 않았습니다. 그 이유는 ChildB와 ChildC가 렌더링 제외 기준을 충족했기 때문에 리액트가 렌더링을 건너 뛴 것입니다.

 

 

 

 

참고:

https://velog.io/@eunbinn/when-does-react-render-your-component#%ED%9D%90%EB%A6%84%EB%8F%84

페이지를 벗어날때 사용자에게 팝업이나 알림을 띄워 잡을 수 있는 hooks이 useBeforeLeave입니다.

useEffect(()=>{
	document.addEventListener("mouseLeave", handle);
    return() => document.removeEventListener("mouseLeave", handle);
})

case1: 페이지 별 방문자 수 수집

GA가 4로 변경되었기에 react-ga4를 사용한다.

https://www.npmjs.com/package/react-ga4

 

react-ga4

React Google Analytics 4. Latest version: 2.1.0, last published: 6 months ago. Start using react-ga4 in your project by running `npm i react-ga4`. There are 162 other projects in the npm registry using react-ga4.

www.npmjs.com

 

설치)

npm i react-ga4

 

적용)

set은 page수집 메소드이다.

import { useEffect } from 'react';
import ReactGA from 'react-ga4';

export const gaPageView = (
  pathname: string, // location.pathname
  vId: string, // 페이지 ID
  vName?: string, // 페이지명
): void => {

    ReactGA.send({
      hitType: 'pageview',
      title: `${pageName} ${_vId}`,
      page: pathname, 
    });
};

 

JS바코드 사용 바코드 구현

react에서 js바코드를 사용하기 위해서 패키지를 다운로드

using NPM

npm i react-jsbarcode

 

using yarn

yarn add react-jsbarcode

 

사용 예시 코드

바코드

import React,{useState} from 'react';

import JsBarcode from 'jsbarcode';

function Barcode() : React.ReactElement {
  const [barcode, setBarcode] = useState(''); // 바코드 변수 설정
  const [timeLeft, setTimeLeft] = useState(''); // 타임아웃 설정
  const [isExpired, setIsExpired] = useState(false); // 바코드 사용 시간 만료 설정
  const [isLoading, setIsLoading] = useState(false); // 더블클릭 방지용 

  
  const setBarcodeInfo = useCallback(() => {
    setBarcode(1700800045789999);
	setTimeLeft(180);
    // 해당 부부은 api가 있다면, 받아오는 역할의 const
  },[]);

  const handleClickRefreshBtn = () => {
    if (isLoading) return; //더블클릭 방지
    setIsLoading(true);
  };
  
  //타이머 만료 여부 확인
  const getIsExpired = useCallback((val: boolean) => {
    setIsExpired(val);
  }, []);


  useEffect(()=>{
    
    const generateBarcode = () => {
      if (barcode) {
        const barcodeString = 
              barcode.substring(0,4) + 
              '  ' + 
              barcode.substring(4,8) + 
              '  ' + 
              barcode.substring(8,12) + 
              '  ' + 
              barcode.substring(12,16);

        JsBarcode('#barcode-img', barcode, {
          backgroound: '#ffffff',
          text : barcodeString,
          height : 70,
          textAlign : 'center',
          fontSize : 15,
        });
      }
    };
    generateBarcode();
  }, [barcode]);
  
  
  return (
  	<section>
    	<div>
    		<h3>바코드</h3>
    		<div>
    			<svg id="barcode-img" width="270px" height="107px" /> 
    		</div>
    		{isExpired && (
			<div>
				<button onClick={handleClickRefreshBtn}><sapn>새로고침</span></button>
    		</div>
			)}
	    {isExpired ? (
         <p className={cx('txt_timer')}>인증 유효시간 초과</p>
         ) :
		 (
          <barCodeTimer
           timeLeft={timeLeft}
           getIsExpired={getIsExpired}
          />
        )}
    	</div>
  </section>
)

export default Barcode;

 

 

바코드 타이머

import React,{useState, useEffect} from 'react';

import { fillZero } from 'src/utills/utility';

interface Props {
	timeLeft : number;
	getIsExpired : (boolean) => void;
}

function BarcodeTimer(props: Props) : React.ReactElement {
  const [tiem, setTime] = useState({min : '', sec: ''});
  let seconds = props.timeLeft;
  
  const timer = seconds => {
    const min = Math.floor(seconds / 60).toString();
    const sec = fillzore(2, (seconds & 60).toString());
    return { min, sec };
  };
  
  useEffect(() => {
    if (seconds < 1) return;
    
    setTime(timer(seconds)); // 초기값 세팅
    
    const clear = setInterval(function () {
      if (seconds > 0) {
        seconds--;
        setTime(timer(seconds));
      } else {
        clearInterval(clear);
      }
    }, 1000);
    return () => {
      clearInterval(clear);
    };
  }, [props, seconds]);
  
  useEffect(() => {
    if (time.min === '0' && time.sec ==='00') {
      props.getIsExpired(true);
    }
   },[time, porps]);
  
  return (
  <p>남은 시간
    <span>
    	<em>{time.min} : {time.sec}</em>
	</span>
  </p>
  );
} 

export default BarcodeTimer;

 

* utility - fillZero 함수

/**
 * 숫자앞에 0 채우기
 * @param date  string
 */
export const fillZero = (width: number, str: string): string => {
  return str.length >= width
    ? str
    : new Array(width - str.length + 1).join('0') + str; //남는 길이만큼 0으로 채움
};

 

 

참고:

https://www.npmjs.com/package/react-jsbarcode

 

react-jsbarcode

JSBarcode component for React. Latest version: 1.0.1, last published: 6 months ago. Start using react-jsbarcode in your project by running `npm i react-jsbarcode`. There are 5 other projects in the npm registry using react-jsbarcode.

www.npmjs.com

 

https://velog.io/@rlagurtns1/React-BarcodeBarcode%EB%A7%8C%EB%A3%8C-%EA%B5%AC%ED%98%84

 

[React] Barcode/Barcode만료 구현

JS바코드 사용 바코드 구현 react에서 js바코드를 사용하기 위해서 패키지를 다운로드 using NPM using yarn 사용 예시 코드 import React,{useState} from 'react'; import JsBarcode from 'js

velog.io

 

 

'Framework > React' 카테고리의 다른 글

React Hooks - useBeforeLeave  (0) 2024.11.19
[React] 구글 애널리틱스  (1) 2024.11.15
[React] React Portal  (1) 2024.11.08
[React] createBrowserHistory() - history 패키지  (0) 2024.11.07
[React] Popup Wrapper  (0) 2024.11.07

1) 리액트 포탈을 사용하는 이유가 무엇일까요?

  • 리액트는 부모 컴포넌트가 렌더링 되면 자식 컴포넌트가 렌더링 되는 Tree 구조를 가지고 있다. 이런 Tree 구조는 종종 불편함을 가지게 되는데 부모-자식 관계를 가지고 있어 DOM 계층 구조에 영향을 미치게 된다. 하지만 리액트 포탈을 사용하면 독립적인 위치에서 렌더링하기 때문에 편리하게 사용이 가능하다. 대표적인 예로 스타일링이 간편하다. 독립적인 위치에서 렌더링 하게 된다면 overflow: hidden, z-index 와 같은 속성을 부모 컴포넌트에 영향을 받지 않도록 할 수 있다. 따라서 리액트 포탈을 사용하면 스타일링을 간편하게 사용 가능하고 이런 독립된 스타일링은 유지 보수성을 향상시키고 CSS 충돌을 방지한다.

2) React Portal 이란?

공식문서에서 Portal은 부모 컴포넌트의 Dom 계층 구조 바깥에 있는 Dom 노드로 자식을 렌더링하는 최고의 방법을 제공라고 적혀있다. 이게 무슨 뜻을까? 이 문장을 이해하기 전에 React Portal의 사용 방법을 알아보자.

 

React Portal의 사용 방법

1) index.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" />
    <title>리액트포탈</title>
  </head>
  <body>
    <div id="root"></div>
    <aside id="aside-root"></aside>
  </body>
</html>

 

2) Home.tsx

import { createPortal } from 'react-dom';

<div>
  <p>리액트 포탈을 사용하려고 합니다.</p>
  {createPortal(
    <p>리액트 포탈의 모달 컴포넌트</p>,
    document.getElementById('aside-root')
  )}
</div>

 

3) createPortal()

ReactDOM.createPortal(child, container, key) 포탈을 생성하려면 createPortal을 호출해야 한다. 첫 번째 파라미터 child는 React로 렌더링 가능한 모든 것이 들어간다. 예로 <div></div>, <ChildComponent />, Fragment <></>, 문자열, 숫자 등이 있다. 두 번째 파라미터 container는 document.getElementById()로 설정한 DOM 엘리먼트이다. html 파일에 만들어둔 Dom element를 설정한다. 세번째 파라미터는 옵션인데 포털의 키로 사용할 고유의 문자와 숫자 key 값이다. 나는 따로 ReactPortal.tsx 파일을 만들어 이런 방식으로 사용했다.

이렇게 사용하게 되면 index.html에 만들어둔 aside-root 태그 속으로 내가 지정한 SimpleModal이 modalOpen 되었을 때 텔레포트 하게 된다. 그럼 React Portal의 특징을 알아보자.

 

React Portal 특징

1) Portal을 사용해 컴포넌트를 설정한 Dom element로 텔레포트

Portal 을 사용하면 모달의 DOM을 다른 위치로 텔레포트 시킬 수 있다. 즉 다른 위치로 렌더링이 가능하다는 말이다. 위 코드를 보면 원래는 ReactPortal 태그의 코드가 Home.tsx 속에 위치해야겠지만 DOM element인 root = document.getElementById('asid-root')로 이동 시킨다. 

React Portal을 사용한 Home.tsx 자식 SimpleModal 컴포넌트는 Home.tsx의 자식 노드의 역할을 하고 오직 물리적 배치만 aside-root로 변경한다. 즉, 리액트 포탈을 사용한 자식 컴포넌트는 부모 컴포넌트 트리가 제공하는 컨텍스트에 접근이 가능하고 이벤트는 여전히 React tree에 따라 자식에서 부모로 버블링 된다.

 

2) 포탈에 렌더링된 컴포넌트는 DOM에서 부모 JSX 요소에 포함되지 않음.

포탈을 사용하지 않는 모달인 경우 부모의 JSX 요소에 영향을 받아 스타일링 시에 문제가 생길 수도 있다. 하지만 리액트 포탈을 사용할 경우 부모 컴포넌트의 스타일링에 영향을 받지 않는다. 따라서 스타일을 단독적으로 좀 더 쉽게 설정이 가능하다.

여기서 중요한 점이 있다. 포털을 사용할 때 사용자가 웹앱에 접근이 가능한지 확인하는 것이 중요하다. 예로 사용자가 포털 안밖으로 초점을 이동 가능하게 키보드 포커스 관리를 잘 하는 것이 중요하다.

 

결론

  • 리액트 포탈을 사용하면 독립적인 DOM으로 렌더링 되기 때문에 기존 컴포넌트의 구조에 영향을 주지 않는다.
  • 따라서 스타일링 하기 간편하고 독립된 스타일링을 통해 유지보수성이 좋아지고 CSS 충돌이 방지된다.
  • 리액트 포털의 사용은 물리적 배치만 변경되고 자식은 부모 트리가 제공하는 컨텍스트에 접근이 가능하고 이벤트는 React tree에 따라 자식에서 부모로 버블링된다.

 

 

참고:

https://velog.io/@jerrychu/React-React-Portal-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0%EC%99%80-%EB%B0%A9%EB%B2%95

createBrowserHistory

history object (객체)
BrowserRouter는 history 객체를 자동으로 생성하고, 컴포넌트 단위로 history 객체를 컨트롤하기 때문에 커스텀 작업이 어렵다. 컴포넌트 바깥에서 history 객체를 컨트롤하기 위해서는 (ex. action creator) BrowserRouter 대신 Router를 사용하고, BrowserHistory 를 별도 생성하여 관리해야한다.

history 객체는 history모듈에서 createBrowserHistory로 받아올 수 있다.

 

history 패키지는 임의의 환경(브라우저까지 포함)에서 구동되는 JavaSciprt 애플리케이션에서 세션 히스토리(방문 기록)의 관리와 내비게이션 등을 쉽게 할 수 있도록 도와주는 라이브러리이다. 이는 세션 히스토리를 다루는 방법을 구동 환경을 기준으로 세 가지로 나눠서 제공하고 있다(createBrowserHistory, createHashHistory, createMemoryHistory).

 

history 객체는 history 모듈에서 createBrowserHistory로 받아 올 수 있다.

import { createBrowserHistory } from 'history';

const history = createBrowserHistory(); // 히스토리 객체 반환
const store = configureStore(history);

 

이렇게 작성해주는 방법과

따로 utils 폴더에 history.js 파일에 분리하여 작성하여 history 파일을 import 해오고 Router에 history를 props로 전달한다.

그러면 각 컴포넌트는 history, location, math 객체를 props로부터 제공받아 사용가능 하다.

import { createBrowserHistory } from "history";

export default createBrowserHistory();

 

 


 

<BrowserRouter>  <Router>

BrowserRouter

  • <BrowserRouter>는 클라이언트 사이드 라우팅을 위해 라우팅 관련 컴포넌트들의 최상단에 위치시켜야하는 컴포넌트로, react-router-dom 패키지에 속한다. 사실 이는 react-router 패키지에 속해있는 <Router> 컴포넌트를 래핑한 컴포넌트이다.
  • <BrowserRouter> 컴포넌트는 <Router> 컴포넌트를 렌더링할때 props 로 history 객체를 전달하는데, 이 객체는 history 패키지의 createBrowserHistory() 함수를 호출함으로써 생성된다.

Router

  • <Router> 컴포넌트는 마운트되는 순간에 props로 전달받은 history 객체의 프로퍼티인 location 객체를 자신의 지역 상태에 저장한다.
  • props로 전달받은 history 객체를 구독하여(history.listen 메소드) 브라우저의 현재 URL이 변경될때마다 자신의 지역상태에 해당하는 location 객체가 새로운 location 객체로 대체되도록 한다.
  • 즉, 브라우저의 현재 URL 정보를 <Router> 컴포넌트가 지역상태로서 실시간으로 추적하겠다는 의미이다.

 

참고:

https://velog.io/@yoonvelog/Redux-thunk-%EC%97%90%EC%84%9C-history

 

Slide up popup wrapper

 

SlideUpPopupWrapper

/**
 * @names 밑에서 위로 올라오는 팝업
 * @params isOpen 팝업 열고 닫기 여부
 */
export function SlideUpPopupWrapper(
  props: TSlideUpPopupProps,
): React.ReactElement {
  return (
    <StyledSlideUpPopupWrapper
      id="slideupWrapper"
      zIndex={props.zIndex}
      className={props.isOpen === E_POPUP_STATE.OPEN ? 'open' : 'close'}
      overflow={props.overflow}
    >
      {props.children}
    </StyledSlideUpPopupWrapper>
  );
}

 

styles.css

/**
 * @name 밑에서 위로 올라오는 팝업 스타일 컴포넌트
 */
export const StyledSlideUpPopupWrapper = styled.div<{
  zIndex?: number;
  overflow?: string;
}>`
  position: fixed;
  overflow: ${props =>
    props.overflow && props.overflow === 'N' ? '' : 'hidden'};
  z-index: ${({ zIndex }) => (zIndex ? zIndex : 201)};
  top: 100%;
  left: 0;
  max-height: 95vh;
  width: 100%;
  background-color: white;
  border-radius: 1.5rem 1.5rem 0 0;
  &.open {
    animation: ${KeyframeSlideUpPopup} 0.5s 0s ease 1 forwards;
  }
  &.close {
    animation: ${KeyframeSlideDownPopup} 0.5s 0s ease 1 backwards;
  }
`;

 

styles.ts

/**
 * @name 밑으로 위로 올라오는 팝업 키프레임
 */
export const KeyframeSlideUpPopup = keyframes`
  0%{
    transform: translateY(0);
  }
  100%{
    transform: translateY(calc(-100% + 1px));
  }
`;
export const KeyframeSlideDownPopup = keyframes`
  0%{
    transform: translateY(-100%);
  }
  100%{
    transform: translateY(0);
  }
`;

 

응용

<Dim zIndex={509} isDisplay={props.isOpen === E_POPUP_STATE.OPEN} />
<SlideUpPopupWrapper zIndex={90} isOpen={props.isOpen}>
	<div>팝업 내용</div>
</StyledSnowPlanPopupWrapper>

 

 

 

 


Dim - 팝업 뜨는 동안 배경 어둡게 처리

 

Dim

function Dim({ ...props }: TDimProps): React.ReactElement {
  return <StyledDim {...props} />;
}

export default Dim;

 

 

styles.ts

/**
 * @name 팝업Dim 스타일
 * @params imageStyles 이미지 스타일
 */
export const StyledDim = styled.div<TDimStyleProps>`
  overflow: hidden;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  ${({ isDisplay = false }) => css`
    display: ${isDisplay ? 'block' : 'none'};
  `}
  align-items: center;
  justify-content: center;
  ${({ zIndex = 100 }) => css`
    z-index: ${zIndex};
  `}
`;

 

 

타입 정의

export type TDimStyleProps = {
  isDisplay?: boolean;
  zIndex: number;
};

 

응용

<Dim zIndex={89} isDisplay={props.isOpen === E_POPUP_STATE.OPEN} />

 

 

 

 


 

Centered popup wrapper

 

CenteredPopupWrapper

function CenteredPopupWrapper(props: Props): React.ReactElement {
  return (
    <StyledCenteredPopupWrapper
      padding={props.padding}
      zIndex={props.zIndex}
      isBanner={props.isBanner}
    >
      {props.children}
    </StyledCenteredPopupWrapper>
  );
}

export default CenteredPopupWrapper;

 

 

타입 정의

type Props = {
  children?: React.ReactNode;
  padding?: string;
  zIndex?: number;
  isBanner?: boolean;
};

 

 

styles.ts

/**
 * @name 중앙 팝업 스타일 컴포넌트
 */
export const StyledCenteredPopupWrapper = styled.div<{
  padding?: string;
  zIndex?: number;
  isBanner?: boolean;
}>`
  z-index: ${props => (props.zIndex ? props.zIndex : 301)};
  box-sizing: border-box;
  display: block;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 90%;
  @media all and (min-aspect-ratio: 3/4) {
    max-width: 50%;
    max-height: 90%;
  }
  ${props =>
    !props.isBanner &&
    css`
      background: white;
      border-radius: ${rem(20)};
      padding: ${props.padding
        ? props.padding
        : `${rem(50.5)} ${rem(20)} ${rem(20)}`};
    `}
`;

 

응용

<Dim zIndex={509} isDisplay={props.isOpen === E_POPUP_STATE.OPEN} />
<CenteredPopupWrapper padding="0" zIndex={510}>
	<div>내용</div>
</CenteredPopupWrapper>

'Framework > React' 카테고리의 다른 글

[React] React Portal  (1) 2024.11.08
[React] createBrowserHistory() - history 패키지  (0) 2024.11.07
[React] mutate와 mutateAsync 차이  (0) 2024.11.05
[React] dangerouslySetInnerHTML  (0) 2024.11.04
[React] lazy(() => )  (0) 2024.10.24

mutate

mutate 를 기다리고 실행 될 동작이 없다

 

mutateAsyng

async/await 문을 썼고, mutateAsync를 기다리고 실행 될 동작이 있다

async/await 문을 썼지만, mutate 를 쓴다면 경고문이 뜬다.

 

 

차이점:

1️⃣ 반환값:

  • mutate: undefined
  • mutateAsync: Promise

2️⃣ 사용시점:

대부분 mutate함수로 충분하지만 비동기 작업의 결과를 명시적으로 처리할 때는 mutateAsync함수를 사용.

 

    const result = await apiPostMutate.mutateAsync(
      data.id,
    );

+ Recent posts