於 2017.09.26 Facebook 發佈 React v16.0 版本,時至今日已更新到 React v16.6,且引入了大量的使人振奮的新特性,本文章將帶領你們根據 React 更新的時間脈絡瞭解 React16 的新特性。前端
按照 React16 的更新時間,從 React v16.0 ~ React v16.6 進行概述。node
React v16.0react
React v16.1git
React v16.2github
React v16.3算法
React v16.4npm
React v16.5api
React v16.6數組
React v16.7(~Q1 2019)promise
React v16.8(~Q2 2019)
React v16.9(~mid 2019)
下面將按照上述的 React16 更新路徑對每一個新特性進行詳細或簡短的解析。
// 不須要再將元素做爲子元素裝載到根元素下面
render() {
return [
<li/>1</li>,
<li/>2</li>,
<li/>3</li>,
];
}
複製代碼
React15 在渲染過程當中遇到運行時的錯誤,會致使整個 React 組件的崩潰,並且錯誤信息不明確可讀性差。React16 支持了更優雅的錯誤處理策略,若是一個錯誤是在組件的渲染或者生命週期方法中被拋出,整個組件結構就會從根節點中卸載,而不影響其餘組件的渲染,能夠利用 error boundaries 進行錯誤的優化處理。
class ErrorBoundary extends React.Component {
state = { hasError: false };
componentDidCatch(error, info) {
this.setState({ hasError: true });
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>數據錯誤</h1>;
}
return this.props.children;
}
}
複製代碼
createPortal 的出現爲 彈窗、對話框 等脫離文檔流的組件開發提供了便利,替換了以前不穩定的 API unstable_renderSubtreeIntoContainer,在代碼使用上能夠作兼容,如:
const isReact16 = ReactDOM.createPortal !== undefined;
const getCreatePortal = () =>
isReact16
? ReactDOM.createPortal
: ReactDOM.unstable_renderSubtreeIntoContainer;
複製代碼
使用 createPortal 能夠快速建立 Dialog 組件,且不須要牽扯到 componentDidMount、componentDidUpdate 等生命週期函數。
而且經過 createPortal 渲染的 DOM,事件能夠從 portal 的入口端冒泡上來,若是入口端存在 onDialogClick 等事件,createPortal 中的 DOM 也可以被調用到。
import React from 'react';
import { createPortal } from 'react-dom';
class Dialog extends React.Component {
constructor() {
super(props);
this.node = document.createElement('div');
document.body.appendChild(this.node);
}
render() {
return createPortal(
<div> {this.props.children} </div>,
this.node
);
}
}
複製代碼
之前的 React 版本 DOM 不識別除了 HTML 和 SVG 支持的之外屬性,在 React16 版本中將會把所有的屬性傳遞給 DOM 元素。這個新特性可讓咱們擺脫可用的 React DOM 屬性白名單。筆者以前寫過一個方法,用於過濾非 DOM 屬性 filter-react-dom-props,16 以後便可再也不須要這樣的方法。
React16 使用 Rollup 針對不一樣的目標格式進行代碼打包,因爲打包工具的改變使得庫文件大小獲得縮減。
Fiber 是對 React 核心算法的一次從新實現,將本來的同步更新過程碎片化,避免主線程的長時間阻塞,使應用的渲染更加流暢。
在 React16 以前,更新組件時會調用各個組件的生命週期函數,計算和比對 Virtual DOM,更新 DOM 樹等,這整個過程是同步進行的,中途沒法中斷。當組件比較龐大,更新操做耗時較長時,就會致使瀏覽器惟一的主線程都是執行組件更新操做,而沒法響應用戶的輸入或動畫的渲染,很影響用戶體驗。
Fiber 利用分片的思想,把一個耗時長的任務分紅不少小片,每個小片的運行時間很短,在每一個小片執行完以後,就把控制權交還給 React 負責任務協調的模塊,若是有緊急任務就去優先處理,若是沒有就繼續更新,這樣就給其餘任務一個執行的機會,惟一的線程就不會一直被獨佔。
所以,在組件更新時有可能一個更新任務尚未完成,就被另外一個更高優先級的更新過程打斷,優先級高的更新任務會優先處理完,而低優先級更新任務所作的工做則會徹底做廢,而後等待機會重頭再來。因此 React Fiber 把一個更新過程分爲兩個階段:
在使用 Fiber 後,須要要檢查與第一階段相關的生命週期函數,避免邏輯的屢次或重複調用:
與第二階段相關的生命週期函數:
react-call-return 目前仍是一個獨立的 npm 包,主要是針對 父組件須要根據子組件的回調信息去渲染子組件場景 提供的解決方案。
在 React16 以前,針對上述場景通常有兩個解決方案:
首先讓子組件初始化渲染,經過回調函數把信息傳給父組件,父組件完成處理後更新子組件 props,觸發子組件的第二次渲染才能夠解決,子組件須要通過兩次渲染週期,可能會形成渲染的抖動或閃爍等問題;
首先在父組件經過 children 得到子組件並讀取其信息,利用 React.cloneElement 克隆產生新元素,並將新的屬性傳遞進去,父組件 render 返回的是克隆產生的子元素。雖然這種方法只須要使用一個生命週期,可是父組件的代碼編寫會比較麻煩;
React16 支持的 react-call-return,提供了兩個函數 unstable_createCall 和 unstable_createReturn,其中 unstable_createCall 是 父組件使用,unstable_createReturn 是 子組件使用,父組件發出 Call,子組件響應這個 Call,即 Return。
在父組件 render 函數中返回對 unstable_createCall 的調用,第一個參數是 props.children,第二個參數是一個回調函數,用於接受子組件響應 Call 所返回的信息,第三個參數是 props;
在子組件 render 函數返回對 unstable_createReturn 的調用,參數是一個對象,這個對象會在unstable_createCall 第二個回調函數參數中訪問到;
當父組件下的全部子組件都完成渲染週期後,因爲子組件返回的是對 unstable_createReturn 的調用因此並無渲染元素,unstable_createCall 的第二個回調函數參數會被調用,這個回調函數返回的是真正渲染子組件的元素;
針對普通場景來講,react-call-return 有點過分設計的感受,可是若是針對一些特定場景的話,它的做用仍是很是明顯,好比,在渲染瀑布流佈局時,利用 react-call-return 能夠先緩存子組件的 ReactElement,等必要的信息足夠以後父組件再觸發 render,完成渲染。
import React from 'react';
import { unstable_createReturn, unstable_createCall } from 'react-call-return';
const Child = (props) => {
return unstable_createReturn({
size: props.children.length,
renderItem: (partSize, totalSize) => {
return <div>{ props.children } { partSize } / { totalSize }</div>;
}
});
};
const Parent = (props) => {
return (
<div> { unstable_createCall( props.children, (props, returnValues) => { const totalSize = returnValues.map(v => v.size).reduce((a, b) => a + b, 0); return returnValues.map(({ size, renderItem }) => { return renderItem(size, totalSize); }); }, props ) } </div>
);
};
複製代碼
Fragment 組件其做用是能夠將一些子元素添加到 DOM tree 上且不須要爲這些元素提供額外的父節點,至關於 render 返回數組元素。
render() {
return (
<Fragment> Some text. <h2>A heading</h2> More text. <h2>Another heading</h2> Even more text. </Fragment>
);
}
複製代碼
全新的 Context API 能夠很容易穿透組件而無反作用,其包含三部分:React.createContext,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>
);
}
}
複製代碼
React16 規範了 Ref 的獲取方式,經過 React.createRef 取得 Ref 對象。
// before React 16
···
componentDidMount() {
const el = this.refs.myRef
}
render() {
return <div ref="myRef" />
}
···
// React 16+
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return <div ref={this.myRef} />
}
···
複製代碼
React.forwardRef 是 Ref 的轉發, 它可以讓父組件訪問到子組件的 Ref,從而操做子組件的 DOM。 React.forwardRef 接收一個函數,函數參數有 props 和 ref。
const TextInput = React.forwardRef((props, ref) => (
<input type="text" placeholder="Hello forwardRef" ref={ref} />
))
const inputRef = React.createRef()
class App extends Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
handleSubmit = event => {
event.preventDefault()
alert('input value is:' + inputRef.current.value)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<TextInput ref={inputRef} />
<button type="submit">Submit</button>
</form>
)
}
}
複製代碼
React16 採用了新的內核架構 Fiber,Fiber 將組件更新分爲兩個階段:Render Parse 和 Commit Parse,所以 React 也引入了 getDerivedStateFromProps 、 getSnapshotBeforeUpdate 及 componentDidCatch 等三個全新的生命週期函數。同時也將 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 標記爲不安全的方法。
getDerivedStateFromProps(nextProps, prevState) 其做用是根據傳遞的 props 來更新 state。它的一大特色是無反作用,因爲處在 Render Phase 階段,因此在每次的更新都會觸發該函數, 在 API 設計上採用了靜態方法,使其沒法訪問實例、沒法經過 ref 訪問到 DOM 對象等,保證了該函數的純粹高效。
爲了配合將來的 React 異步渲染機制,React v16.4 對 getDerivedStateFromProps 作了一些改變, 使其不只在 props 更新時會被調用,setState 時也會被觸發。
static getDerivedStateFromProps(props, state) {
if (props.value !== state.controlledValue) {
return {
controlledValue: props.value,
};
}
return null;
}
複製代碼
getSnapshotBeforeUpdate(prevProps, prevState) 會在組件更新以前獲取一個 snapshot,並能夠將計算得的值或從 DOM 獲得的信息傳遞到 componentDidUpdate(prevProps, prevState, snapshot) 函數的第三個參數,經常用於 scroll 位置定位等場景。
componentDidCatch 函數讓開發者能夠自主處理錯誤信息,諸如錯誤展現,上報錯誤等,用戶能夠建立本身的 Error Boundary 來捕獲錯誤。
componentWillMount 被標記爲不安全,由於在 componentWillMount 中獲取異步數據或進行事件訂閱等操做會產生一些問題,好比沒法保證在 componentWillUnmount 中取消掉相應的事件訂閱,或者致使屢次重複獲取異步數據等問題。
componentWillReceiveProps / componentWillUpdate 被標記爲不安全,主要是由於操做 props 引發的 re-render 問題,而且對 DOM 的更新操做也可能致使從新渲染。
StrictMode 能夠在開發階段開啓嚴格模式,發現應用存在的潛在問題,提高應用的健壯性,其主要能檢測下列問題:
class App extends React.Component {
render() {
return (
<div> <React.StrictMode> <ComponentA /> </React.StrictMode> </div> ) } } 複製代碼
指針事件是爲指針設備觸發的 DOM 事件。它們旨在建立單個 DOM 事件模型來處理指向輸入設備,例如鼠標,筆 / 觸控筆或觸摸(例如一個或多個手指)。指針是一個與硬件無關的設備,能夠定位一組特定的屏幕座標。擁有指針的單個事件模型能夠簡化建立 Web 站點和應用程序,並提供良好的用戶體驗,不管用戶的硬件如何。可是,對於須要特定於設備的處理的場景,指針事件定義了一個 pointerType 屬性,用於檢查產生事件的設備類型。
React 新增 onPointerDown / onPointerMove / onPointerUp / onPointerCancel / onGotPointerCapture / onLostPointerCapture / onPointerEnter / onPointerLeave / onPointerOver / onPointerOut 等指針事件。
這些事件只能在支持 指針事件 規範的瀏覽器中工做。若是應用程序依賴於指針事件,建議使用第三方指針事件 polyfill。
React 16.5 添加了對新的 profiler DevTools 插件的支持。這個插件使用 React 的 Profiler 實驗性 API 去收集全部 component 的渲染時間,目的是爲了找出 React App 的性能瓶頸,它將會和 React 即將發佈的 時間片 特性徹底兼容。
React.memo() 只能做用在簡單的函數組件上,本質是一個高階函數,能夠自動幫助組件執行shouldComponentUpdate(),但只是執行淺比較,其意義和價值有限。
const MemoizedComponent = React.memo(props => {
/* 只在 props 更改的時候纔會從新渲染 */
});
複製代碼
React.lazy() 提供了動態 import 組件的能力,實現代碼分割。
Suspense 做用是在等待組件時 suspend(暫停)渲染,並顯示加載標識。
目前 React v16.6 中 Suspense 只支持一個場景,即便用 React.lazy() 和 <React.Suspense> 實現的動態加載組件。
import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense>
);
}
複製代碼
static contextType 爲 Context API 提供了更加便捷的使用體驗,能夠經過 this.context 來訪問 Context。
const MyContext = React.createContext();
class MyClass extends React.Component {
static contextType = MyContext;
componentDidMount() {
const value = this.context;
}
componentDidUpdate() {
const value = this.context;
}
componentWillUnmount() {
const value = this.context;
}
render() {
const value = this.context;
}
}
複製代碼
static getDerivedStateFromError(error) 容許開發者在 render 完成以前渲染 Fallback UI,該生命週期函數觸發的條件是子組件拋出錯誤,getDerivedStateFromError 接收到這個錯誤參數後更新 state。
class ErrorBoundary extends React.Component {
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;
}
}
複製代碼
Hooks 要解決的是狀態邏輯複用問題,且不會產生 JSX 嵌套地獄,其特性以下:
Hooks 並非經過 Proxy 或者 getters 實現,而是經過數組實現,每次 useState 都會改變下標,若是 useState 被包裹在 condition 中,那每次執行的下標就可能對不上,致使 useState 導出的 setter 更新錯數據。
更多 Hooks 使用場景能夠閱讀下列文章:
function App() {
const [open, setOpen] = useState(false);
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
Open Modal
</Button>
<Modal
visible={open}
onOk={() => setOpen(false)}
onCancel={() => setOpen(false)}
/>
</>
);
}
複製代碼
Concurrent Rendering 併發渲染模式是在不阻塞主線程的狀況下渲染組件樹,使 React 應用響應性更流暢,它容許 React 中斷耗時的渲染,去處理高優先級的事件,如用戶輸入等,還能在高速鏈接時跳過沒必要要的加載狀態,用以改善 Suspense 的用戶體驗。
目前 Concurrent Rendering 還沒有正式發佈,也沒有詳細相關文檔,須要等待 React 團隊的正式發佈。
Suspense 經過 ComponentDidCatch 實現用同步的方式編寫異步數據的請求,而且沒有使用 yield / async / await,其流程:調用 render 函數 -> 發現有異步請求 -> 暫停渲染,等待異步請求結果 -> 渲染展現數據。
不管是什麼異常,JavaScript 都能捕獲,React就是利用了這個語言特性,經過 ComponentDidCatch 捕獲了全部生命週期函數、render函數等,以及事件回調中的錯誤。若是有緩存則讀取緩存數據,若是沒有緩存,則會拋出一個異常 promise,利用異常作邏輯流控制是一種擁有較深的調用堆棧時的手段,它是在虛擬 DOM 渲染層作的暫停攔截,代碼可在服務端複用。
import { fetchMovieDetails } from '../api';
import { createFetch } from '../future';
const movieDetailsFetch = createFetch(fetchMovieDetails);
function MovieDetails(props) {
const movie = movieDetailsFetch.read(props.id);
return (
<div>
<MoviePoster src={movie.poster} />
<MovieMetrics {...movie} />
</div>
);
}
複製代碼
從 React16 的一系列更新和新特性中咱們能夠窺見,React 已經不只僅只在作一個 View 的展現庫,而是想要發展成爲一個包含 View / 數據管理 / 數據獲取 等場景的前端框架,以 React 團隊的技術實力以及想法,筆者仍是很期待和看好 React 的將來,不過它漸漸地已經對開發新手們不太友好了。
討論地址是:[精讀《React16 新特性》 · Issue #115 · dt-fe/weekly]github.com/dt-fe/weekl…)
若是你想參與討論,請點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。