React中的組件有幾種寫法

內容提要

  • 元素與組件 Element & Component
  • 函數定義與類定義組件 Functional & Class
  • 展現與容器組件 Presentational & Container
  • 有狀態與無狀態組件 Stateful & Stateless
  • 受控與非受控組件 Controlled & Uncontrolled
  • 組合與繼承 Composition & Inheritance

元素與組件 Element & Component

元素react

元素是構建React應用的最小單位。元素所描述的也就是你在瀏覽器中可以看到的東西。根據咱們在上節課中講到的內容,咱們在編寫React代碼時通常用JSX來描述React元素。ajax

在做用上,咱們能夠把React元素理解爲DOM元素;但實際上,React元素只是JS當中普通的對象。React內部實現了一套叫作React DOM的東西,或者咱們稱之爲Virtual DOM也就是虛擬DOM.經過一個樹狀結構的JS對象來模擬DOM樹。算法

說到這裏咱們能夠稍微講一下,React爲何會有這一層虛擬DOM呢?在課程介紹中咱們曾經提到過,React很快、很輕。它之因此快就是由於這一套虛擬DOM的存在,React內部還實現了一個低複雜度高效率的Diff算法,不一樣於以往框架,例如angular使用的髒檢查。在應用的數據改變以後,React會盡力少地比較,而後根據虛擬DOM只改變真實DOM中須要被改變的部分。React也藉此實現了它的高效率,高性能。json

固然這不是虛擬DOM惟一的意義,經過這一層單獨抽象的邏輯讓React有了無限的可能,就好比react native的實現,可讓你只掌握JS的知識也能在其餘平臺系統上開發應用,而不僅是寫網頁,甚至是以後會出現的React VR或者React物聯網等等別的實現。redux

話說回來,元素也就是React DOM之中描述UI界面的最小單位。剛纔咱們說到了,元素其實就是普通的JS對象。不過咱們用JSX來描述React元素在理解上可能有些困難,事實上,咱們也能夠不使用JSX來描述:瀏覽器

const element = <h1>Hello, world</h1>;
// 用JSX描述就至關因而調用React的方法建立了一個對象
const element = React.createElement('h1', null, 'Hello, world');

組件服務器

要注意到,在React當中元素和組件是兩個不一樣的概念,之因此在前面講了這麼多,就是擔憂你們不當心會混淆這兩個概念。首先咱們須要明確的是,組件是構建在元素的基礎之上的。微信

React官方對組件的定義呢,是指在UI界面中,能夠被獨立劃分的、可複用的、獨立的模塊。其實就相似於JS當中對function函數的定義,它通常會接收一個名爲props的輸入,而後返回相應的React元素,再交給ReactDOM,最後渲染到屏幕上。框架

函數定義與類定義組件 Functional & Class

新版本的React裏提供了兩種定義組件的方法。固然以前的React.createClass也能夠繼續用,不過咱們在這裏先不歸入咱們討論的範圍。less

第一種函數定義組件,很是簡單啦,咱們只須要定義一個接收props傳值,返回React元素的方法便可:

function Title(props) {
  return <h1>Hello, {props.name}</h1>;
}

甚至使用ES6的箭頭函數簡寫以後能夠變成這樣:

const Title = props => <h1>Hello, {props.name}</h1>

第二種是類定義組件,也就是使用ES6中新引入的類的概念來定義React組件:

class Title extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

以後呢,根據咱們在上一節課中瞭解到的,組件在定義好以後,能夠經過JSX描述的方式被引用,組件之間也能夠相互嵌套和組合。

展現與容器組件 Presentational & Container

接下來咱們還會介紹一些更深刻的關於組件概念,如今聽起來可能會比較抽象枯燥,不過接下來要介紹的這幾個概念在以後的課程中都是會被應用到的,同窗們也能夠根據本身的實際狀況,在學習完後續的課程以後,再返回來聽聽看,相信必定會對你理解React有所幫助。

首先是最重要的一組概念:展現組件與容器組件。一樣,在課程介紹中咱們提到的,React並非傳統的MVVM框架,它只是在V層,視圖層上下功夫。同窗們應該對MVVM或MVC都有所瞭解,那麼既然咱們的框架如今只有V層的話,在實際開發中應該如何處理數據與視圖的關係呢?

爲了解決React只有V層的這個問題,更好地區分咱們的代碼邏輯,展現組件與容器組件這一對概念就被引入了。這一樣也是咱們在開發React應用時的最佳實踐。

咱們仍是先看一個具體的例子來解釋這兩個概念:

