React16 新生命週期和 Hooks 介紹

[TOC]javascript

React16 新特性總結

生命週期函數

不建議使用的生命週期函數

  • componentWillReceiveProps
      React 團隊對於捨棄 componentWillReceiveProps 函數的解釋是開發者錯誤地在該函數中引入 side effect(反作用,調用外部函數等),致使組件重複屢次執行 Updating。正確的作法應該是將 Updating 中的 side effect 都放在 componentDidUpdate 生命週期函數中進行。   另外我我的以爲 componentWillReceiveProps 的功能沒有一個明確的定位,每每用於實現經過 props 更新 state 的功能。但 componengWillReceiveProps 只在 Updating 中 props 更新時被調用,沒法覆蓋全部場景,好比 Mounting 時須要在 contructor 中用 props 初始化 state。故須要使用多個生命週期函數才能實現 props 更新 state 這一單一功能。而 React16 中新提出來的 getDerivedStateFromProps 函數覆蓋了全部 state 更新的場景。
  • componentWillUpdate   React 捨棄 componentWillUpdate 函數的解釋與 componentWillReceiveProps 的解釋一致。開發者能夠將 componentWillUpdate 中的一些 side effects 放置在 componentDidUpdate中。若是開發者想在組件更新前獲取 DOM 的信息,能夠經過 React16 中新提出來的 getSnapshotBeforeUpdate 函數來實現。
  • componentWillMount   React 團隊 React16 中提供的最核心的 async rendering (異步渲染)功能(目前暫未發佈)中,組件的渲染可能會被中斷。因此假若開發者在 componentWillMount 進行了事件綁定,在 componentWillUnmount 中取消事件綁定的話,就可能發生由於異步渲染取消,於是沒有調用 componentWillUnmount 函數,從而致使事件回調函數對象的內存泄漏。
  • 小結   從數量上能夠看出,React 對類組件的生命週期函數進行了精簡。主要取消了 render 階段中,容易讓開發者引入 side effects 的生命週期函數。這樣作的主要目的是爲了確保 render 階段順利執行,不會由於 side effects 帶來的重複執行 render 階段。從而強迫開發者將 side effects 移入 commit 階段的生命週期函數中。

static getDerivedStateFromProps(props, state)

  新的生命週期函數 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

  • getDerivedStateFromProps 爲類的靜態方法,沒法使用 this 關鍵字,此舉也下降了開發者在該函數中引入 side effects 的可能。
  • getDerivedStateFromProps 的參數 props 爲將要更新的 props(nextProps)。因爲函數中沒法使用 this 關鍵字,須要訪問 this.props 的話,能夠將 props 中須要用於更新 state 的字段記錄在 state 中(如代碼中的 lastA 和 lastB)。
  • getDerivedStateFromProps 返回的對象和跟 state 進行合併,更新後的 state 用於 render。若是 state 不須要更新,則必須返回 null。
  • 其實 React 官方並不提倡使用 derived state,由於這樣會使類組件邏輯變得複雜代碼臃腫。對於須要根據 props 更新 state 的需求,React 官方建議優化組件的設計來避免。我的總結主要有如下兩種方式:
    • 直接將組件拆分爲徹底受控組件,去掉組件的 state
    • 對於非受控組件,更新 props 時經過修改組件的 key 值,讓組件從新掛載

