欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React?之?Suspense提出的背景及使用詳解

 更新時(shí)間:2023年03月27日 13:07:45   作者:Aaaaaaaaaaayou  
這篇文章主要為大家介紹了React?之?Suspense提出的背景及使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

Suspense 提出的背景

假設(shè)我們現(xiàn)在有如下一個(gè)應(yīng)用:

const Articles = () => {
  const [articles, setArticles] = useState(null)
  useEffect(() => {
    getArticles().then((a) => setArticles(a))
  }, [])
  if (articles === null) {
    return <p>Loading articles...</p>
  }
  return (
    <ul>
      {articles.map((article) => (
        <li key={article.id}>
          <h4>{article.title}</h4>
          <p>{article.abstract}</p>
        </li>
      ))}
    </ul>
  )
}
export default function Profile() {
  const [user, setUser] = useState(null)
  useEffect(() => {
    getUser().then((u) => setUser(u))
  }, [])
  if (user === null) {
    return <p>Loading user...</p>
  }
  return (
    <>
      <h3>{user.name}</h3>
      <Articles articles={articles} />
    </>
  )
}

該應(yīng)用是一個(gè)用戶的個(gè)人主頁,包含用戶的基本信息(例子中只有名字)以及用戶的文章列表,并且規(guī)定了必須等待用戶獲取成功后才能渲染其基本信息以及文章列表。 該應(yīng)用看似簡單,但卻存在著以下幾個(gè)問題:

  • "Waterfalls",意思是文章列表必須要等到用戶請求成功以后才能開始渲染,從而對于文章列表的請求也會(huì)被用戶阻塞,但其實(shí)對于文章的請求是可以同用戶并行的。
  • "fetch-on-render",無論是 Profile 還是 Articles 組件,都是需要等到渲染一次后才能發(fā)出請求。

對于第一個(gè)問題,我們可以通過修改代碼來優(yōu)化:

const Articles = ({articles}) => {
  if (articles === null) {
    return <p>Loading articles...</p>
  }
  return (
    <ul>
      {articles.map((article) => (
        <li key={article.id}>
          <h4>{article.title}</h4>
          <p>{article.abstract}</p>
        </li>
      ))}
    </ul>
  )
}
export default function Profile() {
  const [user, setUser] = useState(null)
  const [articles, setArticles] = useState(null)
  useEffect(() => {
    getUser().then((u) => setUser(u))
    getArticles().then((a) => setArticles(a))
  }, [])
  if (user === null) {
    return <p>Loading user...</p>
  }
  return (
    <>
      <h3>{user.name}</h3>
      <Articles articles={articles} />
    </>
  )
}

現(xiàn)在獲取用戶和獲取文章列表的邏輯已經(jīng)可以并行了,但是這樣又導(dǎo)致 Articles 組件同其數(shù)據(jù)獲取相關(guān)的邏輯分離,隨著應(yīng)用變得復(fù)雜后,這種方式可能會(huì)難以維護(hù)。同時(shí)第二個(gè)問題 "fetch-on-render" 還是沒有解決。而 Suspense 的出現(xiàn)可以很好的解決這些問題,接下來就來看看是如何解決的。

Suspense 的使用

Suspense 用于數(shù)據(jù)獲取

還是上面的例子,我們使用 Suspense 來改造一下:

