1. 程式人生 > 其它 >React元件設計模式-純元件,函式元件,高階元件

React元件設計模式-純元件,函式元件,高階元件

一、元件

(1) 函式元件

如果你想寫的元件只包含一個 render 方法,並且不包含 state,那麼使用函式元件就會更簡單。我們不需要定義一個繼承於 React.Component 的類,我們可以定義一個函式,這個函式接收 props 作為引數,然後返回需要渲染的元素。

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
    {props.value}    </button>
  );
}

(2) React.Component

shouldComponentUpdate 僅檢查了 props.color 或 state.count 是否改變。如果這些值沒有改變,那麼這個元件不會更新

class CounterButton extends React.Component {

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

(3) PureComponent

如果你的元件更復雜一些,你可以使用類似“淺比較”的模式來檢查 props 和 state 中所有的欄位,以此來決定是否元件需要更新。React 已經提供了一位好幫手來幫你實現這種常見的模式 - 你只要繼承 React.PureComponent 就行了。

class CounterButton extends React.PureComponent {}

大部分情況下,你可以使用 React.PureComponent 來代替手寫 shouldComponentUpdate。但它只進行淺比較 (例如:1 == 1或者ture==true,陣列和物件引用是否相同),所以當 props 或者 state 某種程度是可變的話,淺比較會有遺漏,那你就不能使用它了。

不要在props和state中改變物件和陣列,如果你在你的父元件中改變物件,你的PureComponent將不會更新。雖然值已經被改變,但是子元件比較的是之前props的引用是否相同,所以不會檢測到不同。

因此,你可以通過使用es6的assign方法或者陣列的擴充套件運算子或者使用第三方庫,強制返回一個新的物件。

當資料結構很複雜時,情況會變得麻煩,存在效能問題
(比較原始值和物件引用是低耗時操作。如果你有一列子物件並且其中一個子物件更新,對它們的props和state進行檢查要比重新渲染每一個子節點要快的多。)

(4) 何時使用Component 或 PureComponent ?

<1> 當元件是獨立的,元件在頁面中的個數為1或2的,元件有很多props、state,並且當中還有些是陣列和物件的,元件需要每次都渲染的,使用Component

<2> 當元件經常作為子元件,作為列表,元件在頁面中數量眾多,元件props, state屬性少,並且屬性中基本沒有陣列和物件,元件不需要每次都渲染,只有變化了才渲染,使用PureComponent

憑主觀,我覺得
以下元件適合Component

Button
Input

以下元件適合PureComponent

Radio
Checkbox
Option

二、高階函式

HOC ( 高階元件higherOrderComponent ) 自身不是 React API 的一部分,它是一種基於 React 的組合特性而形成的設計模式

元件是將 props 轉換為 UI,而高階元件是將元件轉換為另一個元件。(元件是 React 中程式碼複用的基本單元。)

高階元件例如 Redux 的 connect 和 Relay 的 createFragmentContainer。

(1)HOC 不會修改傳入的元件,也不會使用繼承來複制其行為。

相反,HOC 通過將元件包裝在容器元件中來組成新元件。HOC 是純函式,沒有副作用。

(2)HOC 應該透傳與自身無關的 props

HOC 為元件新增特性。自身不應該大幅改變約定。
HOC 應該透傳與自身無關的 props,HOC 返回的元件與原元件應保持類似的介面。

(3)約定:包裝顯示名稱以便輕鬆除錯HOC

建立的容器元件會與任何其他元件一樣,會顯示在 React Developer Tools 中。為了方便除錯,請選擇一個顯示名稱,以表明它是 HOC 的產物

最常見的方式是用 HOC 包住被包裝元件的顯示名稱。比如高階元件名為 withSubscription,並且被包裝元件的顯示名稱為 CommentList,顯示名稱應該為 WithSubscription(CommentList):

function withSubscription(WrappedComponent) {
  class WithSubscription extends React.Component {/* ... */}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
  return WithSubscription;
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

(4) 注意事項:

<1> 不要在 render 方法中使用 HOC

render() {
  // 每次呼叫 render 函式都會建立一個新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2

  const EnhancedComponent = enhance(MyComponent);

  // 這將導致子樹每次渲染都會進行解除安裝,和重新掛載的操作!

  return <EnhancedComponent />;
}

<2>務必複製靜態方法

有時在 React 元件上定義靜態方法很有用。例如,Relay 容器暴露了一個靜態方法 getFragment 以方便組合 GraphQL 片段。

但是,當你將 HOC 應用於元件時,原始元件將使用容器元件進行包裝。這意味著新元件沒有原始元件的任何靜態方法。

// 定義靜態函式
WrappedComponent.staticMethod = function() {/*...*/}
// 現在使用 HOC
const EnhancedComponent = enhance(WrappedComponent);

// 增強元件沒有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
為了解決這個問題,你可以在返回之前把這些方法拷貝到容器元件上:

你可以使用 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;
}

除了匯出元件,另一個可行的方案是再額外匯出這個靜態方法。

// 使用這種方式代替...
MyComponent.someFunction = someFunction;
export default MyComponent;

// ...單獨匯出該方法...
export { someFunction };

// ...並在要使用的元件中,import 它們
import MyComponent, { someFunction } from './MyComponent.js';

<3> Refs 不會被傳遞

雖然高階元件的約定是將所有 props 傳遞給被包裝元件,但這對於 refs 並不適用。那是因為 ref 實際上並不是一個 prop - 就像 key 一樣,它是由 React 專門處理的。如果將 ref 新增到 HOC 的返回元件中,則 ref 引用指向容器元件,而不是被包裝元件。

這個問題的解決方案是通過使用 React.forwardRef API(React 16.3 中引入)

三、React Redux 的 connect

React Redux 的 connect 函式是一個 返回高階元件的高階函式

最常見的 HOC 簽名如下:

// React Redux 的 `connect` 函式
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

剛剛發生了什麼?!如果你把它分開,就會更容易看出發生了什麼。

// connect 是一個函式,它的返回值為另外一個函式。
const enhance = connect(commentListSelector, commentListActions);

// 返回值為 HOC,它會返回已經連線 Redux store 的元件
const ConnectedComment = enhance(CommentList);

這種形式可能看起來令人困惑或不必要,但它有一個有用的屬性。最大化可組合性
像 connect 函式返回的單引數 HOC 具有簽名 Component => Component。 輸出型別與輸入型別相同的函式很容易組合在一起。參考 React面試題詳細解答

// 而不是這樣...
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))

