一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務器之家 - 編程語言 - JavaScript - React - 如何編寫高性能的 React 代碼:規則、模式、注意事項

如何編寫高性能的 React 代碼:規則、模式、注意事項

2022-02-24 21:40前端充電寶CUGGZ React

本文將通過逐步實現一個簡單的應用來帶大家看看如何編寫編寫高性能的 React 代碼。

首先會以常規的模式來實現組件,然后再考慮性能的情況下重構每個步驟,并從每個步驟中提取一個通用規則,這些規則可以應用于大多數應用程序。然后比較最后的結果。

下面將編寫一個“國家設置”頁面,用戶可以從列表中選擇國家,查看該國家的信息,可以保存國家:

如何編寫高性能的 React 代碼:規則、模式、注意事項

可以看到,左側有一個國家列表,帶有“已保存”和“已選擇”狀態,當點擊列表中的選項時,右側會顯示該國家的詳細信息。當按下保存按鈕時,選定的國家會變成已保存,已保存的選項的背景會變成藍色。

1. 構建應用程序

首先,根據設計圖,我們要考慮應用的結構以及要實現哪些組件:

頁面組件:在其中處理提交邏輯和國家選擇邏輯;

國家列表組件:將呈現列表中的所有國家,并進行過濾和排序等操作;

國家選項組件:將所有國家呈現在國家列表組件中;

選定國家組件:將呈現選定國家的詳細信息,并具有保存按鈕。

如何編寫高性能的 React 代碼:規則、模式、注意事項

當然,這不是實現這個頁面的唯一方式,實現方式僅供參考。下面就來看看如何實現這些組件。

2. 實現頁面組件

下面終于要開始寫代碼了,下面就從根開始,實現Page組件,步驟如下:

  • 需要組件包含頁面標題、國家列表和選定國家組件;
  • 將頁面參數中的國家列表數據傳遞給CountriesList組件,以便它可以呈現這些數據;
  • 頁面中應該有一個已選中國家的狀態,它將從 CountriesList 件接收,并傳遞給 SelectedCountry 組件;
  • 頁面中應該有一個已保存國家的狀態,它將從 SelectedCountry 組件接收,并傳遞給 CountriesList 組件。
export const Page = ({ countries }: { countries: Country[] }) => { const [selectedCountry, setSelectedCountry] = useState<Country>(countries[0]); const [savedCountry, setSavedCountry] = useState<Country>(countries[0]); return ( <> <h1>Country settingsh1> <div css={contentCss}> <CountriesList
          countries={countries} onCountryChanged={(c) => setSelectedCountry(c)} savedCountry={savedCountry} /> <SelectedCountry
          country={selectedCountry} onCountrySaved={() => setSavedCountry(selectedCountry)} /> div>  ); }; 

3. 重構頁面組件(考慮性能)

在 React 中,當組件的 state 或者 props 發生變化時,組件會重新渲染。在 Page 組件中,當 setSelectedCountry 或者 setSavedCountry 被調用時,組件就會重新渲染。當組件的國家列表數據(props)發生變化時,組件也會重新渲染。CountriesList 和 SelectedCountry 組件也是如此,當它們的 props 發生變化時,都會重新渲染。

我們知道,React 會對 props 進行嚴格相等的比較,并且內聯函數每次都會創建新值。這就導致了一個錯誤的的觀念:為了減少 CountriesList 和SelectedCountry 組件的重新渲染,需要通過在 useCallback 中包裝內聯函數來避免在每次渲染中重新創建內聯函數。

export const Page = ({ countries }: { countries: Country[] }) => { const onCountryChanged = useCallback((c) => setSelectedCountry(c), []); const onCountrySaved = useCallback(() => setSavedCountry(selectedCountry), []); return ( <> ... <CountriesList
          onCountryChanged={onCountryChange} /> <SelectedCountry
          onCountrySaved={onCountrySaved} /> ...  ); }; 

而實際上,這樣并不會起作用。因為它沒有考慮到:如果父組件 Page 被重新渲染,子組件 CountriesList 也總是會重新渲染,即使它根本沒有任何 props。

可以這樣來簡化 Page 組件:

const CountriesList = () => { console.log("Re-render!!!!!"); return <div>countries list, always re-rendersdiv>; }; export const Page = ({ countries }: { countries: Country[] }) => { const [counter, setCounter] = useState<number>(1); return ( <> <h1>Country settingsh1> <button onClick={() => setCounter(counter + 1)}> Click here to re-render Countries list (open the console) {counter} button> <CountriesList />  ); }; 

