1. 程式人生 > >掌握react,這一篇就夠了

掌握react,這一篇就夠了

react眾所周知的前端3大主流框架之一,由於出色的效能,完善的周邊設施風頭一時無兩。本文就帶大家一起掌握react。

jsx語法

前端MVVM主流框架都有一套自己的模板處理方法,react則使用它獨特的jsx語法。在元件中插入html類似的語法,簡化建立view的流程。

下面讓我們來認識一下構建的兩種元素

原生元素

ReactDOM.render((
  <div>
    <h1>標題</h1>
  </div>
), document.getElementById('root'))

通過簡單的語法頁面就會被插入一個div+一個h1標籤。原生的html元素可以被直接使用。以上的語法並不是js支援的語法,需要被轉換之後才能執行。

自定義元素

react強大之處就在於可以元件的自定義,實現元件的複用。如果我們建立了一個元件。我們也可以通過jsx語法呼叫。

import * as React from 'react'

class Page extends React.Component {
  render() {
    return (<div>
      home111 &copy; © \ua9
    </div>)
  }
}

ReactDOM.render((
  <div>
    <Page/>
  </div>
), document.getElementById('root'))

我們定義了一個Page元件,可以在jsx裡面像呼叫html一樣直接呼叫。

插入動態資料

let name = 'hi'

ReactDOM.render((
  <div>
    {name}
  </div>
), document.getElementById('root'))

使用{}就可以插入資料,但是{}中間的必須是js表示式,不能是語句。如果表示式的執行結果是一個數組,則會自動join。

註釋

jsx語法和html語法一樣,也是可以插入註釋,只不過寫的時候有一些區別

子元件註釋

let name = 'hi'

ReactDOM.render((
  <div>
    {/* 註釋 */}
    {name}
  </div>
), document.getElementById('root'))

在子元件中插入註釋,需要使用{}包裹起來,在//之間插入註釋文字。

屬性註釋

let name = 'hi'

ReactDOM.render((
  <div>
    {name}
    <img /* 
        多行註釋
    */ src="1.jpg"/>
  </div>
), document.getElementById('root'))

在標籤中間,可以插入一個多行註釋,類似上面的程式碼。

屬性props

  1. 可以向使用html的attr一樣使用屬性,就像下面img的src一樣
let name = 'hi'

ReactDOM.render((
  <div>
    <img src="1.png"/>
  </div>
), document.getElementById('root'))
  1. 如果需要傳遞動態屬性,使用{},多個屬性,使用展開運算子
let props = {
    src: '1.png',
    alt: '1圖片'
}

ReactDOM.render((
  <div>
    <img src={"1.png"}/>
    <img {...props}/>
  </div>
), document.getElementById('root'))
  1. 兩個轉換,class-->className for-->htmlFor

因為class和for是javascript關鍵字,所以這裡需要用轉換之後名稱

ReactDOM.render((
  <div className="tab">
    <label htmlFor="name">姓名:</label><input id="name"/>
  </div>
), document.getElementById('root'))
  1. 布林屬性

如果一個屬性的值是布林值,當這個值是true的時候則可以省略=後面的值,只保留key。

ReactDOM.render((
  <div className="tab">
    <input type="text" required/>
    <input type="text" required={true}/>
  </div>
), document.getElementById('root'))
  1. 原生元素的自定義屬性

react對元素屬性做了校驗,如果在原生屬性上使用此元素不支援的屬性,則不能編譯成功。必須使用data-字首

ReactDOM.render((
  <div className="tab">
    <input type="text" data-init="22"/>
  </div>
), document.getElementById('root'))

插入html

如果動態的插入html元素,react出於安全性考慮會自動幫我們轉義。所以一定要動態的插入元素的話,使用dangerouslySetInnerHTML

ReactDOM.render((
  <div className="tab">
    <div dangerouslySetInnerHTML={{__html: '<span>test</span>'}}></div>
  </div>
), document.getElementById('root'))

React元件建立

React.createClass

這是舊版本的api,使用React.createClass建立元件,配套的一些api,有getDefaultProps, getinitialstate。官方已經不建議使用了,使用下面新的api替代。

ES6 classes

import * as React from 'react'

class Page extends React.Component {
  render() {
    return (<div>
      home
    </div>)
  }
}

這是一個實現了render方法的class。也是一個基本的react元件。

無狀態函式

