Говоря об оптимизации производительности React из реализации исходного кода Context

React.js
Говоря об оптимизации производительности React из реализации исходного кода Context

Изучив эту статью, вы получите:

  1. К пониманиюContextПринцип реализации

  2. Мастер на исходном уровнеReactкомпонентrenderтайминг, тем самым написав высокопроизводительныйReactкомпоненты

  3. Понимание на исходном уровнеshouldComponentUpdate,React.memo,PureComponentВнедрение других методов оптимизации производительности

Я постараюсь написать статью максимально понятно. Однако, чтобы полностью понять содержание статьи, вам необходимо иметь следующие предпосылки:

  1. FiberОбщий рабочий процесс архитектуры

  2. 优先级а также更新существуетReactЗначение в исходном коде

Если у вас нет необходимых знаний, вы можете сначала прочитать егоДемистификация технологии React.

Когда компонент отрисовывается

Contextреализация и компонентrenderтесно связаны. Прежде чем объяснять его реализацию, давайте сначала разберемсяrenderвремя.

другими словами,组件во сколькоrender?

Ответ на этот вопрос уже естьКогда рендерятся компоненты React?болтал. Резюмировать здесь:

существуетReact, всякий раз, когда срабатывает更新(например, вызовthis.setState,useState), создаст соответствующийfiberузел.

fiberУзлы связаны друг с другом, образуяFiberДерево.

Есть 2 способа созданияfiberузел:

  1. bailout, то есть повторно использовать компонент, соответствующий предыдущему обновлению компонентаfiberУзел как это обновлениеfiberузел.

  2. render, после того как алгоритм сравнения сгенерирует новыйfiberузел. компонентrender(НапримерClassComponentизrenderвызов метода,FunctionComponentвыполнение) происходит на этом шаге.

Студенты часто спрашивают:ReactКаждое обновление будет восстанавливатьFiberДерево, не будет ли спектакль плохим?

ReactПроизводительность действительно не очень. Но, как видите,FiberНе все компоненты будут сгенерированы во время генерации дереваrender, некоторые компоненты, удовлетворяющие условиям оптимизации, уйдутbailoutлогика.

Например, для следующей демонстрации:

function Son() {
  console.log('child render!');
  return <div>Son</div>;
}


function Parent(props) {
  const [count, setCount] = React.useState(0);

  return (
    <div onClick={() => {setCount(count + 1)}}>
      count:{count}
      {props.children}
    </div>
  );
}


function App() {
  return (
    <Parent>
      <Son/>
    </Parent>
  );
}

const rootEl = document.querySelector("#root");
ReactDOM.render(<App/>, rootEl);

Адрес онлайн-демонстрации

нажмитеParentкомпонентdivдочерний компонент, запускает обновление, ноchild render!и не будет печатать.

Это потому чтоSonкомпоненты войдутbailoutлогика.

Условия спасения

входитьbailoutлогика, потребностьв то же времясоблюдаются 4 условия:

  1. oldProps === newProps

То есть это обновлениеpropsравный последнему обновлениюprops.

Обратите внимание, здесьКонгруэнтное сравнение.

мы знаем компонентыrenderвернусьJSX,JSXдаReact.createElementсинтаксический сахар.

такrenderВозвращаемый результат на самом делеReact.createElementРезультат выполнения , то есть содержащийpropsсвойства объекта.

Даже если это обновление совпадает с последним обновлениемpropsВсе параметры не изменились, но это обновлениеReact.createElementРезультат выполнения - совершенно новыйpropsцитировать, такoldProps !== newProps.

  1. context valueбез изменений

мы знаем, что в настоящее времяReactВ версии и новая и старая существуют одновременноcontextЗдесь имеется в виду старая версияcontext.

  1. workInProgress.type === current.type

До и после обновленияfiber.typeбез изменений, напримерdivне сталp.

  1. !includesSomeLane(renderLanes, updateLanes) ?

токfiberСуществует ли на更新, если он существует, то更新из优先级Это то же самое, что и все это дерево?Fiberдерево планирования优先级Последовательный?

Если он непротиворечивый, значит, на компоненте есть обновление, нужно перейтиrenderлогика.

bailoutОптимизация не останавливается на достигнутом. если одинfiberВсе узлы в поддереве не обновляются, даже если все потомкиfiberУходитеbailoutЛогика по-прежнему имеет стоимость обхода.

Итак, вbailout, проверитfiberвсе потомкиfiberВыполняется ли условие 4 (это проверяет временную сложностьO(1)).

если все потомкиfiberНа этот раз обновление выполнять не нужно, тогдаbailoutвернется прямоnull. Все поддерево пропускается.

Не будетbailoutни будетrender, как будто его и не было. Соответствующий DOM не вносит никаких изменений.

Реализация старого Context API

Теперь у нас есть общее представлениеrenderвремя. С помощью этого понятия можно понятьContextКак был реализован API и почему он подвергся рефакторингу.

Посмотрим на заброшенный старыйContextРеализация API.

