網站首頁 編程語言 正文
跨組件狀態管理
簡單場景
對于不復雜的情況,比如父組件傳遞狀態給子組件,可以使用 props
進行傳遞。如果需要傳遞的狀態過多,我們還可以使用組合組件的方法將子組件內的部分組件提升到父組件中去,這樣就不需要再一層層的傳遞狀態了
服務端狀態
對于服務端狀態的維護,也就是發送網絡請求才能獲取到的數據,如果這些數據需要在多個組件中共享,以前可能很多人會選擇用 context
或 redux
來管理服務端的狀態,但這樣會導致他們所維護的狀態樹過重,不利于項目的維護。
不過隨著 react-query
和 swr
這些庫的火爆,越來越多的人愿意使用它們來管理服務端的狀態,除了管理狀態以外,他們還可以對我們的項目做一些優化,比如 react-query
可以避免重復發送相同的請求、以及方便實現樂觀更新等操作
客戶端狀態
對于復雜的客戶端狀態來說,我們一般有幾種方法來管理,一是通過網頁的 url
,這種方式可以有效的管理較少的狀態。二是通過傳統的 redux
,但是大部分的項目其實并沒有那么多的全局狀態要管理,如果都使用 redux
進行管理的話,反而會讓整個項目顯得笨重很多,所以,隨著 react hooks
的火爆,contex
結合 hooks
慢慢成為了項目中狀態管理的主流
項目初始化
- 使用
React
官方提供的腳手架create-react-app
來初始化React + TS
項目 - 使用統一的代碼格式化工具
prettier
(www.prettier.cn/docs/instal…),這樣你的團隊成員無論使用什么IDE
和插件,格式化項目的時候效果都是一樣的,不容易產生分歧 - 配置
commitlint
幫助我們檢查每次git commit
的信息是否符合規范,如果不符合就讓本次提交失敗,這回讓團隊協作的效率更高,項目的可維護性也會更強
Mock 方案
- 直接在代碼中寫死
Mock
數據,或者請求本地的JSON
文件 - 請求攔截向后端發送的請求,比如使用 Mock.js 來模擬數據
- 使用接口管理工具來
mock
數據,比如apipost
、yapi
等等,但前提項目是文檔先行而不是代碼先行 - 自己用
node
開啟一個本地服務器,也可以借助一些好用的庫,比如json-server
錯誤邊界
錯誤邊界是一種 React
組件,這種組件可以捕獲發生在其子組件樹任何位置的 JavaScript
錯誤,并打印這些錯誤,同時展示降級 UI
,而并不會渲染那些發生崩潰的子組件樹。
錯誤邊界可以捕獲發生在整個子組件樹的渲染期間、生命周期方法以及構造函數中的錯誤。
useState 的惰性初始化
當參數是函數的時候,useState
幫我們保存的狀態并不是該函數,而是其返回值,為了獲取到該返回值,react
會在組件第一次渲染的時候執行該函數,后續組件的重復渲染中,該函數就不會再被執行了,所以這種初始化 state
的方法也叫作惰性初始化,其適用于初始 state
需要通過復雜計算才能獲得的情況。所以如果想用 useState
保存函數,不能直接傳入函數,而是需要多嵌套一層函數
樂觀更新
客戶端假設請求必然成功,因此不等待接口的返回,先行對視圖進行更新,隨后再根據請求返回的結果調整數據,如果請求是成功的,則不改變提前更新好的 UI
;如果請求失敗了,則需要將數據和 UI
視圖回滾至請求前的狀態并用此時數據庫中的準確數據代替
性能追蹤
Profiler
測量一個 React
應用多久渲染一次以及渲染一次的“代價”。 它的目的是識別出應用中渲染較慢的部分,或是可以使用類似 memoization
優化的部分,并從相關優化中獲益
性能追蹤是我們在開發項目時經常會被忽略的一個問題,React 恰好為我們提供了用于性能追蹤的一個 API—Profiler,它的用法很簡單,只需要嵌套在組件外部就可以知道該組件渲染所花費的時間,很方便幫助我們去定位哪些組件需要被優化
自動化測試
很多時候我們都需要對原先的項目添加新的功能,相信很多人都會遇到添加了新功能后,原先的功能卻出現 bug
的情況,以至于我們每次加一個功能都還需要手動檢查原先的功能有沒有被影響
自動化測試就可以很方便的解決剛剛的問題,每當代碼更改都會使用我們預先寫好的代碼測試原先的函數、hook
、組件、頁面是否能夠正常工作,返回我們想要的結果,這樣我們開發完新功能之后就不用在手動的去測試了,不過由于這一塊知識點難度較大,所以我也只是做了一個簡單的了解~
Q & A
1. 如何實現頁面刷新后持久化存儲用戶信息?
在正常的項目中,我們可以先在本地保存用戶的 token
和用戶信息,每當用戶初始化頁面時,我們就判斷用戶的瀏覽器本地是否存有 token
,如果沒有則直接跳轉到登錄頁面;如果有則先展示本地存儲的用戶信息,然后根據 token
查詢數據庫中最新的用戶信息并更新到本地
2. 如何在不同路由組件中實現網頁標題的切換?
網上已經針對該問題給出了很多的解決方案,比如可以使用 react-helmet 這個庫,不過我們也可以自己實現:使用原生的 doucument.title
來控制標題的變換。為了增強該功能的復用性,我們可以將其封裝成一個自定義 hook
在不同的組件中使用
export default function useDocumentTitle(title: string, keepOnUnmount: boolean = true) { // 保存當前路由對應的標題 const oldTitle = useRef(document.title).current useEffect(() => { // 當該組件掛載到頁面中時,將開發者指定的文本替換成網頁標題 document.title = title // 根據傳入該函數的參數來決定組件卸載時是否回滾到先前的網頁標題 return () => { if (!keepOnUnmount) document.title = oldTitle } }, [oldTitle, keepOnUnmount, title]) }
3. 如何避免無限渲染問題?
我們盡量控制傳遞給 useEffect
的依賴項數組中的變量可以是組件中管理的狀態 state
,也可以是 useRef
保存的值或者基本數據類型,但一定不能是對象類型的變量,因為不同的對象即使看起來是一樣的但本質上它們的地址卻是不同的,而 react
內部在比較新舊兩個變量時用的是 ===
號,在這種情況下,每次組件重復渲染時 useEffect
所比對出的新舊依賴都不相同,從而導致組件無限渲染。所以如果要傳遞普通對象,則該對象一定需要用 useMemo
包裹處理以避免組件無限循環渲染
4.TS 允許擴展組件的 props
如何在自己封裝的組件以 antd 組件為基礎的情況下,讓 TS 允許擴展組件的 props?
ComponentProps
是 React
內置的類型,用于獲取組件的 props
類型,其需要傳遞一個組件(函數式或類)的類型
import { ComponentProps } from "react"; import { Select } from "antd"; type SelectProps = ComponentProps<typeof Select>; // 其實上述的方法和下面的是等價的 // type SelectProps = Parameters<typeof Select>[0] // Parameters 可以獲取函數的參數并放置到一個元組中,而 antd 的組件恰好是一個函數,所以 [0] 就表示取出 props 的類型 // 創建自己組件的 props 類型,用好 TS 的內置類型 Omit 來防止一些我們添加的屬性影響到原先 antd 組件的屬性 interface IdSelectProps extends Omit< SelectProps, "value" | "setState" | "defaultOptionName" | "options" > { value?: Raw | null | undefined; setState?(value?: number): void; defaultOptionName?: string; options?: { name: string; id: number }[]; } // 在 antd 組件的基礎上再封裝一個組件,使得該組件不僅可以傳遞原先 antd 組件中的參數,還可以傳遞一些我們定義的屬性 export default function IdSelect(props: IdSelectProps) { const { value, setState, defaultOptionName, options, ...restProps } = props; return ( <Select value={value || 0} onChange={(value) => setState?.(toNumber(value) || undefined)} // 這個 {} 并不表示對象的意思,不要理解錯了,而是 jsx 語法要求在變量外邊需要套個 {} {...restProps} > {defaultOptionName ? ( <Select.Option value={0}>{defaultOptionName}</Select.Option> ) : null} {options?.map((item) => item ? ( <Select.Option key={item.id} value={item.id}> {item.name} </Select.Option> ) : null )} </Select> ); }
5. 文件命名
什么時候將文件命名為 tsx 和 jsx,什么時候又命名為 ts 與 js?
當文件中包含組件的時候用 tsx
或 jsx
后綴命名,其它情況下用 ts
或 js
命名就可以了。正確的使用文件后綴名可以提高項目的可讀性,當別人一看到該文件的后綴是以 tsx
結尾的,就能立馬知道該文件中包含的是一個組件而不是普通的函數
6. 什么時候使用組件組合?
Context 主要應用場景在于很多不同層級的組件需要訪問同樣一些的數據。請謹慎使用,因為這會使得組件的復用性變差。
如果你只是想避免層層傳遞一些屬性, 組件組合(component composition) 有時候是一個比 context 更好的解決方案。—— React 官網
聽起來很高大上的名詞,其實非常簡單,就是在面對一些狀態需要層層跨組件傳遞時,如果這些狀態都是集中在某一個區域里面使用,那么可以把這一塊區域抽離出一個組件放置到狀態初始化的地方。這樣原本需要層層傳遞多個狀態,現在就只需要將組件傳遞過去即可。
這種做法還有一個好處就是子組件不需要擔心如何消費上層傳入過來的狀態,只需要將注意力放到渲染傳入進來的組件上,下面是官網給出的示例:
function Page(props) { const user = props.user; const userLink = ( <Link href={user.permalink}> <Avatar user={user} size={props.avatarSize} /> </Link> ); return <PageLayout userLink={userLink} />; } // 現在,我們有這樣的組件: <Page user={user} avatarSize={avatarSize} /> // ... 渲染出 ...Page的子組件 <PageLayout userLink={...} /> // ... 渲染出 ...PageLayout的子組件 <NavigationBar userLink={...} /> // ... 渲染出 ... {props.userLink}
7. 如何將自己的項目部署到 github 上?
- 新建一個名為
你的github用戶名.github.io
的倉庫 - 在開發環境下安裝
gh-pages
依賴yarn add gh-pages -D
,該庫是github
專門為開發者提供用來部署項目的 - 在
package.json
文件夾中找到scripts
字段,新加下列代碼中的信息
// 如果你是用 npm 來啟動項目的,也可以修改為 npm run build "predeploy": "yarn build", // 該字段表示的意思為將打包后的 build 文件夾推送到指定倉庫的 main 分支 "deploy": "gh-pages -d build -r 創建的倉庫地址 -b main"
- 在命令行中執行
yarn deploy
命令,其會預先predeploy
字段對應的命令yarn build
,然后將打包后的內容push
到指定的倉庫中去,所有操作完成之后打開github
指定的網頁即可看到你的應用啦!如果后續需要更新項目,只需要更新代碼后重新執行該命令即可
8. 部署 github 頁面報404
部署到 github 上的項目為啥有時刷新頁面會報 404 的錯誤?
假設我們部署在 github 上的地址為 sindu12jun.github.io,由于我們的項目是單頁面應用,里面用的都是前端路由,如果當前 url 變化為了 sindu12jun.github.io/projects,此時…
這樣服務端接收到這個 url 對應的請求后會誤認為是服務器路由,其并找不到該 url 匹配的接口,也不會像訪問首頁 url 一樣將 index.html 響應給客戶端,而是返回一個404的頁面,網上已經有很多成熟的解決方案了,可以參考 github.com/rafgraph/sp…
9. 我們的項目是用什么工具把 TS 編譯成 JS 文件的?
可能學習過 TS
的朋友都知道其真正要在瀏覽器或者 node
中被執行需要提前編譯成 JS
文件,對于這個編譯工具大家的第一反應可能是 tsc
其實不是, 目前大多數的 ts
項目都是 ts 類型檢查 + babel
編譯 這樣的組合,這個項目也不例外 (可以去項目 node_modules
下面看一下,會發現有個 @babel
文件夾),用 babel
編譯 ts
,就可以實現 babel
編譯一切,從而降低開發/配置成本
10. 所有的函數式組件都應該被 React.memo 所包裹嗎?
在子組件沒有經過特殊處理的情況下,父組件由于狀態改變導致重復渲染時,子組件也會進行重復渲染,但有的時候我們并不想讓子組件進行無用的渲染,這時就會想到在組件外用 React.memo
來包裹
React.memo
會比較函數式組件前后兩次的 props 是否發生了變化,比較方法是淺層比較,如果判斷為沒有變化,則組件不會重新渲染,聽起來是一個很不錯的優化方案,那是不是可以在每一個組件外面都包裹一層 React.memo
呢?
其實是不需要的,React.memo
由于自身需要做前后兩次 props
的淺層比較,是要消耗一定性能的。再者有 React diff
算法的加持下,其實很多DOM
元素并不會被真正的渲染,所以很多組件就算沒有做 memo
優化,仍然不會對項目的性能造成什么影響。
所以我們在想使用 React.memo
之前最好先想想這個組件重新渲染和淺層比較 props
誰花費的性能較大再決定是否使用它,如果子組件本身比較復雜,那確實是可以在它外面套一層 memo
進行優化的
總結
通過這個項目不僅鞏固了自己的 React
和 TS
知識,同時也學到了很多 Mock
數據、性能化、性能追蹤等新知識,希望自己可以憑借新的項目和自己的理解慢慢在前端領域有自己的見解~
原文鏈接:https://juejin.cn/post/7137325435956199455
相關推薦
- 2022-07-08 Python數據分析之使用matplotlib繪制折線圖、柱狀圖和柱線混合圖_python
- 2022-05-16 Qt5中QML自定義環形菜單/環形選擇框的實現_C 語言
- 2022-04-05 關于Unity中RectTransform與transform的區別_C#教程
- 2022-06-30 Python+SymPy實現秒解微積分詳解_python
- 2022-10-05 Android開發Activity毛玻璃背景效果_Android
- 2021-12-06 樹莓派4B+EdgeX+MQTT的填坑之旅
- 2022-11-25 jar包在linux服務器已經運行好但是訪問不到地址的問題及解決方法_Linux
- 2023-03-29 C語言楊氏矩陣實例教你編寫_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支