02.react進階指南

react 進階

懶加載

React.lazy函數能讓你像渲染常規組件一樣處理動態引入(的組件)。
Suspense加載指示器爲組件做優雅降級。
fallback屬性接受任何在組件加載過程中你想展示的 React 元素。

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

Context

Context提供了一個無需爲每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法,設計目的是爲了共享那些對於一個組件樹而言是“全局”的數據。

// Context 可以讓我們無須明確地傳遍每一個組件,就能將值深入傳遞進組件樹。
// 爲當前的 theme 創建一個 context(“light”爲默認值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一個 Provider 來將當前的 theme 傳遞給以下的組件樹。
    // 無論多深,任何組件都能讀取這個值。
    // 在這個例子中,我們將 “dark” 作爲當前的值傳遞下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中間的組件再也不必指明往下傳遞 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 讀取當前的 theme context。
  // React 會往上找到最近的 theme Provider,然後使用它的值。
  // 在這個例子中,當前的 theme 值爲 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

請謹慎使用,因爲這會使得組件的複用性變差。

API:

React.createContext
創建一個 Context 對象。當 React 渲染一個訂閱了這個 Context 對象的組件,這個組件會從組件樹中離自身最近的那個匹配的 Provider 中讀取到當前的 context 值。只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效。

Context.Provider
每個 Context 對象都會返回一個 Provider React 組件,它允許消費組件訂閱 context 的變化。當 Provider 的 value 值發生變化時,它內部的所有消費組件都會重新渲染。

Class.contextType
掛載在 class 上的 contextType 屬性會被重賦值爲一個由 React.createContext() 創建的 Context 對象。這能讓你使用 this.context 來消費最近 Context 上的那個值。你可以在任何生命週期中訪問到它,包括 render 函數中。

Context.Consumer
這裏,React 組件也可以訂閱到 context 變更。這能讓你在函數式組件中完成訂閱 context。

Refs

Refs 提供了一種方式,允許我們訪問 DOM 節點或在 render 方法中創建的 React 元素。不能在函數組件上使用 ref 屬性,因爲他們沒有實例,可以在函數組件內部使用 ref 屬性。

適合使用 refs 的情況:

  • 管理焦點,文本選擇或媒體播放。
  • 觸發強制動畫。
  • 集成第三方 DOM 庫。

使用方法:

  1. 創建 Refs

    • Refs 是使用 React.createRef() 創建的,並通過 ref 屬性附加到 React 元素。
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}
  1. 訪問 Refs

    • 在 ref 的 current 屬性中被訪問
const node = this.myRef.current;

Refs 轉發

Ref 轉發是一項將 ref 自動地通過組件傳遞到其一子組件的技巧。子組件使用React.forwardRef接收ref。可用於Hoc處理ref。

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你可以直接獲取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

上例中,FancyButton 使用 React.forwardRef 來獲取傳遞給它的 ref,然後轉發到它渲染的 DOM button。這樣,使用 FancyButton 的組件可以獲取底層 DOM 節點 buttonref ,並在必要時訪問,就像其直接使用 DOM button 一樣。

Fragments

Fragments 允許你將子列表分組,而無需向 DOM 添加額外節點。key 是唯一可以傳遞給 Fragment 的屬性

render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}

<React.Fragment></React.Fragment>可簡寫爲<></>

高階組件(HOC)

HOC是參數爲組件,返回值爲新組件的函數。

HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 通過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有副作用。

示例:

// 此函數接收一個組件...
function withSubscription(WrappedComponent, selectData) {
  // ...並返回另一個組件...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ...負責訂閱相關的操作...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ... 並使用新數據渲染被包裝的組件!
      // 請注意,我們可能還會傳遞其他屬性
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

上例中class組件爲HOC的容器組件,容器組件擔任分離將高層和低層關注的責任,由容器管理訂閱和狀態,並將 prop 傳遞給處理渲染 UI。HOC 使用容器作爲其實現的一部分,你可以將 HOC 視爲參數化容器組件。

注意事項:

  • 不要在 render 方法中使用 HOC。

    在render中使用會導致diff 算法在對比組件變化時每次檢測都不一樣,每次渲染都會進行卸載,和重新掛載的操作,這不僅僅是性能問題 - 重新掛載組件會導致該組件及其所有子組件的狀態丟失。

  • 務必複製靜態方法到容器組件上。

    可以使用hoist-non-react-statics自動拷貝所有非 React 靜態方法

    import hoistNonReactStatic from 'hoist-non-react-statics';
    function enhance(WrappedComponent) {
      class Enhance extends React.Component {/*...*/}
      hoistNonReactStatic(Enhance, WrappedComponent);
      return Enhance;
    }
  • Refs 不會被傳遞。

    可用過Refs 轉發解決

常見的HOC:

redux的 connect

React.PureComponent

大部分情況下,你可以使用 React.PureComponent 來代替手寫 shouldComponentUpdate。只有當檢測數據是數組或對象時,由於淺拷貝的問題會導致比較出現偏差不能使用,此時使用深拷貝仍可繼續使用。

如以下代碼:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

可替換爲:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

Portals

Portal 提供了一種將子節點渲染到存在於父組件以外的 DOM 節點的優秀的方案。

一個 portal 的典型用例是當父組件有 overflow: hidden 或 z-index 樣式時,但你需要子組件能夠在視覺上“跳出”其容器。例如,對話框、懸浮卡以及提示框:

render() {
  // React 並*沒有*創建一個新的 div。它只是把子元素渲染到 `domNode` 中。
  // `domNode` 是一個可以在任何位置的有效 DOM 節點。
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}

React.StrictMode

StrictMode 不會渲染任何可見的 UI。它爲其後代元素觸發額外的檢查和警告。嚴格模式檢查僅在開發模式下運行;它們不會影響生產構建。

作用:
  • 識別不安全的生命週期
  • 關於使用過時字符串 ref API 的警告
  • 關於使用廢棄的 findDOMNode 方法的警告
  • 檢測意外的副作用
  • 檢測過時的 context API

原文git地址 覺得有用的話,來個star鼓勵,持續更新中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章