«React Advanced» рассказывает о прошлой и настоящей жизни асинхронных компонентов React.

JavaScript React.js
«React Advanced» рассказывает о прошлой и настоящей жизни асинхронных компонентов React.

Введение

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

Старые правила, мы все еще начинаем сегодняшнее мышление с вопросов? (Самопроверка мастерства)

  • 1 Что такое асинхронные компоненты React и какие проблемы они решают?
  • 2 Как componentDidCatch отлавливает ошибки этапа рендеринга и компенсирует их.
  • 3 Как React.lazy обеспечивает динамическую загрузку?
  • 4 Почему React.lazy находится внутри Supsonse.
  • 5 Каков принцип Supsonse?

B133F89642196BA0F81EBE54E8034659.jpg

(исходная версия v16.13.1)

Два первых знакомства: асинхронные компоненты

1 Что такое асинхронный компонент

Давайте сначала подумаем о текущем приложении React, используяajaxилиfetchСценарий взаимодействия с данными в основном такой, в компоненте классаcomponentDidMountи функциональные компонентыeffectВзаимодействие с данными выполняется в пользовательском интерфейсе, и после получения данных отображается представление пользовательского интерфейса. Так может ли рендеринг компонента ждать завершения асинхронного запроса данных, а затем рендерить его после получения данных?

Для вышеописанной ситуации первое ощущение невероятное: что, если рендеринг можно прервать и дождаться рендеринга после запроса данных? То естьSusponse, невозможные вещи, упомянутые выше,SusponseТак и было, добавил React 16.6,SusponseЗаставьте компонент «ждать» асинхронной операции, пока асинхронная операция не завершится для рендеринга.

Традиционный режим: визуализация компонента -> запрос данных -> повторная визуализация компонента.
Асинхронный режим: запрос данных -> компонент рендеринга.

2 Включите режим ожидания

Взаимодействие данных в традиционном режиме должно выглядеть так.

function Index(){
    const [ userInfo , setUserInfo ] = React.useState(0)
    React.useEffect(()=>{
       /* 请求数据交互 */
       getUserInfo().then(res=>{
           setUserInfo(res)
       })
    },[])
    return <div>
        <h1>{userInfo.name}</h1>;
    </div>
}
export default function Home(){
    return <div>
        <Index />
    </div>
}
  • Процесс: монтирование инициализации страницы,useEffectВнутренние данные запроса, черезuseStateИзмените данные и дважды обновите данные рендеринга компонента.

тогда, если вы используетеSusponseАсинхронный режим можно записать так:

function FutureAsyncComponent (){
    const userInfo = getUserInfo()
    return <div>
        <h1>{userInfo.name}</h1>; 
    </div>
}

/* 未来的异步模式 */
export default function Home(){
   return <div>
      <React.Suspense  fallback={ <div  > loading... </div> } >
          <FutureAsyncComponent/>
      </React.Suspense>
   </div>
}

Когда данные не были загружены, они будут отображатьсяSuspenseсерединаfallbackконтент, чтобы компенсировать эффект перехода в данных запроса.Хотя этот режим официально не доступен в текущей версии, React будет поддерживать эту форму кода в будущем.

Три возможности отслеживания: от componentDidCatch до Suspense

Что касается того, как Suspense делает невозможное возможным? Это изcomponentDidCatchКстати говоря, когда React запустил v16, была добавлена ​​новая функция жизненного цикла.componentDidCatch. Если компонент определяетcomponentDidCatch, то когда все дочерние компоненты в этом компоненте выдают исключение во время рендеринга, этоcomponentDidCatchбудет вызвана функция.

использование компонентаDidCatch

componentDidCatch может ловить исключения, он принимает два параметра:

  • 1 ошибка - выброшена ошибка.
  • 2 info — объект с ключом componentStack, который содержит информацию стека об ошибках, вызванных компонентом.

Давайте смоделируем ситуацию, когда дочерний компонент не отрисовывается:

/* 正常组件,可以渲染 */
function Children(){
  return <div> hello ,let us learn React </div>
}
 /* 非React组件,将无法正常渲染 */
function Children1(){
  return 
}
export default class Index extends React.Component{
  componentDidCatch(error,info){
      console.log(error,info)
  }
  render(){
    return <div>
      <Children />
      <Children1/>
    </div>
  }
}

Как и выше, мы имитируем сценарий сбоя рендеринга и визуализируем компонент Children1, не являющийся React, как обычный компонент React, чтобы на этапе рендеринга сообщалось об ошибке и отображалось сообщение об ошибке.componentDidCatchЗахвачено, сообщение об ошибке выглядит следующим образом:

