生命週期
其中標紅的( componentWillMount、 componentWillReceiveProps、 componentWillUpdate )是 react 16.3 版本要移除的生命週期,移除原因:react 打算在17版本中,新增 async rendering , react 將一個更新過程分為 render 前後兩個階段,render 前是可以被打斷(比如有緊急任務),當生命週期被打斷後,再次執行,並不會從斷點繼續執行,是重新執行的,所以這些生命週期就可能會執行多次。
同時為了彌補失去這三個生命,react 新增了兩個生命週期: static getDerivedStateFromProps、 getSnapshotBeforeUpdate
static getDerivedStateFromProps
getDerivedStateFromProps 會在呼叫 render 方法之前呼叫,並且在初始掛載及後續更新時都會被呼叫。它應返回一個物件來更新 state,如果返回 null 則不更新任何內容。
此方法適用於罕見的用例,即 state 的值在任何時候都取決於 props。
static getDerivedStateFromProps(nextProps, prevState) {
//根據nextProps和prevState計算出預期的狀態改變,返回結果會被送給setState
//這是一個static,簡單說應該是一個純函式
}
複製程式碼
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節點)之前呼叫。它使得元件能在發生更改之前從 DOM 中捕獲一些資訊(例如,滾動位置)。此生命週期的任何返回值將作為引數傳遞給 componentDidUpdate()。
getSnapshotBeforeUpdate 返回的值會做為第三個引數傳遞給 componentDidUpdate。
getSnapshotBeforeUpdate(prevProps, prevState) {
...
return snapshot;
}
componentDidUpdate(prevProps, prevState, snapshot) {
}
複製程式碼
效能優化
shouldComponentUpdate
這是一個元件的子樹。每個節點中,SCU 代表 shouldComponentUpdate 返回的值,而 vDOMEq 代表返回的 React 元素是否相同。最後,圓圈的顏色代表了該元件是否需要更新。
react 父元件觸發更新時,它的所有子元件都會觸發更新(即使 props 和 state )並沒有改變,這樣時候我們在子元件中新增 shouldComponentUpdate 生命週期,判斷狀態是否變更,如果沒有變更返回 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 渲染 */
});
複製程式碼
長列表優化
- 虛擬列表:用陣列儲存所有列表元素的位置,只渲染可視區內的列表元素,當可視區滾動時,根據滾動的 offset 大小以及所有列表元素的位置,計算在可視區應該渲染哪些元素。常用庫( react-window 、 react-virtualized )
- 事件代理:簡單的說就是將同類行的子元素事件,繫結在一個父元素上,從而達到減少事件的註冊。
- 懶載入:常用於長列表圖片載入,元件先不渲染,當監聽到元件可以時,在去渲染元件。常用庫( react-lazyload )
key
key 幫助 React 識別哪些元素改變了,比如被新增或刪除。當子元素擁有 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>
複製程式碼
程式碼複用
Mixin
概念:將公用方法包裝成 Mixin 方法,然後注入各個元件,從而實現程式碼複用。( 已經不推薦 )
缺點:
- 命名覆蓋:比如你在 A Mixin 中定義的 get 方法,在 B Mixin 中也定義了 get 方法。
- 複雜度高後,容易混亂:比如 Mixin 中可以呼叫 setState 的,當多個 Mixin 中都呼叫了 setSate , state 的更新來源會變得混淆不清。
- ES6 class 語法不支援 Mixin。
HOC(高階元件)
概念:可以理解為元件工廠,傳入原始元件,新增功能,返回新的元件。
缺點:
- 命名覆蓋,難以溯源:當存在多個 HOC 時,僅僅通過新元件,並不能知道 props 是來源於哪個 HOC,同時如果兩個工廠,傳入了相同名稱的props,就會產生覆蓋。
- 靜態構建:工廠返回的新元件,不會立即執行,即 HOC 工廠函式裡定義的生命週期函式只有新組建渲染時才會執行。
Render Props
概念:Render Props 就是一個 render 函式作為 props 傳遞給了父元件,使得父元件知道如何渲染子元件。
優點:
- 解決了命名衝突,難以溯源的問題,可以通過 render props 的引數直接看到 props 來源於哪個元件。
- 動態構建,可以更好的利用元件內的生命週期。
缺點:
- 無法利用 shouldComponentUpdate 來實現渲染效能的優化。
React hooks
概念:hooks 遵循函數語言程式設計的理念,主旨是在函式元件中引入類元件中的狀態和生命週期,並且這些狀態和生命週期函式也可以被抽離,實現複用的同時,減少函式元件的複雜性和易用性。
hooks api:
- 基礎: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 抽象成一個物件樹,通過對比新舊兩個樹的區別(diff 演算法),然後將更新部分渲染出來。
diff 演算法基於兩個策略:
- 兩個相同元件產生類似的 DOM 結構,不同的元件產生不同的 DOM 結構。
- 對於同一層次的一組子節點,它們可以通過唯一的 id 進行區分。
逐層進行節點比較
在 react 中,樹的比對非常簡單,react 只會對兩棵樹進行逐層比較,即比較處於同一層級的節點。
節點比較
節點比較分成兩種情況:(1)節點型別不同,(2)節點型別相同,屬性不同。
情況(1):
// DOM Diff 操作
C.destroy();
D = new D();
A.append(D);
複製程式碼
情況(2):
//變更錢
<div style={{fontSize: '16px'}} ></div>
//變更後
<div style={{color: 'red'}} ><div/>
//執行操作
=> removeStyle fontSize
=> addStyle color 'red'
複製程式碼
列表節點的比較(key)
當列表中插入 F 節點,如下圖:
這個時候就會有兩種情況:(1)節點沒有表示唯一 key ,(2)節點表示了唯一 key
情況(1)結果如下圖:
//執行過程
C unmount
D unmount
E unmount
F mount
C mount
D mount
E mount
複製程式碼
情況(2)結果如下圖:
//執行過程
F mount
複製程式碼
React 16.x 部分新特性
Error Boundaries
如果在元件的渲染或生命週期方法中引發錯誤,整個元件結構就會從根節點中解除安裝,而不影響其他元件的渲染,可以利用 error boundaries 進行錯誤的優化處理。
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 節點,事件還是按元件原有位置冒泡。
render() {
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
複製程式碼
Fiber
Fiber 是對 react 核心演算法 reconciliation 的更新實現,將原本的同步更新分成兩個階段。階段1(Reconciliation Phase)的任務是片段化執行的,每個片段執行完成之後,就會把控制權重新交給 react 排程模組,如果有更高優先順序的任務就去處理,而低優先順序更新任務所做的工作則會完全作廢,然後等待機會重頭再來,如果沒有就執行下一個片段。階段2(Commit Phase)是同步執行的,reactDom 會根據階段1計算出來的 effect-list 來更新 DOM 。
階段1涉及的生命週期(可能會執行多次):componentWillMount、 componentWillReceiveProps、 shouldComponentUpdate、 componentWillUpdate
階段2涉及的生命週期:componentDidMount、 componentDidUpdate、 componentWillUnmount
createContext
全新的 Context API 可以很容易實現祖先節點和子節點通訊,並且沒有副作用。
- React.createContext 是一個函式,它接收初始值並返回帶有 Provider 和 Consumer 元件的物件
- Provider 釋出方
- 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>
);
}
}
複製程式碼
生命週期函式的更新(見上文)
React.memo(見上文)
lazy / Suspense
React.lazy() 提供了動態 import 元件的能力,實現程式碼分割。
Suspense 作用是在等待元件時 suspend(暫停)渲染,並顯示載入標識。
import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}
複製程式碼