class CommentList extends React.Component {
  constructor(props) {
    super(props)
    this.state = { comments: [] }
  }
  
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments})
      }.bind(this)
    })
  }
  
  renderComment({body, author}) {
    return <li>{body}—{author}</li>
  }

  render() {
    return <ul> {this.state.comments.map(this.renderComment)} </ul>
  }
  
}

這是一個回覆列表組件,乍看上去很正常也很合理。但實際上在開發React應用時,咱們應該避免寫出這樣的組件,由於這類組件擔負的功能太多了。它只是一個單一的組件,但須要同時負責初始化state,經過ajax獲取服務器數據,渲染列表內容,在實際應用中,可能還會有更多的功能依賴。這樣,在後續維護的時候,不論是咱們要修改服務器數據交互仍是列表樣式內容,都須要去修改同一個組件,邏輯嚴重耦合,多個功能在同一個組件中維護也不利於團隊協做。

經過應用展現組件與容器組件的概念,咱們能夠把上述的單一組件重構爲一個展現回覆列表組件和回覆列表容器:

// 展現組件

class CommentList extends React.Component {
  constructor(props) {
    super(props);
  }

  renderComment({body, author}) {
    return <li>{body}—{author}</li>;
  }
  
  render() { 
    return <ul> {this.props.comments.map(this.renderComment)} </ul>;
  } 
  
}

// 容器組件

class CommentListContainer extends React.Component {
  constructor() {
    super()
    this.state = { comments: [] }
  }
  
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments})
      }.bind(this)
    })
  }
  
  render() {
    return <CommentList comments={this.state.comments} />
  }
}

像這樣回覆列表如何展現與如何獲取回覆數據的邏輯就被分離到兩個組件當中了。咱們再來明確一下展現組件和容器組件的概念:

展現組件

  • 主要負責組件內容如何展現
  • 從props接收父組件傳遞來的數據
  • 大多數狀況能夠經過函數定義組件聲明

容器組件

  • 主要關注組件數據如何交互
  • 擁有自身的state,從服務器獲取數據,或與redux等其餘數據處理模塊協做
  • 須要經過類定義組件聲明,幷包含生命週期函數和其餘附加方法

那麼這樣寫具體有什麼好處呢?

  • 解耦了界面和數據的邏輯
  • 更好的可複用性,好比同一個回覆列表展現組件能夠套用不一樣數據源的容器組件
  • 利於團隊協做,一我的負責界面結構,一我的負責數據交互

有狀態與無狀態組件 Stateful & Stateless

有狀態組件

意思是這個組件可以獲取儲存改變應用或組件自己的狀態數據,在React當中也就是state,一些比較明顯的特徵是咱們能夠在這樣的組件當中看到對this.state的初始化,或this.setState方法的調用等等。

無狀態組件

這樣的組件通常只接收來自其餘組件的數據。通常這樣的組件中只能看到對this.props的調用,一般能夠用函數定義組件的方式聲明。它自己不會掌握應用的狀態數據,即便觸發事件,也是經過事件處理函數傳遞到其餘有狀態組件當中再對state進行操做。

咱們仍是來看具體的例子比較能清楚地說明問題,與此同時,咱們已經介紹了三組概念,爲了防止混淆,我這裏特地使用了兩個展現組件來作示例,其中一個是有狀態組件,另外一個是無狀態組件,也是爲了證實,並非全部的展現組件都是無狀態組件,全部的容器組件都是有狀態組件。再次強調一下,這是兩組不一樣的概念,以及對組件不一樣角度的劃分方式。

//Stateful Component
class StatefulLink extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      active: false
    }
  }
  handleClick() {
    this.setState({
      active: !this.state.active
    })
  }
  render() {
    return <a 
          style={{ color: this.state.active ? 'red' : 'black' }}
          onClick={this.handleClick.bind(this)}
         >
           Stateful Link
         </a>
  }
}

// Stateless Component
class StatelessLink extends React.Component {
  constructor(props) {
    super(props)
  }
  handleClick() {
    this.props.handleClick(this.props.router)
  }
  render() {
    const active = this.props.activeRouter === this.props.router
    return (
        <li>
            <a 
              style={{ color: active ? 'red' : 'black' }}
              onClick={this.handleClick.bind(this)}
             >
                Stateless Link
            </a>
    </li>
    )
  }
}

class Nav extends React.Component {
  constructor() {
    super()
    this.state={activeRouter: 'home'}
  }
  handleSwitch(router) {
    this.setState({activeRouter: router})
  }
  render() {
    return (
    <ul>
        <StatelessLink activeRouter={this.state.activeRouter} router='home' handleClick={this.handleSwitch.bind(this)} />
        <StatelessLink activeRouter={this.state.activeRouter} router='blog' handleClick={this.handleSwitch.bind(this)} />
        <StatelessLink activeRouter={this.state.activeRouter} router='about' handleClick={this.handleSwitch.bind(this)} />
    </ul>
    )
  }
}