1.jpg

Для вышеизложенного, если при рендеринге дочернего компонента возникает ошибка, это приведет к сбою рендеринга всего компонента и невозможности его отображения.Childrenтакже будет замешан, в это время нам нужноcomponentDidCatchсделать некоторые средства, как мы нашлиcomponentDidCatchне получится, могу датьChildren1Добавьте элемент управления состоянием, который прекращает работу в случае сбоя рендеринга.Children1оказывать.

function ErroMessage(){
  return <div>渲染出现错误~</div>
}

export default class Index extends React.Component{
  state={ errorRender:false }
  componentDidCatch(error,info){
      /* 补救措施 */
      this.setState({
        errorRender:true
      })
  }
  render(){
    return <div>
      <Children />
      { this.state.errorRender ? <ErroMessage/> : <Children1/>  }
    </div>
  }
}

2.jpg

Если возникает ошибка, перейдитеsetStateПовторно визуализируйте и удалите неисправный компонент, чтобы компонент мог нормально визуализироваться, не затрагивая монтирование дочерних элементов.componentDidCatchС одной стороны, ошибки, возникающие на этапе рендеринга, могут быть зафиксированы, а с другой стороны, побочные эффекты могут быть выполнены в течение жизненного цикла для восстановления потерь, вызванных исключениями рендеринга.

принцип componentDidCatch

componentDidCatchПринцип должен быть хорошо понят, и внутреннее может быть пропущено черезtry{}catch(error){}для обнаружения ошибок рендеринга и обработки ошибок рендеринга.

try {
  //尝试渲染子组件
} catch (error) {
  // 出现错误,componentDidCatch被调用,
}

Можно ли перенести идею componentDidCatch в Suspense?

Затем вернемся к нашим асинхронным компонентам.Если асинхронный код выполняется синхронно, то он точно не будет нормально отображаться.Нам все равно нужно сначала запросить данные.ЖдатьКогда данные возвращаются, а затем используют возвращенные данные для рендеринга, то основное внимание уделяется этомуЖдатьВорд, как остановить синхронный рендеринг и дождаться асинхронного запроса данных? Можно ли генерировать исключения? Исключения могут остановить выполнение кода и, конечно же, рендеринг.

SuspenseЭто рендеринг, который прерывается выдачей исключения.Suspenseнужен одинcreateFetcherФункция инкапсулирует асинхронную операцию при попыткеcreateFetcherПри чтении данных с возвращаемым результатом возможны две возможности: первая — данные готовы, тогда результат возвращается напрямую; другая — асинхронная операция не закончилась и данные не готовы в это время.createFetcherвыдаст "исключение".

Является ли это «исключение» обычной ошибкой кода? Нет, это исключение представляет собой объект Promise, который инкапсулирует данные запроса, и это настоящий метод запроса данных.componentDidCatchизtry{}catch{} чтобы получить это исключение.

Что делать после получения этого исключения?Мы знаем, что это исключениеPromise, то, конечно, следующим шагом будет выполнение этогоPromise, в успешном состоянии получить данные, а затем снова визуализировать компонент, рендеринг в это время считывает обычные данные, тогда рендеринг может выполняться в обычном режиме. Далее мы моделируемcreateFetcherа такжеSuspense

Мы моделируем простой createFetcher

/**
 * 
 * @param {*} fn  我们请求数据交互的函数,返回一个数据请求的Promise 
 */
function createFetcher(fn){
    const fetcher = {
        status:'pedding',
        result:null,
        p:null
    }
    return function (){
      const getDataPromise = fn()
      fetcher.p = getDataPromise
      getDataPromise.then(result=>{ /* 成功获取数据 */
         fetcher.result = result 
         fetcher.status = 'resolve'
      })
  
      if(fetcher.status === 'pedding'){ /* 第一次执行中断渲染,第二次 */
         throw fetcher
      }
      /* 第二次执行 */
      if(fetcher.status)
      return fetcher.result
    }
}
  • Возвращает функцию, выполненную на этапе рендеринга, при первом рендеринге компонента из-заstatus = peddingПоэтому выбрасывается исключениеfetcherДатьSusponse, рендеринг прерван.
  • Susponseбудет внутриcomponentDidCatchЧтобы обработать этот сборщик, выполнитеgetDataPromise.then, В настоящее времяstatusужеresolveстатус, данные также могут быть возвращены в обычном режиме.
  • следующийSusponseВизуализируйте компонент снова, в это время вы можете получить данные в обычном режиме.

Мы моделируем простой Suspense

