programing

리액트 메모는 언제 사용하면 안 되나요?

starjava 2023. 3. 28. 20:33
반응형

리액트 메모는 언제 사용하면 안 되나요?

리액트랑 장난치고 있어 16.6.0최근 리액트 메모의 아이디어는 마음에 듭니다만, 실장에 최적인 시나리오에 대해서는 아무것도 찾을 수 없었습니다.

React 문서(https://reactjs.org/docs/react-api.html#reactmemo)에서는 단순히 모든 기능 컴포넌트에 정보를 보내는 것으로는 아무런 영향이 없는 것 같습니다.

재렌더가 필요한지 여부를 파악하기 위해 얄팍한 비교를 하기 때문에 성능에 부정적인 영향을 미치는 상황은 발생하지 않습니까?

다음과 같은 상황은 구현하기에 명백한 선택인 것 같습니다.

// NameComponent.js
import React from "react";
const NameComponent = ({ name }) => <div>{name}</div>;
export default React.memo(NameComponent);

// CountComponent.js
import React from "react";
const CountComponent = ({ count }) => <div>{count}</div>;
export default CountComponent;

// App.js
import React from "react";
import NameComponent from "./NameComponent";
import CountComponent from "./CountComponent";

class App extends Component {
  state = {
    name: "Keith",
    count: 0
  };

  handleClick = e => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <NameComponent name={this.state.name} />
        <CountComponent count={this.state.count} />
        <button onClick={this.handleClick}>Add Count</button>
      </div>
    );
  }
}

★★★★★★★★★★★★★★★★★★name이 문맥에서는 변경되지 않습니다.메모화하는 것은 타당합니다.

하지만 소품들이 자주 바뀌는 상황은 어떨까요?
을 하나 더 하면 어떻게 요?따라서 쌈을 싸는 요?CountComponent메모에 의하면, 이 컴포넌트는 자주 갱신되도록 설계되어 있는데도 말이죠?

모든 것이 순수하다면 리액트 메모로 기능 컴포넌트를 포장하지 않는 경우가 있습니까?

써야 요.React.memo그대로 하는 것은 한 더 비싸기 입니다.props (프로퍼티)

아무 를 에 사용하세요.React.memoReact.memo컴포넌트의 되어 있을 (으로 비교하기 된 속성을 ) 。 (개체를 표면적으로 비교하고 컴포넌트 내의 하위 객체의 중첩된 속성을 사용하고 있을 가능성이 있기 때문에) = )

이상입니다. 이것이 React가 자동으로 메모를 사용하지 않는 유일한 이유입니다.=)

버전 하위 .0.0을 만들 수 .React.memo 및 하는 기능을 예: "이 동작을 취소하는 기능").React.deepProps=

이론가들의 말을 듣지 마세요=) 규칙은 간단합니다.

항상 하는 것이 전화하는 .그렇지 않으면 항상 사용합니다.두 개체를 비교하는 것이 통화하는 것보다 항상 저렴합니다.React.createElement()2개의 트리의 비교, 파이버 노드 작성 등입니다.

이론가들은 자신이 모르는 것에 대해 이야기하고, 반응 코드를 분석하지 않으며, FRP를 이해하지 못하며, 자신이 무엇을 조언하는지 이해하지 못합니다 =)

. 가 P.S.를 사용하고 children 침침,,React.memo된다children는 항상 어레이를 .prop은 새로운 어레이를 만듭니다. 이 는 신경 않는 것이 또, 이러한 도, 이 컴포넌트도, 이 컴포넌트도, 이 컴포넌트도, 이 컴포넌트도, 이 컴포넌트도, 이 컴포넌트도, 이 컴포넌트인, 이 컴포넌트인, 이 컴포넌트인, 이 컴포넌트인, 이 컴포넌트인, 이 컴포넌트인, 이 인, 이 컴포넌트인, 이 컴포넌트인, 이 컴포넌트인, 이 컴포넌트인, 이 컴포넌트,React.memo이치노

는 'Resact Components'를 구현합니다.shouldComponentUpdate()확장 )React.Component참말(「」를 통해)React.memo 또는 컴포넌트의 React.PureComponent는 'class components'의 입니다.shouldComponentUpdate()method - 상태와 소품을 얄팍하게 비교합니다.

