-
React
中一個(gè)常見模式是為一個(gè)組件返回多個(gè)元素嘱能。Fragments
可以讓你聚合一個(gè)子元素列表吝梅,并且不在DOM
中增加額外節(jié)點(diǎn)。
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
}
注意:①key
是唯一可以傳遞給 Fragment
的屬性焰檩。② 在 React
中憔涉,<></>
是 <React.Fragment><React.Fragment/>
的語(yǔ)法糖订框。
-
Strict Mode
嚴(yán)格模式
StrictMode
是一個(gè)用以標(biāo)記出應(yīng)用中潛在問(wèn)題的工具析苫。與Fragment
類似,StrictMode
不會(huì)渲染任何真實(shí)的UI
穿扳。它為其后代元素觸發(fā)額外的檢查和警告衩侥。
import { StrictMode, Component } from 'react'
class Child extends Component {
// 以下三個(gè)函數(shù)在 React v16.3 已不被推薦,未來(lái)的版本會(huì)廢棄矛物。
componentWillMount() {
console.log('componentWillMount')
}
componentWillUpdate() {
console.log('componentWillUpdate')
}
componentWillReceiveProps() {
console.log('componentWillReceiveProps')
}
render() {
return (
<div />
)
}
}
export default class StrictModeExample extends Component {
render() {
return (
<StrictMode>
<Child />
</StrictMode>
)
}
}
由于在StrictMode
內(nèi)使用了三個(gè)即將廢棄的API
茫死,打開控制臺(tái) ,可看到如下錯(cuò)誤提醒:
注釋:嚴(yán)格模式檢查只在開發(fā)模式下運(yùn)行履羞,不會(huì)與生產(chǎn)模式?jīng)_突峦萎。
-
createRef (v16.3)
(1) 老版本ref
使用方式
① 字符串形式:<input ref="input" />
② 回調(diào)函數(shù)形式:<input ref={input => (this.input = input)} />
(2) 字符串形式缺點(diǎn)
① 需要內(nèi)部追蹤ref
的this
取值,會(huì)使React
稍稍變慢忆首。
② 有時(shí)候this
與你想象的并不一致爱榔。
import React from 'react'
class Children extends React.Component {
componentDidMount() {
// <h1></h1>
console.log('children ref', this.refs.titleRef)
}
render() {
return (
<div>
{this.props.renderTitle()}
</div>
)
}
}
class Parent extends React.Component {
// 放入子組件渲染
renderTitle = () => (
<h1 ref='titleRef'>{this.props.title}</h1>
)
componentDidMount() {
// undefined
console.log('parent ref:', this.refs.titleRef)
}
render() {
return (
<Children renderTitle={this.renderTitle}></Children>
)
}
}
export default Parent
(3) createRef
語(yǔ)法
import React from 'react'
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
componentDidMount() {
this.inputRef.current.focus();
}
}
export default MyComponent
- 調(diào)用
setState
更新狀態(tài)時(shí),若之后的狀態(tài)依賴于之前的狀態(tài)糙及,推薦使用傳入函數(shù)形式详幽。
語(yǔ)法:setState((prevState, props) => stateChange, [callback])
例如,假設(shè)我們想通過(guò)props.step
在狀態(tài)中增加一個(gè)值:
this.setState((prevState, props) => {
return {counter: prevState.counter + props.step};
});
- 錯(cuò)誤邊界
(1) 錯(cuò)誤邊界用于捕獲其子組件樹JavaScript
異常浸锨,記錄錯(cuò)誤并展示一個(gè)回退的UI
的React
組件唇聘,避免整個(gè)組件樹異常導(dǎo)致頁(yè)面空白。
(2) 錯(cuò)誤邊界在渲染期間柱搜、生命周期方法內(nèi)迟郎、以及整個(gè)組件樹構(gòu)造函數(shù)內(nèi)捕獲錯(cuò)誤。
(3) 組件如果定義了static getDerivedStateFromError()
或componentDidCatch()
中的任意一個(gè)或兩個(gè)生命周期方法 聪蘸。當(dāng)其子組件拋出錯(cuò)誤時(shí)谎亩,可使用static getDerivedStateFromError()
更新state
,可使用componentDidCatch()
記錄錯(cuò)誤信息宇姚。
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 };
}
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;
}
}
而后你可以像一個(gè)普通的組件一樣使用:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
注釋:getDerivedStateFromError
和componentDidCatch
方法使用一個(gè)即可捕獲子組件樹錯(cuò)誤匈庭。
-
Portal
(1)Portals
提供了一種很好的將子節(jié)點(diǎn)渲染到父組件以外的DOM
節(jié)點(diǎn)的方式。
(2) 通過(guò)Portals
進(jìn)行事件冒泡
盡管portal
可以被放置在DOM
樹的任何地方浑劳,但在其他方面其行為和普通的React
子節(jié)點(diǎn)行為一致阱持。一個(gè)從portal
內(nèi)部會(huì)觸發(fā)的事件會(huì)一直冒泡至包含React
樹 的祖先。
import React from 'react';
import ReactDOM from 'react-dom';
class Modal extends React.Component {
render() {
return this.props.clicks % 2 === 1
? this.props.children
: ReactDOM.createPortal(
this.props.children,
document.getElementById('modal'),
);
}
}
class Child extends React.Component {
render() {
return (
<button>Click</button>
);
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {clicks: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
clicks: prevState.clicks + 1
}));
}
render() {
return (
<div onClick={this.handleClick}>
<p>Number of clicks: {this.state.clicks}</p>
<Modal clicks={this.state.clicks}>
<Child />
</Modal>
</div>
);
}
}
export default Parent
注釋:當(dāng)組件由portal
渲染方式切換為普通渲染方式魔熏,會(huì)導(dǎo)致該組件被卸載之后重新渲染衷咽。組件放大功能如果通過(guò)portal
方式實(shí)現(xiàn)鸽扁,放大前的狀態(tài)(滾動(dòng)位置、焦點(diǎn)位置等)無(wú)法保持镶骗。
-
fiber
介紹
fiber
是React 16
中新的和解引擎桶现。它的主要目的是使虛擬DOM
能夠進(jìn)行增量渲染。
(1) 同步更新過(guò)程的局限
當(dāng)React
決定要加載或者更新組件樹時(shí)鼎姊,會(huì)做很多事骡和,比如調(diào)用各個(gè)組件的生命周期函數(shù),計(jì)算和比對(duì)Virtual DOM
相寇,最后更新DOM
樹慰于,這整個(gè)過(guò)程是同步進(jìn)行的。瀏覽器那個(gè)唯一的主線程都在專心運(yùn)行更新操作唤衫,無(wú)暇去做任何其他的事情婆赠。
(2)React Fiber
的方式
破解JavaScript
中同步操作時(shí)間過(guò)長(zhǎng)的方法其實(shí)很簡(jiǎn)單——分片。
React Fiber
把更新過(guò)程碎片化佳励,執(zhí)行過(guò)程如下面的圖所示休里,每執(zhí)行完一段更新過(guò)程,就把控制權(quán)交還給React
負(fù)責(zé)任務(wù)協(xié)調(diào)的模塊赃承,看看有沒(méi)有其他緊急任務(wù)要做妙黍,如果沒(méi)有就繼續(xù)去更新,如果有緊急任務(wù)楣导,那就去做緊急任務(wù)废境。
維護(hù)每一個(gè)分片的數(shù)據(jù)結(jié)構(gòu),就是Fiber
筒繁。
(3)React Fiber
更新過(guò)程的兩個(gè)階段
在React Fiber
中噩凹,一次更新過(guò)程會(huì)分成多個(gè)分片完成,所以完全有可能一個(gè)更新任務(wù)還沒(méi)有完成毡咏,就被另一個(gè)更高優(yōu)先級(jí)的更新過(guò)程打斷驮宴,這時(shí)候,優(yōu)先級(jí)高的更新任務(wù)會(huì)優(yōu)先處理完呕缭,而低優(yōu)先級(jí)更新任務(wù)所做的工作則會(huì)完全作廢堵泽,然后等待機(jī)會(huì)重頭再來(lái)。
React Fiber
一個(gè)更新過(guò)程被分為兩個(gè)階段(Phase
):第一個(gè)階段Reconciliation Phase
和第二階段Commit Phase
恢总。
在第一階段Reconciliation Phase
迎罗,React Fiber
會(huì)找出需要更新哪些DOM
,這個(gè)階段是可以被打斷的片仿;但是到了第二階段Commit Phase
纹安,那就一鼓作氣把DOM
更新完,絕不會(huì)被打斷。
(4)React Fiber
對(duì)現(xiàn)有代碼的影響
因?yàn)榈谝浑A段的過(guò)程會(huì)被打斷而且“重頭再來(lái)”厢岂,就會(huì)造成第一階段中的生命周期函數(shù)在一次加載和更新過(guò)程中可能會(huì)被多次調(diào)用光督!
第一個(gè)階段的四個(gè)生命周期函數(shù)中,componentWillReceiveProps
塔粒、componentWillMount
和componentWillUpdate
這三個(gè)函數(shù)可能包含副作用结借,所以當(dāng)使用React Fiber
的時(shí)候一定要重點(diǎn)看這三個(gè)函數(shù)的實(shí)現(xiàn)。
注釋:大家應(yīng)該都清楚進(jìn)程(Process
)和線程(Thread
)的概念卒茬,在計(jì)算機(jī)科學(xué)中還有一個(gè)概念叫做Fiber
船老,英文含義就是“纖維”,意指比Thread
更細(xì)的線,也就是比線程(Thread
)控制得更精密的并發(fā)處理機(jī)制允蜈。 - 聲明周期變化
從v16.3
開始,原來(lái)的三個(gè)生命周期componentWillMount
、componentWillUpdate
岭佳、componentWillReceiveProps
將被廢棄,取而代之的是兩個(gè)全新的生命周期:
①static getDerivedStateFromProps
②getSnapshotBeforeUpdate
-
getDerivedStateFromProps
用法
static getDerivedStateFromProps(nextProps, prevState)
組件實(shí)例化后和接受新屬性時(shí)將會(huì)調(diào)用getDerivedStateFromProps
喂走。它應(yīng)該返回一個(gè)對(duì)象來(lái)更新狀態(tài)呼胚,或者返回null
來(lái)表明新屬性不需要更新任何狀態(tài)。
如果父組件導(dǎo)致了組件的重新渲染张惹,即使屬性沒(méi)有更新舀锨,這一方法也會(huì)被調(diào)用。
如果你只想處理變化宛逗,你可能想去比較新舊值坎匿。調(diào)用this.setState()
通常不會(huì)觸發(fā)getDerivedStateFromProps()
。 -
getStapshotBeforeUpdate
getSnapshotBeforeUpdate()
在最新的渲染輸出提交給DOM
前將會(huì)立即調(diào)用雷激。它讓你的組件能在當(dāng)前的值可能要改變前獲得它們替蔬。這一生命周期返回的任何值將會(huì) 作為參數(shù)被傳遞給componentDidUpdate()
。
class ScrollingList extends React.Component {
listRef = React.createRef();
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the current height of the list so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
return this.listRef.current.scrollHeight;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
if (snapshot !== null) {
this.listRef.current.scrollTop +=
this.listRef.current.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
在上面的例子中屎暇,為了支持異步渲染承桥,在getSnapshotBeforeUpdate
中讀取scrollHeight
而不是componentWillUpdate
,這點(diǎn)很重要根悼。由于異步渲染凶异,在“渲染”時(shí)期(如componentWillUpdate
和render
)和“提交”時(shí)期(如getSnapshotBeforeUpdate
和componentDidUpdate
)間可能會(huì)存在延遲。如果一個(gè)用戶在這期間做了像改變?yōu)g覽器尺寸的事挤巡,從componentWillUpdate
中讀出的scrollHeight
值將是滯后的剩彬。
-
Context
用法
import React from 'react'
const ThemeContext = React.createContext();
const ThemedButton = (props) => (
<ThemeContext.Consumer>
{ context => <span style={{color: context}}>{props.text}</span> }
</ThemeContext.Consumer>
)
const Toolbar = () => (
<ThemeContext.Provider value='red'>
<ThemedButton text="Context API"/>
</ThemeContext.Provider>
)
export default Toolbar
注釋:在 Context.Consumer
中,children
必須為函數(shù)矿卑。