function Button(props, context) {
    return (
        <button>
            <em>{props.text}</em>
            <span>{context.name}</span>
        </button>
    );
}

純函式,不存在state,只接受props和state。純函式有優點,優點就是易於測試,無副作用。

React資料流

state

state是元件的內部狀態,需要在視圖裡面用到的狀態,才需要放到state裡面去。如下,我們在類上建立一個state屬性,在視圖裡面通過使用this.state.name去引用。而這裡的state定義則代替的是getinitialstate方法。

import * as React from 'react'

class Page extends React.Component {
  state = {
      name: '小明'
  }    

  render() {
    return (<div>
      {this}
    </div>)
  }
}

如何更新state呢,直接更改state其實可以可以的,不過這樣子無法觸發元件檢視的更新機制。所以使用setState()api。值得注意的是setState是非同步的,原因是react內部需要對setState做優化,不是state變了立刻去更新檢視,而是攔截一部分state的改變,等到合適的時機再去更新檢視。

import * as React from 'react'

class Page extends React.Component {
  state = {
      name: '小明'
  }    

  render() {
    setTimeout(() => this.setState({name: '小明兒子'}), 5000)
  
    return (<div>
      {this.state.name}
    </div>)
  }
}
真實開發中絕不要在render函式裡面去更改state,以上只是為了演示

props

props是元件之間傳遞資料的最主要api, react推崇的是自頂向下的資料流向,也就是元件的資料要從父元件傳給子元件。如果子元件需要向父元件傳遞資料,則需要使用回撥函式的方式。

import * as React from 'react'

class Child extends React.Component {
  render() {
    return (<div>
      {this.props.parentName}
    </div>)
  }
}

class Parent extends React.Component {
  state = {
      name: '小明'
  }    

  render() {
    setTimeout(() => this.setState({name: '小明兒子'}), 5000)
  
    return (<div>
      <Child parentName={this.state.name}/>
    </div>)
  }
}

可以看到Child元件顯示了父元件的name。當父元件狀態更新了,子元件同步更新。那如何在子元件中更改父元件狀態呢?答案是回撥函式。

import * as React from 'react'

class Child extends React.Component {
  update() {
      this.props.onChange('小明名字改了')
  }

  render() {
    return (<div>
      {this.props.parentName}
      <button onClick={this.update.bind(this)}>更新</button>
    </div>)
  }
}

class Parent extends React.Component {
  state = {
      name: '小明'
  }    

  changeName(name) {
      this.setState({
          name
      })
  }
  
  render() {
    setTimeout(() => this.setState({name: '小明兒子'}), 5000)
  
    return (<div>
      <Child onChange={this.changeName.bind(this)} parentName={this.state.name}/>
    </div>)
  }
}

注意哈:props是不可以更改的,這既不符合react單向資料流思想,也為維護帶來災難。

事件

react裡面的使用者事件都是合成事件,被React封裝過。內部使用的還是事件的委託機制。常用的事件有點選事件onClick,input的onChange事件等,官網都可以查到。

合成事件的this指向問題

就像上文一樣,我們繫結事件的方式很奇怪,使用了bind來顯示繫結this的指向。因為傳遞到元件內部的只是一個函式,而脫離了當前物件的函式的this指向是不能指到當前元件的,需要顯示指定。

通過bind

<button onClick={this.update.bind(this)}>更新</button>

構造器內部指定

import * as React from 'react'

class Child extends React.Component {
  constructor(props) {
     super(props) 
     this.update = this.update.bind(this)
  }

  update() {
      this.props.onChange('小明名字改了')
  }

  render() {
    return (<div>
      {this.props.parentName}
      <button onClick={this.update}>更新</button>
    </div>)
  }
}

箭頭函式

import * as React from 'react'

class Child extends React.Component {
  update => e = {
      this.props.onChange('小明名字改了')
  }

  render() {
    return (<div>
      {this.props.parentName}
      <button onClick={this.update}>更新</button>
    </div>)
  }
}

裝飾器

import * as React from 'react'

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

  @autoBind
  update() {
      this.props.onChange('小明名字改了')
  }

  render() {
    return (<div>
      {this.props.parentName}
      <button onClick={this.update}>更新</button>
    </div>)
  }
}

裝飾器是es7語法,如果需要使用需要安裝對應的babel:present版本。而typescript則原生支援。

autoBind原理大概就是劫持get方法,get時改變this指向

如何獲得evnt原生事件

通過e.nativeEvent獲取原生事件物件