當每次點擊按鈕時,即使沒有任何 props,都會看到 CountriesList 組件被重新渲染。由此,總結出第一條規則:「如果想把 props 中的內聯函數提取到 useCallback 中,以此來避免子組件的重新渲染,請不要這樣做,它不起作用。」

現在,有幾種方法可以處理上述情況,最簡單的一種就是使用 useMemo,它本質上就是緩存傳遞給它的函數的結果。并且僅在 useMemo 的依賴項發生變化時才會重新執行。這就就將 CountriesList 組件使用 useMemo 包裹,只有當 useMemo 依賴項發生變化時,才會重新渲染 ComponentList 組件:

export const Page = ({ countries }: { countries: Country[] }) => { const [counter, setCounter] = useState<number>(1); const list = useMemo(() => { return <CountriesList />; }, []); return ( <> <h1>Country settingsh1> <button onClick={() => setCounter(counter + 1)}> Click here to re-render Countries list (open the console) {counter} button> {list}  ); }; 

當然,在這個簡化的例子中是不行的,因為它沒有任何依賴項。那我們該如何簡化 Page 頁面呢?下面再來看一下它的結構:

export const Page = ({ countries }: { countries: Country[] }) => { const [selectedCountry, setSelectedCountry] = useState<Country>(countries[0]); const [savedCountry, setSavedCountry] = useState<Country>(countries[0]); return ( <> <h1>Country settingsh1> <div css={contentCss}> <CountriesList
          countries={countries} onCountryChanged={(c) => setSelectedCountry(c)} savedCountry={savedCountry} /> <SelectedCountry
          country={selectedCountry} onCountrySaved={() => setSavedCountry(selectedCountry)} /> div>  ); }; 

可以看到:

  • 在 CountriesList 組件中不會使到 selectedCountry 狀態;
  • 在 SelectedCountry 組件中不會使用到 savedCountry 狀態;

如何編寫高性能的 React 代碼:規則、模式、注意事項

這意味著當 selectedCountry 狀態發生變化時,CountriesList 組件不需要重新渲染。savedCountry 狀態發生變化時,SelectedCountry 組件也不需要重新渲染。可以使用 useMemo 來包裹它們,以防止不必要的重新渲染:

export const Page = ({ countries }: { countries: Country[] }) => { const [selectedCountry, setSelectedCountry] = useState<Country>(countries[0]); const [savedCountry, setSavedCountry] = useState<Country>(countries[0]); const list = useMemo(() => { return ( <CountriesList
        countries={countries} onCountryChanged={(c) => setSelectedCountry(c)} savedCountry={savedCountry} /> ); }, [savedCountry, countries]); const selected = useMemo(() => { return ( <SelectedCountry
        country={selectedCountry} onCountrySaved={() => setSavedCountry(selectedCountry)} /> ); }, [selectedCountry]); return ( <> <h1>Country settingsh1> <div css={contentCss}> {list} {selected} div>  ); }; 

由此總結出第二條規則:「如果組件需要管理狀態,就找出渲染樹中不依賴于已更改狀態的部分,并將其使用 useMemo 包裹,以減少其不必要的重新渲染。」

4. 實現國家列表組件

Page 頁面已經完美實現了,是時候編寫它的子組件了。首先來實現比較復雜的 CountriesList 組件。這個組件一個接收國家列表數據,當在列表中選中一個國家時,會觸發 onCountryChanged 回調,并應以不同的顏色突出顯示保存的國家:

type CountriesListProps = { countries: Country[]; onCountryChanged: (country: Country) => void; savedCountry: Country; }; export const CountriesList = ({ countries, onCountryChanged, savedCountry }: CountriesListProps) => { const Item = ({ country }: { country: Country }) => { // 根據國家選項是否已選中來切換不同的className
    const className = savedCountry.id === country.id ? "country-item saved" : "country-item"; const onItemClick = () => onCountryChanged(country); return ( <button className={className} onClick={onItemClick}> <img src={country.flagUrl} /> <span>{country.name}span> button> ); }; return ( <div> {countries.map((country) => ( <Item country={country} key={country.id} /> ))} div> ); }; 

這里只做了兩件事:

根據接收到的 props 來生成 Item 組件,它依賴于onCountryChanged和savedCountry;

遍歷 props 中的國家數組,來渲染國家列表。

5. 重構國家列表組件(考慮性能)

如果在一個組件渲染期間創建了另一個組件(如上面的 Item 組件),會發生什么呢?從 React 的角度來看,Item 只是一個函數,每次渲染都會返回一個新的結果。它將刪除以前生成的組件,包括其DOM樹,將其從頁面中刪除,并生成和裝載一個全新的組件。每次父組件重新渲染時,都會使用一個全新的DOM樹。