上述的例子可能稍有些複雜,事實上,在React的實際開發當中,咱們編寫的組件大部分都是無狀態組件。畢竟React的主要做用是編寫用戶界面。再加上ES6的新特性,絕大多數的無狀態組件均可以經過箭頭函數簡寫成相似下面這樣:

/* function SimpleButton(props) {
  return <button>{props.text}</button>
} */

const SimpleButton = props => <button>{props.text}</button>

受控與非受控組件 Controlled & Uncontrolled

受控組件

通常涉及到表單元素時咱們纔會使用這種分類方法,在後面一節課程表單及事件處理中咱們還會再次談論到這個話題。受控組件的值由props或state傳入,用戶在元素上交互或輸入內容會引發應用state的改變。在state改變以後從新渲染組件,咱們才能在頁面中看到元素中值的變化,假如組件沒有綁定事件處理函數改變state,用戶的輸入是不會起到任何效果的,這也就是「受控」的含義所在。

非受控組件

相似於傳統的DOM表單控件,用戶輸入不會直接引發應用state的變化,咱們也不會直接爲非受控組件傳入值。想要獲取非受控組件,咱們須要使用一個特殊的ref屬性,一樣也可使用defaultValue屬性來爲其指定一次性的默認值。

咱們仍是來看具體的例子:

class ControlledInput extends React.Component {
  constructor() {
    super()
    this.state = {value: 'Please type here...'}
  }

  handleChange(event) {
    console.log('Controlled change:',event.target.value)
    this.setState({value: event.target.value})
  }

  render() {
    return (
      <label>
        Controlled Component:
        <input type="text"
               value={this.state.value}
               onChange={(e) => this.handleChange(e)}
        />
      </label>
    );
  }
}

class UncontrolledInput extends React.Component {
  constructor() {
    super();
  }

  handleChange() {
    console.log('Uncontrolled change:',this.input.value);
  }

  render() {
    return (
        <label>
          Uncontrolled Component:
          <input type="text"
                 defaultValue='Please type here...'
                 ref={(input) => this.input = input}
                 onChange={() =>this.handleChange()}
          />
        </label>
    );
  }
}

一般狀況下,React當中全部的表單控件都須要是受控組件。但正如咱們對受控組件的定義,想讓受控組件正常工做,每個受控組件咱們都須要爲其編寫事件處理函數,有的時候確實會很煩人,比方說一個註冊表單你須要寫出全部驗證姓名電話郵箱驗證碼的邏輯,固然也有一些小技巧可讓同一個事件處理函數應用在多個表單組件上,但生產開發中並無多大實際意義。更有可能咱們是在對已有的項目進行重構,除了React以外還有一些別的庫須要和表單交互,這時候使用非受控組件可能會更方便一些。

組合與繼承 Composition & Inheritance

前面咱們已經提到了,React當中的組件是經過嵌套或組合的方式實現組件代碼複用的。經過props傳值和組合使用組件幾乎能夠知足全部場景下的需求。這樣也更符合組件化的理念,就好像使用互相嵌套的DOM元素同樣使用React的組件,並不須要引入繼承的概念。

固然也不是說咱們的代碼不能這麼寫,來看下面這個例子:

// Inheritance
class InheritedButton extends React.Component {
  constructor() {
    super()
    this.state = {
      color: 'red'
    }
  }
  render() {
    return (
    <button style={{backgroundColor: this.state.color}} class='react-button'>Inherited Button</button>
    )
  }
}

class BlueButton extends InheritedButton {
  constructor() {
    super()
    this.state = {
      color: '#0078e7'
    }
  }
}

// Composition
const CompositedButton = props => <button style={{backgroundColor:props.color}}>Composited Button</button>

const YellowButton = () => <CompositedButton color='#ffeb3b' />

但繼承的寫法並不符合React的理念。在React當中props實際上是很是強大的,props幾乎能夠傳入任何東西,變量、函數、甚至是組件自己:

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

React官方也但願咱們經過組合的方式來使用組件,若是你想實現一些非界面類型函數的複用,能夠單獨寫在其餘的模塊當中在引入組件進行使用。

本文爲《從零學習React技術棧教程·理論篇》當中的一節內容,學習React,閱讀更多用心有愛的教程內容,請關注 餘博倫 微信公衆號。

圖片描述

相關文章
相關標籤/搜索