React 16.6新API
一.概覽
新增了幾個方便的特性:
-
React.memo
:函式式元件也有“shouldComponentUpdate”生命週期了 -
React.lazy
:配合Suspense特性輕鬆優雅地完成程式碼拆分(Code-Splitting) -
static contextType
:class元件可以更容易地訪問單一Context -
static getDerivedStateFromError()
:SSR友好的“componentDidCatch”
其中最重要的是Suspense特性,在之前的
另外,將來會提供一個suspense(掛起)API,允許掛起檢視渲染,等待非同步操作完成,讓loading場景更容易控制,具體見Sneak Peek: Beyond React 16演講視訊裡的第2個Demo
而現在(v16.6.0,釋出於2018/10/23),就是大約8個月之後的“將來”
二.React.memo
const MyComponent = React.memo(function MyComponent(props) { /* only rerenders if props change */ });
還有個可選的 compare
function MyComponent(props) { /* render using props */ } function areEqual(prevProps, nextProps) { /* return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false */ } export default React.memo(MyComponent, areEqual);
類似於 PureComponent
的高階元件,包一層 memo
,就能讓普通函式式元件擁有 PureComponent
的效能優勢:
React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.
內部實現
實現上非常簡單:
export default function memo<Props>( type: React$ElementType, compare?: (oldProps: Props, newProps: Props) => boolean, ) { return { $$typeof: REACT_MEMO_TYPE, type, compare: compare === undefined ? null : compare, }; }
無非就是 外掛式shouldComponentUpdate生命週期 ,對比class元件:
// 普通class元件 class MyClassComponent { // 沒有預設的shouldComponentUpdate,可以手動實現 shouldComponentUpdate(oldProps: Props, newProps: Props): boolean { return true; } } // 繼承自PureComponent的元件相當於 class MyPureComponent { // 擁有預設shouldComponentUpdate,即shallowEqual shouldComponentUpdate(oldProps: Props, newProps: Props): boolean { return shallowEqual(oldProps, newProps); } } // 函式式元件 function render() { // 函式式元件,不支援shouldComponentUpdate } // Memo元件相當於 const MyMemoComponent = { type: function render() { // 函式式元件,不支援shouldComponentUpdate } // 擁有預設的(掛在外面的)shouldComponentUpdate,即shallowEqual compare: shallowEqual };
如此這般,就給函式式元件粘了個 shouldComponentUpdate
上去,接下來的事情猜也能猜到了:
// ref: react-16.6.3/react/packages/react-reconciler/src/ReactFiberBeginWork.js function updateMemoComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps: any, updateExpirationTime, renderExpirationTime: ExpirationTime, ): null | Fiber { // Default to shallow comparison let compare = Component.compare; compare = compare !== null ? compare : shallowEqual; if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) { return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } } }
所以,從實現上來看, React.memo()
這個API與memo關係倒不大,實際意義是: 函式式元件也有“shouldComponentUpdate”生命週期了
注意, compare
預設是 shallowEqual
,所以 React.memo
第二個引數 compare
實際含義是 shouldNotComponentUpdate ,而不是我們所熟知的相反的那個。API設計上確實有些迷惑,非要引入一個相反的東西:
Unlike the shouldComponentUpdate() method on class components, this is the inverse from shouldComponentUpdate.
P.S.RFC定稿過程中第二個引數確實備受爭議( equal, arePropsEqual, arePropsDifferent, renderOnDifference, isEqual, shouldUpdate...
等10000個以內),具體見 React.memo()
手動實現個memo?
話說回來,這樣一個高階元件其實不難實現:
function memo(render, shouldNotComponentUpdate = shallowEqual) { let oldProps, rendered; return function(newProps) { if (!shouldNotComponentUpdate(oldProps, newProps)) { rendered = render(newProps); oldProps = newProps; } return rendered; } }
手動實現的這個盜版與官方版本功能上等價(甚至效能也不相上下),所以又一個錦上添花的東西
三.React.lazy: Code-Splitting with Suspense
相當漂亮 的特性,篇幅限制(此處刪掉了276行),暫不展開
四.static contextType
v16.3推出了新Context API:
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 ( // 這一部分看起來很麻煩,讀個context而已 <ThemeContext.Consumer> {theme => <Button theme={theme} />} </ThemeContext.Consumer> ); } }
為了讓class元件訪問Context資料方便一些,新增了 static contextType
特性:
class ThemedButton extends React.Component { static contextType = ThemeContext; render() { let theme = this.context; return ( // 喧囂停止了 <Button theme={theme} /> ); } }
其中 contextType
( 注意 ,之前那個舊的多個 s
,叫 contextTypes
)只支援 React.createContext()
返回型別,翻新了 舊Context API 的 this.context
(變成單一值了,之前是物件)
用法上不那麼變態了,但 只支援訪問單一Context值 。要訪問一堆Context值的話,只能用上面看起來 很麻煩的那種方式 :
// A component may consume multiple contexts function Content() { return ( // 。。。。 <ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer> ); }
五.static getDerivedStateFromError()
static getDerivedStateFromError(error)
又一個錯誤處理API:
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 }; } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; } }
用法與v16.0的 componentDidCatch(error, info) 非常相像:
class ErrorBoundary extends React.Component { componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); } }
二者都會在子樹渲染出錯後觸發,但 觸發時機上存在微妙的差異 :
-
static getDerivedStateFromError
:在render階段觸發,不允許含有副作用(否則多次執行會出問題) -
componentDidCatch
:在commit階段觸發,因此允許含有副作用(如logErrorToMyService
)
前者的觸發時機足夠早,所以能夠多做一些補救措施,比如避免 null ref
引發連鎖錯誤
另一個區別是 Did
系列生命週期(如 componentDidCatch
)不支援SSR,而 getDerivedStateFromError
從設計上就考慮到了SSR(目前v16.6.3還不支援,但說了會支援)
目前這兩個API在功能上是有重疊的,都可以在子樹出錯之後通過改變 state
來做UI降級,但後續會細分各自的職責 :
-
static getDerivedStateFromError
:專做UI降級 -
componentDidCatch
:專做錯誤上報
六.過時API
又兩個API要被打入冷宮:
-
ReactDOM.findDOMNode()
:效能原因以及 設計上的問題 ,建議換用 ref forwarding -
舊Context API:效能及實現方面的原因,建議換用新Context API
P.S.暫時還能用,但將來版本會去掉,可以藉助 StrictMode 完成遷移
七.總結
函式式元件也迎來了“shouldComponentUpdate”,還有漂亮的Code-Splitting支援,以及緩解Context Consumer繁瑣之痛的補丁API,和職責清晰的UI層兜底方案
13種React元件
v16.6新增了幾類元件( REACT_MEMO_TYPE
、 REACT_LAZY_TYPE
、 REACT_SUSPENSE_TYPE
),細數一下,竟然有這麼多了:
-
REACT_ELEMENT_TYPE
:普通React元件型別,如<MyComponent />
-
REACT_PORTAL_TYPE
: Protals 元件,ReactDOM.createPortal()
-
REACT_FRAGMENT_TYPE
: Fragment 虛擬元件,<></>
或<React.Fragment></React.Fragment>
或[,]
-
REACT_STRICT_MODE_TYPE
:帶過時API檢查的 嚴格模式 元件,<React.StrictMode>
-
REACT_PROFILER_TYPE
:用來開啟元件範圍效能分析,見 Profiler RFC ,目前還是實驗性API,<React.unstable_Profiler>
穩定之後會變成<React.Profiler>
-
REACT_PROVIDER_TYPE
:Context資料的生產者 Context.Provider ,<React.createContext(defaultValue).Provider>
-
REACT_CONTEXT_TYPE
:Context資料的消費者 Context.Consumer ,<React.createContext(defaultValue).Consumer>
-
REACT_ASYNC_MODE_TYPE
:開啟非同步特性的非同步模式元件,過時了,換用REACT_CONCURRENT_MODE_TYPE
-
REACT_CONCURRENT_MODE_TYPE
:用來開啟非同步特性,暫時還沒放出來,處於 Demo階段 ,<React.unstable_ConcurrentMode>
穩定之後會變成<React.ConcurrentMode>
-
REACT_FORWARD_REF_TYPE
:向下 傳遞Ref的元件 ,React.forwardRef()
-
REACT_SUSPENSE_TYPE
:元件範圍 延遲渲染 ,<Suspense fallback={<MyLoadingComponent>}>
-
REACT_MEMO_TYPE
: 類似於PureComponent的高階元件 ,React.memo()
-
REACT_LAZY_TYPE
: 動態引入的元件 ,React.lazy()
曾幾何時,v15-只有1種 REACT_ELEMENT_TYPE
……