// Profile.js
import React, {Suspense} from 'react'
import User from './User'
import Articles from './Articles'
export default function Profile() {
  return (
    <Suspense fallback={<p>Loading user...</p>}>
      <User />
      <Suspense fallback={<p>Loading articles...</p>}>
        <Articles />
      </Suspense>
    </Suspense>
  )
}
// Articles.js
import React from 'react'
import {getArticlesResource} from './resource'
const articlesResource = getArticlesResource()
const Articles = () => {
  debugger
  const articles = articlesResource.read()
  return (
    <ul>
      {articles.map((article) => (
        <li key={article.id}>
          <h4>{article.title}</h4>
          <p>{article.abstract}</p>
        </li>
      ))}
    </ul>
  )
}
// User.js
import React from 'react'
import {getUserResource} from './resource'
const userResource = getUserResource()
const User = () => {
  const user = userResource.read()
  return <h3>{user.name}</h3>
}
// resource.js
export function wrapPromise(promise) {
  let status = 'pending'
  let result
  let suspender = promise.then(
    (r) => {
      debugger
      status = 'success'
      result = r
    },
    (e) => {
      status = 'error'
      result = e
    }
  )
  return {
    read() {
      if (status === 'pending') {
        throw suspender
      } else if (status === 'error') {
        throw result
      } else if (status === 'success') {
        return result
      }
    },
  }
}
export function getArticles() {
  return new Promise((resolve, reject) => {
    const list = [...new Array(10)].map((_, index) => ({
      id: index,
      title: `Title${index + 1}`,
      abstract: `Abstract${index + 1}`,
    }))
    setTimeout(() => {
      resolve(list)
    }, 2000)
  })
}
export function getUser() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        name: 'Ayou',
        age: 18,
        vocation: 'Program Ape',
      })
    }, 3000)
  })
}
export const getUserResource = () => {
  return wrapPromise(getUser())
}
export const getArticlesResource = () => {
  return wrapPromise(getArticles())
}

首先,在 Profile.js 中開始引入 UserArticles 的時(shí)候就已經(jīng)開始請求數(shù)據(jù)了,即 "Render-as-You-Fetch"(渲染的時(shí)候請求),且兩者是并行的。當(dāng)渲染到 User 組件的時(shí)候,由于此時(shí)接口請求還未返回,const user = userResource.read() 會(huì)拋出異常:

...
  read() {
    if (status === 'pending') {
      throw suspender
    } else if (status === 'error') {
      throw result
    } else if (status === 'success') {
      return result
    }
  },
...

Suspense 組件的作用是,當(dāng)發(fā)現(xiàn)其包裹的組件拋出異常且異常為 Promise 對象時(shí),會(huì)渲染 fallback 中的內(nèi)容,即 <p>Loading user...</p>。等到 Promise 對象 resolve 的時(shí)候會(huì)再次觸發(fā)重新渲染,顯示其包裹的內(nèi)容,又因?yàn)楂@取文章列表的時(shí)間比用戶短,所以這里會(huì)同時(shí)顯示用戶信息及其文章列表(具體過程后續(xù)會(huì)再進(jìn)行分析)。這樣,通過 Suspense 組件,我們就解決了前面的兩個(gè)問題。

同時(shí),使用 Suspense 還會(huì)有另外一個(gè)好處,假設(shè)我們現(xiàn)在改變我們的需求,允許用戶信息和文章列表獨(dú)立渲染,則使用 Suspense 重構(gòu)起來會(huì)比較簡單:

而如果使用原來的方式,則需要修改的地方比較多:

可見,使用 Suspense 會(huì)帶來很多好處。當(dāng)然,上文為了方便說明,寫得非常簡單,實(shí)際開發(fā)時(shí)會(huì)結(jié)合 Relay 這樣的庫來使用,由于這一款目前還處于試驗(yàn)階段,所以暫時(shí)先不做過多的討論。

Suspense 除了可以用于上面的數(shù)據(jù)獲取這種場景外,還可以用來實(shí)現(xiàn) Lazy Component。

Lazy Component

import React, {Suspense} from 'react'
const MyComp = React.lazy(() => import('./MyComp'))
export default App() {
  return (
    <Suspense fallback={<p>Loading Component...</p>}>
      <MyComp />
    </Suspense>
  )
}

我們知道 import('./MyComp') 返回的是一個(gè) Promise 對象,其 resolve 的是一個(gè)模塊,既然如此那這樣也是可以的:

import React, {Suspense} from 'react'
const MyComp = React.lazy(
  () =>
    new Promise((resolve) =>
      setTimeout(
        () =>
          resolve({
            default: function MyComp() {
              return <div>My Comp</div>
            },
          }),
        1000
      )
    )
)
export default function App() {
  return (
    <Suspense fallback={<p>Loading Component...</p>}>
      <MyComp />
    </Suspense>
  )
}

