生命周期
其中標(biāo)紅的( componentWillMount漂问、 componentWillReceiveProps叠纷、 componentWillUpdate )是 react 16.3 版本要移除的生命周期款慨,移除原因:react 打算在17版本中阳欲,添加 async rendering 临扮, react 將一個更新過程分為 render 前后兩個階段孤个,render 前是可以被打斷(比如有緊急任務(wù))剃允,當(dāng)生命周期被打斷后,再次執(zhí)行齐鲤,并不會從斷點繼續(xù)執(zhí)行斥废,是重新執(zhí)行的,所以這些生命周期就可能會運(yùn)行多次给郊。
同時為了彌補(bǔ)失去這三個生命牡肉,react 新增了兩個生命周期: static getDerivedStateFromProps、 getSnapshotBeforeUpdate
static getDerivedStateFromProps
getDerivedStateFromProps 會在調(diào)用 render 方法之前調(diào)用淆九,并且在初始掛載及后續(xù)更新時都會被調(diào)用统锤。它應(yīng)返回一個對象來更新 state,如果返回 null 則不更新任何內(nèi)容炭庙。
此方法適用于罕見的用例饲窿,即 state 的值在任何時候都取決于 props。
static getDerivedStateFromProps(nextProps, prevState) {
//根據(jù)nextProps和prevState計算出預(yù)期的狀態(tài)改變焕蹄,返回結(jié)果會被送給setState
//這是一個static逾雄,簡單說應(yīng)該是一個純函數(shù)
}
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節(jié)點)之前調(diào)用。它使得組件能在發(fā)生更改之前從 DOM 中捕獲一些信息(例如腻脏,滾動位置)鸦泳。此生命周期的任何返回值將作為參數(shù)傳遞給 componentDidUpdate()。
getSnapshotBeforeUpdate 返回的值會做為第三個參數(shù)傳遞給 componentDidUpdate永品。
getSnapshotBeforeUpdate(prevProps, prevState) {
...
return snapshot;
}
componentDidUpdate(prevProps, prevState, snapshot) {
}
性能優(yōu)化
shouldComponentUpdate
這是一個組件的子樹做鹰。每個節(jié)點中,SCU 代表 shouldComponentUpdate 返回的值鼎姐,而 vDOMEq 代表返回的 React 元素是否相同誊垢。最后,圓圈的顏色代表了該組件是否需要更新症见。
react 父組件觸發(fā)更新時,它的所有子組件都會觸發(fā)更新(即使 props 和 state )并沒有改變殃饿,這樣時候我們在子組件中添加 shouldComponentUpdate 生命周期谋作,判斷狀態(tài)是否變更,如果沒有變更返回 false , 這個子組件就不會重新 render乎芳。
React.PureComponent
React.PureComponent 是 React 自動幫我們在 shouldComponentUpdate 做了一層淺比較遵蚜。
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps)
|| !shallowEqual(inst.state, nextState);
}
React.memo
React.memo 是一個高階組件帖池,類似于 React.PureComponent,不同于 React.memo 是 function 組件吭净,React.PureComponent 是 class 組件睡汹。
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
長列表優(yōu)化
- 虛擬列表:用數(shù)組保存所有列表元素的位置,只渲染可視區(qū)內(nèi)的列表元素寂殉,當(dāng)可視區(qū)滾動時囚巴,根據(jù)滾動的 offset 大小以及所有列表元素的位置,計算在可視區(qū)應(yīng)該渲染哪些元素友扰。常用庫( react-window 彤叉、 react-virtualized )
- 事件代理:簡單的說就是將同類行的子元素事件,綁定在一個父元素上村怪,從而達(dá)到減少事件的注冊秽浇。
- 懶加載:常用于長列表圖片加載,組件先不渲染甚负,當(dāng)監(jiān)聽到組件可以時柬焕,在去渲染組件。常用庫( react-lazyload )
key
key 幫助 React 識別哪些元素改變了梭域,比如被添加或刪除斑举。當(dāng)子元素?fù)碛?key 時,React 使用 key 來匹配原有樹上的子元素以及最新樹上的子元素碰辅。正確的使用 key 可以使 react 的更新開銷變小懂昂。
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
代碼復(fù)用
Mixin
概念:將公用方法包裝成 Mixin 方法,然后注入各個組件没宾,從而實現(xiàn)代碼復(fù)用凌彬。( 已經(jīng)不推薦 )
缺點:
- 命名覆蓋:比如你在 A Mixin 中定義的 get 方法,在 B Mixin 中也定義了 get 方法循衰。
- 復(fù)雜度高后铲敛,容易混亂:比如 Mixin 中可以調(diào)用 setState 的,當(dāng)多個 Mixin 中都調(diào)用了 setSate , state 的更新來源會變得混淆不清会钝。
- ES6 class 語法不支持 Mixin伐蒋。
HOC(高階組件)
概念:可以理解為組件工廠,傳入原始組件迁酸,添加功能先鱼,返回新的組件。
缺點:
- 命名覆蓋奸鬓,難以溯源:當(dāng)存在多個 HOC 時焙畔,僅僅通過新組件,并不能知道 props 是來源于哪個 HOC串远,同時如果兩個工廠宏多,傳入了相同名稱的props儿惫,就會產(chǎn)生覆蓋。
- 靜態(tài)構(gòu)建:工廠返回的新組件伸但,不會立即執(zhí)行肾请,即 HOC 工廠函數(shù)里定義的生命周期函數(shù)只有新組建渲染時才會執(zhí)行。
Render Props
概念:Render Props 就是一個 render 函數(shù)作為 props 傳遞給了父組件更胖,使得父組件知道如何渲染子組件铛铁。
優(yōu)點:
- 解決了命名沖突,難以溯源的問題函喉,可以通過 render props 的參數(shù)直接看到 props 來源于哪個組件避归。
- 動態(tài)構(gòu)建,可以更好的利用組件內(nèi)的生命周期管呵。
缺點:
- 無法利用 shouldComponentUpdate 來實現(xiàn)渲染性能的優(yōu)化梳毙。
React hooks
概念:hooks 遵循函數(shù)式編程的理念,主旨是在函數(shù)組件中引入類組件中的狀態(tài)和生命周期捐下,并且這些狀態(tài)和生命周期函數(shù)也可以被抽離账锹,實現(xiàn)復(fù)用的同時,減少函數(shù)組件的復(fù)雜性和易用性坷襟。
hooks api:
- 基礎(chǔ):useState奸柬、 useEffect、 useContext
- 額外:useReducer婴程、 useCallback廓奕、 useMemo、 useRef档叔、 useImperativeHandle桌粉、 useLayoutEffect、 useDebugValue
一個簡單的 custom hooks:
import { useEffect } from 'react';
function useTitle(title){
useEffect(()=>{
document.title = title;
}, [title]);
}
虛擬 Dom diff 算法
虛擬 Dom:react 將 Dom 抽象成一個對象樹衙四,通過對比新舊兩個樹的區(qū)別(diff 算法)铃肯,然后將更新部分渲染出來。
diff 算法基于兩個策略:
- 兩個相同組件產(chǎn)生類似的 DOM 結(jié)構(gòu)传蹈,不同的組件產(chǎn)生不同的 DOM 結(jié)構(gòu)押逼。
- 對于同一層次的一組子節(jié)點,它們可以通過唯一的 id 進(jìn)行區(qū)分惦界。
逐層進(jìn)行節(jié)點比較
在 react 中挑格,樹的比對非常簡單,react 只會對兩棵樹進(jìn)行逐層比較沾歪,即比較處于同一層級的節(jié)點恕齐。
節(jié)點比較
節(jié)點比較分成兩種情況:(1)節(jié)點類型不同,(2)節(jié)點類型相同,屬性不同显歧。
情況(1):
// DOM Diff 操作
C.destroy();
D = new D();
A.append(D);
情況(2):
//變更錢
<div style={{fontSize: '16px'}} ></div>
//變更后
<div style={{color: 'red'}} ><div/>
//執(zhí)行操作
=> removeStyle fontSize
=> addStyle color 'red'
列表節(jié)點的比較(key)
當(dāng)列表中插入 F 節(jié)點,如下圖:
這個時候就會有兩種情況:(1)節(jié)點沒有表示唯一 key 确镊,(2)節(jié)點表示了唯一 key
情況(1)結(jié)果如下圖:
//執(zhí)行過程
C unmount
D unmount
E unmount
F mount
C mount
D mount
E mount
情況(2)結(jié)果如下圖:
//執(zhí)行過程
F mount
React 16.x 部分新特性
Error Boundaries
如果在組件的渲染或生命周期方法中引發(fā)錯誤蕾域,整個組件結(jié)構(gòu)就會從根節(jié)點中卸載拷肌,而不影響其他組件的渲染,可以利用 error boundaries 進(jìn)行錯誤的優(yōu)化處理旨巷。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
createPortal
react 支持將組建掛在到其他 dom 節(jié)點巨缘,事件還是按組件原有位置冒泡。
render() {
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
Fiber
Fiber 是對 react 核心算法 reconciliation 的更新實現(xiàn)采呐,將原本的同步更新分成兩個階段若锁。階段1(Reconciliation Phase)的任務(wù)是片段化執(zhí)行的,每個片段執(zhí)行完成之后斧吐,就會把控制權(quán)重新交給 react 調(diào)度模塊又固,如果有更高優(yōu)先級的任務(wù)就去處理,而低優(yōu)先級更新任務(wù)所做的工作則會完全作廢煤率,然后等待機(jī)會重頭再來仰冠,如果沒有就執(zhí)行下一個片段。階段2(Commit Phase)是同步執(zhí)行的蝶糯,reactDom 會根據(jù)階段1計算出來的 effect-list 來更新 DOM 洋只。
階段1涉及的生命周期(可能會執(zhí)行多次):componentWillMount、 componentWillReceiveProps昼捍、 shouldComponentUpdate识虚、 componentWillUpdate
階段2涉及的生命周期:componentDidMount、 componentDidUpdate端三、 componentWillUnmount
createContext
全新的 Context API 可以很容易實現(xiàn)祖先節(jié)點和子節(jié)點通信舷礼,并且沒有副作用。
- React.createContext 是一個函數(shù)郊闯,它接收初始值并返回帶有 Provider 和 Consumer 組件的對象
- Provider 發(fā)布方
- Consumer 訂閱方
const ThemeContext = React.createContext('light');
class ThemeProvider extends React.Component {
state = {theme: 'light'};
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
class ThemedButton extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{theme => <Button theme={theme} />}
</ThemeContext.Consumer>
);
}
}
生命周期函數(shù)的更新(見上文)
React.memo(見上文)
lazy / Suspense
React.lazy() 提供了動態(tài) import 組件的能力妻献,實現(xiàn)代碼分割。
Suspense 作用是在等待組件時 suspend(暫停)渲染团赁,并顯示加載標(biāo)識育拨。
import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}