Jira 任務(wù)管理系統(tǒng)項(xiàng)目總結(jié)講解
跨組件狀態(tài)管理
簡(jiǎn)單場(chǎng)景
對(duì)于不復(fù)雜的情況,比如父組件傳遞狀態(tài)給子組件,可以使用 props
進(jìn)行傳遞。如果需要傳遞的狀態(tài)過(guò)多,我們還可以使用組合組件的方法將子組件內(nèi)的部分組件提升到父組件中去,這樣就不需要再一層層的傳遞狀態(tài)了
服務(wù)端狀態(tài)
對(duì)于服務(wù)端狀態(tài)的維護(hù),也就是發(fā)送網(wǎng)絡(luò)請(qǐng)求才能獲取到的數(shù)據(jù),如果這些數(shù)據(jù)需要在多個(gè)組件中共享,以前可能很多人會(huì)選擇用 context
或 redux
來(lái)管理服務(wù)端的狀態(tài),但這樣會(huì)導(dǎo)致他們所維護(hù)的狀態(tài)樹過(guò)重,不利于項(xiàng)目的維護(hù)。
不過(guò)隨著 react-query
和 swr
這些庫(kù)的火爆,越來(lái)越多的人愿意使用它們來(lái)管理服務(wù)端的狀態(tài),除了管理狀態(tài)以外,他們還可以對(duì)我們的項(xiàng)目做一些優(yōu)化,比如 react-query
可以避免重復(fù)發(fā)送相同的請(qǐng)求、以及方便實(shí)現(xiàn)樂觀更新等操作
客戶端狀態(tài)
對(duì)于復(fù)雜的客戶端狀態(tài)來(lái)說(shuō),我們一般有幾種方法來(lái)管理,一是通過(guò)網(wǎng)頁(yè)的 url
,這種方式可以有效的管理較少的狀態(tài)。二是通過(guò)傳統(tǒng)的 redux
,但是大部分的項(xiàng)目其實(shí)并沒有那么多的全局狀態(tài)要管理,如果都使用 redux
進(jìn)行管理的話,反而會(huì)讓整個(gè)項(xiàng)目顯得笨重很多,所以,隨著 react hooks
的火爆,contex
結(jié)合 hooks
慢慢成為了項(xiàng)目中狀態(tài)管理的主流
項(xiàng)目初始化
- 使用
React
官方提供的腳手架create-react-app
來(lái)初始化React + TS
項(xiàng)目 - 使用統(tǒng)一的代碼格式化工具
prettier
(www.prettier.cn/docs/instal…),這樣你的團(tuán)隊(duì)成員無(wú)論使用什么IDE
和插件,格式化項(xiàng)目的時(shí)候效果都是一樣的,不容易產(chǎn)生分歧 - 配置
commitlint
幫助我們檢查每次git commit
的信息是否符合規(guī)范,如果不符合就讓本次提交失敗,這回讓團(tuán)隊(duì)協(xié)作的效率更高,項(xiàng)目的可維護(hù)性也會(huì)更強(qiáng)
Mock 方案
- 直接在代碼中寫死
Mock
數(shù)據(jù),或者請(qǐng)求本地的JSON
文件 - 請(qǐng)求攔截向后端發(fā)送的請(qǐng)求,比如使用 Mock.js 來(lái)模擬數(shù)據(jù)
- 使用接口管理工具來(lái)
mock
數(shù)據(jù),比如apipost
、yapi
等等,但前提項(xiàng)目是文檔先行而不是代碼先行 - 自己用
node
開啟一個(gè)本地服務(wù)器,也可以借助一些好用的庫(kù),比如json-server
錯(cuò)誤邊界
錯(cuò)誤邊界是一種 React
組件,這種組件可以捕獲發(fā)生在其子組件樹任何位置的 JavaScript
錯(cuò)誤,并打印這些錯(cuò)誤,同時(shí)展示降級(jí) UI
,而并不會(huì)渲染那些發(fā)生崩潰的子組件樹。
錯(cuò)誤邊界可以捕獲發(fā)生在整個(gè)子組件樹的渲染期間、生命周期方法以及構(gòu)造函數(shù)中的錯(cuò)誤。
useState 的惰性初始化
當(dāng)參數(shù)是函數(shù)的時(shí)候,useState
幫我們保存的狀態(tài)并不是該函數(shù),而是其返回值,為了獲取到該返回值,react
會(huì)在組件第一次渲染的時(shí)候執(zhí)行該函數(shù),后續(xù)組件的重復(fù)渲染中,該函數(shù)就不會(huì)再被執(zhí)行了,所以這種初始化 state
的方法也叫作惰性初始化,其適用于初始 state
需要通過(guò)復(fù)雜計(jì)算才能獲得的情況。所以如果想用 useState
保存函數(shù),不能直接傳入函數(shù),而是需要多嵌套一層函數(shù)
樂觀更新
客戶端假設(shè)請(qǐng)求必然成功,因此不等待接口的返回,先行對(duì)視圖進(jìn)行更新,隨后再根據(jù)請(qǐng)求返回的結(jié)果調(diào)整數(shù)據(jù),如果請(qǐng)求是成功的,則不改變提前更新好的 UI
;如果請(qǐng)求失敗了,則需要將數(shù)據(jù)和 UI
視圖回滾至請(qǐng)求前的狀態(tài)并用此時(shí)數(shù)據(jù)庫(kù)中的準(zhǔn)確數(shù)據(jù)代替
性能追蹤
Profiler
測(cè)量一個(gè) React
應(yīng)用多久渲染一次以及渲染一次的“代價(jià)”。 它的目的是識(shí)別出應(yīng)用中渲染較慢的部分,或是可以使用類似 memoization
優(yōu)化的部分,并從相關(guān)優(yōu)化中獲益
性能追蹤是我們?cè)陂_發(fā)項(xiàng)目時(shí)經(jīng)常會(huì)被忽略的一個(gè)問題,React 恰好為我們提供了用于性能追蹤的一個(gè) API—Profiler,它的用法很簡(jiǎn)單,只需要嵌套在組件外部就可以知道該組件渲染所花費(fèi)的時(shí)間,很方便幫助我們?nèi)ザㄎ荒男┙M件需要被優(yōu)化
自動(dòng)化測(cè)試
很多時(shí)候我們都需要對(duì)原先的項(xiàng)目添加新的功能,相信很多人都會(huì)遇到添加了新功能后,原先的功能卻出現(xiàn) bug
的情況,以至于我們每次加一個(gè)功能都還需要手動(dòng)檢查原先的功能有沒有被影響
自動(dòng)化測(cè)試就可以很方便的解決剛剛的問題,每當(dāng)代碼更改都會(huì)使用我們預(yù)先寫好的代碼測(cè)試原先的函數(shù)、hook
、組件、頁(yè)面是否能夠正常工作,返回我們想要的結(jié)果,這樣我們開發(fā)完新功能之后就不用在手動(dòng)的去測(cè)試了,不過(guò)由于這一塊知識(shí)點(diǎn)難度較大,所以我也只是做了一個(gè)簡(jiǎn)單的了解~
Q & A
1. 如何實(shí)現(xiàn)頁(yè)面刷新后持久化存儲(chǔ)用戶信息?
在正常的項(xiàng)目中,我們可以先在本地保存用戶的 token
和用戶信息,每當(dāng)用戶初始化頁(yè)面時(shí),我們就判斷用戶的瀏覽器本地是否存有 token
,如果沒有則直接跳轉(zhuǎn)到登錄頁(yè)面;如果有則先展示本地存儲(chǔ)的用戶信息,然后根據(jù) token
查詢數(shù)據(jù)庫(kù)中最新的用戶信息并更新到本地
2. 如何在不同路由組件中實(shí)現(xiàn)網(wǎng)頁(yè)標(biāo)題的切換?
網(wǎng)上已經(jīng)針對(duì)該問題給出了很多的解決方案,比如可以使用 react-helmet 這個(gè)庫(kù),不過(guò)我們也可以自己實(shí)現(xiàn):使用原生的 doucument.title
來(lái)控制標(biāo)題的變換。為了增強(qiáng)該功能的復(fù)用性,我們可以將其封裝成一個(gè)自定義 hook
在不同的組件中使用
export default function useDocumentTitle(title: string, keepOnUnmount: boolean = true) { // 保存當(dāng)前路由對(duì)應(yīng)的標(biāo)題 const oldTitle = useRef(document.title).current useEffect(() => { // 當(dāng)該組件掛載到頁(yè)面中時(shí),將開發(fā)者指定的文本替換成網(wǎng)頁(yè)標(biāo)題 document.title = title // 根據(jù)傳入該函數(shù)的參數(shù)來(lái)決定組件卸載時(shí)是否回滾到先前的網(wǎng)頁(yè)標(biāo)題 return () => { if (!keepOnUnmount) document.title = oldTitle } }, [oldTitle, keepOnUnmount, title]) }
3. 如何避免無(wú)限渲染問題?
我們盡量控制傳遞給 useEffect
的依賴項(xiàng)數(shù)組中的變量可以是組件中管理的狀態(tài) state
,也可以是 useRef
保存的值或者基本數(shù)據(jù)類型,但一定不能是對(duì)象類型的變量,因?yàn)椴煌膶?duì)象即使看起來(lái)是一樣的但本質(zhì)上它們的地址卻是不同的,而 react
內(nèi)部在比較新舊兩個(gè)變量時(shí)用的是 ===
號(hào),在這種情況下,每次組件重復(fù)渲染時(shí) useEffect
所比對(duì)出的新舊依賴都不相同,從而導(dǎo)致組件無(wú)限渲染。所以如果要傳遞普通對(duì)象,則該對(duì)象一定需要用 useMemo
包裹處理以避免組件無(wú)限循環(huán)渲染
4.TS 允許擴(kuò)展組件的 props
如何在自己封裝的組件以 antd 組件為基礎(chǔ)的情況下,讓 TS 允許擴(kuò)展組件的 props?
ComponentProps
是 React
內(nèi)置的類型,用于獲取組件的 props
類型,其需要傳遞一個(gè)組件(函數(shù)式或類)的類型
import { ComponentProps } from "react"; import { Select } from "antd"; type SelectProps = ComponentProps<typeof Select>; // 其實(shí)上述的方法和下面的是等價(jià)的 // type SelectProps = Parameters<typeof Select>[0] // Parameters 可以獲取函數(shù)的參數(shù)并放置到一個(gè)元組中,而 antd 的組件恰好是一個(gè)函數(shù),所以 [0] 就表示取出 props 的類型 // 創(chuàng)建自己組件的 props 類型,用好 TS 的內(nèi)置類型 Omit 來(lái)防止一些我們添加的屬性影響到原先 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 組件的基礎(chǔ)上再封裝一個(gè)組件,使得該組件不僅可以傳遞原先 antd 組件中的參數(shù),還可以傳遞一些我們定義的屬性 export default function IdSelect(props: IdSelectProps) { const { value, setState, defaultOptionName, options, ...restProps } = props; return ( <Select value={value || 0} onChange={(value) => setState?.(toNumber(value) || undefined)} // 這個(gè) {} 并不表示對(duì)象的意思,不要理解錯(cuò)了,而是 jsx 語(yǔ)法要求在變量外邊需要套個(gè) {} {...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. 文件命名
什么時(shí)候?qū)⑽募麨?tsx 和 jsx,什么時(shí)候又命名為 ts 與 js?
當(dāng)文件中包含組件的時(shí)候用 tsx
或 jsx
后綴命名,其它情況下用 ts
或 js
命名就可以了。正確的使用文件后綴名可以提高項(xiàng)目的可讀性,當(dāng)別人一看到該文件的后綴是以 tsx
結(jié)尾的,就能立馬知道該文件中包含的是一個(gè)組件而不是普通的函數(shù)
6. 什么時(shí)候使用組件組合?
Context 主要應(yīng)用場(chǎng)景在于很多不同層級(jí)的組件需要訪問同樣一些的數(shù)據(jù)。請(qǐng)謹(jǐn)慎使用,因?yàn)檫@會(huì)使得組件的復(fù)用性變差。
如果你只是想避免層層傳遞一些屬性, 組件組合(component composition) 有時(shí)候是一個(gè)比 context 更好的解決方案。—— React 官網(wǎng)
聽起來(lái)很高大上的名詞,其實(shí)非常簡(jiǎn)單,就是在面對(duì)一些狀態(tài)需要層層跨組件傳遞時(shí),如果這些狀態(tài)都是集中在某一個(gè)區(qū)域里面使用,那么可以把這一塊區(qū)域抽離出一個(gè)組件放置到狀態(tài)初始化的地方。這樣原本需要層層傳遞多個(gè)狀態(tài),現(xiàn)在就只需要將組件傳遞過(guò)去即可。
這種做法還有一個(gè)好處就是子組件不需要擔(dān)心如何消費(fèi)上層傳入過(guò)來(lái)的狀態(tài),只需要將注意力放到渲染傳入進(jìn)來(lái)的組件上,下面是官網(wǎng)給出的示例:
function Page(props) { const user = props.user; const userLink = ( <Link href={user.permalink}> <Avatar user={user} size={props.avatarSize} /> </Link> ); return <PageLayout userLink={userLink} />; } // 現(xiàn)在,我們有這樣的組件: <Page user={user} avatarSize={avatarSize} /> // ... 渲染出 ...Page的子組件 <PageLayout userLink={...} /> // ... 渲染出 ...PageLayout的子組件 <NavigationBar userLink={...} /> // ... 渲染出 ... {props.userLink}
7. 如何將自己的項(xiàng)目部署到 github 上?
- 新建一個(gè)名為
你的github用戶名.github.io
的倉(cāng)庫(kù) - 在開發(fā)環(huán)境下安裝
gh-pages
依賴yarn add gh-pages -D
,該庫(kù)是github
專門為開發(fā)者提供用來(lái)部署項(xiàng)目的 - 在
package.json
文件夾中找到scripts
字段,新加下列代碼中的信息
// 如果你是用 npm 來(lái)啟動(dòng)項(xiàng)目的,也可以修改為 npm run build "predeploy": "yarn build", // 該字段表示的意思為將打包后的 build 文件夾推送到指定倉(cāng)庫(kù)的 main 分支 "deploy": "gh-pages -d build -r 創(chuàng)建的倉(cāng)庫(kù)地址 -b main"
- 在命令行中執(zhí)行
yarn deploy
命令,其會(huì)預(yù)先predeploy
字段對(duì)應(yīng)的命令yarn build
,然后將打包后的內(nèi)容push
到指定的倉(cāng)庫(kù)中去,所有操作完成之后打開github
指定的網(wǎng)頁(yè)即可看到你的應(yīng)用啦!如果后續(xù)需要更新項(xiàng)目,只需要更新代碼后重新執(zhí)行該命令即可
8. 部署 github 頁(yè)面報(bào)404
部署到 github 上的項(xiàng)目為啥有時(shí)刷新頁(yè)面會(huì)報(bào) 404 的錯(cuò)誤?
假設(shè)我們部署在 github 上的地址為 sindu12jun.github.io,由于我們的項(xiàng)目是單頁(yè)面應(yīng)用,里面用的都是前端路由,如果當(dāng)前 url 變化為了 sindu12jun.github.io/projects,此時(shí)…
這樣服務(wù)端接收到這個(gè) url 對(duì)應(yīng)的請(qǐng)求后會(huì)誤認(rèn)為是服務(wù)器路由,其并找不到該 url 匹配的接口,也不會(huì)像訪問首頁(yè) url 一樣將 index.html 響應(yīng)給客戶端,而是返回一個(gè)404的頁(yè)面,網(wǎng)上已經(jīng)有很多成熟的解決方案了,可以參考 github.com/rafgraph/sp…
9. 我們的項(xiàng)目是用什么工具把 TS 編譯成 JS 文件的?
可能學(xué)習(xí)過(guò) TS
的朋友都知道其真正要在瀏覽器或者 node
中被執(zhí)行需要提前編譯成 JS
文件,對(duì)于這個(gè)編譯工具大家的第一反應(yīng)可能是 tsc
其實(shí)不是, 目前大多數(shù)的 ts
項(xiàng)目都是 ts 類型檢查 + babel
編譯 這樣的組合,這個(gè)項(xiàng)目也不例外 (可以去項(xiàng)目 node_modules
下面看一下,會(huì)發(fā)現(xiàn)有個(gè) @babel
文件夾),用 babel
編譯 ts
,就可以實(shí)現(xiàn) babel
編譯一切,從而降低開發(fā)/配置成本
10. 所有的函數(shù)式組件都應(yīng)該被 React.memo 所包裹嗎?
在子組件沒有經(jīng)過(guò)特殊處理的情況下,父組件由于狀態(tài)改變導(dǎo)致重復(fù)渲染時(shí),子組件也會(huì)進(jìn)行重復(fù)渲染,但有的時(shí)候我們并不想讓子組件進(jìn)行無(wú)用的渲染,這時(shí)就會(huì)想到在組件外用 React.memo
來(lái)包裹
React.memo
會(huì)比較函數(shù)式組件前后兩次的 props 是否發(fā)生了變化,比較方法是淺層比較,如果判斷為沒有變化,則組件不會(huì)重新渲染,聽起來(lái)是一個(gè)很不錯(cuò)的優(yōu)化方案,那是不是可以在每一個(gè)組件外面都包裹一層 React.memo
呢?
其實(shí)是不需要的,React.memo
由于自身需要做前后兩次 props
的淺層比較,是要消耗一定性能的。再者有 React diff
算法的加持下,其實(shí)很多DOM
元素并不會(huì)被真正的渲染,所以很多組件就算沒有做 memo
優(yōu)化,仍然不會(huì)對(duì)項(xiàng)目的性能造成什么影響。
所以我們?cè)谙胧褂?React.memo
之前最好先想想這個(gè)組件重新渲染和淺層比較 props
誰(shuí)花費(fèi)的性能較大再?zèng)Q定是否使用它,如果子組件本身比較復(fù)雜,那確實(shí)是可以在它外面套一層 memo
進(jìn)行優(yōu)化的
總結(jié)
通過(guò)這個(gè)項(xiàng)目不僅鞏固了自己的 React
和 TS
知識(shí),同時(shí)也學(xué)到了很多 Mock
數(shù)據(jù)、性能化、性能追蹤等新知識(shí),希望自己可以憑借新的項(xiàng)目和自己的理解慢慢在前端領(lǐng)域有自己的見解~
以上就是Jira 任務(wù)管理系統(tǒng)項(xiàng)目總結(jié)講解的詳細(xì)內(nèi)容,更多關(guān)于Jira 任務(wù)管理系統(tǒng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于webpack4搭建的react項(xiàng)目框架的方法
本篇文章主要介紹了基于webpack4搭建的react項(xiàng)目框架的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06詳解如何封裝和使用一個(gè)React鑒權(quán)組件
JavaScript?和?React?提供了靈活的事件處理機(jī)制,特別是通過(guò)利用事件的捕獲階段來(lái)阻止事件傳播可以實(shí)現(xiàn)精細(xì)的權(quán)限控制,本文將詳細(xì)介紹這一技術(shù)的應(yīng)用,并通過(guò)實(shí)踐案例展示如何封裝和使用一個(gè)?React?鑒權(quán)組件,需要的可以參考下2024-03-03使用react-virtualized實(shí)現(xiàn)圖片動(dòng)態(tài)高度長(zhǎng)列表的問題
一般我們?cè)趯憆eact項(xiàng)目中,同時(shí)渲染很多dom節(jié)點(diǎn),會(huì)造成頁(yè)面卡頓, 空白的情況。為了解決這個(gè)問題,今天小編給大家分享一篇教程關(guān)于react-virtualized實(shí)現(xiàn)圖片動(dòng)態(tài)高度長(zhǎng)列表的問題,感興趣的朋友跟隨小編一起看看吧2021-05-05