一。概覽
新增了幾個方便的特性:
-
React.memo:函數式組件也有"shouldComponentUpdate"生命週期了 -
React.lazy:配合 Suspense 特性輕鬆優雅地完成代碼拆分(Code-Splitting) -
static contextType:class 組件可以更容易地訪問單一 Context -
static getDerivedStateFromError():SSR 友好的"componentDidCatch"
其中最重要的是 Suspense 特性,在之前的 React Async Rendering 中提到過:
另外,將來會提供一個 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
相當漂亮 的特性,篇幅限制,具體見 React Suspense
四。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……
暫無評論,快來發表你的看法吧