컴포넌트의 라이프 사이클 방법에 관한 문서를 참조하면,shouldComponentUpdate()렌더링 전에 항상 호출됩니다.즉, 컴포넌트를 메모하면 모든 업데이트에 이러한 얕은 비교가 추가됩니다.

이 점을 고려하여 컴포넌트를 메모하는 것은 퍼포먼스에 영향을 미칩니다.이러한 영향의 크기는 어플리케이션의 프로파일링과 메모가 있는 경우와 없는 경우의 동작 여부를 판단함으로써 판단해야 합니다.

해야 할 안 될 때 하지 않지만, 우선으로 할 것인지 되어야 한다고 합니다.shouldComponentUpdate(): 권장 프로파일링 툴을 사용하여 성능 문제를 찾아 컴포넌트를 최적화할 필요가 있는지 여부를 확인합니다.

퍼포먼스에 악영향을 미치는 상황은 없을까요?

요소를 네로 수 있습니다.React.memo.

그것은 많은 경우에 필요하지 않다.퍼포먼스에 중요한 컴포넌트를 사용하여 시험해 보려면 먼저 몇 가지 측정을 수행하고 메모화를 추가한 후 다시 측정하여 복잡도를 높일 가치가 있는지 확인합니다.

React.memo

메모된 구성요소는 뉴스 소품과 오래된 구성요소를 비교하여 각 렌더링 사이클을 재렌더할지 여부를 결정합니다.
플레인 컴포넌트는 상관하지 않고 부모에서 소품/상태 변경 후 렌더링합니다.

에서 호출된 React 구현을 살펴봅니다.

하지 않을 React memo

★★★★★★★★★★★★★★★★★★★★★★★★★★을 미치는 일React.memo★★★★★★★★★★★★★★★★★★:

  1. 어쨌든 바뀐 소품으로 재장착하는 경우가 많은 컴포넌트
  2. 컴포넌트는 재설치 비용이 저렴합니다.
  3. 비교 기능은 실행하는 데 비용이 많이 든다

1: 이 : 1 : 우 、React.memo재검출을 막을 수는 없지만 추가 계산을 수행해야 했습니다.
광고 2: 추가 비교 비용은 렌더링, 조정, DOM 변경 및 부작용 비용 측면에서 "단순한" 컴포넌트에는 가치가 없습니다.
광고 3: 소품이 많을수록 계산도 많아집니다.또한 보다 복잡한 사용자 정의 비교기를 전달할 수도 있습니다.

할 때React.memo

소품만 체크하고 컨텍스트 변경이나 상태 변경은 체크하지 않습니다. React.memo 컴포넌트가 포함되어 있는 에도 수 없습니다.childrenuseMemo할 수 있다memo예를 들어 다음과 같습니다.

// inside React.memo component
const ctxVal = useContext(MyContext); // context change normally trigger re-render
return useMemo(() => <Child />, [customDep]) // prevent re-render of children

질문은 React GitHub 문제 추적기에 마커릭슨의 답변이 있습니다.여기 대답보다 엄지손가락이 훨씬 많네요.

합니다.React.memo shouldComponentUpdate ★★★★★★★★★★★★★★★★★」PureComponent).또한 컴포넌트가 올바르게 메모되지 않는 시나리오가 있습니다(특히 컴포넌트가 다음 컴포함).props.children★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★실제 운영 모드에서 앱이 어떻게 동작하는지 확인하고, React의 프로파일링 빌드 및 DevTools 프로파일러를 사용하여 병목 현상이 어디에 있는지 확인하고, 이러한 도구를 전략적으로 사용하여 이러한 최적화의 이점을 실제로 얻을 수 있는 구성 요소 트리의 일부를 최적화하십시오.

자주 변경될 가능성이 있는 데이터에 대해서는 메모화를 사용하지 않는 것이 좋습니다.블로그에서 설명한 바와 같이 이러한 유형의 데이터에 따라 달라지는 콜백도 포함됩니다.예를 들어 다음과 같은 기능이 있습니다.

<Foo onClick={() => handle(visitCount)}/>

나는 이 간단한 읽기를 정말 즐겼다.예시는 훌륭합니다.https://dmitripavlutin.com/use-react-memo-wisely/

간단한 답은 React.memo가 기능 컴포넌트에 무엇을 반응시키느냐입니다.Pure Component는 클래스 컴포넌트에 적용됩니다.그런 의미에서 메모를 사용하면 해당 기능 컴포넌트에 대한 소품이 변경되었는지 여부를 평가하며, 변경되면 기능 반환이 실행되며, 그렇지 않으면 컴포넌트가 다시 렌더링되지 않습니다.