export class Suspense extends React.Component{
   state={ isRender: true  }
   componentDidCatch(e){
     /* 异步请求中,渲染 fallback */
     this.setState({ isRender:false })
     const { p } = e
     Promise.resolve(p).then(()=>{
       /* 数据请求后,渲染真实组件 */
       this.setState({ isRender:true })
     })
   }
   render(){
     const { isRender } = this.state
     const { children , fallback } = this.props
     return isRender ? children : fallback
   }
}
  • Используйте componentDidCatch для захвата асинхронных запросов.Если есть асинхронный запрос на резервную визуализацию, дождитесь завершения выполнения асинхронного запроса, а затем визуализируйте настоящий компонент, чтобы завершить весь асинхронный процесс. Но для того, чтобы все поняли процесс, это всего лишь симуляция асинхронного процесса, а реальный процесс намного сложнее этого.

блок-схема:

3.jpg

4 Практика: от саспенса к React.lazy

Введение в React.lazy

SuspenseРеволюция, вызванная асинхронными компонентами, еще не дала существенного результата, и текущая версия официально не введена в действие, ноReact.lazyЭто лучшая практика для текущей версии Suspense. мы все знаемReact.lazyСотрудничатьSuspenseМожно реализовать ленивую загрузку и загрузку по требованию, что очень способствует сегментации кода и не будет загружать большое количество файлов во время инициализации, сокращая время на первом экране.

Основное использование React.lazy

const LazyComponent = React.lazy(()=>import('./text'))

React.lazy Принимает функцию, которую необходимо вызвать динамическиimport(). он должен вернутьPromise,ДолженPromise нужноresolveОдинdefault exportизReactкомпоненты.

Давайте сначала посмотрим на основное использование:

const LazyComponent = React.lazy(() => import('./test.js'))

export default function Index(){
   return <Suspense fallback={<div>loading...</div>} >
       <LazyComponent />
   </Suspense>
}

мы используемPromiseимитировать этоimport()Эффект будет как вышеLazyComponentИзмените его, чтобы он выглядел так:

function Test(){
  return <div className="demo"  >《React进阶实践指南》即将上线~</div>
}
const LazyComponent =  React.lazy(()=> new Promise((resolve)=>{
  setTimeout(()=>{
      resolve({
          default: ()=> <Test />
      })
  },2000)
}))

Эффект:

5.gif

Интерпретация принципа React.lazy

Как React.lazy работает с Susponse для достижения динамической загрузки?На самом деле, createFetcher сделан внутри lazy, и вышеупомянутый createFetcher получает отрендеренные данные, а createFetcher, который идет с lazy, асинхронно запрашивает компоненты. lazy внутренне имитирует сценарий спецификации promiseA. Мы можем полностью понять, что React.lazy использует Promise для имитации процесса запроса данных, но результатом запроса являются не данные, а динамический компонент.

Далее, давайте посмотрим, как обрабатывается lazy

react/src/ReactLazy.js

function lazy(ctor){
    return {
         $$typeof: REACT_LAZY_TYPE,
         _payload:{
            _status: -1,  //初始化状态
            _result: ctor,
         },
         _init:function(payload){
             if(payload._status===-1){ /* 第一次执行会走这里  */
                const ctor = payload._result;
                const thenable = ctor();
                payload._status = Pending;
                payload._result = thenable;
                thenable.then((moduleObject)=>{
                    const defaultExport = moduleObject.default;
                    resolved._status = Resolved; // 1 成功状态
                    resolved._result = defaultExport;/* defaultExport 为我们动态加载的组件本身  */ 
                })
             }
            if(payload._status === Resolved){ // 成功状态
                return payload._result;
            }
            else {  //第一次会抛出Promise异常给Suspense
                throw payload._result; 
            }
         }
    }
}
  • React.lazyОбернутые компоненты отмеченыREACT_LAZY_TYPEэлемент типа, который на этапе согласования становитсяLazyComponentТипfiber,ReactправильноLazyComponentБудет отдельная логика обработки, первый рендеринг будет выполняться первым_initметод. В это время это_initметод можно понимать какcreateFetcher.

Давайте посмотрим на выполнение функции инициализации в ленивом режиме:

react-reconciler/src/ReactFiberBeginWork.js

function mountLazyComponent(){
    const init = lazyComponent._init;
    let Component = init(payload);
}
  • как указано вышеmountLazyComponentВыполняется во время инициализации_initметод, который будет выполнятьсяlazyПервая функция , получаетPromise, связыватьPromise.then Успешный обратный вызов, мы получаем наш компонент в обратном вызовеdefaultExport, здесь следует отметить, что когда вышеприведенная функция оценивается по второму if, потому что состояние неResolved, так пойдетelse, выдает исключение Promise, выдача исключения приведет к прекращению текущего рендеринга.

  • Susponseсправиться с этим внутриpromise, а затем снова визуализировать компонент, а при следующем рендеринге компонент будет визуализироваться напрямую. достичь цели динамической загрузки.

