React躬行記(5)——React和DOM

  React實現了一套與瀏覽器無關的DOM系統,包括元素渲染、節點查詢、事件處理等機制。html

1、ReactDOM

  自React v0.14開始,官方將與DOM相關的操做從React中剝離,組成單獨的react-dom庫,從而讓React能兼容更多的終端。在引入react-dom庫後,就能調用一個全局對象:ReactDOM,雖然在以前的章節中已屢次使用該對象,可是都沒有給出過多的講解,本節將對其作重點分析。react

  ReactDOM只包含了unmountComponentAtNode()、findDOMNode()、createPortal()和render()等爲數很少的幾個方法,其中在第2篇中介紹了render()的功能,又在第4篇提到用unmountComponentAtNode()方法移除DOM中已掛載的組件。接下來會講解剩餘的兩個方法。數組

1)findDOMNode()瀏覽器

  當組件被渲染到頁面真實的DOM中後,就能經過findDOMNode()方法獲得生成的DOM元素,而後就能完成諸如讀值、計算尺寸等操做。dom

  注意,findDOMNode()只能獲取已掛載的組件,而且不能用於函數組件。在組件的生命週期中,它只能存在於componentDidMount()和componentDidUpdate()兩個回調函數中,在其它地方調用會拋出一個錯誤,具體以下所示。函數

class Btn extends React.Component {
  render() {
    ReactDOM.findDOMNode(this);        //拋出錯誤  
    return <button>提交</button>;
  }
  componentDidMount() {
    ReactDOM.findDOMNode(this);        //<button>提交</button>
  }
}

  在上面的示例中,this指向的是Btn組件實例,在將this傳給findDOMNode()方法後,獲得了一個<button>元素。有一點要注意,若是組件中的render()返回null或false,那麼findDOMNode()只會返回null。動畫

2)createPortal()this

  在React v16中,新增了Portal特性,能讓組件渲染到父組件之外的DOM節點中。這個特性適用於須要跳出容器的場景,例如建立頁面內定製的彈框。spa

  在React中使用Portal特性,須要調用ReactDOM上的一個新方法:createPortal()。此方法能接收2個參數,第一個是可渲染的React子元素,例如字符串、React元素數組等;第二個是DOM元素,也就是要掛載的容器。關於這個方法的具體使用可參考下面的示例。code

class Btn extends React.Component {
  render() {
    return ReactDOM.createPortal(this.props.children, document.body);
  }
}
ReactDOM.render(<Btn><p>按鈕</p></Btn>, document.getElementById("container"));

  在上面的render()方法中調用了ReactDOM.createPortal(),使得<p>元素最終掛載到了<body>元素中,而不是id屬性爲「container」的元素。

2、Refs

  Refs是一種訪問方式,經過它可讀取render()方法內生成的組件實例和DOM元素,經常使用來處理元素的焦點、觸發動畫、集成第三方DOM庫等。注意,在組件的生命週期中,要讓Refs有效,得將其放在componentDidMount()和componentDidUpdate()兩個回調函數中才行。雖然Refs能給某些場景帶來便利,可是它破壞了React經過props傳遞數據的典型數據流,所以要儘可能避免使用Refs。

  若是要使用Refs的功能,那麼就得設置React元素的ref屬性,它的值能夠是對象、回調函數和字符串,下面會分別講解ref屬性的這三類值。

1)對象

  此處的對象是React.createRef()方法的返回值,包含一個current屬性,而該屬性指向的正是要讀取的組件實例或DOM元素。下面的示例展現了ref屬性和React.createRef()方法的配合過程。

class Btn extends React.Component {
  constructor(props) {
    super(props);
    this.myBtn = React.createRef();
  }
  render() {
    return <button ref={this.myBtn}>提交</button>;
  }
  componentDidMount() {
    let btn = this.myBtn.current;
    console.log(btn);         //<button>提交</button>
  }
}

  首先在組件的構造函數中調用React.createRef();再將返回值賦給this.myBtn,這樣就能在組件內部的任意位置使用該對象了;而後讓this.myBtn成爲<button>元素的ref屬性的值;最後在componentDidMount()中就能成功讀取到current屬性的值,從而完成了一次Refs式的訪問。