import React, { memo } from 'react';

const Component = () => {
  debugger;
  return (
    <div>Hello World!</div>
  );
};

const MemoComponent = memo(() => {
  debugger;
  return (
    <div>Hello World!</div>
  );
});

「 」를 사용하고 Component업데이트되는 컨테이너의 하위 구성 요소로, 상위 구성 요소가 업데이트될 때마다 다시 업데이트됩니다(매번 트리거됨).에 '보다 낫다'를 MemoComponent재실행되지 않습니다(실행은 첫 번째 렌더링에서만 트리거됩니다).

이 예에서는 기능 컴포넌트에 소품이 없기 때문에 소품이 있는 경우 소품이 변경되었을 경우에만 소품이 변경됩니다.

구성 가능한 내장형 테스트 케이스

테스트 케이스를 몇 가지 파라미터로 설정하여 동일한 컴포넌트의 메모 버전과 비메모 버전 모두에 대해 평균 렌더 시간을 측정했습니다.아래의 코드 스니펫을 실행하여 시험해 볼 수 있습니다.이는 결정적인 해답이 아니라 다양한 상황이 성능에 어떻게 영향을 미치는지 보여주는 데 도움이 됩니다.

메모 포함 확인란만 변경하면 평균 시간이 유지되므로 두 항목을 비교할 수 있습니다.그 외의 설정을 변경하면, 카운터가 리셋 됩니다.

입력 요소에 의해 소량의 일정한 시간이 추가되지만 상당히 안정적이며 파라미터의 상대적 영향을 관찰하는 데 방해가 되지 않습니다.좀 더 신뢰할 수 있고 정확하게 할 수 있다면 얼마든지 이 답변을 조정해 주세요.

메모 버전의 경우 히트 및 미스 평균 시간도 별도로 추적합니다.히트는 메모가 렌더링을 건너뛸 수 있음을 의미합니다.

메모 구성요소의 실행 빈도를 설정할 수 있습니다(기본값: 10회 렌더링).

마지막으로 차이가 감지되는 즉시 변화 감지 알고리즘이 정지한다고 가정하고, 변경되는 소품 양을 줄이기 위해 파라미터를 추가했습니다.하나의 소품만 바꾸려면 최대 100%까지 올리거나 모든 소품을 바꾸려면 0%(기본값)까지 올릴 수 있습니다.마지막 소품은 항상 바뀐다.지금까지 이것은 어떠한 측정 가능한 변화도 초래하지 않은 것으로 보인다.이 설정은 무시해도 됩니다.

한계

소품은 단순한 같은 크기의 끈으로 되어 있기 때문에 실제보다 비교 조작이 용이할 것입니다.

일부 타이밍 정보는 너무 많이 "방해"하지 않도록 효과적으로 쓰여져 있습니다.그 결과 덮어쓸 때까지 오래된 시간 정보가 표시됩니다.

첫 번째 렌더링이 상당히 느리므로 나중에 몇 가지 렌더링을 실행하여 취소해야 합니다.

정확성.

이는 정확한 측정을 의미하는 것이 아니라 유사한 상황에서 두 변종의 동작을 비교하는 방법입니다.

App 컴포넌트는 고가의 로직을 가지고 있지만, 시간은 그 후에 측정됩니다.저는 타이머를 정지시키는 효과를 사용했는데, 이는 렌더링을 마친 후 리액트가 가장 먼저 하는 일이기 때문에 이 목적을 위해 충분히 근접해야 합니다.

나의 관찰

제가 실시한 테스트 결과는 현재 가장 많이 투표된 답을 확인시켜 줍니다.추가 체크의 영향은 극히 미미하며, 터무니없이 많은 소품 및/또는 현이 큰 소품도 마찬가지입니다.

문자 그대로 매번 변경되는 구성요소의 메모(변경 간격 1)를 사용하는 경우, 일정 정도만 느려질 수 있습니다.그래서 당신이 그것을 통해 이익을 얻기 시작하는 전환점이 있습니다.

다만, 2분의 1의 확률로 렌더링 할 필요가 있어도, 메모는 양호하게 나왔습니다.

사실 메모의 영향은 매우 작기 때문에, 많은, 큰 소품이라도 관찰하기 어렵습니다.

