激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

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

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

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

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

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

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

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

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

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

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

1. 構建應用程序

首先,根據(jù)設計圖,我們要考慮應用的結構以及要實現(xiàn)哪些組件:

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

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

國家選項組件:將所有國家呈現(xiàn)在國家列表組件中;

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

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

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

2. 實現(xiàn)頁面組件

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

  • 需要組件包含頁面標題、國家列表和選定國家組件;
  • 將頁面參數(shù)中的國家列表數(shù)據(jù)傳遞給CountriesList組件,以便它可以呈現(xiàn)這些數(shù)據(jù);
  • 頁面中應該有一個已選中國家的狀態(tài),它將從 CountriesList 件接收,并傳遞給 SelectedCountry 組件;
  • 頁面中應該有一個已保存國家的狀態(tài),它將從 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 發(fā)生變化時,組件會重新渲染。在 Page 組件中,當 setSelectedCountry 或者 setSavedCountry 被調用時,組件就會重新渲染。當組件的國家列表數(shù)據(jù)(props)發(fā)生變化時,組件也會重新渲染。CountriesList 和 SelectedCountry 組件也是如此,當它們的 props 發(fā)生變化時,都會重新渲染。

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

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 組件被重新渲染。由此,總結出第一條規(guī)則:「如果想把 props 中的內聯(lián)函數(shù)提取到 useCallback 中,以此來避免子組件的重新渲染,請不要這樣做,它不起作用?!?/p>

現(xiàn)在,有幾種方法可以處理上述情況,最簡單的一種就是使用 useMemo,它本質上就是緩存?zhèn)鬟f給它的函數(shù)的結果。并且僅在 useMemo 的依賴項發(fā)生變化時才會重新執(zhí)行。這就就將 CountriesList 組件使用 useMemo 包裹,只有當 useMemo 依賴項發(fā)生變化時,才會重新渲染 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 狀態(tài);
  • 在 SelectedCountry 組件中不會使用到 savedCountry 狀態(tài);

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

這意味著當 selectedCountry 狀態(tài)發(fā)生變化時,CountriesList 組件不需要重新渲染。savedCountry 狀態(tài)發(fā)生變化時,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>  ); }; 

由此總結出第二條規(guī)則:「如果組件需要管理狀態(tài),就找出渲染樹中不依賴于已更改狀態(tài)的部分,并將其使用 useMemo 包裹,以減少其不必要的重新渲染?!?/p>

4. 實現(xiàn)國家列表組件

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

type CountriesListProps = { countries: Country[]; onCountryChanged: (country: Country) => void; savedCountry: Country; }; export const CountriesList = ({ countries, onCountryChanged, savedCountry }: CountriesListProps) => { const Item = ({ country }: { country: Country }) => { // 根據(jù)國家選項是否已選中來切換不同的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> ); }; 

這里只做了兩件事:

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

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

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

如果在一個組件渲染期間創(chuàng)建了另一個組件(如上面的 Item 組件),會發(fā)生什么呢?從 React 的角度來看,Item 只是一個函數(shù),每次渲染都會返回一個新的結果。它將刪除以前生成的組件,包括其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} /> ))}  ); }; 

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

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

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組件就會重現(xiàn)渲染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> ); }; 

現(xiàn)在,每次父組件重新渲染時不會再重新裝載 Item 組件,由此可以總結出第三條規(guī)則:「不要在一個組件的渲染內創(chuàng)建新的組件?!?/p>

6. 實現(xiàn)選定國家組件

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

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

7. 實現(xiàn)頁面主題

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

首先,創(chuàng)建主題 context:

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

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

為國家選項根據(jù)主題上色:

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

這是一種實現(xiàn)頁面主題的最常見的方式。

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

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

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

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

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

如果現(xiàn)在添加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,那什么都不會發(fā)生—— Item 不是 Page 組件的子級,CountriesList 組件因為被緩存而不會重新渲染,所以 Item 組件也不會。但是,本例是提供者-使用者的模式,因此每次提供者提供的值發(fā)生變化時,所有使用者都將重新渲染。由于一直在向該值傳遞新對象,因此每個計數(shù)器上都會重新呈現(xiàn)不必要的項。Context 基本繞過了useMemo,使它毫無用處。

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

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

現(xiàn)在計數(shù)器就不會再導致所有 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> ) } 

根據(jù)這個結果就可以得出第四條規(guī)則:「在使用 context 時,如果 value 屬性不是數(shù)字、字符串或布爾值,請使用 useMemo 來緩存它?!?/p>

9. 總結

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

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

避免不必要的重新渲染的規(guī)則如下:

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

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

作者:NADIA MAKAREVICH

譯者:CUGGZ

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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: h视频在线免费看 | 亚久久| 一级电影在线观看 | 国产精品一区二av18款 | 欧美日韩一区三区 | 欧美成人精品欧美一级乱黄 | 中文字幕一区2区 | a免费毛片| 国产精品久久久久av | 一区二区三区欧美在线观看 | 国产欧美在线观看不卡一 | 欧美日韩亚洲国产 | 久草在线新时代视觉 | 亚洲一区成人在线观看 | 日韩电影一区二区三区 | 天天曰夜夜操 | 精品影视一区二区 | 麻豆一二区 | 欧美一级淫片免费播放口 | 国产一国产精品一级毛片 | 中文字幕精品在线视频 | 日韩视频―中文字幕 | 欧美一级做一级爱a做片性 久久久资源网 | 午夜视频在线免费 | 久久免费视频精品 | 136福利视频 | 亚洲电影在线观看高清免费 | 国产免费一区二区三区视频 | 久久最新网址 | 日韩精品中文字幕在线播放 | gogo全球大胆高清人露出91 | 国产精品久久久免费观看 | 黄色大片在线免费看 | 免费视频爱爱太爽了 | 午夜视频播放 | 精品久久一区二区三区 | 久草视频手机在线观看 | 精品中文字幕久久久久四十五十骆 | xxxxhd86日本护士hd | 成年免费在线视频 | 久久婷婷一区二区三区 |