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

React?Suspense解決競(jìng)態(tài)條件詳解

 更新時(shí)間:2022年11月10日 08:38:14   作者:冴羽  
這篇文章主要為大家介紹了React?Suspense解決競(jìng)態(tài)條件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

在上一篇《React 之 Race Condition》中,我們最后引入了 Suspense 來(lái)解決競(jìng)態(tài)條件問(wèn)題,本篇我們來(lái)詳細(xì)講解一下 Suspense。

Suspense

React 16.6 新增了 <Suspense> 組件,讓你可以“等待”目標(biāo)代碼加載,并且可以直接指定一個(gè)加載的界面(像是個(gè) spinner),讓它在用戶等待的時(shí)候顯示。

目前,Suspense 僅支持的使用場(chǎng)景是:通過(guò) React.lazy 動(dòng)態(tài)加載組件

const ProfilePage = React.lazy(() => import('./ProfilePage')); // 懶加載
// 在 ProfilePage 組件處于加載階段時(shí)顯示一個(gè) spinner
<Suspense fallback={<Spinner />}>
  <ProfilePage />
</Suspense>

執(zhí)行機(jī)制

但這并不意味著 Suspense 不可以單獨(dú)使用,我們可以寫個(gè) Suspense 單獨(dú)使用的例子,不過(guò)目前使用起來(lái)會(huì)有些麻煩,但相信 React 官方會(huì)持續(xù)優(yōu)化這個(gè) API。

let data, promise;
function fetchData() {
  if (data) return data;
  promise = new Promise(resolve => {
    setTimeout(() => {
      data = 'data fetched'
      resolve()
    }, 3000)
  })
  throw promise;
}
function Content() {
  const data = fetchData();
  return <p>{data}</p>
}
function App() {
  return (
    <Suspense fallback={'loading data'}>
      <Content />
    </Suspense>
  )
}

這是一個(gè)非常簡(jiǎn)單的使用示例,但卻可以用來(lái)解釋 Suspense 的執(zhí)行機(jī)制。

最一開始 <Content> 組件會(huì) throw 一個(gè) promise,React 會(huì)捕獲這個(gè)異常,發(fā)現(xiàn)是 promise 后,會(huì)在這個(gè) promise 上追加一個(gè) then 函數(shù),在 then 函數(shù)中執(zhí)行 Suspense 組件的更新,然后展示 fallback 內(nèi)容。

等 fetchData 中的 promise resolve 后,會(huì)執(zhí)行追加的 then 函數(shù),觸發(fā) Suspense 組件的更新,此時(shí)有了 data 數(shù)據(jù),因?yàn)闆](méi)有異常,React 會(huì)刪除 fallback 組件,正常展示 <Content /> 組件。

實(shí)際應(yīng)用

如果我們每個(gè)請(qǐng)求都這樣去寫,代碼會(huì)很冗余,雖然有 react-cache 這個(gè) npm 包,但上次更新已經(jīng)是 4 年之前了,不過(guò)通過(guò)查看包源碼以及參考 React 官方的示例代碼,在實(shí)際項(xiàng)目中,我們可以這樣去寫:

// 1. 通用的 wrapPromise 函數(shù)
function wrapPromise(promise) {
  let status = "pending";
  let result;
  let suspender = promise.then(
    r => {
      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;
      }
    }
  };
}
// 這里我們模擬了請(qǐng)求過(guò)程
const fakeFetch = () => {
  return new Promise(res => {
    setTimeout(() => res('data fetched'), 3000);
  });
};
// 2. 在渲染前發(fā)起請(qǐng)求
const resource = wrapPromise(fakeFetch());
function Content() {
  // 3. 通過(guò) resource.read() 獲取接口返回結(jié)果
  const data = resource.read();
  return <p>{data}</p>
}
function App() {
  return (
    <Suspense fallback={'loading data'}>
      <Content />
    </Suspense>
  )
}

在這段代碼里,我們聲明了一個(gè) wrapPromise 函數(shù),它接收一個(gè) promise,比如 fetch 請(qǐng)求。函數(shù)返回一個(gè)帶有 read 方法的對(duì)象,這是因?yàn)榉庋b成方法后,代碼可以延遲執(zhí)行,我們就可以在 Suspense 組件更新的時(shí)候再執(zhí)行方法,從而獲取最新的返回結(jié)果。