如果簡化國家示例來展示這個過程,將是這樣的:

const CountriesList = ({ countries }: { countries: Country[] }) => { const Item = ({ country }: { country: Country }) => { useEffect(() => { console.log("Mounted!"); }, []); console.log("Render"); return <div>{country.name}div>; }; return ( <> {countries.map((country) => ( <Item country={country} /> ))}  ); }; 

從性能角度來看,與完全重新創建組件相比,正常的重新渲染的性能會好很多。在正常情況下,帶有空依賴項數組的 useEffect 只會在組件完成裝載和第一次渲染后觸發一次。之后,React 中的輕量級重新渲染過程就開始了,組件不是從頭開始創建的,而是只在需要時更新。這里假如有100個國家,當點擊按鈕時,就會輸出100次 Render 和100次 Mounted,Item 組件會重新裝載和渲染100次。

解決這個問題最直接的辦法就是將 Item 組件移到渲染函數外:

const Item = ({ country }: { country: Country }) => { useEffect(() => { console.log("Mounted!"); }, []); console.log("Render"); return <div>{country.name}div>; }; const CountriesList = ({ countries }: { countries: Country[] }) => { return ( <> {countries.map((country) => ( <Item country={country} /> ))}  ); }; 

這樣在點擊按鈕時,Item組件就會重現渲染100次,只輸出100次 Render,而不會重新裝載組件。保持了不同組件之間的邊界,并使代碼更簡潔。下面就來看看國家列表組件在修改前后的變化。

修改前:

export const CountriesList = ({ countries, onCountryChanged, savedCountry }: CountriesListProps) => { const Item = ({ country }: { country: Country }) => { // ... }; return ( <div> {countries.map((country) => ( <Item country={country} key={country.id} /> ))} div> ); }; 

修改后:

type ItemProps = { country: Country; savedCountry: Country; onItemClick: () => void; }; const Item = ({ country, savedCountry, onItemClick }: ItemProps) => { // ... }; export const CountriesList = ({ countries, onCountryChanged, savedCountry }: CountriesListProps) => { return ( <div> {countries.map((country) => ( <Item
          country={country} key={country.id} savedCountry={savedCountry} onItemClick={() => onCountryChanged(country)} /> ))} div> ); }; 

現在,每次父組件重新渲染時不會再重新裝載 Item 組件,由此可以總結出第三條規則:「不要在一個組件的渲染內創建新的組件。」

6. 實現選定國家組件

這個組件比較簡單,就是接收一個屬性和一個回調函數,并呈現國家信息:

