[TOC]javascript
新的生命週期函數 getDerivedStateFromProps 位於 Mounting 掛載階段和由 props 更新觸發的 Updating 階段。getDerivedStateFromProps 主要用於替換 componentWillReceiveProps 函數,其功能更加明確,就是根據 props 更新組件的 state。html
class Demo extends React.Component{
state = {
tmpA: 1,
tmpB: 'test',
lastA: null,
lastB: null
};
static getDerivedStateFromProps(props, state){
if(props.a !== state.lastA){
return {
tmpA: props.a,
lastA: props.a
}
}
return null;
}
render(){
return <div> <input value={this.state.tmpA} onChange={e => { this.setState({tmpA: e.target.value}) }} /> </div> } } 複製代碼
須要注意的幾點是:java
getSnapshotBeforeUpdate 函數調用於 render 函數以後 componentDidUpdate 函數以前,主要用於獲取更新前的 DOM 元素的信息。關於該函數的用法,React 的官方示例爲:react
```javascript
//這是一個帶滾動條的列表組件
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
//若是這次更新中,列表變長,則記錄更新前滾動的位置
//並做爲componentDidUpdate函數的第三個參數傳入
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
//默認返回null
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
//根據更新前的滾動位置,設置更新後列表的滾動長度
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
```
複製代碼
須要注意的幾點:設計模式
能夠看出 React16 中,類組件的一個組件的生命週期被劃分爲類 render 和 commit 兩個階段,render 階段主要負責組件渲染相關,包括對渲染數據 state 的更新。爲了防止一次更新中 render 階段重複執行,React 將該階段可能引入 side effects 的生命週期函數 componentWillReceiveProps、componentWillUpdate、componentWillUnmount 等函數移除。
針對須要經過 props 計算 derived state 的需求,提供靜態函數 getDerivedStateFromProps。針對獲取更新前 DOM 元素的需求,React16 提供了 getSnapshotBeforeUpdate 生命週期函數。數組
React 官方文檔對 Hooks 的介紹是 Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class,從介紹中咱們可知:服務器
組件邏輯代碼複用 相比於 HOC、renderProps 而言,須要提供一個更加靈活輕便而且是原生支持的狀態邏輯複用機制。我的總結 renderProps 和 HOC 的缺點在於:app
HOC、render props 和 Hooks 實現邏輯代碼複用示例
當咱們須要實現一個簡單的顯示隱藏的功能時,通常是在組件的 state 中定義一個控制現實仍是隱藏的布爾變量,並再添加一個 show 和 hide 方法來控制該變量。若是要將這段邏輯代碼提取出來複用的話,能夠經過高階組件 HOC、render props 或者 Hooks 來實現,如下分別列出這三種方法的實現代碼以及對應的 React 組件樹結構。能夠看出使用 Hooks 複用邏輯代碼時,因爲沒有建立額外的組件,故不管是代碼仍是最後生成的 React 組件樹,都是 Hooks 的實現方式更簡潔。dom
const Wrapper = WrappedComponent => class extends React.Component{
constructor(props) {
super(props);
this.state = {
isDisplayed: defaultTo(props.initialState, false),
};
this.hide = this.hide.bind(this);
this.show = this.show.bind(this);
}
hide() {
this.setState({isDisplayed: false,});
}
show() {
this.setState({isDisplayed: true,});
}
render(){
const newProps = {
...this.props,
...this.state,
hide: this.hide,
show: this.show
};
return <WrappedComponent {...newProps}/> } }; const App = Wrapper(({isDisplayed, hide, show}) => { return ( <div className="App"> { isDisplayed && <p onClick={hide}>Click to hide</p> } <button onClick={show}>Click to display</button> </div> ); }) 複製代碼
class VisibilityHelper extends React.Component {
constructor(props) {
super(props);
this.state = {
isDisplayed: defaultTo(props.initialState, false),
};
this.hide = this.hide.bind(this);
this.show = this.show.bind(this);
}
hide() {
this.setState({isDisplayed: false});
}
show() {
this.setState({isDisplayed: true});
}
render() {
return this.props.children({
...this.state,
hide: this.hide,
show: this.show,
});
}
}
const App = () => (
<div className="App"> <VisibilityHelper> { ({isDisplayed, hide, show}) => ( <div> { isDisplayed && <p onClick={hide}>Click to hide</p> } <button onClick={show}>Click to display</button> </div> ) } </VisibilityHelper> </div>
);
複製代碼
import React, {useState} from 'react';
function useIsDisplayed(initialValue) {
const [isDisplayed, setDisplayed] = useState(defaultTo(initialValue, false));
function show(){
setDisplayed(true);
}
function hide() {
setDisplayed(false);
}
return {isDisplayed, show, hide};
}
const App = () => {
const {isDisplayed, show, hide} = useIsDisplayed(false);
return (
<div className="App"> { isDisplayed && <p onClick={hide}>Click to hide</p> } <button onClick={show}>Click to display</button> </div>
);
};
複製代碼
武裝函數式組件 React 如今力推函數式組件,並逐漸棄用 class 組件(具體緣由後面章節再討論)。以前的函數式組件因爲沒有 state 以及類型 class 組件生命週期函數的機制,每每用來做展現型組件。而經過 useState hook 能夠爲函數式組件添加 state,經過 useEffect 能夠在函數式組件中實現 class 組件生命週期函數的功能。異步
useState 函數用於給函數式組件提供可以持久存儲並能將變化映射到視圖上的狀態 state hook,相似 class 組件中的 state。在上節給出的 Hooks 實現邏輯代碼複用的例子中已經展現了 useState 函數的使用。useState 函數的返回值爲一個數組,數組中第一個元素爲 state 對象,第二個元素爲一個 dispatch 方法,用於更新該 state。基本使用爲:
import React, {useState} from 'react';
function Counter(){
//聲明一個 count 變量,能夠經過 setCount dispatch 一個新的 count 值
const [count, setCount] = useState(0);
useState('the second state');
useState('the third state');
return <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>+</button> </div>
}
複製代碼
useEffect 函數用於建立一個 effect hook。effect hooks 用於在函數式組件的更新後執行一些 side effects,其之於函數式組件就至關於 componentDidMount 和 componentDidUpdate 之於 class 組件。useEffect 的基本用法爲:
const App = () => {
const [text, setText] = useState('');
useEffect(() => {
window.addEventListener('keydown', writing, false);
return () => {
window.removeEventListener('keydown', writing, false);
}
});
function writing(e){
setText(text + e.key);
}
return (
<div className="App"> <p>{text}</p> </div>
);
};
複製代碼
上例是一個使用函數式組件和 state hooks 以及 effect hooks 實現的監聽用戶鍵盤輸入並現實輸入字符串的功能。能夠看到,咱們使用 state hooks 來存儲以及更新用戶輸入的字符串,用 effect hooks 來監聽以及取消監聽 keydown 事件。
window.removeEventListener
再執行 window.addEventListener
useEffect(() => {}, [])
。當傳入第二個參數時,只有當第二個參數中的某個 state 或者 props 改變時,該 effect hook 纔會被調用。值得注意的是:此時 effect hook 以及在其間的回調函數只能訪問到 useEffect 數組參數中的 state 和 props 的最新值,其它 state 和 props 只能獲取到初始值和 React16 中提供的 createRef 方法同樣,用於獲取 React 組件的 ref。官方示例:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
//inputEl 的 current 屬性指向 input 組件的 dom 節點
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
複製代碼
除了用於獲取組件 ref 之外,useRef 還能夠用於實現相似於 class 組件的實例屬性(this.XXX),直接經過對 useRef 方法返回對象的 current 屬性進行讀寫便可。
useReducer 實現了 Redux 中的 reducer 功能。當 state 的邏輯比較複雜的時候,能夠考慮使用 useReducer 來定義一個 state hook。示例:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 複製代碼
上例是 React 官方提供的 useReducer 的使用示例。固然和 Redux 同樣,dispatch 的 action 能夠由 actionCreator 來生成。
var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null;
本節直接使用 React 官網提供的示例代碼,須要注意的是,自定義 Hooks 也須要知足上節中提到的使用規則。
import React, { useState, useEffect } from 'react';
//useFriendStatus 爲根據好友 ID 獲取好友是否在線的自定義 Hook
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
//聊天對象選擇列表組件
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1);
//經過使用 useFriendStatus Hook,
//將獲取當前好友是否在線的邏輯從組件中分離出去
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}
複製代碼
function F({count = 0, isPure = false}){
console.log(`render ${isPure ? 'pure FC' : 'FC'} ${count} times`);
return <h2>{count}</h2>
}
const PureFC = React.memo(F, /*areEqual(prevProps, nextProps)*/);
class App extends React.Component{
state = {
count: 0,
fakeState: false
};
render(){
const {count, fakeState} = this.state;
return <div>
<F count={count} isPure={false}/>
<PureFC count={count} isPure={true}/>
<button onClick={() => {
this.setState({count: count + 1})}
}>increase</button>
<button onClick={() => {this.setState({fakeState: !fakeState})}}>Click2</button>
</div>;
}
}
//*click increase button
//render FC 1 times
//render pure FC 1 times
//*click Click2 button
//render FC 1 times
複製代碼
上例說明,即便 FC(函數式組件) 的 props 沒有變化,當父組件更新時,仍是會從新渲染 FC。但用 React.memo 高階組件包裹的 FC 卻能夠跳過 props 沒有變化的更新。爲了支持更加靈活的 props 對比,React.memo 還支持傳入第二個函數參數 areEqual(prevProps, nextProps)。該函數返回 true 時不更新所包裹的 FC,反之更新 FC,這點與 shouldComponentUpdate 函數相反。
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<> {child1} {child2} </> ) } 複製代碼
Context 主要解決了 React 組件樹非父子組件的狀態共享問題,以及子組件與祖先組件以前多層 props 傳遞繁瑣的問題。官方示例:
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>
);
}
}
const ThemedButton = () => {
render() {
return (
<ThemeContext.Consumer>
{theme => <Button theme={theme} />}
</ThemeContext.Consumer>
);
}
}
複製代碼
###Error Boundary React16 中若是組件生命週期中拋出了未經捕獲的異常,會致使整個組件樹卸載。React16 提供了兩個生命週期函數用於捕獲子組件中在生命週期中拋出的異常。一個是 static getDerivedStateFromError(error) 在渲染階段 render 函數前調用,,另外一個是 componentDidCatch 在 commit 階段即完成渲染後調用。關於這兩個函數,React 官方示例爲:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
//主要用於根據額 error 更新 state.
//和 getDerivedStateFromProps 函數相似,
//返回的對象會更新到 state
return { hasError: true };
}
componentDidCatch(error, info) {
//主要用於在 commit 階段處理錯誤相關的 side effects
//好比此處的發送錯誤信息到服務器
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
複製代碼
關於 Error Boundary 須要注意的幾點是:
在使用 React16 時,若是咱們在渲染組件時須要渲染一個脫離於當前組件樹以外的組件(如對話框、tooltip等),能夠經過 ReactDOM.createPortal(Child, mountDom)* 函數建立一個 Portal,將 React 組件 Child 掛載到真實 DOM 元素 mountDom 上。示例代碼:
//html <body> <div id="root"></div> <div id="modal"></div> </body> //js const modalDom = document.querySelector('#modal'); function Child(){ function handleClick(){ console.log('click child'); } return <button onClick={handleClick}>Child Button</button> } function App(){ const [count, setCount] = useState(0); return <div onClick={() => {setCount(count + 1)}}> <h1>{count}</h1> { ReactDOM.createPortal( <Child/>, modalDom //將 Child 掛載到 id=modal 的 div 元素下 ) } </div> } //將 App 掛載到 id=root 的 div 元素下 ReactDOM.render(<App />, document.getElementById('root')); 複製代碼
上例中,雖然 Child 組件的真實 DOM 節點掛載在 modal 下,而 App 組件的真實 DOM 節點掛載在 root 下。但 Child 組件中的 Click 事件仍然會冒泡到 App 組件。故咱們點擊 button 時,會依次觸發 Child 組件的 handleClick 函數,以及 App 組件的 setCount 操做。
React16 中能夠經過 React.Fragment 組件來組合一列組件,而不須要爲了返回一列組件專門引入一個 DIV 組件。其中 <></>
是 <React.Fragment></React.Fragment>
的簡寫。