Как ускорить рендеринг приложения 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 реквизитов на компонент.

Наличие большого количества реквизитов всегда проблематично, каждый реквизит означает еще одну переменную, которую следует учитывать, когда дело доходит до производительности, и это затруднит чтение и поддержку вашего кода. Вместо создания компонентов с 10 реквизитами создайте 3 меньших фрагмента с 3–4 реквизитами.

// 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 легко, но позвольте мне гарантировать вам, что это не так. Когда ваши формы усложняются и вам нужно выполнить некоторые проверки и т. д., вы вызовете слишком много рендеринга. Вот почему важно использовать хорошую библиотеку. За последние пару лет я пробовал много разных библиотек, но здесь стоит упомянуть только одну; реагировать-крюк-форма.

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>
  );
}