最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

前言

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

Suspense

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

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

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

執(zhí)行機制

但這并不意味著 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í)行機制。

最一開始 <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 /> 組件。

實際應用

如果我們每個請求都這樣去寫,代碼會很冗余,雖然有 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ā)送請求的時機,我們都是在 compentDidMount 的時候再請求的,React 是先渲染的節(jié)點再發(fā)送的請求,然而使用 Suspense,我們是先發(fā)送請求再渲染的節(jié)點,這就帶來了體驗上的提升。

尤其當請求多個接口的時候,借助 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 的正確時機,執(zhí)行順序是:1. 請求數(shù)據(jù) 2. 數(shù)據(jù)返回 3. setState 數(shù)據(jù)

而在 Suspense 中,我們請求后,立刻就設置了 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;
      }
    }
  };
}

當 status 為 error 的時候,會 throw result 出來,如果 throw 是一個 promise,React 可以處理,但如果只是一個 error,React 就處理不了了,這就會導致渲染出現(xiàn)問題,所以我們有必要針對 status 為 error 的情況進行處理,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>
  )
}

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

所以當我們寫一個 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)條件詳解的詳細內(nèi)容,更多關(guān)于React Suspense競態(tài)條件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • react-router-dom的使用說明

    react-router-dom的使用說明

    這篇文章主要介紹了react-router-dom的使用說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • React中的useEffect(副作用)介紹

    React中的useEffect(副作用)介紹

    這篇文章主要介紹了React中的useEffect(副作用),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • React替換傳統(tǒng)拷貝方法的Immutable使用

    React替換傳統(tǒng)拷貝方法的Immutable使用

    Immutable.js出自Facebook,是最流行的不可變數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)之一。它實現(xiàn)了完全的持久化數(shù)據(jù)結(jié)構(gòu),使用結(jié)構(gòu)共享。所有的更新操作都會返回新的值,但是在內(nèi)部結(jié)構(gòu)是共享的,來減少內(nèi)存占用
    2023-02-02
  • 詳解React中如何拆分組件

    詳解React中如何拆分組件

    這篇文章主要為大家詳細介紹了React中拆分組件的相關(guān)知識,文中的示例代碼講解詳細,對我們掌握React有一定的幫助,需要的小伙伴可以參考一下
    2023-12-12
  • react-native動態(tài)切換tab組件的方法

    react-native動態(tài)切換tab組件的方法

    在APP中免不了要使用tab組件,有的是tab切換,也有的是tab分類切換.這篇文章主要介紹了react-native動態(tài)切換tab組件的方法,非常具有實用價值,需要的朋友可以參考下
    2018-07-07
  • React報錯map()?is?not?a?function詳析

    React報錯map()?is?not?a?function詳析

    這篇文章主要介紹了React報錯map()?is?not?a?function詳析,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • React-Native做一個文本輸入框組件的實現(xiàn)代碼

    React-Native做一個文本輸入框組件的實現(xiàn)代碼

    這篇文章主要介紹了React-Native做一個文本輸入框組件的實現(xiàn)代碼,非常具有實用價值,需要的朋友可以參考下
    2017-08-08
  • 快速創(chuàng)建React項目并配置webpack

    快速創(chuàng)建React項目并配置webpack

    這篇文章主要介紹了創(chuàng)建React項目并配置webpack,在這里需要注意,Create?React?App?requires?Node?14?or?higher.需要安裝高版本的node,本文給大家介紹的非常詳細,需要的朋友參考下吧
    2022-01-01
  • react路由v6版本NavLink的兩個小坑及解決

    react路由v6版本NavLink的兩個小坑及解決

    這篇文章主要介紹了react路由v6版本NavLink的兩個小坑及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 使用React實現(xiàn)輪播效果組件示例代碼

    使用React實現(xiàn)輪播效果組件示例代碼

    React剛出來不久,組件還比較少,不像jquery那樣已經(jīng)有很多現(xiàn)成的插件了,于是自己寫了一個基于React的輪播效果組件,現(xiàn)在分享給大家,有需要的可以參考借鑒。
    2016-09-09

最新評論

武陟县| 东阳市| 沙坪坝区| 方城县| 和平县| 阳原县| 九寨沟县| 景宁| 赣州市| 东兴市| 江陵县| 德清县| 石城县| 五寨县| 资中县| 古丈县| 五大连池市| 合肥市| 柘荣县| 涪陵区| 安丘市| 灌南县| 平山县| 新兴县| 祁门县| 中宁县| 成武县| 保亭| 当阳市| 皮山县| 同江市| 肇庆市| 固原市| 菏泽市| 仁怀市| 喀什市| 石景山区| 郸城县| 彭州市| 江源县| 容城县|