// ... 你可以編寫組合工具函式
// compose(f, g, h) 等同於 (...args) => f(g(h(...args)))

const enhance = compose(
  // 這些都是單引數的 HOC
  withRouter,
  connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)

//(同樣的屬性也允許 connect 和其他 HOC 承擔裝飾器的角色)

四、其他

(1)key

每當一個列表重新渲染時,React 會根據每一項列表元素的 key 來檢索上一次渲染時與每個 key 所匹配的列表項。如果 React 發現當前的列表有一個之前不存在的 key,那麼就會創建出一個新的元件。如果 React 發現和之前對比少了一個 key,那麼就會銷燬之前對應的元件。如果一個元件的 key 發生了變化,這個元件會被銷燬,然後使用新的 state 重新建立一份。

我們強烈推薦,每次只要你構建動態列表的時候,都要指定一個合適的 key。

如果你沒有指定任何 key,React 會發出警告,並且會把陣列的索引當作預設的 key。但是如果想要對列表進行重新排序、新增、刪除操作時,把陣列索引作為 key 是有問題的

顯式地使用 key={i} 來指定 key 確實會消除警告,但是仍然和陣列索引存在同樣的問題,所以大多數情況下最好不要這麼做。

元件的 key 值並不需要在全域性都保證唯一,只需要在當前的同一級元素之前保證唯一即可。