函數(shù)內(nèi)部記錄了三種狀態(tài),pending、successerror,根據(jù)狀態(tài)返回不同的內(nèi)容。

你可能會(huì)想,如果我們還要根據(jù) id 之類的數(shù)據(jù)點(diǎn)擊請(qǐng)求數(shù)據(jù)呢?使用 Suspense 該怎么做呢?React 官方文檔也給了示例代碼:

const fakeFetch = (id) => {
  return new Promise(res => {
    setTimeout(() => res(`${id} data fetched`), 3000);
  });
};
// 1. 依然是直接請(qǐng)求數(shù)據(jù)
const initialResource = wrapPromise(fakeFetch(1));
function Content({resource}) {
  // 3. 通過(guò) resource.read() 獲取接口返回結(jié)果
  const data = resource.read();
  return <p>{data}</p>
}
function App() {
  // 2. 將 wrapPromise 返回的對(duì)象作為 props 傳遞給組件
  const [resource, setResource] = useState(initialResource);
  // 4. 重新請(qǐng)求
  const handleClick = (id) => () => {
    setResource(wrapPromise(fakeFetch(id)));
  }
  return (
    <Fragment>
      <button onClick={handleClick(1)}>tab 1</button>
      <button onClick={handleClick(2)}>tab 2</button>
      <Suspense fallback={'loading data'}>
        <Content resource={resource} />
      </Suspense>
    </Fragment>
  )
}

好處:請(qǐng)求前置

使用 Suspense 一個(gè)非常大的好處就是請(qǐng)求是一開始就執(zhí)行的。回想過(guò)往的發(fā)送請(qǐng)求的時(shí)機(jī),我們都是在 compentDidMount 的時(shí)候再請(qǐng)求的,React 是先渲染的節(jié)點(diǎn)再發(fā)送的請(qǐng)求,然而使用 Suspense,我們是先發(fā)送請(qǐng)求再渲染的節(jié)點(diǎn),這就帶來(lái)了體驗(yàn)上的提升。

尤其當(dāng)請(qǐng)求多個(gè)接口的時(shí)候,借助 Suspense,我們可以實(shí)現(xiàn)接口并行處理以及提早展現(xiàn),舉個(gè)例子:

function fetchData(id) {
  return {
    user: wrapPromise(fakeFetchUser(id)),
    posts: wrapPromise(fakeFetchPosts(id))
  };
}
const fakeFetchUser = (id) => {
  return new Promise(res => {
    setTimeout(() => res(`user ${id} data fetched`), 5000 * Math.random());
  });
};
const fakeFetchPosts = (id) => {
  return new Promise(res => {
    setTimeout(() => res(`posts ${id} data fetched`), 5000 * Math.random());
  });
};
const initialResource = fetchData(1);
function User({resource}) {
  const data = resource.user.read();
  return <p>{data}</p>
}
function Posts({resource}) {
  const data = resource.posts.read();
  return <p>{data}</p>
}
function App() {
  const [resource, setResource] = useState(initialResource);
  const handleClick = (id) => () => {
    setResource(fetchData(id));
  }
  return (
    <Fragment>
      <p><button onClick={handleClick(Math.ceil(Math.random() * 10))}>next user</button></p>
      <Suspense fallback={'loading user'}>
        <User resource={resource} />
        <Suspense fallback={'loading posts'}>
          <Posts resource={resource} />
        </Suspense>
      </Suspense>
    </Fragment>
  )
}

在這個(gè)示例代碼中,user 和 posts 接口是并行請(qǐng)求的,如果 posts 接口提前返回,而 user 接口還未返回,會(huì)等到 user 接口返回后,再一起展現(xiàn),但如果 user 接口提前返回,posts 接口后返回,則會(huì)先展示 user 信息,然后顯示 loading posts,等 posts 接口返回,再展示 posts 內(nèi)容。