2)回調函數

  這個回調函數能接收一個參數(以下代碼所示),當組件被掛載時,參數的值爲組件實例或DOM元素;當組件被卸載時,參數的值爲null。

class Btn extends React.Component {
  render() {
    return (
      <button ref={btn => { this.myBtn = btn }}>提交</button>
    );
  }
  componentDidMount() {
    let btn = this.myBtn;
    console.log(btn);           //<button>提交</button>
  }
}

  與前一種使用方式最大的不一樣是解除了對React.createRef()方法的依賴,在回調函數中直接將其參數賦給this.myBtn,就能獲得預期的結果,不用再調用一次current屬性。

3)字符串

  ref屬性的值還能夠是字符串,例以下面代碼中的"myBtn",經過this.refs.myBtn就能訪問到想要的組件實例或DOM元素。

class Btn extends React.Component {
  render() {
    return <button ref="myBtn">提交</button>;
  }
  componentDidMount() {
    let btn = this.refs.myBtn;
    console.log(btn);           //<button>提交</button>
  }
}

  不過,官方已經不推薦這種寫法了,在將來的版本中有可能會被移除,所以建議改用回調函數的方式。

4)使用場景

  ref屬性不只能像以前示例那樣應用於DOM元素上,還能在類組件中使用ref屬性,以下代碼所示。

class Btn extends React.Component {
  render() {
    return <button>提交</button>;
  }
}
class Container extends React.Component {
  render() {
    return <Btn ref={btn => { this.myBtn = btn }}>提交</Btn>;
  }
  componentDidMount() {
    let btn = this.myBtn;
    console.log(btn);           //Btn組件的實例
  }
}

  Container是Btn的父組件,在其render()方法中,經過回調函數將Btn組件的實例賦給了this.myBtn。

  因爲函數組件沒有實例,所以不能對其添加ref屬性。

5)子組件的DOM元素

  在父組件中,若是要訪問子組件的某個DOM元素,那麼單靠ref屬性是沒法實現的,由於ref屬性獲得的只能是子組件的實例。不過,有一種間接的方式能夠實現這個需求,那就是將ref屬性和ReactDOM.findDOMNode()配合使用。下面套用上一節使用場景中的Btn和Container兩個組件,代碼只列出了修改部分,其他都已省略。

class Container extends React.Component {
  componentDidMount() {
    let btn = this.myBtn;
    let dom = ReactDOM.findDOMNode(btn);
    console.log(dom);         //<button>提交</button>
  }
}

  在componentDidMount()方法中,調用了一次ReactDOM.findDOMNode(),從而獲得了子組件所擁有的DOM元素。

3、Fragments

  JSX結構有一個限制,那就是在最外層必須用一個元素包裹,即便這是一個冗餘的元素,也得加上。例如爲一個<ul>元素掛載一組元素集合,以下所示。

class Btns extends React.Component {
  render() {
    return (
      <div>
        <li>1</li>
        <li>2</li>
        <li>3</li>
      </div>
    );
  }
}

  在頁面上渲染出的DOM會像下面這樣,其中<div>元素在此處是沒有做用的。

<ul id="container">
  <div>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </div>
</ul>

  爲了不這種無心義的輸出,React引入了Fragments,其結構以下代碼所示。只需將最外層的<div>元素的開始和結束標籤分別改爲<>和</>,就不用在DOM中增長額外的元素了。

class Btns extends React.Component {
  render() {
    return (
      <>
        <li>1</li>
        <li>2</li>
        <li>3</li>
      </>
    );
  }
}

1)React.Fragment

  <>和</>最終會被編譯成React.Fragment組件的開始和結束標籤,也就是說,前者是後者的語法糖。下面的代碼和上一個Fragments的示例是等價的。

class Btns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <li>1</li>
        <li>2</li>
        <li>3</li>
      </React.Fragment>
    );
  }
}

  若是要爲Fragments添加Keys標識(即爲其定義key屬性),那麼只能用React.Fragment組件包裹子元素。注意,key是React.Fragment組件目前惟一可用的屬性。

相關文章
相關標籤/搜索