반면 렌더링을 건너뛰는 데 걸리는 시간은 단순한 컴포넌트라도 눈에 띄게 늘어납니다.

다른 매개 변수를 사용하여 직접 사용해 볼 수 있습니다. 수표는 항상 회피하는 작업보다 훨씬 저렴합니다.메모를 사용하지 않는 것이 평균적으로 빠른 구성을 찾을 수 없었습니다.아니면 제가 그랬어요?

memo매우 큰 문자열에서는 속도가 느립니다.*

*컴포넌트가 큰 문자열을 사용하지 않는 경우.

답변을 제출하려고 할 때 다시 100만 글자로 문자열을 늘렸습니다.으로 ★★★★★★★★★★★★★★★★★★★★.memo컴포넌트는 그렇지 않은 반면, 고전하고 있었습니다.

여기에 이미지 설명 입력

10개 중 1개만 "실수"해도 평균 속도는 확실히 느립니다.

단, 그 크기의 현을 소품으로 전달한다면 퍼포먼스에 문제가 1개 이상 있을 수 있으며, 이것이 가장 큰 문제는 아닐 것입니다.

또, 드물게 패스할 필요가 있는 경우는 반드시 컴포넌트에서 사용합니다.그러면 속도가 몇 배 느려질 수 있습니다.현재 테스트 코드는 이러한 큰 값을 사용하지 않습니다.

let renderTimes = 0;
let compRenderTimes = 0;

function randomString(length) {
    var result           = '';
    var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for ( var i = 0; i < length; i++ ) {
      result += characters.charAt(Math.floor(Math.random() *  charactersLength));
   }
   return result;
}

const {useState, useEffect, memo} = React;

let lastCompRenderStart;

function Comp (props) {
  compRenderTimes++;
  lastCompRenderStart = performance.now();
  
  // Expensive task.
  //console.log(props);
  /*Object.entries(props).forEach(([k,v]) => {
    if (`${k}${v}` === '4abc') {
      alert('What a coincidence!');
    }
  });*/
  
  useEffect(()=>{
    const duration = performance.now() - lastCompRenderStart;
    document.getElementById('lastCompRenderTime').innerHTML = duration.toFixed(2);
    document.getElementById('nCompRenders').innerHTML = compRenderTimes;
  });

  return <p className="test">Testing {Object.keys(props).length} props, last rendered {performance.now()}</p>;
};

const MemoComp = memo(Comp);

let lastProps = {};
let lastRenderStart;
let lastWasHit = false;
let currentTotal = 0;
let memoRenders = 0;
let memoHitTotal = 0;
let memoHitRenders = 0;
let memoMissTotal = 0;
let memoMissRenders = 0;
let nomemoRenders = 0;
let nomemoTotal = 0;

