摘要:引言于發布版本,時至今日已更新到,且引入了大量的令人振奮的新特性,本文章將帶領大家根據更新的時間脈絡了解的新特性。其作用是根據傳遞的來更新。新增等指針事件。
1 引言
于 2017.09.26 Facebook 發布 React v16.0 版本,時至今日已更新到 React v16.6,且引入了大量的令人振奮的新特性,本文章將帶領大家根據 React 更新的時間脈絡了解 React16 的新特性。
2 概述按照 React16 的更新時間,從 React v16.0 ~ React v16.6 進行概述。
React v16.0
render 支持返回數組和字符串、Error Boundaries、createPortal、支持自定義 DOM 屬性、減少文件體積、fiber;
React v16.1
react-call-return;
React v16.2
Fragment;
React v16.3
createContext、createRef、forwardRef、生命周期函數的更新、Strict Mode;
React v16.4
Pointer Events、update getDerivedStateFromProps;
React v16.5
Profiler;
React v16.6
memo、lazy、Suspense、static contextType、static getDerivedStateFromError();
React v16.7(~Q1 2019)
Hooks;
React v16.8(~Q2 2019)
Concurrent Rendering;
React v16.9(~mid 2019)
Suspense for Data Fetching;
下面將按照上述的 React16 更新路徑對每個新特性進行詳細或簡短的解析。
3 精讀 React v16.0 render 支持返回數組和字符串// 不需要再將元素作為子元素裝載到根元素下面 render() { return [1, 2, 3, ]; } Error Boundaries
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) { returncreatePortal數據錯誤
; } 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(支持自定義 DOM 屬性{this.props.children}, this.node ); } }
以前的 React 版本 DOM 不識別除了 HTML 和 SVG 支持的以外屬性,在 React16 版本中將會把全部的屬性傳遞給 DOM 元素。這個新特性可以讓我們擺脫可用的 React DOM 屬性白名單。筆者之前寫過一個方法,用于過濾非 DOM 屬性 filter-react-dom-props,16 之后即可不再需要這樣的方法。
減少文件體積React16 使用 Rollup 針對不同的目標格式進行代碼打包,由于打包工具的改變使得庫文件大小得到縮減。
React 庫大小從 20.7kb(壓縮后 6.9kb)降低到 5.3kb(壓縮后 2.2kb)
ReactDOM 庫大小從 141kb(壓縮后 42.9kb)降低到 103.7kb(壓縮后 32.6kb)
React + ReactDOM 庫大小從 161.7kb(壓縮后 49.8kb)降低到 109kb(壓縮后 43.8kb)
FiberFiber 是對 React 核心算法的一次重新實現,將原本的同步更新過程碎片化,避免主線程的長時間阻塞,使應用的渲染更加流暢。
在 React16 之前,更新組件時會調用各個組件的生命周期函數,計算和比對 Virtual DOM,更新 DOM 樹等,這整個過程是同步進行的,中途無法中斷。當組件比較龐大,更新操作耗時較長時,就會導致瀏覽器唯一的主線程都是執行組件更新操作,而無法響應用戶的輸入或動畫的渲染,很影響用戶體驗。
Fiber 利用分片的思想,把一個耗時長的任務分成很多小片,每一個小片的運行時間很短,在每個小片執行完之后,就把控制權交還給 React 負責任務協調的模塊,如果有緊急任務就去優先處理,如果沒有就繼續更新,這樣就給其他任務一個執行的機會,唯一的線程就不會一直被獨占。
因此,在組件更新時有可能一個更新任務還沒有完成,就被另一個更高優先級的更新過程打斷,優先級高的更新任務會優先處理完,而低優先級更新任務所做的工作則會完全作廢,然后等待機會重頭再來。所以 React Fiber 把一個更新過程分為兩個階段:
第一個階段 Reconciliation Phase,Fiber 會找出需要更新的 DOM,這個階段是可以被打斷的;
第二個階段 Commit Phase,是無法別打斷,完成 DOM 的更新并展示;
在使用 Fiber 后,需要要檢查與第一階段相關的生命周期函數,避免邏輯的多次或重復調用:
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
與第二階段相關的生命周期函數:
componentDidMount
componentDidUpdate
componentWillUnmount
React v16.1 Call Return(react-call-return npm)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) => { returnReact v16.2 Fragment{ props.children } { partSize } / { totalSize }; } }); }; const Parent = (props) => { return ({ 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 ) }); };
Fragment 組件其作用是可以將一些子元素添加到 DOM tree 上且不需要為這些元素提供額外的父節點,相當于 render 返回數組元素。
render() { return (React v16.3 createContextSome text. ); }A heading
More text.Another heading
Even more text.
全新的 Context API 可以很容易穿透組件而無副作用,其包含三部分:React.createContext,Provider,Consumer。
React.createContext 是一個函數,它接收初始值并返回帶有 Provider 和 Consumer 組件的對象;
Provider 組件是數據的發布方,一般在組件樹的上層并接收一個數據的初始值;
Consumer 組件是數據的訂閱方,它的 props.children 是一個函數,接收被發布的數據,并且返回 React Element;
const ThemeContext = React.createContext("light"); class ThemeProvider extends React.Component { state = {theme: "light"}; render() { return (createRef / forwardRef{this.props.children} ); } } class ThemedButton extends React.Component { render() { return ({theme => } ); } }
React16 規范了 Ref 的獲取方式,通過 React.createRef 取得 Ref 對象。
// before React 16 ··· componentDidMount() { const el = this.refs.myRef } render() { return } ··· // React 16+ constructor(props) { super(props) this.myRef = React.createRef() } render() { return } ···
React.forwardRef 是 Ref 的轉發, 它能夠讓父組件訪問到子組件的 Ref,從而操作子組件的 DOM。 React.forwardRef 接收一個函數,函數參數有 props 和 ref。
const TextInput = React.forwardRef((props, 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 () } } 生命周期函數的更新
React16 采用了新的內核架構 Fiber,Fiber 將組件更新分為兩個階段:Render Parse 和 Commit Parse,因此 React 也引入了 getDerivedStateFromProps 、 getSnapshotBeforeUpdate 及 componentDidCatch 等三個全新的生命周期函數。同時也將 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 標記為不安全的方法。
static getDerivedStateFromProps(nextProps, prevState)getDerivedStateFromProps(nextProps, prevState) 其作用是根據傳遞的 props 來更新 state。它的一大特點是無副作用,由于處在 Render Phase 階段,所以在每次的更新都會觸發該函數, 在 API 設計上采用了靜態方法,使其無法訪問實例、無法通過 ref 訪問到 DOM 對象等,保證了該函數的純粹高效。
為了配合未來的 React 異步渲染機制,React v16.4 對 getDerivedStateFromProps 做了一些改變, 使其不僅在 props 更新時會被調用,setState 時也會被觸發。
如果改變 props 的同時,有副作用的產生,這時應該使用 componentDidUpdate;
如果想要根據 props 計算屬性,應該考慮將結果 memoization 化;
如果想要根據 props 變化來重置某些狀態,應該考慮使用受控組件;
static getDerivedStateFromProps(props, state) { if (props.value !== state.controlledValue) { return { controlledValue: props.value, }; } return null; }getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate(prevProps, prevState) 會在組件更新之前獲取一個 snapshot,并可以將計算得的值或從 DOM 得到的信息傳遞到 componentDidUpdate(prevProps, prevState, snapshot) 函數的第三個參數,常常用于 scroll 位置定位等場景。
componentDidCatch(error, info)componentDidCatch 函數讓開發者可以自主處理錯誤信息,諸如錯誤展示,上報錯誤等,用戶可以創建自己的 Error Boundary 來捕獲錯誤。
componentWillMount(nextProps, nextState)componentWillMount 被標記為不安全,因為在 componentWillMount 中獲取異步數據或進行事件訂閱等操作會產生一些問題,比如無法保證在 componentWillUnmount 中取消掉相應的事件訂閱,或者導致多次重復獲取異步數據等問題。
componentWillReceiveProps(nextProps) / componentWillUpdate(nextProps, nextState)componentWillReceiveProps / componentWillUpdate 被標記為不安全,主要是因為操作 props 引起的 re-render 問題,并且對 DOM 的更新操作也可能導致重新渲染。
Strict ModeStrictMode 可以在開發階段開啟嚴格模式,發現應用存在的潛在問題,提升應用的健壯性,其主要能檢測下列問題:
識別被標志位不安全的生命周期函數
對棄用的 API 進行警告
探測某些產生副作用的方法
檢測是否使用 findDOMNode
檢測是否采用了老的 Context API
class App extends React.Component { render() { return (React v16.4 Pointer Events) } }
指針事件是為指針設備觸發的 DOM 事件。它們旨在創建單個 DOM 事件模型來處理指向輸入設備,例如鼠標,筆 / 觸控筆或觸摸(例如一個或多個手指)。指針是一個與硬件無關的設備,可以定位一組特定的屏幕坐標。擁有指針的單個事件模型可以簡化創建 Web 站點和應用程序,并提供良好的用戶體驗,無論用戶的硬件如何。但是,對于需要特定于設備的處理的場景,指針事件定義了一個 pointerType 屬性,用于檢查產生事件的設備類型。
React 新增 onPointerDown / onPointerMove / onPointerUp / onPointerCancel / onGotPointerCapture / onLostPointerCapture / onPointerEnter / onPointerLeave / onPointerOver / onPointerOut 等指針事件。
這些事件只能在支持 指針事件 規范的瀏覽器中工作。如果應用程序依賴于指針事件,建議使用第三方指針事件 polyfill。
React v16.5 ProfilerReact 16.5 添加了對新的 profiler DevTools 插件的支持。這個插件使用 React 的 Profiler 實驗性 API 去收集所有 component 的渲染時間,目的是為了找出 React App 的性能瓶頸,它將會和 React 即將發布的 時間片 特性完全兼容。
React v16.6 memoReact.memo() 只能作用在簡單的函數組件上,本質是一個高階函數,可以自動幫助組件執行shouldComponentUpdate(),但只是執行淺比較,其意義和價值有限。
const MemoizedComponent = React.memo(props => { /* 只在 props 更改的時候才會重新渲染 */ });lazy / Suspense
React.lazy() 提供了動態 import 組件的能力,實現代碼分割。
Suspense 作用是在等待組件時 suspend(暫停)渲染,并顯示加載標識。
目前 React v16.6 中 Suspense 只支持一個場景,即使用 React.lazy() 和
import React, {lazy, Suspense} from "react"; const OtherComponent = lazy(() => import("./OtherComponent")); function MyComponent() { return (Loading...
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; } }getDerivedStateFromError
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 returnReact v16.7(~Q1 2019) HooksSomething went wrong.
; } return this.props.children; } }
Hooks 要解決的是狀態邏輯復用問題,且不會產生 JSX 嵌套地獄,其特性如下:
多個狀態不會產生嵌套,依然是平鋪寫法;
Hooks 可以引用其他 Hooks;
更容易將組件的 UI 與狀態分離;
Hooks 并不是通過 Proxy 或者 getters 實現,而是通過數組實現,每次 useState 都會改變下標,如果 useState 被包裹在 condition 中,那每次執行的下標就可能對不上,導致 useState 導出的 setter 更新錯數據。
更多 Hooks 使用場景可以閱讀下列文章:
精讀《怎么用 React Hooks 造輪子》
function App() { const [open, setOpen] = useState(false); return ( <>React v16.8(~Q2 2019) Concurrent RenderingsetOpen(false)} onCancel={() => setOpen(false)} /> > ); }
Concurrent Rendering 并發渲染模式是在不阻塞主線程的情況下渲染組件樹,使 React 應用響應性更流暢,它允許 React 中斷耗時的渲染,去處理高優先級的事件,如用戶輸入等,還能在高速連接時跳過不必要的加載狀態,用以改善 Suspense 的用戶體驗。
目前 Concurrent Rendering 尚未正式發布,也沒有詳細相關文檔,需要等待 React 團隊的正式發布。
React v16.9(~mid 2019) Suspense for Data FetchingSuspense 通過 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 (4 總結); }
從 React16 的一系列更新和新特性中我們可以窺見,React 已經不僅僅只在做一個 View 的展示庫,而是想要發展成為一個包含 View / 數據管理 / 數據獲取 等場景的前端框架,以 React 團隊的技術實力以及想法,筆者還是很期待和看好 React 的未來,不過它漸漸地已經對開發新手們不太友好了。
5 更多討論討論地址是:[精讀《React16 新特性》 · Issue #115 · dt-fe/weekly]https://github.com/dt-fe/week...
如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。前端精讀 - 幫你篩選靠譜的內容。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99869.html
摘要:更容易將組件的與狀態分離。也就是只提供狀態處理方法,不會持久化狀態。大體思路是利用共享一份數據,作為的數據源。精讀帶來的約定函數必須以命名開頭,因為這樣才方便做檢查,防止用判斷包裹語句。前端精讀幫你篩選靠譜的內容。 1 引言 React Hooks 是 React 16.7.0-alpha 版本推出的新特性,想嘗試的同學安裝此版本即可。 React Hooks 要解決的問題是狀態共享,...
摘要:拿到的都是而不是原始值,且這個值會動態變化。精讀對于的與,筆者做一些對比。因此采取了作為優化方案只有當第二個依賴參數變化時才返回新引用。不需要使用等進行性能優化,所有性能優化都是自動的。前端精讀幫你篩選靠譜的內容。 1. 引言 Vue 3.0 的發布引起了軒然大波,讓我們解讀下它的 function api RFC 詳細了解一下 Vue 團隊是怎么想的吧! 首先官方回答了幾個最受關注的...
摘要:引言發布了幾個新特性,主要變化是類型檢查更嚴格,對一些時髦功能拓展了類型支持。精讀這次改動意圖非常明顯,是為了跟上的新語法。基本可以算是對社區的回饋。討論地址是精讀新特性如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。 1 引言 Typescript 3.2 發布了幾個新特性,主要變化是類型檢查更嚴格,對 ES6、ES7 一些時髦功能拓展了類型支持。 2 概要 下面挑一...
摘要:今天我們就來解讀一下的源碼。比較有意思,將定時器以方式提供出來,并且提供了方法。實現方式是,在組件內部維護一個定時器,實現了組件更新銷毀時的計時器更新銷毀操作,可以認為這種定時器的生命周期綁定了組件的生命周期,不用擔心銷毀和更新的問題。 1. 引言 React PowerPlug 是利用 render props 進行更好狀態管理的工具庫。 React 項目中,一般一個文件就是一個類,...
閱讀 2906·2021-11-15 18:02
閱讀 3800·2021-10-14 09:43
閱讀 3732·2021-09-08 10:41
閱讀 2522·2019-08-30 15:53
閱讀 1803·2019-08-30 14:14
閱讀 1943·2019-08-29 16:12
閱讀 3138·2019-08-29 14:03
閱讀 1280·2019-08-29 13:46