從Mixin到hooks,談談對React16.7.0-alpha中即將引入的hooks的理解

來源:互聯網
上載者:User

標籤:eal   構建   函數式   執行   listen   orm   listener   接受   admin   

??為了實現分離商務邏輯代碼,實現組件內部相關商務邏輯的複用,在React的迭代中針對類組件中的代碼複用依次發布了Mixin、HOC、Render props等幾個方案。此外,針對函數組件,在React v16.7.0-alpha 中提出了hooks的概念,在本身無狀態的函數組件,引入獨立的狀態空間,也就是說在函數組件中,也可以引入類組件中的state和組件生命週期,使得函數組件變得豐富多彩起來,此外,hooks也保證了邏輯代碼的複用性和獨立性。

??本文從針對類組件的複用解決方案開始說起,先後介紹了從Mixin、HOC到Render props的演化,最後介紹了React v16.7.0-alpha 中的 hooks以及自訂一個hooks

  • Mixin
  • HOC
  • Render props
  • React hooks的介紹以及如何自訂一個hooks

原文地址在我的部落格中:https://github.com/forthealll...

歡迎star和fork~

一、Mixin

Mixin是最早出現的複用類組件中商務邏輯代碼的解決方案,首先來介紹以下如何適應Mixin。下面是一個Mixin的例子:

const someMixins={  printColor(){    console.log(this.state.color);  }  setColor(newColor){    this.setState({color:newColor})  }  componentDidMount(){    ..  }}

下面是一個使用Mixin的組件:

class Apple extends React.Component{  //僅僅作為示範,mixins一般是通過React.createClass建立,並且ES6中沒有這種寫法  mixins:[someMixins]  constructor(props){    super(props);    this.state={      color:'red'    }    this.printColor=this.printColor.bind(this);  }  render(){    return <div className="m-box" onClick={this.printColor}>                這是一個蘋果           </div>  }}

在類中mixin引入公用商務邏輯:

mixins:[someMixins]

從上面的例子,我們來總結以下mixin的缺點:

  • Mixin是可以存在多個的,是一個數組的形式,且Mixin中的函數是可以調用setState方法組件中的state的,因此如果有多處Mixin的模組中修改了相同的state,會無法確定state的更新來源
  • ES6 classes支援的是繼承的模式,而不支援Mixins
  • Mixin會存在覆蓋,比如說兩個Mixin模組,存在相同生命週期函數或者相同函數名的函數,那麼會存在相同函數的覆蓋問題。

Mixin已經被廢除,具體缺陷可以參考Mixins Considered Harmful

二、HOC

??為瞭解決Mixin的缺陷,第二種解決方案是高階組件(high order component,簡稱HOC)。

1、舉例幾種HOC的形式

??HOC簡單理解就是組件工廠,接受原始組件作為參數,添加完功能與業務後,返回新的組件。下面來介紹HOC參數的幾個例子。

(1)參數僅為原始組件
const redApple = withFruit(Apple);
(2)參數為原始組件和一個對象
const redApple = withFruit(Apple,{color:'red',weight:'200g'});

但是這種情況比較少用,如果對象中僅僅傳遞的是屬性,其實完全可以通過組件的props實現值的傳遞,我們用HOC的主要目的是分離業務,關於UI的展示,以及一些組件中的屬性和狀態,我們一般通過props來指定比較方便

(3)參數為原始組件和一個函數
const redApp=withFruit(App,()=>{console.log('I am a fruit')})
(4)柯裡化

最常見的是僅以一個原始組件作為參數,但是在外層包裹了商務邏輯,比如react-redux的conect函數中:

class Admin extends React.Component{}const mapStateToProps=(state)=>{  return {  };}const mapDispatchToProps=(dispatch)=>{  return {  }}const connect(mapStateToProps,mapDispatchToProps)(Admin)
2、HOC的缺點

HOC解決了Mixin的一些缺陷,但是HOC本身也有一些缺點:

(1)難以溯源,且存在屬性覆蓋問題

??如果原始組件A,先後通過工廠函數1,工廠函數2,工廠函數3….構造,最後產生了組件B,我們知道組件B中有很多與A組件不同的props,但是我們僅僅通過組件B,並不能知道哪個組件來自於哪個工廠函數。同時,如果有2個工廠函數同時修改了組件A的某個同名屬性,那麼會有屬性覆蓋的問題,會使得前一個工廠函數的修改結果失效。

(2)HOC是靜態構建的

??所謂靜態構建,也就是說產生的是一個新的組件,並不會馬上render,HOC組件工廠是靜態構建一個組件,這類似於重新聲明一個組件的部分。也就是說,HOC工廠函數裡面的聲明周期函數,也只有在新組件被渲染的時候才會執行。

(3)會產生無用的空組件三、Render Prop

??Render Props從名知義,也是一種剝離重複使用的邏輯代碼,提升組件複用性的解決方案。在被複用的組件中,通過一個名為“render”(屬性名稱也可以不是render,只要值是一個函數即可)的屬性,該屬性是一個函數,這個函數接受一個對象並返回一個子組件,會將這個函數參數中的對象作為props傳入給新產生的組件。

??這種方法跟直接的在父組件中,將父組件中的state直接傳給子組件的區別是,通過Render Props不用寫死子組件,可以動態決定父組件需要渲染哪一個子組件。

或者再概括一點:

Render Props就是一個函數,做為一個屬性被賦值給父組件,使得父組件可以根據該屬性去渲染子組件。

(1)標準父子組件通訊方法

??首先來看常用的在類組件中常用的父子組件,父組件將自己的狀態state,通過props傳遞給子組件。

class Son extends React.Component{  render(){  const {feature} = this.props;   return <div>             <span>My hair is {feature.hair}</span>             <span>My nose is {feature.nose}</span>          </div>  }}class FatherToSon extends React.Component{   constructor(){      this.state = {        hair:'black',        nose:'high'      }   }  render(){    return <Son feature = {this.state}>  }}

??我們定義了父組件FatherToSon,存在自身的state,並且將自身的state通過props的方式傳遞給了子組件。

??這種就是常見的利用組件的props父子間傳值的方式,這個值可以是變數,對象,也可以是方法,但是僅僅使用只能一次性的給特定的子組件使用。如果現在有個Daughter組件也想複用父組件中的方法或者狀態,那麼必須新構建一個新組件:

class FatherToDaughter extends React.Component{   constructor(){      this.state = {        hair:'black',        nose:'high'      }   }  render(){    return <Daughter feature = {this.state}>  }}

從上面的例子可以看出通過標準模式的父子組件的通訊方法,雖然能夠傳遞父組件的狀態和函數,但是無法實現複用。

(2)Render Props的引出

我們根據Render Props的特點:

Render Props就是一個函數,做為一個屬性被賦值給父組件,使得父組件可以根據該屬性去渲染子組件。

重新去實現上述的(1)中的例子。

class FatherChild extends React.Component{   constructor(){      this.state = {        hair:'black',        nose:'high'      }   }  render(){    <React.Fragment>      {this.props.render}    </React.Fragment>  }}

此時如果子組件要複用父組件中的屬性或者函數,則可以直接使用,比如子組件Son現在可以直接調用:

<FatherChild render={(obj)=>(<Son feature={obj}>)} />

如果子組件Daughter要複用父組件的方法,可以直接調用:

<FatherChild render={(obj)=>(<Daughter feature={obj}>)} />

??從這個例子中可以看出,通過Render Props我們實現同樣實現了一個組件工廠,可以實現商務邏輯代碼的複用,相比與HOC,Render Props有以下幾個優點。