function App() {
  renderTimes++;
  const [,refresh] = useState();
  const [propAmount, setPropAmount] = useState(10);
  const [propSize, setPropSize] = useState(10);
  const [changeInterval, setChangeInterval] = useState(10);
  const [changedPropOffset, setChangedPropOffset] = useState(0);
  const [withMemo, setWithMemo] = useState(true);
  
  useEffect(()=>{
    
    renderTimes = 1;
    compRenderTimes = 1;
    currentTotal = 0;
    memoRenders = 0;
    memoHitTotal = 0;
    memoHitRenders = 0;
    memoMissTotal = 0;
    memoMissRenders = 0;
    nomemoRenders = 0;
    nomemoTotal = 0;
  }, [propAmount, propSize, changeInterval, changedPropOffset]);
  
  let props = {};
  lastWasHit = renderTimes !== 1 && renderTimes % changeInterval !== 0;
  if (lastWasHit) {
    // Reuse last props;
    props = lastProps;
  } else {
    // Generate random new values after offset.
    for (let i = 0; i < propAmount; i++) {
      if (!!lastProps[i] && (i * 100 / propAmount < changedPropOffset) && i < propAmount - 1) {
        props[i] = lastProps[i];
      } else {
        props[i] = randomString(propSize);
      }
    }
    lastProps = props;
  }

  useEffect(()=>{
    const duration = performance.now() - lastRenderStart;
    document.getElementById('lastRenderTime').innerHTML = duration.toFixed(2);
    
    if (!withMemo) {
      nomemoRenders++;
      nomemoTotal += duration;
      document.getElementById('no-memo-renders').innerHTML = nomemoRenders;
      document.getElementById('average-no-memo').innerHTML = (nomemoTotal / nomemoRenders).toFixed(2);

    } else {
      memoRenders++;
      currentTotal += duration;
      document.getElementById('memo-renders').innerHTML = memoRenders;
      document.getElementById('average').innerHTML = (currentTotal / memoRenders).toFixed(2);

      if (lastWasHit) {
        memoHitRenders++;
        memoHitTotal += duration;
        document.getElementById('average-memo-hit').innerHTML = (memoHitTotal / memoHitRenders).toFixed(2);
      } else {
        memoMissRenders++;
        document.getElementById('memo-renders-miss').innerHTML = memoMissRenders;

        memoMissTotal += duration;
        document.getElementById('average-memo-miss').innerHTML = (memoMissTotal / memoMissRenders).toFixed(2);
      }
    }
  });

  const ActualComp = withMemo ? MemoComp : Comp;
  
  // This should give us the time needed for rendering the rest.
  // I assume the settings inputs have has a more or less constant small impact on performance, at least while they're not being changed.
  lastRenderStart = performance.now();

  return <div>
    <button onClick={() => refresh(renderTimes)} title='Trigger a render of App component'>render</button>
    <input type='checkbox' onChange={event=>setWithMemo(!withMemo)} checked={withMemo}/>
    <label  onClick={event=>setWithMemo(!withMemo)}>
     with memo - 
    </label>
    - Prop amount: <input type='number' title='How many props are passed to memoed component' value={propAmount} onChange={event=>setPropAmount(event.target.value)}/>
    Prop size: <input type='number' title='How many random characters in prop value string?' value={propSize} onChange={event=>setPropSize(event.target.value)}/><br/>
    Change interval: <input type='number' title='Change memoized component props every X renders.' value={changeInterval} onChange={event=>setChangeInterval(event.target.value)}/>
    Changed prop offset <input type='number' min={0} max={100} step={10} title='Leave the first X percent of the props unchanged. To test if props comparison algorithm is affected by how fast it can find a difference. The last prop is always changed.' value={changedPropOffset} onChange={event=>setChangedPropOffset(event.target.value)}/>
    <ActualComp {...props} />
  </div>;
};


ReactDOM.render(<App/>, document.getElementById('root'));
#lastRenderTime {
  background: yellow;
}

#lastCompRenderTime {
  background: lightblue;
}

.test {
  background: lightgrey;
  border-radius: 4px;
}

td {
  border: 1px solid lightgrey;
  padding: 4px;
 }
 
 input[type=number] {
     max-width: 72px;
 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

<div id="root"></div>

<table>
<th>
  <td>N renders</td>
  <td>Average duration</td>
  <td>Memo (hit) duration</td>
  <td>Memo (miss) duration</td>
</th>
<tr>
<tr>
<td>No memo</td>
<td><span id="no-memo-renders"></span></td>
<td><span id="average-no-memo"></span></td>
</tr>


<tr>
<td>memo</td>
<td><span id="memo-renders"></span>, <span id="memo-renders-miss"></span> miss</td>
<td><span id="average"></span></td>
<td><span id="average-memo-hit"></span></td>
<td><span id="average-memo-miss"></span></td>
</tr>
</table>
=====
<table>
  <tr>
<td>Last rendered App</td>
<td><span id="lastRenderTime"></span></td>
</tr>
<tr>
<td>Component renders</td>
<td><span id="nCompRenders"></span></td>
</tr>
<tr>
<td>Last rendered Component</td>
<td><span id="lastCompRenderTime"></span></td>
</tr>
</table>

useMemo에 전달된 함수는 렌더링 중에 실행됩니다.렌더링 중에는 보통 하지 않는 작업은 하지 마십시오.예를 들어, 부작용은 useEffect에 속하며 useMemo에 속하지 않습니다.

useMemo는 시멘틱 보증이 아닌 퍼포먼스 최적화로 사용할 수 있습니다.향후 React는 이전에 메모된 값을 "잊어버리고" 다음 렌더링 시 재계산하도록 선택할 수 있습니다(예: 오프스크린 컴포넌트용 메모리 확보).useMemo 없이 동작하도록 코드를 작성한 후 성능을 최적화하기 위해 코드를 추가합니다(값을 재계산할 필요가 없는 드문 경우 참조를 천천히 초기화할 수 있습니다).)"

https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

언급URL : https://stackoverflow.com/questions/53074551/when-should-you-not-use-react-memo

반응형