const SelectedCountry = ({ country, onSaveCountry }: { country: Country; onSaveCountry: () => void }) => { return ( <> <ul> <li>Country: {country.name}li> ... // 要渲染的國家信息 ul> <button onClick={onSaveCountry} type="button">Savebutton>  ); }; 

7. 實現頁面主題

最后來實現頁面的黑暗模式和明亮模式的切換。考慮到當前主題會在很多組件中使用,如果通過 props 傳遞就會非常麻煩。因此 Context 是比較合適的解決方案。

首先,創建主題 context:

type Mode = 'light' | 'dark'; type Theme = { mode: Mode }; const ThemeContext = React.createContext<Theme>({ mode: 'light' }); const useTheme = () => { return useContext(ThemeContext); }; 

添加 context provider ,以及切換主題的按鈕:

為國家選項根據主題上色:

const Item = ({ country }: { country: Country }) => { const { mode } = useTheme(); const className = `country-item ${mode === "dark" ? "dark" : ""}`; // ... } 

這是一種實現頁面主題的最常見的方式。

8. 重構主題(考慮性能)

在發現上面組件的問題之前,先來看看導致組件重新渲染的另一個原因:如果一個組件使用 context,當 provider 提供的值發生變化時,該組件就會重新渲染。

再來看看簡化的例子,這里記錄了渲染結果以避免重新渲染:

const Item = ({ country }: { country: Country }) => { console.log("render"); return <div>{country.name}div>; }; const CountriesList = ({ countries }: { countries: Country[] }) => { return ( <> {countries.map((country) => ( <Item country={country} /> ))}  ); }; export const Page = ({ countries }: { countries: Country[] }) => { const [counter, setCounter] = useState<number>(1); const list = useMemo(() => <CountriesList countries={countries} />, [ countries ]); return ( <> <h1>Country settingsh1> <button onClick={() => setCounter(counter + 1)}> Click here to re-render Countries list (open the console) {counter} button> {list}  ); }; 

每次點擊按鈕時,頁面狀態發生變化,頁面組件會重新渲染。但 CountriesList 組件使用useMemo緩存了,會獨立于該狀態,因此不會重新渲染,因此 Item 組件也不會重新渲染。

如果現在添加Theme context,Provider 在 Page 組件中:

export const Page = ({ countries }: { countries: Country[] }) => { // ...
  const list = useMemo(() => <CountriesList countries={countries} />, [ countries ]); return ( <ThemeContext.Provider value={{ mode }}> // ... ThemeContext.Provider> ); }; 

context 在 Item 組件中:

const Item = ({ country }: { country: Country }) => { const theme = useTheme(); console.log("render"); return <div>{country.name}div>; }; 

如果它們只是普通的組件和Hook,那什么都不會發生—— Item 不是 Page 組件的子級,CountriesList 組件因為被緩存而不會重新渲染,所以 Item 組件也不會。但是,本例是提供者-使用者的模式,因此每次提供者提供的值發生變化時,所有使用者都將重新渲染。由于一直在向該值傳遞新對象,因此每個計數器上都會重新呈現不必要的項。Context 基本繞過了useMemo,使它毫無用處。

解決方法就是確保 provider 中的值不會發生超出需要的變化。這里只需要把它記下來:

export const Page = ({ countries }: { countries: Country[] }) => { // ...
  const theme = useMemo(() => ({ mode }), [mode]); return ( <ThemeContext.Provider value={theme}> // ... ThemeContext.Provider> ); }; 

現在計數器就不會再導致所有 Items 重新渲染。下面就將這個解決方案應用于主題組件,以防止不必要的重新渲染:

export const Page = ({ countries }: { countries: Country[] }) => { // ...
  const [mode, setMode] = useState<Mode>("light"); const theme = useMemo(() => ({ mode }), [mode]); return ( <ThemeContext.Provider value={theme}> <button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>Toggle themebutton> // ... ThemeContext.Provider> ) } 

根據這個結果就可以得出第四條規則:「在使用 context 時,如果 value 屬性不是數字、字符串或布爾值,請使用 useMemo 來緩存它。」

9. 總結

導致 React 組件重新渲染的時機主要有以下三種:

  • 當state或props發生變化時;
  • 當父組件重現渲染時;
  • 當組件使用 context,并且 provider 的值發生變化時。

避免不必要的重新渲染的規則如下:

  • 如果想把 props 中的內聯函數提取到 useCallback 中,以此來避免子組件的重新渲染,不要這樣做,它不起作用。
  • 如果組件需要管理狀態,就找出渲染樹中不依賴于已更改狀態的部分,并將其使用 useMemo 包裹,以減少其不必要的重新渲染。
  • 不要在一個組件的渲染內創建新的組件;
  • 在使用 context 時,如果value屬性不是數字、字符串或布爾值,請使用useMemo來緩存它。

這些規則將有助于從開始就編寫高性能的 React 應用程序。

作者:NADIA MAKAREVICH

譯者:CUGGZ

原文:https://www.developerway.com/posts/how-to-write-performant-react-code

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产一区二区三区毛片 | 猥琐对着美女飞机喷到脸上 | 娇小异类videos| 99热在线国产 | 精品国产一区二区在线观看 | 大胆暴露亚洲美女xxxx | 成版人快猫永久破解版 | 国产精品秒播无毒不卡 | 国产福利不卡一区二区三区 | 欧美成人香蕉在线观看 | 久久视频这里只精品99热在线观看 | h日本漫画全彩在线观看 | 日本高清中文字幕一区二区三区 | 成人观看免费观看视频 | 高清不卡日本v在线二区 | 精品欧美一区二区三区久久久 | 国产精品视频第一区二区 | 黄瓜视频导航 | 亚洲老头与老太hd | 国产精品视频在线观看 | 美女张开双腿让男人捅 | 午夜久久久久久网站 | 男人懂得网站 | 4hu影院在线观看 | 午夜宅男在线观看 | 国士李风起全文在线阅读 | 黄 色 成 年人在线 幻女free性俄罗斯第一次摘花 | 99精品久久久久久 | 好大夫在线个人空间 | 天天干夜夜玩 | 国产成人激烈叫床视频 | 99在线精品日韩一区免费国产 | 俄罗斯一级淫片bbbb | 无限资源在线观看播放 | 白丝爆动漫羞羞动漫软件 | 四虎影视e456fcom四虎影视 | 波多野结衣中文字幕乱七八糟 | 亚洲AV久久无码精品蜜桃 | 深夜在线观看网站 | 人人最怕九月羊 | 乌克兰精品摘花处破 |