getSnapshotBeforeUpdate(prevProps, prevState)

   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>
    );
  }
}
```
複製代碼

須要注意的幾點:設計模式

  • getSnapshotBeforeUpdate 函數須要和 componentDidUpdate 一塊兒使用,不然會報 warning
  • getSnapshotBeforeUpdate 在生命週期中爲何要在 render 函數以後而不是以前調用?我認爲主要仍是由於 React 想保證 render 階段的無反作用,從而在 commit 階段強行添加一個 pre-commit 階段來獲取更新前的 DOM。能夠看出,getSnapshotBeforeUpdate 的引入只是一個折中方案。

小結

  能夠看出 React16 中,類組件的一個組件的生命週期被劃分爲類 render 和 commit 兩個階段,render 階段主要負責組件渲染相關,包括對渲染數據 state 的更新。爲了防止一次更新中 render 階段重複執行,React 將該階段可能引入 side effects 的生命週期函數 componentWillReceivePropscomponentWillUpdatecomponentWillUnmount 等函數移除。
  針對須要經過 props 計算 derived state 的需求,提供靜態函數 getDerivedStateFromProps。針對獲取更新前 DOM 元素的需求,React16 提供了 getSnapshotBeforeUpdate 生命週期函數。數組

Hooks

簡介

  React 官方文檔對 Hooks 的介紹是 Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class,從介紹中咱們可知:服務器

  1. Hooks只能在函數組件中使用
  2. Hooks 爲函數式組件添加了原本只有類組件纔有的一些特性,好比 state。

動機

  • 組件邏輯代碼複用   相比於 HOC、renderProps 而言,須要提供一個更加靈活輕便而且是原生支持的狀態邏輯複用機制。我的總結 renderProps 和 HOC 的缺點在於:app

    • 兩者都會引入 wrapper hell,不管 HOC 仍是 renderProps,都是將須要複用都邏輯代碼經過另一個組件引入進來。這些組件雖然只負責邏輯代碼的複用,但在 React 組件樹中仍然須要渲染或者做爲一個抽象組件(不會渲染成真實的 DOM 組件)存在。當複用較多時,React 組件樹就會由於多層的 HOC wrapper 或者 render props 的引入而變得臃腫。
    • 當對原有代碼進行重構時,render props 和 HOC 都會去修改原有組件的結構。
    • renderProps 和 HOC 只是在已有機制上的一種設計模式,React 還缺乏一個原生機制來支持組件內部邏輯代碼的複用。
    • renderProps 和 HOC 目前還存在各自的缺點,而 Hooks 能夠成功避免這些缺點:
      • HOC 須要藉助 React 的 class 組件來進行實現,而 React 已經在逐步拋棄 class 組件模式
      • renderProps 較多時會致使 render props 的 callback hell
  • HOC、render props 和 Hooks 實現邏輯代碼複用示例
      當咱們須要實現一個簡單的顯示隱藏的功能時,通常是在組件的 state 中定義一個控制現實仍是隱藏的布爾變量,並再添加一個 showhide 方法來控制該變量。若是要將這段邏輯代碼提取出來複用的話,能夠經過高階組件 HOC、render props 或者 Hooks 來實現,如下分別列出這三種方法的實現代碼以及對應的 React 組件樹結構。能夠看出使用 Hooks 複用邏輯代碼時,因爲沒有建立額外的組件,故不管是代碼仍是最後生成的 React 組件樹,都是 Hooks 的實現方式更簡潔。dom

    • HOC
    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> ); }) 複製代碼

    • render props
    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>
    );
    複製代碼

    • Hooks
    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 介紹

   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>
}
複製代碼
  • state 如何存儲   上例中咱們能夠看到,除了第一次執行組件渲染時調用來 useState(0) 生成一個 state 後,以後從新執行組件渲染時,獲取到到 state 都是更改以後都值,故在 React 應該對 state 作了外部存儲。React16 中,一次函數式組件的更新中 Hooks 調用邏輯在函數 renderWithHooks 中實現。模塊全局變量 firstCurrentHook 指向當前渲染組件的第一個 Hook
    這裏須要注意的幾點是
  1. 同一個組件中的 hooks (state hooks 以及後面的 effect hooks) 都是以鏈表的形式組織的**,每一個 hook.next 指向下一個被聲明的 hook。故在示例圖中只用找到第一個 hook (firstWorkInProgressHook) 便可。
  2. 組件的 FiberNode.memoizedState 指向組件的第一個 Hook。下圖是本節例子中組件的 hooks 連接結構。當組件被銷燬的時候,hooks 纔會被銷燬,更新過程當中的 state 都保存在 FiberNode.memoizedState 屬性中。另外 useState 方法返回的第二個 dispatch 函數就是 下圖 queue 中的 dispatch 函數
  • state hooks 如何觸發組件從新渲染   當咱們調用 setCount 更新 count 值後,就會觸發 Counter 組件的從新渲染。setCount 就是 state hook count 的 dispatch 函數。在初始化 state hooks 的時候,React 會將組件的 Fiber 綁定到 dispatch 函數上,每次 dispatch 函數執行完後會建立一個 Fiber 的更新任務。
  • 關於 useState 的使用
  1. 建議 useState 聲明的 state 儘可能細顆粒,由於每次更新 state 時都是替換整個 state,若是使用 useState 聲明一個大對象的話,不利於 state hooks 的複用
  2. 儘管組件每次更新時都會調用 useState 函數,但 useState 做爲 state 初始值的參數只有在函數首次渲染時纔會被使用。因此不建議在 useState 參數中調用 state 的初始化函數對 state 進行初始化,由於在後面每次組件更新中,都會執行一次 state 初始化函數的無效調用。正確的作法是,將 state 初始化函數做爲參數傳入 useState,由於 state 初始化函數只有在第一次調用 useState 函數時纔會執行。

useEffect 介紹

  useEffect 函數用於建立一個 effect hook。effect hooks 用於在函數式組件的更新後執行一些 side effects,其之於函數式組件就至關於 componentDidMountcomponentDidUpdate 之於 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 事件。

  • useEffect 的輸入是一個函數,該函數在組件渲染完成後執行,主要用於處理一些 side effects
  • useEffect 的輸入函數能夠 return 一個函數,該函數在每次組件從新渲染前以及組件 Unmount 前執行,主要用於解除事件監聽
  • 函數式組件從新渲染時,先執行 effect hook 的返回函數,再執行 effect hook。對於示例代碼而言,就是先執行 window.removeEventListener 再執行 window.addEventListener
  • 默認狀況下,每次從新渲染時都會執行 effect hooks,好比上例中,每次輸入字符後,window 都會執行一邊取消監聽以及監聽 keydown 事件。出於性能的考慮,useEffect 方法提供了第二個數組參數,即 useEffect(() => {}, [])。當傳入第二個參數時,只有當第二個參數中的某個 state 或者 props 改變時,該 effect hook 纔會被調用。值得注意的是:此時 effect hook 以及在其間的回調函數只能訪問到 useEffect 數組參數中的 state 和 props 的最新值,其它 state 和 props 只能獲取到初始值
  • 使用 effect hooks 能夠在一個 useEffect 函數中完成一個事件綁定/解除綁定、訂閱/取消訂閱的操做。相比於 class 組件中將事件相關的邏輯分散在 componentDidMountcomponentWillUnmount 兩個函數中,useEffect 代碼更爲緊湊。

useRef 介紹

  和 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 介紹

  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 來生成。

Hooks 使用規則

  1. 不要在非 React 函數式組件外調用 useXXX   在調用 useXXX 方法時,會讀取當前的組件的 Fiber,若是 Fiber 不存在則報錯
  2. 不要在條件語句中調用 useXXX
      在介紹 useState 的時候咱們提到過,同一個組件內的全部 Hooks 是由聲明的前後順序,經過鏈表進行存儲的。
    • 在 React 的 renderWithHooksupdateWorkInProgressHook 函數中會經過 currentHook 來記錄當前執行的 hook
    • currentHook 初始化以及每次執行完組件渲染後都會置爲 null。每當執行完 currentHook 後,會將 currentHook 指向 currentHook.next
    • 執行完組件渲染後,若是 currentHook.next 不爲 null,說明被渲染組件的 Hooks 鏈表中仍有未執行的 Hooks ,報錯 var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null;

自定義 Hook

  本節直接使用 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>
    </>
  );
}
複製代碼

使用 Hooks 代替生命週期函數

  • constructor class 組件中 constructor 函數通常實現:建立 this綁定事件處理函數以及初始化 state 等功能。在 FC 中不須要 this 關鍵字,另外能夠經過傳入 state 初始化函數到 useState 第二個參數來初始化 state
  • render FC 組件函數自己就是 render 函數
  • componentDidMount 使用 *useEffect(() => {}, []) 代替 componentDidMount。因爲 useEffect 第二個參數爲空數組,故第一個參數中的函數只有在組件掛載和卸載時調用
  • componentWillUnmountcomponentDidMount 同樣,使用第二個參數爲空數組的 useEffect 代替。當組件卸載時,useEffect 第一個函數參數的返回函數會被調用。componentWillUnmount 裏面的邏輯能夠卸載該函數中
  • componentDidUpdate 使用 useEffect 代替 componentDidUpdate,首次渲染觸發的 effect hook 中添加判斷跳過便可。
  • getDerivedStateFromProps 函數式組件中,要實現 class 組件的 getDerivedStateFromProps 函數須要考慮亮點:一是保存上一次的 props 用於和當前要更新的 props 進行比較;二是當 props 變化後觸發相應的 derived state 改變。前者能夠經過定一個 state hook (稱做 prevPropsHook)來記錄最近一次更新的 props。後者能夠在組件函數調用時經過對比 prevPropsHook 的值與 props 是否相同,若不一樣則改變相應 state hook 的值。
  • shouldComponentUpdate 在 class 組件中,props 和 state 的更新都會觸發 shouldComponentUpdate 函數。
    • 在函數式組件中,經過高階組件 React.memo 實現 props 更新判斷時候須要從新渲染組件。React.memo 使用方式爲
    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 函數相反。
    • 因爲 FC 經過 state hooks 模擬了 class 組件的 state,因此當 state hooks 更新時也須要一個機制模擬 shouldComponentUpdate 函數的跳過 state 更新的功能。React 官方提供的方法比較彆扭,即用 useMemo 包裹渲染了 state 的子組件來實現。useMemo 只會在第二個數組參數中的某個元素變化時,纔會觸發 第一個函數函數的從新執行。官方示例爲:
    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} </> ) } 複製代碼
  • 其它生命週期函數 諸如 getSnapshotBeforeUpdategetStateFromErrorcomponentDidCatch 函數在 hooks 中還未找到替代方法。

Context

   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>
    );
  }
}
複製代碼
  • React.createContext   用於建立一個 Context 對象,該對象支持訂閱發佈功能。組件能夠經過 Context.Provider 發佈一個新的值,其全部訂閱了該 Context 對象的子組件就能夠即便獲取到更新後的值。上例中 createContext 方法的參數做爲 Context 對象 ThemeContext 的默認值,訂閱了 ThemeContext 的組件在獲取 Context 值的時候會沿着副組件向上搜索 Context.Provider,若是沒有搜索到 Context.Provider,則會使用該默認值。
  • Context.Provider   用於發佈 Context,Provider 的子組件會從 Context.Provider 組件的 value 屬性獲取 Context 的值。
  • Context.Consumer   能夠監聽 Context 的變化。當 Context.Provider 的 value 屬性變化後,該 Provider 組件下的全部 Consumer 組件都會從新渲染,而且不受 shouldComponentUpdate 返回值的影響。Consumer 採用了 render props 模式, 其子組件爲一個函數,函數參數爲其監聽的 Context 的值。

###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 須要注意的幾點是:

  1. 添加了 getDerivedStateFromError 或者 componentDidCatch 函數的組件能夠做爲 ErrorBoundary。ErrorBoundary 只會捕獲其子組件中拋出的異常,沒法捕獲自身拋出的異常
  2. ErrorBoundary 只能捕獲子組件生命週期中拋出的異常。其它的,好比回調函數以及事件處理函數中拋出的異常就沒法捕獲。
  3. 事件處理函數中建議使用 JavaScript 的 try/catch 進行異常捕獲

其它

Portal

  在使用 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 操做。

React.Fragment 組件

  React16 中能夠經過 React.Fragment 組件來組合一列組件,而不須要爲了返回一列組件專門引入一個 DIV 組件。其中 <></><React.Fragment></React.Fragment>的簡寫。

相關文章
相關標籤/搜索