import * as React from 'react'

class Child extends React.Component {
  constructor(props) {
     super(props) 
     this.update = this.update.bind(this)
  }

  update(e) {
      console.log(e.nativeEvent)
  }

  render() {
    return (<div>
      <button onClick={this.update}>更新</button>
    </div>)
  }
}

解決冒泡和取消預設事件

e.preventDefault() //取消預設行為
e.stopPropagation() //取消冒泡

這個和瀏覽器原生事件處理方案是一致的。問題是我們只可以調合成事件的e的方法,不可以通過e.nativeEvent方法做這些操作,原因是上文講過的委託。

ReactDom

ref

特殊的props,ref元件物件的引用,現在官方也不建議直接給ref賦值,需要通過函式來賦值。

ReactDOM.render((
  <div>
    <Calendar ref={ref => this.c = ref} any-ss="text"/>
  </div>
), document.getElementById('root'))

render

頂層api,只有在根元件時候才需要使用。第一個引數是Component,第二個引數是dom節點

findDOMNode

通過傳入component例項獲取此component根dom節點,在這裡可以去dom節點進行操作了,雖然極其不建議這麼做,但是你確實可以做。

unmountComponentAtNode

解除安裝此元件,並銷燬元件state和事件

接收元件的引用,也就是ref。僅僅是取消掛載,元件還在,如果需要徹底清除的話,需要手動刪掉此dom。

表單

onchange配合value

與vue框架不同的是,react如果要實現表單元素變化,狀態同步更新,必須要自己去監聽表單事件。

import * as React from 'react'

class Child extends React.Component {
  state = {
      name: '小明'
  }

  constructor(props) {
     super(props) 
     this.update = this.update.bind(this)
  }

  update(e) {
      this.setState({
          name: e.target.value
      })
  }

  render() {
    return (<div>
      <input onChange={this.update} value={this.state.name}/>
    </div>)
  }
}

受控元件和非受控元件

受控元件和非受控元件這些都是指的表單元件,當一個表單的值是通過value改變的而不是通過defaultValue是受控元件,否則就是非受控元件。

下面元件中的input就是受控元件

import * as React from 'react'

class Child extends React.Component {
  state = {
      name: '小明'
  }

  constructor(props) {
     super(props) 
     this.update = this.update.bind(this)
  }

  update(e) {
      this.setState({
          name: e.target.value
      })
  }

  render() {
    return (<div>
      <input onChange={this.update} value={this.state.name}/>
    </div>)
  }
}

下面元件中的input是非受控元件

import * as React from 'react'

class Child extends React.Component {
  state = {
      name: '小明'
  }

  constructor(props) {
     super(props) 
     this.update = this.update.bind(this)
  }

  update(e) {
      this.setState({
          name: e.target.value
      })
  }

  render() {
    return (<div>
      <input onChange={this.update} defaultValue={this.state.name}/>
    </div>)
  }
}

元件之間通訊

父子之間通訊

父子之間通訊又分為父->子,子->父。

因為react單向資料流向的緣故,父->子通訊的話直接通過props。父元件資料變動,直接傳遞給子元件。

子->父元件之間就要通過回撥函式來通訊了,父元件傳遞一個回撥函式給子元件,子元件通過呼叫此函式的方式通知父元件通訊。

跨級元件通訊

react為了實現祖先元件和後輩元件之間的通訊問題,引入了contextApi。

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: React.PropTypes.string
};

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: React.PropTypes.string
};

MessageList中的color會自動更新到兒孫元件裡面去,實現跨級啊通訊。如果需要反過來通訊,則需要藉助其他工具,比如事件系統(Pub/Sub)。

沒有巢狀關係元件之間通訊

元件之間通訊最主流的兩種方式脫胎於觀察這模式和中介者模式這兩種。

跨級之間通訊現在最主流的方式就是觀察這模式的實現Pub/Sub,react社群中的redux也是使用這種方式實現的。

vue2.X版本也去掉了跨元件通訊的功能。那如何在2.x中做跨元件通訊呢?如果不借助外力的話,是不是可以使用$parent和$childen的遞迴呼叫實現全域性元件通訊呢?比如我想廣播一個事件,我就查詢到所有的子元件,挨個觸發$emit(xx),上報一個事件也是同理,只不過需要查詢所有的$parent。結合起來就可以實現元件之間的通訊,只不過這種查詢效率比較低,需要慎用和優化