甚至,我們可以通過請求來獲取 Lazy Component 的代碼:

import React, {Suspense} from 'react'
const MyComp = React.lazy(
  () =>
    new Promise(async (resolve) => {
      const code = await fetch('http://xxxx')
      const module = {exports: {}}
      Function('export, module', code)(module.exports, module)
      resolve({default: module.exports})
    })
)
export default function App() {
  return (
    <Suspense fallback={<p>Loading Component...</p>}>
      <MyComp />
    </Suspense>
  )
}

這也是我們實(shí)現(xiàn)遠(yuǎn)程組件的基本原理。

原理

介紹了這么多關(guān)于 Suspense 的內(nèi)容后,你一定很好奇它到底是如何實(shí)現(xiàn)的吧,我們先不研究 React 源碼,先嘗試自己實(shí)現(xiàn)一個(gè) Suspense

import React, {Component} from 'react'
export default class Suspense extends Component {
  state = {
    isLoading: false,
  }
  componentDidCatch(error, info) {
    if (this._mounted) {
      if (typeof error.then === 'function') {
        this.setState({isLoading: true})
        error.then(() => {
          if (this._mounted) {
            this.setState({isLoading: false})
          }
        })
      }
    }
  }
  componentDidMount() {
    this._mounted = true
  }
  componentWillUnmount() {
    this._mounted = false
  }
  render() {
    const {children, fallback} = this.props
    const {isLoading} = this.state
    return isLoading ? fallback : children
  }
}

其核心原理就是利用了 “Error Boundary” 來捕獲子組件中的拋出的異常,且如果拋出的異常為 Promise 對象,則在傳入其 then 方法的回調(diào)中改變 state 觸發(fā)重新渲染。

接下來,我們還是用上面的例子來分析一下整個(gè)過程:

export default function Profile() {
  return (
    <Suspense fallback={<p>Loading user...</p>}>
      <User />
      <Suspense fallback={<p>Loading articles...</p>}>
        <Articles />
      </Suspense>
    </Suspense>
  )
}

我們知道 React 在渲染時(shí)會(huì)構(gòu)建 Fiber Tree,當(dāng)處理到 User 組件時(shí),React 代碼中會(huì)捕獲到異常:

do {
  try {
    workLoopConcurrent()
    break
  } catch (thrownValue) {
    handleError(root, thrownValue)
  }
} while (true)

其中,異常處理函數(shù) handleError 主要做兩件事:

throwException(
  root,
  erroredWork.return,
  erroredWork,
  thrownValue,
  workInProgressRootRenderLanes
)
completeUnitOfWork(erroredWork)

其中,throwException 主要是往上找到最近的 Suspense 類型的 Fiber,并更新其 updateQueue

const wakeables: Set&lt;Wakeable&gt; = (workInProgress.updateQueue: any)
if (wakeables === null) {
  const updateQueue = (new Set(): any)
  updateQueue.add(wakeable) // wakeable 是 handleError(root, thrownValue) 中的 thrownValue,是一個(gè) Promise 對象
  workInProgress.updateQueue = updateQueue
} else {
  wakeables.add(wakeable)
}

completeUnitOfWork(erroredWork)React 源碼解讀之首次渲染流程中已經(jīng)介紹過了,此處就不再贅述了。

render 階段后,會(huì)形成如下所示的 Fiber 結(jié)構(gòu):

之后會(huì)進(jìn)入 commit 階段,將 Fiber 對應(yīng)的 DOM 插入到容器之中:

注意到 Loading articles... 雖然也被插入了,但確是不可見的。

前面提到過 SuspenseupdateQueue 中保存了 Promise 請求對象,我們需要在其 resolve 以后觸發(fā)應(yīng)用的重新渲染,這一步驟仍然是在 commit 階段實(shí)現(xiàn)的:

