[React] Memoization, Portal

Ina·2021년 2월 8일
1

TIL

목록 보기
12/20
post-thumbnail
post-custom-banner

Memoization?

  • 기존에 수행한 연산의 결과값을 저장해두고 동일한 입력이 들어오면 재활용하는 기법.
  • Memoized된 내용을 재사용하여 렌더할 시, 가상 DOM에서 바뀐 부분을 확인하지 않아 성능이 향상됨.

1. Redux - useSelector

💊 useSelector로 Redux를 최적화하는 방법

1. useSelector 를 여러번 사용

store에서 객체를 통째로 불러오는 대신, 필요한 값들을 쪼개어서 useSelector로 선언해줌.

const number = useSelector(state => state.counter.**number**);
const diff = useSelector(state => state.counter.**diff**);

→ number 또는 diff 가 바뀌었을 때만 리렌더링됨.

2. shallowEqual 사용

shallowEqual은 react-redux에 내장되어있는 함수로, 객체 안의 가장 겉에 있는 값들을 비교해줌.

const { number, diff } = useSelector(
    state => ({
      number: state.counter.number,
      diff: state.counter.diff
    }),
    shallowEqual
  );

3. equality function 사용

: useSelector 두번째 인자에 equality function을 직접 작성하여 추가

const result = useSelector(functionA, equalityFn)

참고자료

8. useSelector 최적화

React 에서 useSelector 최적화 하는 3가지 방법.


2. React.memo

컴포넌트를 React.memo()로 래핑시, 렌더링 결과를 Memoizing 하고 다음 렌더링이 일어날 때 props가 일치한다면, React는 Memoizing된 내용을 재사용함.

  • shallow comparison of props

(🙆‍♀️) When to Use

  1. Pure Functional Component에서 (클래스형 컴포넌트 사용 X)
  2. Rendering이 자주 일어날 경우
  3. 동일한 props ⇒ 동일한 렌더링 결과를 제공할 경우
  4. UI element의 양이 많은 컴포넌트의 경우

예시

import React from 'react';

const CreateUser = ({ **username, email, onChange, onCreate** }) => {
  return (
    <div>
      <input
        name="username"
        placeholder="계정명"
        onChange={onChange}
        value={username}
      />
      <input
        name="email"
        placeholder="이메일"
        onChange={onChange}
        value={email}
      />
      <button onClick={onCreate}>등록</button>
    </div>
  );
};

export default **React.memo(CreateUser);**

참고 자료


3. useMemo

  • 사용방법을 제외하고는 React.memo와 매우 흡사함

    React.memo가 component의 결과 값을 memoizing하여 불필요한 re-rendering을 관리한다면, useMemo함수의 결과 값을 memoizing하여 불필요한 연산을 관리함.

예시

const computeExpensiveValue(a, b) = 비싼 함수 계산;
const memoizedValue = **useMemo(**() => computeExpensiveValue(a, b), **[a, b**]**);**
  • useMemo의 [dependency]가 변경되었을 때에만 메모이제이션된 값을 다시 계산함)
// SLOW
const sortedWords = ~~sortWords~~(); 

//FAST
const sortedWords = useMemo(**sortWords**, [words]); 

4. useCallback

  • useMemo특정 결과값을 재사용 할 때 사용하는 반면, useCallback특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용
const App =()=>{

//함수 선언
	const onToggle = **useCallback**(id => {
	  setUsers(
	    users.map(user =>
	      user.id === id ? { ...user, active: !user.active } : user
	    )
	  );
	}, [users]);

....

return (
//prop으로 함수를 pass down
	<CreateUser onToggle={**onToggle**} />
	)
}

주의점 : 메모제이션용 메모리가 추가로 필요하므로, 불필요한 props 비교 절약을 위해서 useCallback, useMemo, React.memo 는 컴포넌트의 성능을 실제로 개선할 수 있는 상황에서만 사용

👉 성능확인은 Dev tools Profiling 탭으로 꼭 확인!


5. Portals

: 부모 컴포넌트 바깥에 있는 DOM 노드로 자식을 렌더링할 수 있게 해주는 기능.

(UI 를 어디에 렌더링 시킬지 DOM 을 사전에 선택하여 부모 컴포넌트의 바깥에 렌더링 할 수 있게 해주는 기능)

ReactDOM.createPortal(**child-렌더링 대상**, **container**-**DOM 엘리먼트**)
  • 존재 위치는 부모 바깥이지만! 모든 다른 면에서 일반적인 React 자식처럼 동작(ex. context, props, 이벤트 버블링)

    → why? DOM 트리 위치는 바뀌지만, portal은 여전히 React 트리에 존재하기 때문.

예시

모달, 호버카드, 툴팁과 같은 경우에 주로 사용됨.

  • 부모 컴포넌트에 overflow: hidden이나 z-index가 있는 경우 주로 사용.

https://codepen.io/gaearon/pen/yzMaBd

예시

  • 부모 컴포넌트
import PortalExample from "./Portal";
import React, { useRef } from "react";

function App() {
  const modalRef = useRef();
  const openModal = () => {
    // 자식에서 정의한 함수를 trigger
    modalRef.current.**openModal**();
  };

  return (
    <div className="App">
      <**PortalExample** **ref={modalRef}**>
        <h1>Modal Header</h1>
        <p> lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum </p>
      </**PortalExample**>
      <button onClick={openModal}>Open Portal Modal</button>
    </div>
  );
}

export default App;
  • 자식 컴포넌트
import React, { useState, forwardRef, useImperativeHandle } from "react";
import ReactDOM from "react-dom"; //

//ref 부모로부터 전달받기 위해 두번째 인자로 ref를 넣어줌.
const PortalExample = **forwardRef**((props, ref) => {
  const [display, setDisplay] = useState(true);

  //useImperativeHandle을 통해 App.js(부모) 에서 아래 함수 사용가능
  **useImperativeHandle**(ref, () => {
    return {
      openModal: () => open(),
      close: () => close(),
    };
  });

	//전달할 함수
  const open = () => {
    setDisplay(true);
  };
  const close = () => {
    setDisplay(false);
  };

  return (
    display &&
    ReactDOM.**createPortal**(
      <Portal>
        <div className="modal-backdrop" onClick={close}></div>
        <div className="modal-box">{props.children}</div>
      </Portal>,
      document.getElementById("modal-root")
    )
  );
});

const Portal = styled.div`
  position: fixed;
 ....
  .modal-backdrop {
    position: fixed;
    top: 0;
	...
  }
  .modal-box {
    position: relative;
    top: 50%;
	 ....
  }
`;
export default PortalExample;

참고자료


profile
프론트엔드 개발자. 기록하기, 요가, 등산
post-custom-banner

0개의 댓글