<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          揭秘 React 異步獲取數(shù)據(jù)的進化歷程

          共 9223字,需瀏覽 19分鐘

           ·

          2021-08-03 14:53

          點擊上方 前端瓶子君,關(guān)注公眾號

          回復(fù)算法,加入前端編程面試算法每日一題群


          本篇文章,以模擬從『Hacker News API[1]』獲取熱門文章為例,通過一步步地代碼優(yōu)化和封裝,闡述 React 異步獲取數(shù)據(jù)的進化歷程(useEffect -> useReducer -> custom hook -> Suspense)。

          1. 使用 useEffect Hook 異步獲取數(shù)據(jù)

          1.1 異步獲取數(shù)據(jù)

          雖然 React 16.6 新增了 Suspense 組件用于異步獲取數(shù)據(jù),但我們相信,截止到 react 18 新特性發(fā)布之前,異步獲取數(shù)據(jù)最常見的方法仍是使用 useEffect,我們稱這種方法為“fetch-on-render”(渲染之后獲取數(shù)據(jù)),因為數(shù)據(jù)的獲取是發(fā)生在組件被渲染到屏幕之后。

          本小節(jié)內(nèi)容將以實現(xiàn)關(guān)鍵詞搜索熱門文章的需求為例,闡述通過 React Hook 異步獲取數(shù)據(jù)的實踐過程。

          首先,我們使用useState創(chuàng)建data來管理數(shù)據(jù)的本地狀態(tài),當(dāng)組件第一次渲染時,從useState()拿到data的初始值[],當(dāng)我們調(diào)用setData(val),React 會再次渲染組件,這一次data的值為val

          我們在useEffect這個鉤子中使用`axios`[2]來異步請求數(shù)據(jù),并將返回的結(jié)果存儲到data中。但是,當(dāng)您運行這個程序時,您會發(fā)現(xiàn),useEffect在組件掛載時觸發(fā),在組件更新時也會頻繁觸發(fā),導(dǎo)致一次又一次的請求相同的數(shù)據(jù)。我們只想在組件掛載時請求一次數(shù)據(jù),因此我們給useEffect的傳遞一個空數(shù)組([])作為第二個參數(shù)。這就告訴 React 你的 effect 不依賴于 props 或 state 中的任何值,所以它永遠(yuǎn)都不需要重復(fù)執(zhí)行。

          這時,已經(jīng)能夠正常獲取數(shù)據(jù)了,但是useEffect會報以下錯誤:Effect callbacks are synchronous to prevent race conditions。問題的原因是請求結(jié)果返回的順序不能保證一致,比如我將App這個組件做為子組件,當(dāng)接收關(guān)鍵詞不同時,會重新渲染組件,那么先請求關(guān)鍵詞為react的文章,然后請求關(guān)鍵詞為vue的文章,但關(guān)鍵詞為vue的請求更先返回。請求更早但返回更晚的情況會錯誤地覆蓋狀態(tài)值data(此時關(guān)鍵詞是vue,但顯示的是后返回react的結(jié)果),這就叫做競態(tài)(race conditions),具體可以點此查看**示例代碼**[3]

          如果你對“競態(tài)”問題還很迷惑,那一定是因為你對useEffect了解得不夠透徹。請先記住這句話,effects 會在每次渲染后運行,并且概念上它是組件輸出的一部分,可以“看到”屬于某次特定渲染的 props 和 state。 仍然以剛才那個例子為例,假設(shè)第一次渲染的時候search(存儲關(guān)鍵詞的state)是react,第二次渲染的時候是search(存儲關(guān)鍵詞的state)是vue。你可能會認(rèn)為發(fā)生了下面的這些事:

          1. 渲染關(guān)鍵詞為react的 UI。
          2. 運行關(guān)鍵詞為react的 effect。 - 從這之后開始搜索關(guān)鍵詞vue
          3. 清除關(guān)鍵詞為react的 effect。
          4. 渲染關(guān)鍵詞為vue的 UI。
          5. 運行關(guān)鍵詞為vue的 effect。

          你可能會認(rèn)為清除過程“看到”的是舊的 props 因為它是在重新渲染之前運行的,新的 effect“看到”的是新的 props 因為它是在重新渲染之后運行的。其實這個認(rèn)知時錯誤的,React 只會在瀏覽器繪制后運行 effects。這使得你的應(yīng)用更流暢因為大多數(shù) effects 并不會阻塞屏幕的更新。Effect 的清除同樣被延遲了。上一次的 effect 會在重新渲染后被清除,真正的執(zhí)行順序如下:

          1. 渲染關(guān)鍵詞為react的 UI。
          2. 運行關(guān)鍵詞為react的 effect。 - 從這之后開始搜索關(guān)鍵詞vue
          3. 渲染關(guān)鍵詞為vue的 UI。
          4. 清除關(guān)鍵詞為react的 effect。
          5. 運行關(guān)鍵詞為vue的 effect。

          你可能會好奇:如果清除上一次的 effect 發(fā)生在search變成vue之后,那它為什么還能“看到”舊的react?這是因為組件內(nèi)的每一個函數(shù)(包括事件處理函數(shù),effects,定時器或者 API 調(diào)用等等)會捕獲定義它們的那次渲染中的propsstate 這正是為什么 React 能做到在繪制后立即處理 effects — 并且默認(rèn)情況下使你的應(yīng)用運行更流暢。但這么處理有利有弊,就導(dǎo)致了異步情況引發(fā)的“競態(tài)”問題。

          useEffect并沒有解決這個問題,而是通過提示警告你不要使用異步函數(shù)。當(dāng)然,可以對useEffect做如下改造:

          如上圖所示,將異步請求函數(shù)封裝在useEffect中,同時使用didCancel這個布爾值來讓我們的數(shù)據(jù)獲取邏輯知道組件的狀態(tài)(安裝/卸載)。如果組件卸載/更新了,則應(yīng)設(shè)置該標(biāo)志為true,以防止組件卸載/更新后異步請求結(jié)果返回設(shè)置組件狀態(tài)導(dǎo)致出現(xiàn)競態(tài)的問題。以剛才那個例子為例,先請求關(guān)鍵詞為react的文章,在請求結(jié)果還沒返回的時候,關(guān)鍵詞變更為vue,組件即將重新渲染,組件執(zhí)行清除函數(shù),將didCancel置為true,此時,react請求的結(jié)果返回,并不會執(zhí)行setData,從而避免競態(tài)的問題,可點擊查看**示例代碼**[4]

          接下來,我們增加關(guān)鍵詞搜索功能,添加input供用戶任意輸入,添加button確認(rèn)用戶要檢索的關(guān)鍵詞,并設(shè)置querysearch兩個狀態(tài)值,同時對請求 Url 拼接上查詢參數(shù)。由于請求依賴search這個狀態(tài)值,所以在useEffect的依賴數(shù)組中,誠實地添加上search(不添加只會在組件掛載時執(zhí)行一次),這樣,如果search發(fā)生改變,就會重新請求數(shù)據(jù)。

          至此,我們已經(jīng)實現(xiàn)了關(guān)鍵詞搜索熱門文章的功能,可在此沙箱[5]中查看代碼示例。

          1.2 加載提示

          使用 UseEffect 異步獲取數(shù)據(jù)[6]示例中能明顯感受到,當(dāng)用戶搜索新的關(guān)鍵詞直至響應(yīng)時,查詢結(jié)果的展示有明顯的“滯后”效果,為了提升用戶體驗,一般都會加個加載中的狀態(tài)提示。如下圖代碼所示,添加isLoading狀態(tài)值,請求發(fā)起時將isLoading設(shè)為true,接收到請求結(jié)果后將isLoading設(shè)置為false。UI 展示通過二元組進行判斷,當(dāng)isLoading設(shè)為true時,展示加載中動畫,相反,則展示請求結(jié)果。

          1.3 錯誤提示

          網(wǎng)絡(luò)環(huán)境錯綜復(fù)雜,當(dāng)一個請求發(fā)起時,可能會遇到丟包、超時、服務(wù)器宕機、請求出錯等各種情況,對于一個高魯棒性的組件而言,添加錯誤處理機制是必不可少的。如下圖代碼所示,添加isError狀態(tài)值,請求發(fā)起時將isError設(shè)為false,請求結(jié)果出錯被catch捕獲則將isError設(shè)置為true。UI 展示通過三元組進行判斷,當(dāng)isError設(shè)為true時,展示錯誤提示,否則如果當(dāng)isLoading設(shè)為true時,展示加載中動畫,相反,則展示請求結(jié)果。

          至此,我們已經(jīng)實現(xiàn)了異步獲取數(shù)據(jù)的各種狀態(tài)處理方案,你可在此沙箱[7]查看示例代碼。發(fā)送請求時到接收請求結(jié)果期間,會有一個加載動畫。當(dāng)輸入script等不安全的關(guān)鍵詞,會報錯。

          2. 使用 useReducer Hook 異步獲取數(shù)據(jù)

          在上面的 Demo 中,我們使用dataisLoadingisError三個狀態(tài)值來標(biāo)記頁面的展示狀態(tài)(通過三元組判斷邏輯),在數(shù)據(jù)請求加載中、成功、失敗等不同狀態(tài)下都會進行一系列的setState,降低可讀性的同時,useEffect重新執(zhí)行時機變得更加難以預(yù)料。這時,可以使用useReducer進行解耦,組件層面只需要發(fā)出action,無需知道如何更新狀態(tài)(reducer負(fù)責(zé)更新狀態(tài))。另一方面,useEffect再也不需要依賴狀態(tài)值,而是依賴dispatch,能夠避免useEffectuseMemouseCallback需要頻繁重執(zhí)行的問題,同時也提高了性能

          如下圖所示,使用useReducer返回一個狀態(tài)對象state和一個改變狀態(tài)對象的函數(shù)dispatch,通過dispatch發(fā)出action去改變狀態(tài)值,組件層面是無需關(guān)心狀態(tài)值如何改變,這一切都封裝在dataFetchReducer函數(shù)中。第 11 行dispatch({ type: "LOADING" })表示將狀態(tài)對象中的isLoading設(shè)置為加載中,第 17 行dispatch({ type: "SUCCESS", payload: result.data.hits })表示將狀態(tài)對象中的data設(shè)置為請求獲取得到的文章列表數(shù)據(jù),第 20 行dispatch({ type: "FAIL" })表示將狀態(tài)對象中的isError設(shè)置為狀態(tài)錯誤。

          接下來,我們關(guān)注dataFetchReducer函數(shù)的實現(xiàn),type不同時,進行對應(yīng)的操作。具體可以點擊此沙箱[8]查看代碼示例。

          3. 使用自定義 Hook 異步獲取數(shù)據(jù)

          3.1 封裝 useAsyncFn Hook

          至此,我們已經(jīng)使用useReducer進行解耦,將狀態(tài)管理從組件中分離出來,組件層面只需要通過dispatch發(fā)出action去改變狀態(tài)值,從某種程度上而言,已經(jīng)具有一定的可復(fù)用性。但是,異步獲取數(shù)據(jù)是前后端通信的“橋梁”,幾乎所有的業(yè)務(wù)模塊都會使用,盡管每個業(yè)務(wù)模塊都可以復(fù)用同一套dataFetchReducer方法,但仍然需要在數(shù)據(jù)的獲取過程中通過dispatch發(fā)出不同的action去改變狀態(tài)值。其實對于業(yè)務(wù)開發(fā)人員而言,僅需要關(guān)心向哪個 URL 發(fā)起請求,已經(jīng)請求得到的結(jié)果是什么,并不需要關(guān)心狀態(tài)值是如何變化的。

          因此,我們可以封裝一個自定義 hook 來實現(xiàn)這個功能,這里將這個 hook 命名為useAsyncFn。思考一下,useAsyncFn這個 hook 的入?yún)⒑统鰠⒃撊绾卧O(shè)計?首先明確該 hook 的目的是實現(xiàn)異步獲取數(shù)據(jù)過程中的狀態(tài)管理,也就是dispatch發(fā)出action去改變狀態(tài)值這些操作進行二次封裝。那么入?yún)⒈厝挥挟惒胶瘮?shù)Fn,異步函數(shù)Fn所依賴的數(shù)組deps,以及初始狀態(tài)值initialState。出參則是狀態(tài)對象state,以及對Fn進行狀態(tài)處理后的回調(diào)函數(shù)callback

          useAsyncFn函數(shù)的入?yún)⒑统鰠⑷缟纤荆酉聛碇v述每個參數(shù)的類型定義,首先入?yún)惒胶瘮?shù)Fn是返回Promise對象的函數(shù),可定義為type FunctionReturningPromise = (...args: any[]) = Promiseany;,由于出參callback的類型依賴異步函數(shù)Fn,所以可以將其使用泛型T代替。異步函數(shù)Fn所依賴的數(shù)組deps類型同 hook 中的依賴數(shù)組一樣,使用react暴露的DependencyList定義。接下來是初始狀態(tài)值initialState,通過前面章節(jié)的描述,它應(yīng)該包含有dataisLoadingerror三個狀態(tài)值,類型定義為AsyncState,由于data這個狀態(tài)值的類型依賴異步函數(shù)Fn返回的結(jié)果,所以可用泛型A表示,AsyncState類型存在如下圖所示四種狀態(tài),第一種狀態(tài)是初始化,isLoading的值未知;第二種狀態(tài)時加載中;第三種狀態(tài)是返回異常結(jié)果,此時isLoading值為falseerror也有對應(yīng)的賦值;第四種狀態(tài)是返回成功結(jié)果,此時isLoading值為falsedata也有對應(yīng)的賦值;由于泛型A需要有明確的定義,所以將AsyncState二次封裝成StateFromFunctionReturningPromise,具體定義可參照下圖:

          入?yún)⒑统鰠⒂懻撏戤叄酉聛砦覀冎v述useAsyncFn函數(shù)的內(nèi)部實現(xiàn),如下圖所示: 定義一個state(useState hook)來管理狀態(tài),定義callback(useCallback hook)作為出參供外部調(diào)用,該callBack是否執(zhí)行,依賴數(shù)組depscallback函數(shù)就是對狀態(tài)管理的二次封裝,第 10 行執(zhí)行setState將狀態(tài)設(shè)置為加載中,第 11 行執(zhí)行異步函數(shù)Fn,并等待返回結(jié)果。如果返回成功結(jié)果,那么跳轉(zhuǎn)到第 13 行執(zhí)行setState將取消加載中狀態(tài)并將返回結(jié)果賦值給data。如果返回異常結(jié)果,那么跳轉(zhuǎn)到第 17 行執(zhí)行setState將取消加載中狀態(tài)并將拋出的異常賦值給error

          至此,你是否感覺到好像缺了點什么?沒錯,就是前面章節(jié)提到的競態(tài)問題,解決方法就是設(shè)置一個布爾值判斷組件是否掛載,等組件卸載/更新后才返回的結(jié)果無效,不更改狀態(tài)。該方法其實也可以封裝成一個自定義 hook,這里將其命名為useMountedState。如下圖所示,使用useRef定義mountedRef實時追蹤組件的狀態(tài),在useEffect執(zhí)行時,將值設(shè)置為true,表示當(dāng)前組件已經(jīng)掛載。在組件卸載/更新時,將值設(shè)置為false。同時在第三行使用useCallback定義一個回調(diào)函數(shù)可以實時獲得mountedRef的值,并將該回調(diào)函數(shù)返回供外部使用。

          接下來講一下useMountedState如何解決組件(掛載/卸載)引起的異步獲取數(shù)據(jù)的競態(tài)問題。如下圖所示,使用useMountedState定義一個“看不見”的狀態(tài)值實時追蹤組件的狀態(tài),并將回調(diào)函數(shù)返回給變量isMounted,執(zhí)行isMonted函數(shù)可以判斷當(dāng)前組件是否還處于掛載中,只有組件仍然處于掛載中,才會對error或者data賦值(分別對應(yīng) 14、19 行代碼)。

          處理完上面的競態(tài)問題,你是否覺得萬事無憂了。那么我想請你思考一下,當(dāng)你反復(fù)調(diào)用對外暴露的callback函數(shù)時,會發(fā)生什么?沒錯,還是競態(tài)問題。

          如何解決這種競態(tài)問題呢?這就需要對業(yè)務(wù)形態(tài)有一個正確的認(rèn)知,當(dāng)反復(fù)調(diào)用對外暴露的callback函數(shù)時,其實用戶想要獲得的只是最新的一次結(jié)果。基于這個認(rèn)知的前提,你可以使用useRef定義一個變量lastCallId實時追蹤當(dāng)前調(diào)用函數(shù)的id,每調(diào)用一次,就累加一。只有最新一次調(diào)用函數(shù)才能對error或者data賦值(分別對應(yīng) 16、21 行代碼)。

          3.2 useAsyncFn Hook 應(yīng)用

          自此,我們已經(jīng)實現(xiàn)useAsyncFn這個自定義 hook,接下來我們用它來改造我們之前的 Demo,如下圖所示,這里不做贅述。

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

          4.1 何為 Suspense

          上一小節(jié),我們已經(jīng)通過封裝自定義 Hook 將數(shù)據(jù)獲取和組件渲染進行解耦,但仍然存在一些不可避免的問題:

          1. 丑陋的三元表達(dá)式:如下圖所示,加載和錯誤狀態(tài)是通過渲染中的三元組定義的,從而使代碼不必要地復(fù)雜化。我們不是描述了一個渲染函數(shù),我們描述了三個。
          1. Fetch-on-render導(dǎo)致的瀑布問題:先開始渲染組件,每個完成渲染的組件都可能在它們的 effects 或者生命周期函數(shù)中獲取數(shù)據(jù),拿到數(shù)據(jù)后對狀態(tài)進行賦值,觸發(fā)更新,就經(jīng)常導(dǎo)致“瀑布”問題。

          這時,React 16.6 新增的 Suspense 組件派上用場了,讓你可以“等待”任何內(nèi)容(圖像、腳本或者其他異步的操作)加載,并且可以直接指定一個加載的界面。

          通常的想法是,Suspense允許組件“懸停”它們的渲染。例如,如果需要從外部來源加載額外數(shù)據(jù),一旦所有依賴的資源(數(shù)據(jù)或資源文件)都存在了,React 將重新嘗試渲染組件。

          為了實現(xiàn)上面描述的功能,React 使用Promises。組件可以在其render方法中拋出Promise,React 捕獲拋出的Promise并在組件樹上查找最接近的Suspense組件,它充當(dāng)一種邊界。Suspense組件接受一個組件作為fallback屬性,當(dāng)其子樹中的任何子項被掛起時,都會呈現(xiàn)該元素。

          React 還會跟蹤拋出的Promise。一旦Promiseresolve了,就會再次渲染組件。這假定由于Promiseresolve,被"懸停"的組件現(xiàn)在已經(jīng)獲取了能夠正確渲染所需的所有信息。為此,我們使用某種形式的緩存來存儲數(shù)據(jù),在每次渲染時,我們通過這個緩存來確定數(shù)據(jù)是否已經(jīng)可用(然后它只是從變量中讀取它), 在這種情況下它會觸發(fā)fetch,并拋出Promise的結(jié)果來讓 React 捕獲。

          4.2 Suspense 原理

          Suspense的核心概念與錯誤邊界[9]非常相似,錯誤邊界在 React 16 中引入,允許在應(yīng)用程序內(nèi)的任何位置捕獲未捕獲的異常,然后在組件樹中展示跟錯誤信息相關(guān)的組件。以同樣的方式,Suspense組件從其子節(jié)點捕獲任何拋出的Promises,不同之處在于對于Suspense我們不必使自定義組件充當(dāng)邊界,Suspense組件就是那個邊界!

          我們知道,無論是什么異常,JavaScript 都能捕獲,React 就是利用了這個語言特性,捕獲了所有生命周期函數(shù),render 函數(shù),以及事件回調(diào)中的任何錯誤,封裝進一個特殊的生命周期里:ComponentDidCatch。實際上,Suspense就是依賴ComponentDidCatch實現(xiàn)的。

          如下圖所示,是一個簡單版本的Suspense實現(xiàn),通過ComponentDidCatch捕獲異常,只不過這個異常是個Promise,當(dāng)Promise處于pending狀態(tài)時,渲染fallback組件,當(dāng)Promise處于resolved狀態(tài)時,渲染children組件。

          4.3 使用 Suspense 改造案例

          如下圖所示,業(yè)務(wù)組件無需關(guān)心數(shù)據(jù)的獲取情況,將其封裝在fetchData中,第 5 行定義一個resource存儲這個支持Suspense的特殊對象。第 6-8 行當(dāng)搜索關(guān)鍵詞發(fā)生變化時,重新觸發(fā)fetchData,并對resource賦值。第 19-28 行對文章列表組件ArticleList進行封裝,第 21 行已經(jīng)在嘗試加載文章列表信息,但是此時可能請求還未加載完畢。如果未加載完畢就會跑出一個Ptomise被包裹的Suspense組件捕獲,而渲染fallback傳入的組件。React 還會跟蹤拋出的Promise。一旦Promiseresolve了,就會渲染組件ArticleList組件。

          接下來,我們談?wù)?code style="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;padding: 3px;border-radius: 4px;margin: 3px;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);word-break: break-all;">fetchData的具體實現(xiàn),第 1-11 行的fetchArticleData函數(shù)發(fā)起請求,并返回一個Promise對象。第 13-18 行的fetchData是所有請求的封裝(假設(shè)業(yè)務(wù)功能豐富,還有其他請求),第 11 行同步執(zhí)行fetchArticleData函數(shù),articlePromise的值是這個Promise對象,然后通過wrapPromise函數(shù)進行封裝,將其返回給業(yè)務(wù)層。

          wrapPromise函數(shù)的實現(xiàn)如下圖所示,接收Promise對象作為參數(shù),由于我們是同步執(zhí)行fetchArticleData函數(shù),所以當(dāng)前的狀態(tài)必為pending,第 4 行設(shè)置suspender存儲Promise對象執(zhí)行后扭轉(zhuǎn)對應(yīng)的狀態(tài)。最后返回一個read方法,當(dāng)狀態(tài)為pending時,拋出這個suspender對象給業(yè)務(wù)層,這個會被Suspense組件的ComponentDidCatch生命周期捕獲,并渲染渲染fallback傳入的組件。當(dāng)狀態(tài)為error時,拋出異常,Suspense組件并不處理這個異常,而是繼續(xù)向外拋出。當(dāng)狀態(tài)為success時,返回結(jié)果給業(yè)務(wù)層進行渲染。

          每當(dāng)使用 Promises,大概率我們會用 catch() 來做錯誤處理。但當(dāng)我們用 Suspense 時,我們不等待 Promises 就直接開始渲染,這時 catch() 就不適用了。這種情況下,錯誤處理該怎么進行呢?

          在 Suspense 中,獲取數(shù)據(jù)時拋出的錯誤和組件渲染時的報錯處理方式一樣——你可以在需要的層級渲染一個錯誤邊界[10]組件來“捕捉”層級下面的所有的報錯信息。如下圖所示,定義ErrorBoundary這個類,當(dāng)拋出錯誤后,使用 static getDerivedStateFromError() 更新state用戶判斷渲染備用 UI ,使用 componentDidCatch() 打印錯誤信息。第 11-20 行,當(dāng)state中的hasErrortrue時,渲染備用 UI,否則渲染children組件。

          你可以在此沙箱[11]中查看示例代碼。

          5. 最后

          從長遠(yuǎn)來看,Suspense是為數(shù)據(jù)請求而生的,會允許第三方庫通過一些方式告訴 React 暫停渲染直到某些異步事物(任何東西:代碼,數(shù)據(jù),圖片)準(zhǔn)備就緒。根據(jù) React 18 發(fā)布預(yù)告,將包括對 React 服務(wù)器端渲染 (SSR) 性能的架構(gòu)改進,這里的優(yōu)化思路就用到Suspense。當(dāng)Suspense逐漸地覆蓋到更多的數(shù)據(jù)請求使用場景,我覺得useEffect 會退居幕后作為一個強大的工具,用于同步propsstate到某些副作用。不過在那之前,自定義的 Hooks 比如這兒提到的是復(fù)用數(shù)據(jù)請求邏輯很好的方式。

          關(guān)于本文

          作者:蔡小真

          來源:https://juejin.cn/post/6981728346937753630

          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認(rèn)真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持

           》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 62
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人超碰福利 | 乱伦大杂烩视频 | 高清国产一卡二卡三卡四卡免费 | 四季AV一区二区凹凸懂色 | 亚洲伊人大香蕉 |