function commitWork(current: Fiber | null, finishedWork: Fiber): void {
  ...
  case SuspenseComponent: {
    commitSuspenseComponent(finishedWork);
    attachSuspenseRetryListeners(finishedWork);
    return;
  }
  ...
}
function attachSuspenseRetryListeners(finishedWork: Fiber) {
  // If this boundary just timed out, then it will have a set of wakeables.
  // For each wakeable, attach a listener so that when it resolves, React
  // attempts to re-render the boundary in the primary (pre-timeout) state.
  const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any)
  if (wakeables !== null) {
    finishedWork.updateQueue = null
    let retryCache = finishedWork.stateNode
    if (retryCache === null) {
      retryCache = finishedWork.stateNode = new PossiblyWeakSet()
    }
    wakeables.forEach((wakeable) => {
      // Memoize using the boundary fiber to prevent redundant listeners.
      let retry = resolveRetryWakeable.bind(null, finishedWork, wakeable)
      if (!retryCache.has(wakeable)) {
        if (enableSchedulerTracing) {
          if (wakeable.__reactDoNotTraceInteractions !== true) {
            retry = Schedule_tracing_wrap(retry)
          }
        }
        retryCache.add(wakeable)
        // promise resolve 了以后觸發(fā) react 的重新渲染
        wakeable.then(retry, retry)
      }
    })
  }
}

總結(jié)

本文介紹了 Suspense 提出的背景、使用方式以及原理,從文中可看出 Suspense 用于數(shù)據(jù)獲取對我們的開發(fā)方式將是一個(gè)巨大的影響,但是目前還處在實(shí)驗(yàn)階段,所以留給“中國隊(duì)”的時(shí)間還是很充足的。

以上就是React 之 Suspense提出的背景及使用詳解的詳細(xì)內(nèi)容,更多關(guān)于React Suspense使用背景的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React 中的重新渲染實(shí)現(xiàn)

    React 中的重新渲染實(shí)現(xiàn)

    本文主要介紹了React 中的重新渲染實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • React項(xiàng)目build打包頁面空白的解決方案

    React項(xiàng)目build打包頁面空白的解決方案

    React項(xiàng)目執(zhí)行build命令后,在本地服務(wù)器打開頁面是空白的,本文主要介紹了React項(xiàng)目build打包頁面空白的解決方案,感興趣的可以了解一下
    2023-08-08
  • React初始化渲染過程示例詳解

    React初始化渲染過程示例詳解

    這篇文章主要為大家介紹了React初始化渲染過程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 每天一個(gè)hooks學(xué)習(xí)之useUnmount

    每天一個(gè)hooks學(xué)習(xí)之useUnmount

    這篇文章主要為大家介紹了每天一個(gè)hooks學(xué)習(xí)之useUnmount,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • 通過實(shí)例學(xué)習(xí)React中事件節(jié)流防抖

    通過實(shí)例學(xué)習(xí)React中事件節(jié)流防抖

    這篇文章主要介紹了通過實(shí)例學(xué)習(xí)React中事件節(jié)流防抖,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下
    2019-06-06
  • 詳解React 服務(wù)端渲染方案完美的解決方案

    詳解React 服務(wù)端渲染方案完美的解決方案

    這篇文章主要介紹了詳解React 服務(wù)端渲染方案完美的解決方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-12-12
  • React-Native中禁用Navigator手勢返回的示例代碼

    React-Native中禁用Navigator手勢返回的示例代碼

    本篇文章主要介紹了React-Native中禁用Navigator手勢返回的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-09-09
  • React中的受控組件與非受控組件詳解

    React中的受控組件與非受控組件詳解

    在React中,受控組件指的是表單元素的value值受React組件的state或props控制的組件,而非受控組件則是表單元素的value值由DOM自身負(fù)責(zé)管理的組件,本文將給大家詳細(xì)介紹React受控組件與非受控組件,需要的朋友可以參考下
    2023-08-08
  • 深入理解react-router 路由的實(shí)現(xiàn)原理

    深入理解react-router 路由的實(shí)現(xiàn)原理

    這篇文章主要介紹了深入理解react-router 路由的實(shí)現(xiàn)原理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • React翻頁器的實(shí)現(xiàn)(包含前后端)

    React翻頁器的實(shí)現(xiàn)(包含前后端)

    本文主要介紹了React翻頁器的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08

最新評(píng)論