React 애플리케이션 렌더링 속도를 높이는 방법

Nov 27 2022
애플리케이션을 빠르게 제공하는 것은 쉽지만 빠른 애플리케이션을 제공하는 것은 훨씬 더 어렵습니다. 다음은 반응 애플리케이션이 구성 요소를 더 빠르게 렌더링하도록 만드는 몇 가지 팁입니다.

애플리케이션을 빠르게 제공하는 것은 쉽지만 빠른 애플리케이션을 제공하는 것은 훨씬 더 어렵습니다. 다음은 반응 애플리케이션이 구성 요소를 더 빠르게 렌더링하도록 만드는 몇 가지 팁입니다.

우리는 애플리케이션을 더 빨리 제공하려고 노력하면서 모범 사례에 초점을 맞추는 것을 종종 잊었습니다. 잠시 후 구성 요소를 생성하므로 렌더링할 필요가 없는 주요 위치를 입력하면 렌더링됩니다. 여기에 해야 할 일과 하지 말아야 할 일의 목록이 있습니다.

React.Memo 사용

우리는 일반적으로 구성 요소가 부모가 렌더링될 때마다 렌더링되는 것을 원하지 않습니다. 필요한 경우가 아니면 컴포넌트를 렌더링하지 않도록 React에 말할 수 있습니다.

// Wrong usage
// Without React.memo this component will get rendered each time 
// something causes its parent to render
const YourHeavyComponent = (name, title, onClick) => {
    // Render  something really heavy like whole page here
}

export default YourHeavyComponent
// Good usage
// This component will not render unless name, title or onClick changes
const YourHeavyComponent = (name, title, onClick) => {
    // Render  something really heavy like whole page here
}

export default React.memo(YourHeavyComponent)
// This component will not render unless title changes
const YourHeavyComponent = (name, title, onClick) => {
    // Render  something really heavy like whole page here
}

// This usage might better for some stations, use this second argument
// to specify which props  will cause render
export default React.memo(YourHeavyComponent, (prevProps, nextProps) => {
    return prevProps.title === nextProps.title
})

구성 요소당 4개 이상의 소품을 사용하지 마세요.

많은 props를 갖는 것은 항상 problamatic이며 각 props는 성능과 관련하여 고려해야 할 변수가 하나 더 있음을 의미하며 코드를 읽고 유지 관리하기가 더 어려워집니다. 10개의 소품으로 구성 요소를 만드는 대신 3~4개의 소품으로 3개의 작은 청크를 만듭니다.

// this component has too many filters
// it's not a good idea to add filters inside this component
// instead we need to create a seperate component to add filter inputs
// and we can also remove title from here
const List = (title, items, sortBy, keys, filters, onFilterChange, children, pagination) => {
    // Render  something really heavy like whole page here
}
// here userslist wont rendered unless we get a new list
// pagination payload won't cause rendering in filters
const [payload, setPayload] = useState()
const [list, setList] = useState([])
return <>
    <Filters onFilterChange={setPayload} filters={payload.filters} />
    <UserList items={list}/>
    <Pagination onPaginationChange={setPayload} pagination={payload.pagination} />
</>

무언가가 변경된 후에 계산될 변수가 있을 때 연결합니다. 계산에 시간이 걸리거나 계산 후에 배열이나 객체에 대해 다른 메모리 참조를 얻을 수 있기 때문에 유용합니다.

변수가 변경되어 자식에게 전달할 때 자식 구성 요소가 다시 렌더링되는 것을 잊지 마십시오.

// here we use useMemo for purely caching purposes, if we don't use useMemo
// each time this components rendered the calculation will have to re-run 
const heavyCalculated = useMemo(() => doSomeHeavyCalculation(variable1), [variable])
// an example of wrong usage
// each time this components get rendered this
// styles variable will point to different memory address
// and this will cause rerender of YourHeavyComponent
const styles = {
  container:{
    marginLeft: left,
    marginRight: right,
    marginTop: top,
    marginBottom: bottom,
  }
}
// correct usage of useMemo
// this will cache your value and its memory point won't change
// so even if this components gets rendered your YourHeavyComponent won't be rendered again
const styles = useMemo(() => ({
  container:{
    marginLeft: left,
    marginRight: right,
    marginTop: top,
    marginBottom: bottom,
  }
}), [left, right, top, bottom])

