前段時(shí)間React的16版本發(fā)布了,采用了MIT開源許可證,新增了一些新的特性。
1. 使用Error Boundary處理錯(cuò)誤組件
之前,一旦某個(gè)組件發(fā)生錯(cuò)誤,整個(gè)組件樹將會(huì)從根節(jié)點(diǎn)被unmount下來。React 16修復(fù)了這一點(diǎn),引入了Error Boundary的概念,中文譯為“錯(cuò)誤邊界”,當(dāng)某個(gè)組件發(fā)生錯(cuò)誤時(shí),我們可以通過Error Boundary捕獲到錯(cuò)誤并對(duì)錯(cuò)誤做優(yōu)雅處理,如使用Error Boundary提供的內(nèi)容替代錯(cuò)誤組件。Error Boundary可以看作是一種特殊的React組件,新增了componentDidCatch這個(gè)生命周期函數(shù),它可以捕獲自身及子樹上的錯(cuò)誤并對(duì)錯(cuò)誤做優(yōu)雅處理,包括上報(bào)錯(cuò)誤日志、展示出錯(cuò)提示,而不是卸載整個(gè)組件樹。(注:它并不能捕獲runtime所有的錯(cuò)誤,比如組件回調(diào)事件里的錯(cuò)誤,可以把它想象成傳統(tǒng)的try-catch語句)
//最佳實(shí)踐:將ErrorBoundary抽象為一個(gè)公用的組件類 import React, { Component } from 'react' export default class ErrorBoundary extends Component { constructor(props) { super(props) this.state = { hasError: false } } componentDidCatch(err, info) { this.setState({ hasError: true }) //sendErrorReport(err,info) } render(){ if(this.state.hasError){ return <div>Something went wrong!</div> } return this.props.children } }
我們可以在容易出錯(cuò)的組件外使用ErrorBoundary將它包裹起來,如下
//使用方式 render(){ return ( <div> <ErrorBoundary> <Profile user={this.state.user} /> </ErrorBoundary> <button onClick={this.onClick}>Update</button> </div> ) }
如果Profile組件發(fā)生錯(cuò)誤,將會(huì)使用ErrorBoundary提供的<div>Something went wrong</div>代替它,而不會(huì)引起整個(gè)組件樹的卸載。
2. render方法新增返回類型
在React 16中,render方法支持直接返回string,number,boolean,null,portal,以及fragments(帶有key屬性的數(shù)組),這可以在一定程度上減少頁面的DOM層級(jí)。
//string render(){ return 'hello,world' } //number render(){ return 12345 } //boolean render(){ return isTrue?true:false } //null render(){ return null } //fragments,未加key標(biāo)識(shí)符,控制臺(tái)會(huì)出現(xiàn)warning render(){ return [ <div>hello</div>, <span>world</span>, <p>oh</p> ] }
以上各種類型現(xiàn)在均可以直接在render中返回,不需要再在外層包裹一層容器元素,不過在返回的數(shù)組類型中,需要在每個(gè)元素上加一個(gè)唯一且不變的key值,否則控制臺(tái)會(huì)報(bào)一個(gè)warning。
3.使用createPortal將組件渲染到當(dāng)前組件樹之外
Portals機(jī)制提供了一種最直接的方式可以把一個(gè)子組件渲染到父組件渲染的DOM樹之外。默認(rèn)情況下,React組件樹和DOM樹是完全對(duì)應(yīng)的,因此對(duì)于一些Modal,Overlay之類的組件,通常是將它們放在頂層,但邏輯上它們可能只是屬于某個(gè)子組件,不利于組件的代碼組織。通過使用createPortal,我們可以將組件渲染到我們想要的任意DOM節(jié)點(diǎn)中,但該組件依然處在React的父組件之內(nèi)。帶來的一個(gè)特性就是,在子組件產(chǎn)生的event依然可以被React父組件捕獲,但在DOM結(jié)構(gòu)中,它卻不是你的父組件。對(duì)于組件組織,代碼切割來說,這是一個(gè)很好的屬性。
//實(shí)現(xiàn)一個(gè)簡易蒙層效果,抽象出一個(gè)通用的Overlay組件 import React, { Component } from 'react'; import ReactDOM from 'react-dom'; export default class Overlay extends Component { constructor(props) { super(props); this.container = document.createElement('div'); document.body.appendChild(this.container); } componentWillUnmount() { document.body.removeChild(this.container); } render() { return ReactDOM.createPortal( <div className='overlay'> <span className='overlay-close' onClick={this.props.onClose}>×</span> {this.props.children} </div>, this.container ) } } //該組件對(duì)應(yīng)的樣式如下 .overlay{ box-sizing:border-box; position: fixed; top:50%; left:50%; width:260px; height:200px; margin-left:-130px; margin-top:-100px; padding:10px; background-color: #fff; outline: rgba(0,0,0,.5) solid 9999px; } .overlay-close{ position: absolute; top:10px; right:10px; color:red; cursor: pointer; }
使用方式如下:
class App extends Component { constructor(props) { super(props); this.state = { overlayActive: false } this.closeOverlay = this.closeOverlay.bind(this); this.showOverlay = this.showOverlay.bind(this); } closeOverlay() { this.setState({ overlayActive: false }) } showOverlay() { this.setState({ overlayActive: true }) } render() { return ( <div className="App"> <div>hello world!</div> {this.state.overlayActive && <Overlay onClose={this.closeOverlay}>overlay content</Overlay>} <button onClick={this.showOverlay}>show</button> </div> ); } }
效果如圖:
4.支持自定義DOM屬性
在之前的版本中,React會(huì)忽略無法識(shí)別的HTML和SVG屬性,自定義屬性只能通過data-*形式添加,現(xiàn)在它會(huì)把這些屬性直接傳遞給DOM(這個(gè)改動(dòng)讓React可以去掉屬性白名單,從而減少了文件大小),不過有些寫法仍然是無效的。如DOM傳遞的自定義屬性是函數(shù)類型或event handler時(shí),依然會(huì)被React忽略。
//錯(cuò)誤寫法 render(){ return( <div a={()=>{}} onclick={this.showOverlay}></div> ) ) //Warning: Invalid event handler property `onclick`. Did you mean `onClick`? //Warning: Invalid value for prop `a` on <div> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.
現(xiàn)在class和tabindex等屬性可以被傳遞給DOM,但依然會(huì)報(bào)一個(gè)Warning,建議使用標(biāo)準(zhǔn)的駝峰式className,tabIndex等。
5.setState傳入null時(shí)不會(huì)再觸發(fā)更新
比如在一個(gè)選擇城市的函數(shù)中,當(dāng)點(diǎn)擊某個(gè)城市時(shí),newValue的值可能發(fā)生改變,也可能是點(diǎn)擊了原來的城市,值沒有變化,返回null則可以直接避免觸發(fā)更新,不會(huì)引起重復(fù)渲染,不需要在shouldComponentUpdate函數(shù)里面去判斷。
selectCity(e){ const newValue = e.target.value; this.setState((state)=>{ if(state.city===newValue){ return null; } return {city:newValue} }) )
注意:現(xiàn)在setState回調(diào)(第二個(gè)參數(shù))會(huì)在componentDidMount/componentDidUpdate后立即觸發(fā),而不是等到所有組件渲染完成之后。
6.更好的服務(wù)器端渲染
React 16的SSR被完全重寫,新的實(shí)現(xiàn)非常快,接近3倍性能于React 15,現(xiàn)在提供一種流模式streaming,可以更快地把渲染的字節(jié)發(fā)送到客戶端。另外,React 16在hydrating(注:指在客戶端基于服務(wù)器返回的HTML再次重新渲染)方面也表現(xiàn)的更好,React 16不再要求客戶端的初始渲染完全和服務(wù)器返回的渲染結(jié)果一致,而是盡量重用已經(jīng)存在的DOM元素。不會(huì)再有checksum(標(biāo)記驗(yàn)證)!并對(duì)不一致發(fā)出警告。一般來說,在服務(wù)器和客戶端渲染不同的內(nèi)容是不建議的,但這樣做在某些情況下也是有用的(比如,生成timestamp)。
7.新的打包策略
新的打包策略中去掉了process.env檢查。
React 16的體積比上個(gè)版本減小了32%(30% post-gzip),文件尺寸的減小一部分要?dú)w功于打包方法的改變。
react is 5.3 kb (2.2 kb gzipped), down from 20.7 kb (6.9 kb gzipped). react-dom is 103.7 kb (32.6 kb gzipped), down from 141 kb (42.9 kb gzipped). react + react-dom is 109 kb (34.8 kb gzipped), down from 161.7 kb (49.8 kb gzipped).
寫在最后,React 16采用了新的核心架構(gòu)React Fiber。官方解釋是“React Fiber是對(duì)核心算法的一次重新實(shí)現(xiàn)”,后續(xù)再深入學(xué)習(xí)。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com