這聽起來(lái)好像沒(méi)什么,但是想想如果我們是以前會(huì)怎么做,我們可能會(huì)用一個(gè) Promise.all 來(lái)實(shí)現(xiàn),但是 Promise.all 的問(wèn)題就在于必須等待所有接口返回才會(huì)執(zhí)行,而且如果其中有一個(gè) reject 了,都會(huì)走向 catch 邏輯。使用 Suspense,我們可以做到更好的展示效果。

好處:解決競(jìng)態(tài)條件

使用 Suspense 可以有效的解決 Race Conditions(競(jìng)態(tài)條件) 的問(wèn)題,關(guān)于 Race Conditions 可以參考《React 之 Race Condition》。

Suspense 之所以能夠有效的解決 Race Conditions 問(wèn)題,就在于傳統(tǒng)的實(shí)現(xiàn)中,我們需要考慮 setState 的正確時(shí)機(jī),執(zhí)行順序是:1. 請(qǐng)求數(shù)據(jù) 2. 數(shù)據(jù)返回 3. setState 數(shù)據(jù)

而在 Suspense 中,我們請(qǐng)求后,立刻就設(shè)置了 setState,然后就只用等待請(qǐng)求返回,React 執(zhí)行 Suspense 的再次更新就好了,執(zhí)行順序是:1. 請(qǐng)求數(shù)據(jù) 2. setState 數(shù)據(jù) 3. 數(shù)據(jù)返回 4. Suspense 重新渲染,所以大大降低了出錯(cuò)的概率。

const fakeFetch = person => {
  return new Promise(res => {
    setTimeout(() => res(`${person}'s data`), Math.random() * 5000);
  });
};
function fetchData(userId) {
  return wrapPromise(fakeFetch(userId))
}
const initialResource = fetchData('Nick');
function User({ resource }) {
  const data = resource.read();
  return <p>{ data }</p>
}
const App = () => {
  const [person, setPerson] = useState('Nick');
  const [resource, setResource] = useState(initialResource);
  const handleClick = (name) => () => {
    setPerson(name)
    setResource(fetchData(name));
  }
  return (
    <Fragment>
      <button onClick={handleClick('Nick')}>Nick's Profile</button>
      <button onClick={handleClick('Deb')}>Deb's Profile</button>
	    <button onClick={handleClick('Joe')}>Joe's Profile</button>
      <Fragment>
        <h1>{person}</h1>
        <Suspense fallback={'loading'}>
          <User resource={resource} />
        </Suspense>
      </Fragment>
    </Fragment>
  );
};

錯(cuò)誤處理

注意我們使用的 wrapPromise 函數(shù):

function wrapPromise(promise) {
	// ...
  return {
    read() {
      if (status === "pending") {
        throw suspender;
      } else if (status === "error") {
        throw result;
      } else if (status === "success") {
        return result;
      }
    }
  };
}

當(dāng) status 為 error 的時(shí)候,會(huì) throw result 出來(lái),如果 throw 是一個(gè) promise,React 可以處理,但如果只是一個(gè) error,React 就處理不了了,這就會(huì)導(dǎo)致渲染出現(xiàn)問(wèn)題,所以我們有必要針對(duì) status 為 error 的情況進(jìn)行處理,React 官方文檔也提供了方法,那就是定義一個(gè)錯(cuò)誤邊界組件:

// 定義一個(gè)錯(cuò)誤邊界組件
class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };
  static getDerivedStateFromError(error) {
    return {
      hasError: true,
      error
    };
  }
  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }
    return this.props.children;
  }
}
function App() {
  // ...
  return (
    <Fragment>
      <button onClick={handleClick(1)}>tab 1</button>
      <button onClick={handleClick(2)}>tab 2</button>
      <ErrorBoundary fallback={<h2>Could not fetch posts.</h2>}>
        <Suspense fallback={'loading data'}>
          <Content resource={resource} />
        </Suspense>
      </ErrorBoundary>
    </Fragment>
  )
}

當(dāng) <Content /> 組件 throw 出 error 的時(shí)候,就會(huì)被 <ErrorBoundary />組件捕獲,然后展示 fallback 的內(nèi)容。

源碼

