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

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

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

前言

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

Suspense

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

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

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

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

但這并不意味著 Suspense 不可以單獨使用,我們可以寫個 Suspense 單獨使用的例子,不過目前使用起來會有些麻煩,但相信 React 官方會持續(xù)優(yōu)化這個 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>
  )
}

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

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

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

實際應(yīng)用

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

// 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;
      }
    }
  };
}
// 這里我們模擬了請求過程
const fakeFetch = () => {
  return new Promise(res => {
    setTimeout(() => res('data fetched'), 3000);
  });
};
// 2. 在渲染前發(fā)起請求
const resource = wrapPromise(fakeFetch());
function Content() {
  // 3. 通過 resource.read() 獲取接口返回結(jié)果
  const data = resource.read();
  return <p>{data}</p>
}
function App() {
  return (
    <Suspense fallback={'loading data'}>
      <Content />
    </Suspense>
  )
}

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

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

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

const fakeFetch = (id) => {
  return new Promise(res => {
    setTimeout(() => res(`${id} data fetched`), 3000);
  });
};
// 1. 依然是直接請求數(shù)據(jù)
const initialResource = wrapPromise(fakeFetch(1));
function Content({resource}) {
  // 3. 通過 resource.read() 獲取接口返回結(jié)果
  const data = resource.read();
  return <p>{data}</p>
}
function App() {
  // 2. 將 wrapPromise 返回的對象作為 props 傳遞給組件
  const [resource, setResource] = useState(initialResource);
  // 4. 重新請求
  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>
  )
}

好處:請求前置

使用 Suspense 一個非常大的好處就是請求是一開始就執(zhí)行的?;叵脒^往的發(fā)送請求的時機(jī),我們都是在 compentDidMount 的時候再請求的,React 是先渲染的節(jié)點再發(fā)送的請求,然而使用 Suspense,我們是先發(fā)送請求再渲染的節(jié)點,這就帶來了體驗上的提升。

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

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

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

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

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

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

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

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

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

錯誤處理

注意我們使用的 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 的時候,會 throw result 出來,如果 throw 是一個 promise,React 可以處理,但如果只是一個 error,React 就處理不了了,這就會導(dǎo)致渲染出現(xiàn)問題,所以我們有必要針對 status 為 error 的情況進(jìn)行處理,React 官方文檔也提供了方法,那就是定義一個錯誤邊界組件:

// 定義一個錯誤邊界組件
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 的時候,就會被 <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)我們寫一個 Suspense 組件的時候:

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

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

React 系列

以上就是React Suspense解決競態(tài)條件詳解的詳細(xì)內(nèi)容,更多關(guān)于React Suspense競態(tài)條件的資料請關(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)高度的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • 淺談react受控組件與非受控組件(小結(jié))

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

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

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

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

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

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

    React啟動時webpack版本沖突報錯的解決辦法

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

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

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

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

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

    如何在React項目中使用AntDesign

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

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

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

最新評論