блок-схема

4.jpg

5 Перспективы: будущее саспенса можно ожидать

В настоящее время вы не используете Relay, поэтому пока не можете попробовать Suspense в своем приложении. Потому что пока что из библиотек, реализующих Suspense, Relay — единственная, которую мы протестировали в продакшене и уверены в ее работе.

Задержка в настоящее время недоступна. Если вы хотите использовать ее, вы можете попробовать использовать интеграцию в производственной среде.SuspenseизRelay.Руководство по реле!

Что может решить саспенс?

  • SuspenseПусть библиотека выборки данных работает сReactтесно интегрированы. Если библиотека запросов данных реализуетSuspenseподдержку, то вReactиспользуется вSuspenseбудет естественным.
  • SuspenseМожно свободно отображать эффект загрузки в запросе. Позволяет более активно контролировать загрузку представления.
  • SuspenseЭто может сделать данные запроса для рендеринга более плавными и гибкими, нам не нужноcomponentDidMountЗапросите данные, снова запустите рендеринг и передайте всеSuspenseРешите ее за один раз.

Приостановка перед вызовами?

на будущееSuspenseМожно ли использовать его в качестве основного решения для рендеринга данных асинхронных запросов, автор считает, что Suspense по-прежнему полон ожиданий в будущем, поэтому, по моему личному мнению, вызов Suspense заключается в следующих аспектах:

  • 1 concurrentв режимеSusponseЭто может улучшить пользовательский опыт, а команда реагирования может сделать будущее приостановку более гибким, с более четким наборомcreateFetcherИзготовление руководств — это будущееconcurrentв режимеSuspenseКлюч к тому, чтобы выделиться.

  • 2 SuspenseВозможность широкого использования зависит отSuspenseэкологическое развитие, имеется стабильная библиотека запросов данных сSuspenseИдеально подходит.

  • 3 пары разработчиковSuspenseпризнание стоимости, еслиSuspenseЕсли в будущем производительность улучшится, будет больше разработчиков, которые предпочтут сами инкапсулировать набор методов запроса данных и дать отличныеSuspenseзаказ на покупку.

Шесть резюме

В этой статье рассказывается о происхождении React Susponse, принципе его реализации, его текущем состоянии и перспективах на будущее.Что вы думаете о прошлой и настоящей жизни React?Вы можете написать свое мнение в области комментариев или указать на ошибки автора.

Я написал буклет для углубленного систематического изучения React

Чтобы каждый мог систематически изучать React и продвигать его, автор недавно написал буклет «Руководство по расширенной практике React».Базовый Расширенный,Оптимизация и расширенные статьи,Расширенные принципы,Продвинутая экология,Продвинутая практика, пять направлений, чтобы подробно обсудить руководство по использованию React и введение в принципы.

  • существуетБазовый РасширенныйЗдесь мы заново поймем состояние, свойства, ссылку, контекст и другие модули в реакции и подробно объясним их основное использование и игровой процесс высокого уровня.

  • существуетОптимизация и расширенные статьиВ этом уроке мы расскажем о настройке производительности React и подробной обработке, чтобы React можно было написать более элегантно.

  • существуетРасширенные принципыВ этой статье я подробно расскажу о принципах нескольких основных модулей React и решу проблемы принципов React в разовых интервью.

  • существуетПродвинутая экологияЗдесь мы рассмотрим использование ключевой экологии React и проанализируем внутренний механизм работы с принципиальной точки зрения.

  • существуетПродвинутая практикаЗдесь первые несколько модулей будут соединены последовательно для интенсивной практики.

Что касается того, почему буклет называется Advanced Practice Guide, потому что, объясняя расширенный игровой процесс, он также содержит множество небольших демонстраций для практики. В интервью также есть несколько сессий вопросов и ответов, чтобы читатели выделялись из интервью.

В настоящее время и после завершения 20 глав буклет будет выпущен в ближайшее время.Те, кто хочет продвигать React, могут зайти сюда и обратить внимание на последние новости буклета.

Напоследок подарите розы и оставьте в руках стойкий аромат.Друзья, которые чувствуют, что что-то приобрели, могут подарить авторуНравится, следуйОдна волна, обновляйте фронтальные суперхардкорные статьи одну за другой.

Ссылаться на

Реагирование китайской документации