React高階組件 和 Render Props


高階組件

本質

本質是函數,將組件作為接收參數,返回一個新的組件。HOC本身不是React API,是一種基於React組合的特而形成的設計模式。

解決的問題(作用)

  • 一句話概括:功能的復用,減少代碼冗余
  • 進一步解釋:在實際情況中,多個組件可能會做某些相同的事情,有着相同的功能,存在大量的代碼冗余。我們可以將這部分功能拆分出來,每個組件盡量只保留自己獨有的作用,通過HOC生成我們最終需要的組件。

實現方法:

無論哪種方法,都是在HOC函數內定義新的組件,在新的組件內做一些公共的功能和事情

  1. 屬性代理
  2. 反向繼承

屬性代理

這是最常規的寫法,原理等同於ES7裝飾器、Python裝飾器。函數傳入的參數,除了原組件,還可以定義其他的參數,通過這些參數來區別每個實際組件。比如,公共的功能是獲取數據。獲取數據這件事情是相同的,但獲取的內容不同。如何決定最后生成的組件獲取各自指定的內容呢?通過函數傳參。

// 示例用localstorage代替如網絡請求等的異步操作
localStorage.setItem("comment","asuiklfhs");  
localStorage.setItem("read","123");

class Comment extends React.Component {

   render() {
      return (
         <div>
            {this.props.data}
         </div>
      )
   }
}

class Read extends React.Component {

   render() {
      return (
         <div>
            {this.props.data}
         </div>
      )
   }
}

const HOCTest = (key) => {
   return (WrappedComponent) => {
      return class NewComponent extends React.Component{

         constructor(props) {
            super(props);
            this.state = {
               data: ''
            };
         }

         componentDidMount() {
            const data = localStorage.getItem(key);
            this.setState({
               data
            });
         }

         render() {
            console.log(this.myRef);
            return (
               <div ref={(e)=>{this.myRef = e;}}>
                  <WrappedComponent {...this.props} data={this.state.data} />
               </div>
            );
         }
      }
   }
};

const CommentNew = HOCTest("comment")(Comment);  // HOC生成新組件
const ReadNew = HOCTest("read")(Read);

class Root extends React.Component {
   render() {
      return (
         <div className="App">
            Hello World
            <CommentNew/>
            <ReadNew/>
         </div>
      );
   }
}

在這里,React第三方的組件庫通常使用 函數柯里化 的寫法。對於使用者來說,改變了調用函數時的傳參方式,更加容易理解。即:原本調用函數wrapper(Component, params1, params2)  柯里化后調用wrapper(params1,params2)(Component)

屬性代理常見的作用:

  1. 操作props
  2. 通過Refs訪問組件實例
  3. 提取state
  4. 組合更多的html元素

反向繼承

顧名思義,就是將返回的組件繼承了原組件。它允許生成的組件通過this獲取原組件,意味着可以獲取到state,props,生命周期鈎子,以及render

localStorage.setItem("comment","asuiklfhs");
localStorage.setItem("read","123");

class Comment extends React.Component {
   constructor(props){
      super(props);
      this.state = {id:"comment"}
   }

   componentDidMount() {
      console.log("Comment DidMount");
   }

   render() {
      return (
         <div>
            {this.props.data}
         </div>
      )
   }
}

class Read extends React.Component {
   constructor(props){
      super(props);
      this.state = {id:"read"}
   }

   render() {
      return (
         <div>
            {this.props.data}
         </div>
      )
   }
}

const HOCTest = (key) => {
   return (WrappedComponent) => {
      return class NewComponent extends WrappedComponent{

         componentDidMount() {
            console.log("HOC DidMount");
            const data = localStorage.getItem(key);
            this.setState({
               ...this.state,
               data
            });
         }

         render() {
            return (
               <WrappedComponent data={this.state.data}/>
            );
         }
      }
   }
};

const CommentNew = HOCTest("comment")(Comment);
const ReadNew = HOCTest("read")(Read);

class Root extends React.Component {

   render() {
      return (
         <div className="App">
            Hello World
            <CommentNew/>
            <ReadNew/>
         </div>
      );
   }
}

作用:

  1. 渲染劫持。由於新組件可以控制原組件的render方法,可以做各種控制渲染的操作。
  2. 操作state

注意點:

  1. 不要在函數內修改原組件
  2. 使用反向繼承方式時,會丟失原本的顯示名
  3. 不要在render函數中使用HOC

高階組件的缺點:

  1. 難以溯源。如果原始組件A通過好幾個HOC的構造,最終生成了組件B,不知道哪個屬性來自於哪個HOC,需要翻看每個HOC才知道各自做了什么事情,使用了什么屬性。
  2. props屬性名的沖突。某個屬性可能被多個HOC重復使用。
  3. 靜態構建。新的組件是在頁面構建之前生成,先有組件,后生成頁面。

Render Props 

作用

  1. 功能的復用,與HOC類似。
  2. 組件間數據的單向傳遞。

什么是Render Props?

是一個用於告知組件要渲染什么內容的函數屬性。該函數返回一個組件,是渲染出來的內容。

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <p>位置:x:{ mouse.x } y: { mouse.y }</p>
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/*
          Instead of providing a static representation of what <Mouse> renders,
          use the `render` prop to dynamically determine what to render.
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移動鼠標!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

通過以上Demo可以看到,Mouse組件通過render屬性(屬性名也可以是別的名字),指定了渲染哪個子組件,並且子組件可以接收參數值,進而實現內部邏輯。

再進行分析,發現Mouse組件提供可變數據源,是一個基礎數據的提供者,最關鍵的代碼是:

{this.props.render(this.state)}

通過render屬性,將數據傳遞給另外一個組件。至於這個數據拿來干什么,怎么去渲染,就不是它管的事情了。

因此,整個頁面很多地方可能需要用到鼠標坐標數據,以上的例子就可以實現功能的復用。

相對高階組件的優點:

  1. 不用擔心props的命名沖突的問題
  2. 可以溯源,子組件的props一定來自父組件。
  3. 是動態構建的,頁面在渲染后,可以動態地決定渲染哪個組件。
  4. 所有能用HOC完成的事情,Render Props都可以做,且更加靈活。
  5. 除了功能復用,還可以用作兩個組件的單向數據傳遞。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM