React的組件模式

受到我正爲Meetup準備的技術分享啓發,我打算花些時間分享下我所瞭解的React組件模式。組件是React的核心,因此瞭解如何利用它們對於構建優秀的設計結構至關重要。


什麼是組件


根據React官網的介紹,“組件機制允許你將UI切分成獨立、可複用的部分,並針對每個部分單獨考慮一些事情”


當你第一次運行npm install react時,你得到了一個東西:組件以及它的API。就像JavaScript中的函數一樣,組件接收被叫做Props的輸入並返回React元素,它描述(聲明)了用戶界面(UI)應該長什麼樣。這就是爲什麼React被稱爲具有聲明式API的原因,你只需要告訴它你想讓UI長什麼樣,剩下的事情React會幫你處理。


可以將聲明式想象成你坐出租車去某個地方,你只需告訴司機你想去哪兒,然後他/她會開車帶你去那兒。而命令式編程恰好相反,你要親自開車才能到達目的地。


組件的API


所以,當你安裝React時得到的API到底是什麼呢?它們總共有5個,分別是




  • render

  • state

  • props

  • context

  • lifecycle events


雖然每個組件都有完整使用上述API的能力,但你自然會發現,一些組件傾向於只使用其中的一些API,而另一些組件則只會使用另一些API。使用API種類的不同將組件們劃分爲兩類,有狀態組件和無狀態組件。有狀態組件通常使用一些狀態API:如render, state和lifecycle events,而無狀態組件則使用render, props和context。




從這裏開始,我們就要介紹組件模式了。組件模式是使用React時的最佳實踐,最初被引入時是爲了解決將數據(邏輯)層和UI(展示)層分離的問題。通過劃分組件職責,你能夠創造出複用性更強,內聚性更高的組件,這些組件可以被用來組成複雜的UI。這對於構建可擴展應用是極其重要的。


組件模式


常見的組件模式有:

  • 容器組件

  • 展示組件

  • 高階組件

  • 渲染回調


容器組件


“容器組件就是取數據,然後渲染子組件而已” —— Jason Bonta


藍色代表容器組件,灰色代表展示組件


容器組件是應用的數據(邏輯)層,它一般使用狀態API。使用生命週期函數,你可以連接到諸如Redux,Flux等狀態管理容器,並將數據和回調函數作爲props向下傳遞給子組件。容器組件的render方法是你將展示型子組件組合成UI的地方。爲了能夠訪問到所有狀態API,容器組件必須使用class的方式聲明,而不是使用函數式方法聲明。


在下面這個例子中,我們使用class的方式聲明瞭一個組件,叫做Greeting,它具有狀態,聲明周期函數componentDidMount和render方法。

class Greeting extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "",
    };
  }

  componentDidMount() {
    // AJAX
    this.setState(() => {
      return {
        name: "William",
      };
    });
  }

  render() {
    return (
      <div>
        <h1>Hello! {this.state.name}</h1>
      </div>
    );
  }
}

此時,該組件是有狀態組件。爲了讓Greeting成爲一個容器組件,我們可以將UI分離到一個展示組件中,我將在下面進行說明。

展示組件

展示組件使用props, render和context(無狀態API),並且可以被寫成語法純粹的函數式無狀態組件。

const GreetingCard = (props) => {
  return (
    <div>
      <h1>{props.name}</h1>
    </div>
  )
}

展示組件僅能從props中獲取數據和回調函數,這些props由容器組件(父組件)提供。

藍色是展示組件,灰色是容器組件

容器組件和展示組件一起將邏輯和表現封裝在各自的組件中。

const GreetingCard = (props) => {
  return (
    <div>
      <h1>{props.name}</h1>
    </div>
  )
}

class Greeting extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "",
    };
  }

  componentDidMount() {
    // AJAX
    this.setState(() => {
      return {
        name: "William",
      };
    });
  }

  render() {
    return (
      <div>
       <GreetingCard name={this.state.name} />
      </div>
    );
  }
}

如你所見,我已經將Greeting組件中展示相關的部分移動到了它自己的函數式展示組件中。當然,這是非常簡單的應用,對於複雜應用,這種手法是相當基礎的。


高階組件

高階組件是一種函數,它接受一個組件作爲參數,然後返回一個新的組件。

這對於爲多個組件獲取數據和複用組件邏輯是一種強有力的模式。想想react-router-v4和redux。用了react-router-v4後,你可以使用withRouter()來繼承以props形式傳遞給組件的各種方法。同樣,用了redux,你就可以使用connect({})()來訪問以props形式傳遞給組件的各種action。

高階組件有虛線表示,它是一種返回組件的函數


以下面的代碼爲例

import {withRouter} from 'react-router-dom';

class App extends React.Component {
  constructor() {
    super();
    this.state = {path: ''}
  }
  
  componentDidMount() {
    let pathName = this.props.location.pathname;
    this.setState(() => {
      return {
        path: pathName,
      }
    })
  }
  
  render() {
    return (
      <div>
        <h1>Hi! I'm being rendered at: {this.state.path}</h1>
      </div>
    )
  }
}

export default withRouter(App);

導出組件時,我將組件用react-router-v4的withRouter()方法包裹。在上方的componentDidMount()生命週期函數中,我用this.props.location.pathname中提供的值更新state。組件被withRouter()包裹之後能夠通過props訪問react-router-v4提供的方法,因此就有了this.props.location.pathname。這只是衆多例子中的一個。

渲染回調

和高階組件類似,渲染回調或渲染props被用於共享或複用組件組件邏輯。雖然很多開發者更傾向於使用高階組件來複用邏輯,但使用渲染回調依然有很多不錯的理由和優勢——Michael Jackson的《別再另寫一個高階組件》就是最好的解釋。簡而言之,渲染回調爲我們難能可貴地減少了命名空間衝突,並更好的說明了邏輯來源。

虛線表示渲染回調

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  increment = () => {
    this.setState(prevState => {
      return {
        count: prevState.count + 1,
      };
    });
  };

  render() {
    return (
      <div onClick={this.increment}>{this.props.children(this.state)}</div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <Counter>
        {state => (
          <div>
            <h1>The count is: {state.count}</h1>
          </div>
        )}
      </Counter>
    );
  }
}

在上面的Counter這個類中,我將this.props.children嵌套在render方法中,它接收this.state作爲參數。在下面的App類中,我能夠將其他組件包裹在Counter組件內,因此也就有機會接觸Counter組件內的邏輯。渲染回調的手法在第28行,在那裏我寫了{state => ()},bam!我自動獲得了Counter組件邏輯的操作權。

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