  • 不用擔心props的命名問題
  • 可以溯源,子組件的props一定是來自於直接父組件
  • 是動態構建的

Render Props也有一個缺點:

就是無法利用SCU這個生命週期,來實現渲染效能的最佳化。

四、React hooks的介紹以及如何自訂一個hooks

??hooks概念在React Conf 2018被提出來,並將在未來的版本中被引入,hooks遵循函數式編程的理念,主旨是在函數組件中引入類組件中的狀態和生命週期,並且這些狀態和生命週期函數也可以被抽離,實現複用的同時,減少函數組件的複雜性和易用性。

??hooks相關的定義還在beta中,可以在React v16.7.0-alpha中體驗,為了渲染hooks定義的函數組件,必須執行React-dom的版本也為v16.7.0-alpha,引入hooks必須先安裝:

npm i -s [email protected]npm i -s [email protected]

??hooks主要有三部分組成,State Hooks、Effect Hooks和Custom Hooks,下面分別來一一介紹。

(1)State Hooks

??跟類組件一樣,這裡的state就是狀態的含義,將state引入到函數組件中,同時類組件中更新state的方法為setState,在State Hooks中也有相應的更新狀態的方法。

function ExampleWithManyStates() {  // 聲明各種state以及更新相應的state的方法  const [age, setAge] = useState(42);  const [fruit, setFruit] = useState('banana');  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);  // ...}

??上述就聲明了3個State hooks,相應的方法為useState,該方法建立一個傳入初始值,建立一個state。返回一個標識該state的變數,以及更新該state的方法。

??從上述例子我們來看,一個函數組件是可以通過useState建立多個state的。此外State Hooks的定義必須在函數組件的最高一級,不能在嵌套,迴圈等語句中使用。

function ExampleWithManyStates() {  // 聲明各種state以及更新相應的state的方法  if(Math.random()>1){    const [age, setAge] = useState(42);    const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);  }else{    const [fruit, setFruit] = useState('banana');    const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);  }    // ...}

??上述的方式是不被允許的,因為一個函數組件可以存在多個State Hooks,並且useState返回的是一個數組,數組的每一個元素是沒有標識資訊的,完全依靠調用useState的順序來確定哪個狀態對應於哪個變數,所以必須保證使用useState在函數組件的最外層,此外後面要介紹的Effect Hooks的函數useEffect也必須在函數組件的最外層,之後會詳細解釋。

(2)Effect Hooks

??通過State Hooks來定義組件的狀態,同樣通過Effect Hooks來引入生命週期,Effect hooks通過一個useEffect的方法,以一種極為簡化的方式來引入生命週期。
來看一個更新的例子:

import { useState, useEffect } from 'react';function Example() {  const [count, setCount] = useState(0);  useEffect(() => {    document.title = `You clicked ${count} times`;  });  return (    <div>      <p>You clicked {count} times</p>      <button onClick={() => setCount(count + 1)}>        Click me      </button>    </div>  );}

上述就是一個通過useEffect來實現組件中生命週期的例子,useEffect整合了componentDidMount和componentDidUpdate,也就是說在componentDidMount和componentDidUpdate的時候都會執行一遍useEffect的函數,此外為了實現componentWillUnmount這個生命週期函數,useEffect函數如果傳回值是一個函數,這個函數就被定義成在componentWillUnmount這個周期內執行的函數。

useEffect(() => {    //componentDidMount和componentDidUpdate周期的函數體    return ()=>{        //componentWillUnmount周期的函數體    }});

如果存在多個useState和useEffect時,必須按順序書寫,定義一個useState後,緊接著就使用一個useEffect函數。

useState('Mary')           useEffect(persistForm)    useState('Poppins')       useEffect(updateTitle)

因此通useState一樣,useEffect函數也必須位於函數組件的最高一級。

(3)Effect Hooks的補充

上述我們知道useEffect其實包含了componentDidMount和componentDidUpdate,如果我們的方法僅僅是想在componentDidMount的時候被執行,那麼必須傳遞一個空數組作為第二個參數。

useEffect(() => {  //僅在componentDidMount的時候執行},[]);

上述的方法會僅僅在componentDidMount,也就是函數組件第一次被渲染的時候執行,此後及時狀態更新,也不會執行。

此外,為了減少不必要的狀態更新和渲染,可以如下操作:

useEffect(() => {  //僅在componentDidMount的時候執行},[stateName]);

在上述的這個例子中,只有stateName的值發生改變,才會去執行useEffect函數。

(4)Custom Hooks自訂hooks

可以將useState和useEffect的狀態和生命週期函數抽離,組成一個新的函數,該函數就是一個自訂的封裝完畢的hooks。

這是我寫的一個hooks ---> dom-location,

可以這樣引入:

npm i -s dom-location 

並且可以在函數組件中使用。這個自訂的hooks也很簡單,就是封裝了狀態和生命週期函數。

import { useState, useEffect } from 'react'const useDomLocation = (element) =>  {  let [elementlocation,setElementlocation] = useState(getlocation(element));  useEffect(()=>{    element.addEventListener('resize',handleResize);    return ()=>{      element.removeEventListener('resize', handleResize);    }  },[]);  function handleResize(){    setElementlocation(getlocation(element));  }  function getlocation(E){    let rect = E.getBoundingClientRect()    let top = document.documentElement.clientTop    let left= document.documentElement.clientLeft    return{        top    :   rect.top - top,        bottom :   rect.bottom - top,        left   :   rect.left - left,        right  :   rect.right - left    };  }  return elementlocation}

然後直接在函數中使用:

import useDomLocation from 'dom-location';function App() {  ....  let obj = useDomLocation(element);  }

原文地址:1190000016876476

從Mixin到hooks,談談對React16.7.0-alpha中即將引入的hooks的理解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.