那 Suspense 的源碼呢?我們查看 React.js 的源碼

import {
  REACT_SUSPENSE_TYPE
} from 'shared/ReactSymbols';
export {
  REACT_SUSPENSE_TYPE as Suspense
};

再看下shared/ReactSymbols的源碼:

export const REACT_SUSPENSE_TYPE: symbol = Symbol.for('react.suspense');

所以當(dāng)我們寫一個(gè) Suspense 組件的時(shí)候:

<Suspense fallback={'loading data'}>
  <Content />
</Suspense>
// 被轉(zhuǎn)譯為
React.createElement(Suspense, {
  fallback: 'loading data'
}, React.createElement(Content, null));

createElement 傳入的 Suspense 就只是一個(gè)常量而已,具體的處理邏輯會(huì)在以后的文章中慢慢講解。

React 系列

以上就是React Suspense解決競(jìng)態(tài)條件詳解的詳細(xì)內(nèi)容,更多關(guān)于React Suspense競(jìng)態(tài)條件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

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

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

    這篇文章主要為大家介紹了React?之?Suspense提出的背景及使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • React根據(jù)寬度自適應(yīng)高度的示例代碼

    React根據(jù)寬度自適應(yīng)高度的示例代碼

    本篇文章主要介紹了React根據(jù)寬度自適應(yīng)高度的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10
  • 淺談react受控組件與非受控組件(小結(jié))

    淺談react受控組件與非受控組件(小結(jié))

    本篇文章主要介紹了淺談react受控組件與非受控組件(小結(jié)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • 關(guān)于antd tree和父子組件之間的傳值問(wèn)題(react 總結(jié))

    關(guān)于antd tree和父子組件之間的傳值問(wèn)題(react 總結(jié))

    這篇文章主要介紹了關(guān)于antd tree 和父子組件之間的傳值問(wèn)題,是小編給大家總結(jié)的一些react知識(shí)點(diǎn),本文通過(guò)一個(gè)項(xiàng)目需求實(shí)例代碼詳解給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-06-06
  • 官方推薦react-navigation的具體使用詳解

    官方推薦react-navigation的具體使用詳解

    本篇文章主要介紹了官方推薦react-navigation的具體使用詳解,react-navigation是致力于解決導(dǎo)航卡頓,數(shù)據(jù)傳遞,Tabbar和navigator布局,支持redux。非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2018-05-05
  • React啟動(dòng)時(shí)webpack版本沖突報(bào)錯(cuò)的解決辦法

    React啟動(dòng)時(shí)webpack版本沖突報(bào)錯(cuò)的解決辦法

    在啟動(dòng)React應(yīng)用時(shí),遇到Webpack版本不匹配導(dǎo)致的運(yùn)行錯(cuò)誤,解決方法包括刪除全局及局部的webpack和webpack-cli,然后根據(jù)項(xiàng)目需求安裝特定版本的webpack,本文通過(guò)代碼示例給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2024-09-09
  • react中關(guān)于useCallback的用法

    react中關(guān)于useCallback的用法

    這篇文章主要介紹了react中關(guān)于useCallback的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • React函數(shù)組件和類組件的區(qū)別及說(shuō)明

    React函數(shù)組件和類組件的區(qū)別及說(shuō)明

    這篇文章主要介紹了React函數(shù)組件和類組件的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 如何在React項(xiàng)目中使用AntDesign

    如何在React項(xiàng)目中使用AntDesign

    我們?cè)诤笈_(tái)管理系統(tǒng)React項(xiàng)目開發(fā)中會(huì)有Table表格、Form表單、List列表、Button按鈕等組件,這個(gè)時(shí)候我們可以使用AntDesign來(lái)減少開發(fā)中不必要的樣式問(wèn)題,本文就介紹了eact項(xiàng)目中使用AntDesign,感興趣的可以了解一下
    2022-04-04
  • react自動(dòng)化構(gòu)建路由的實(shí)現(xiàn)

    react自動(dòng)化構(gòu)建路由的實(shí)現(xiàn)

    這篇文章主要介紹了react自動(dòng)化構(gòu)建路由的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04

最新評(píng)論