FiberПроцесс генерации дерева прерывается обходомрекурсия, поэтому он делится насдаватьа такжевернуть2 этапа.

ContextСоответствующие данные будут сохранены в стеке.

существуетсдаватьсцена,Contextпродолжайте нажимать на стек. такConcumerв состоянии пройтиContext栈найти соответствующийcontext value.

существуетвернутьсцена,ContextПродолжайте хлопать.

очень старыйContextПочему API устарел? потому что он не можетshouldComponentUpdateилиMemoи другие методы оптимизации производительности.

Реализация shouldComponentUpdate

Чтобы исследовать более глубокие причины, нам нужно понятьshouldComponentUpdateпринцип, именуемый в дальнейшемSCU.

использоватьSCUзаключается в сокращении ненужныхrender, другими словами: пустьrenderКомпоненты идутbailoutлогика.

только что мы представилиbailoutусловия, которые необходимо выполнить. ТакSCUКакое из этих четырех условий применяется?

Очевидно, первое:oldProps === newProps

когда используешьshouldComponentUpdate, этот компонентbailoutУсловия изменятся:

-- oldProps === newProps

++ SCU === false

Точно так же используйтеPureComponenetа такжеReact.memoчас,bailoutтакже меняются условия:

-- oldProps === newProps

++ 浅比较oldProps与newsProps相等

вернуться к старомуContextAPI.

Когда эти оптимизации производительности означают:

  • сделать компонент хитомbailoutлогика

  • В то же время, если поддерево компонента удовлетворяетbailoutусловие 4

затемfiberПоддерево больше не будет просматриваться и генерироваться.

Другими словами, не будет испытыватьContextукладка и поппинг.

В этом случае, даже еслиcontext valueИзменения, потомки компонентов обнаружить не могут.

Реализация нового контекстного API

знать старыйContextДефекты API, давайте посмотрим на новыеContextКак реализован API.

При прохождении:

ctx = React.createContext();

СоздайтеcontextПосле примера вам нужно использоватьProviderпоставкаvalue,использоватьConsumerилиuseContextподпискаvalue.

Такие как:

ctx = React.createContext();

const NumProvider = ({children}) => {
  const [num, add] = useState(0);

  return (
    <Ctx.Provider value={num}>
      <button onClick={() => add(num + 1)}>add</button>
      {children}
    </Ctx.Provider>
  )
}

использовать:

const Child = () => {
  const {num} = useContext(Ctx);
  return <p>{num}</p>
}

При обходе компонентов генерируются соответствующиеfiberпри переходе наCtx.Providerкомпоненты,Ctx.Providerбудет судить внутреннеcontext valueизменять.

еслиcontext valueРазнообразие,Ctx.ProviderВнутри выполняется нисходящий обход в глубину поддерева, чтобы найтиProviderдополнительныйConsumer.

В приведенном выше примере в конечном итоге будет найденоuseContext(Ctx)изChildкомпонент, соответствующийfiber,и дляfiberЗапустите обновление.

Обратите внимание, что реализация здесь довольно умная:

в общем更新Генерируется компонентом, вызывающим метод, запускающий обновление. такие как вышеNumProviderкомпонент, нажмитеbuttonпередачаaddБудет вызвать один раз更新.

вызывать更新Суть в том, чтобы позволить компонентам создавать соответствующиеfiberне удовлетвореныbailoutУсловие 4:

!includesSomeLane(renderLanes, updateLanes) ?

входитьrenderлогика.

это здесь,Ctx.Providerсерединаcontext valueРазнообразие,Ctx.Providerвниз, чтобы найти потреблениеcontext values компонентChild, для которогоfiberЗапустите обновление.

ноChildвести перепискуfiberУсловие 4 не выполняется.

Это решает старыеContextПроблемы с API:

из-заChildвести перепискуfiberУсловие 4 не выполняется, поэтому изCtx.ProviderприбытьChild, это поддерево не может удовлетворить:

Все узлы-потомки в поддереве удовлетворяют условию 4

Таким образом, даже если компонент входит в середине обходаbailoutлогика, тоже не возвращаетсяnull, то есть обход этого поддерева игнорироваться не будет.

Окончательный обход продолжается доChild, так как не удовлетворяет условию 4, то войдетrenderЛогика, вызовите соответствующую функцию компонента.

const Child = () => {
  const {num} = useContext(Ctx);
  return <p>{num}</p>
}

будет вызываться при вызове функцииuseContextотContextНайдите соответствующее обновление в стекеcontext valueи вернуться.

Суммировать

ReactОдин из ключей к производительности: сокращение ненужныхrender.

Как видно из вышеизложенного, суть в том, чтобы компонент выполнил 4 условия, чтобы войти вbailoutлогика.

а такжеContextСуть API в том, чтобы позволитьConsumerКомпонент не удовлетворяет условию 4.

Мы также знаем,ReactХотя каждый раз просматривается все дерево,bailoutлогика оптимизации, не все компоненты будутrender.

В крайнем случае пропускаются даже некоторые поддеревья (bailoutвернутьnull).