함수를 자식 구성 요소에 전달할 때 useCallback 후크를 사용해야 합니다. 구성 요소가 렌더링될 때마다 함수가 다른 메모리 지점을 가리킨다는 사실을 잊지 마십시오. 우리는 이전 콜백을 메모리에 유지해야 메인 구성 요소에서 무언가를 할 때마다 변경되지 않고 자식에서 렌더링이 발생하지 않습니다.

// wrong usage
// each time App components get rendered for some reason
// onClickCallback will be recreated and it will point to different memory address
// so it will cause YourHeavyComponent to re render
const onClickCallback = () => {
  // do some stuff
}

return <div>
  <YourHeavyComponent onClick={onClickCallback} />
</div>

// good usage of onClickCallback
// each time App components if your variable1 don't change
// onClickCallback will point to same memory point 
// so UourHeavyCallback won't render.
const onClickCallback = () => {
  // do some stuff
}

return <div>
  <YourHeavyComponent onClick={onClickCallback} />
</div>

우리는 이 후크를 자식에게 전달하고 인스턴스를 사용하는 데 익숙해졌지만 사용 가능한 사용 사례만 있는 것은 아닙니다. 다시 렌더링하지 않고 일종의 캐싱 및 상태 메커니즘으로 사용할 수 있습니다.

// in this example we look for useRef value instead of setting
// sent reference is being used for checking if we send this message before or not
// this could hold in the state but if we put into state it will cause render
// it into state, because we don't want to re-render after we set sent.current
const sent = useRef(false)
const postMessage = useCallback(() => {
  if (!sent.current) {
    // make your api call here
    sent.current = true
  }
}, [])

구성 요소를 렌더링할 때마다 해당 구성 요소 내부의 변수는 다른 메모리 주소를 가리킬 것입니다. 문자열 및 정수와 같은 기본 유형이 올 때 이것은 문제를 일으키지 않지만 배열, 객체 및 함수로 작업하는 경우 (실제로 각 기능은 후드 뒤의 객체이기 때문에). 몇 가지 예를 보자

const InlineTest = () => {
    // the usage below will cause YourHeavyComponent to render if
    // something causes InlineTest component to render
    return <YourHeavyComponent style={{
        marginLeft: 10,
        marginRight: 10
    }} />
}
const style = {
    marginLeft: 10,
    marginRight: 10
}

// We move styles outside of component
// this way style will point the same memory address regardless of 
// how many tames InlineTest gets rendered

const InlineTest = () => {
    // the usage below will cause YourHeavyComponent to render if
    // something causes InlineTest component to render
    return <YourHeavyComponent style={style} />
}

const InlineTest = () => {
    // the usage below will cause YourHeavyComponent to render if
    // something causes InlineTest component to render
    // because onClick will be assigned to a new function each time
    return <YourHeavyComponent onClick={() => {
        console.log('clicked');
    }} />;
}
import {useCallback} from "react";

const InlineTest = () => {
    // the usage below is correct way to do it
    // using useCallback will make sure onClick function
    // is the same between renders and it won't cause render in below component
    
    const onClick = useCallback(() => {
        console.log('clicked');
    }, [])
    return <YourHeavyComponent onClick={onClick} />;
}

리액트에서 폼을 다루는 것이 쉽다고 생각할 수도 있지만 그렇지 않다는 것을 보장하겠습니다. 양식이 복잡해지고 일부 유효성 검사 등을 수행해야 하는 경우 너무 많은 렌더링이 발생합니다. 좋은 라이브러리를 사용하는 것이 중요한 이유입니다. 지난 몇 년 동안 나는 많은 다른 라이브러리를 시도했지만 여기에서 언급할 가치가 있는 라이브러리는 하나만 있습니다. react-hook-form.

import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const onSubmit = data => console.log(data);


  return (
    /* "handleSubmit" will validate your inputs before invoking "onSubmit" */
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* register your input into the hook by invoking the "register" function */}
      <input defaultValue="test" {...register("example")} />
      
      {/* include validation with required or other standard HTML validation rules */}
      <input {...register("exampleRequired", { required: true })} />
      {/* errors will return when field validation fails  */}
      {errors.exampleRequired && <span>This field is required</span>}
      
      <input type="submit" />
    </form>
  );
}