React高頻困惑點解疑

爲什麼要引入React

import React from 'react';

function A() {
  return (
    <h1>魚魚dev</h1>
  )
}

​ 在這裏我們並沒有使用到React變量,那爲什麼還要引用React進來呢?

​ 可以試一下,如果我們省略了React的引入操作import React from 'react';,就會報以下的錯誤:報錯信息

​ 我們經過babel轉化後,會將上述代碼轉換成:

function A() {
  return React.createElement("h1", null, "魚魚dev");
}

​ 所以可以看出來,JSX只是React.createElement(component, props, ...children)的語法糖。

爲什麼要用className而不用class

​ React一開始的理念是想與瀏覽器的DOM API保持一致,而不是與HTML保持一致。所以更願意選擇DOM API中的className屬性。

爲什麼屬性要用小駝峯

這裏提一句,駝峯命名分爲大駝峯和小駝峯,大駝峯爲首字母爲大寫,其餘每個單詞首字母大寫。小駝峯爲首字母小寫,其餘每個單詞首字母小寫。還有另外兩種主流的命名規則,一是下劃線連接符,一是橫槓連接符。

​ 同上,React的理念是與瀏覽器的DOM API保持一致,也就使用了DOM API中的小駝峯命名的風格。

爲什麼constructor裏要調用super以及傳入props

​ 這裏有兩個問題

  • 爲什麼constructor裏必須要調用super
  • 爲什麼super裏必須傳入props

爲什麼constructor裏必須要調用super

​ 其實這不是React的限制,而是JavaScript的限制。要想在構造函數裏調用this,必須得在此之前調用過super。這裏的原因涉及到了JavaScript的”繼承“。

​ 如若沒有調用super:如若沒有調用super

​ JavaScript是通過原型鏈來實現繼承的,在此我們不深究原型鏈,單說在原型鏈中super的意義。super指代着父類的構造函數,在子類的構造函數中調用super(),則意味着調用了父類的構造函數,從而使得創建子類的實例時,不光有子類的屬性,還會有父類的屬性

​ 所以說,如過說在子類的構造函數中沒有調用super的話,則子類的實例中不會有父類的屬性值。這就使得這個繼承名不副實了。所以JavaScript規定繼承時,必須得在子類的構造函數中調用super

爲什麼super裏必須傳入props

​ 你必須得在super調用時傳入props才能在構造函數中使用this.props,否則使用this.props會報undefined沒有這個屬性。不過在構造函數之後,如render函數中卻能在其中使用this.props

​ 爲什麼呢?

​ 這是因爲在React的構造函數被調用之後,會給創建的React實例對象綁入一個props屬性,其值就是props

const instance = new YourComponent(props);
instance.props = props

​ 不過由於是在構造函數被調用後,才綁如props屬性,也就是說在構造函數執行時,this是沒有props這個尚需經的。

​ 所以說,還是得在super中傳入props,否則就無法在構造函數中使用this.props

class App extends React.Component {
  constructor (props) {
    super(props);// 既調用了super,又傳入了props
    console.log(this.props);// 可以訪問到值,並且不會報錯
  }
  render () {
    console.log(this.props);// 可以訪問到值
    return <h1>魚魚dev</h1>
  }
}
class App extends React.Component {
  constructor (props) {
    super();// 調用了super,不傳入props
    console.log(this.props);// undefined
  }
  render () {
    console.log(this.props);// 可以訪問到值
    return <h1>魚魚dev</h1>
  }
}
class App extends React.Component {
  constructor (props) {
      // 既不調用super,也不傳入props
    console.log(this.props);// 這裏就直接報錯了
  }
  render () {
    console.log(this.props);
    return <h1>魚魚dev</h1>
  }
}

​ 得益於React中的babel的強大,babel提供了es6中都不支持的實例屬性的寫法,也就是說實例屬性你不光可以在構造函數中聲明,還可以像這樣寫:

class A {
    a = '1'
}

​ 而如果用這種寫法,也就不用在類中寫構造函數了。所以繼承也不用手動調用super來繼承父類的實例屬性,默認就幫你調用好了。

class A {
  a = '1'
}
class B extends A {
  b = '2'
}
console.log(new B().a);// '1'

​ 所以React中可以這樣寫:

class App extends React.Component {
  render () {
    console.log(this.props);// 有值
    return <h1>魚魚dev</h1>
  }
}

爲什麼組件名要用大寫開頭

​ 前面有提到,JSX是React.createComponent(component, props, ...children)的語法糖,在這裏component的類型可以是stringReactClasstype

  • 當component值爲string類型,react會覺得他是原生dom節點
  • 當component值爲ReactClasstype,react會覺得他是自定義組件

這是在React.createComponent(component, props, ...children),而在JSX中是如何區別是string還是ReactComponent呢?如果是大寫開頭,是ReactComponent,如果是小寫開頭,是string

function A() {
    // 小寫開頭,React認爲它是原生dom節點。babel後
    // 爲React.createComponent("div",null);
    return <div></div>
}
import MyComponent from './myComponent.js';
function A() {
    // 大寫開頭,React認爲他是自定義組件。babel後
    // 爲React.createComponent(MyComponent,null);
    return <MyComponent></MyComponent>
}
import myComponent from './myComponent.js';// 這裏有個改動,引入自定義組件名小寫開頭
function A() {
    // 小寫開頭,React認爲它是原生dom節點。babel後
    // 爲React.createComponent("myComponent",null);
  // 但由於dom節點中沒有myComponent,則報錯
    return <myComponent></myComponent>
}

若你本身是想用ReactComponent,但卻一小寫開頭:

若你本身是想用ReactComponent,但卻一小寫開頭

爲什麼調用方法要 bind this

​ 相信如果大家有研究過JavaScript的this指向問題的話,都知道像下面這種情況,函數中的this指向會丟失:

const a = {
    func: function () {
        console.log(this);
    }
}
const nextA = a.func;
nextA();// 打印undefined(嚴格模式下)
a.func();// 打印{func: f}

​ 這就是大家常說的this指針丟失的問題。

​ React中的事件綁定也是這樣的:

class Foo extends React.Component {
  handleClick () {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      <!-- 在這裏我綁定事件未bind(this),結果是當觸發click事件,執行handleClick函數時,內部this指向爲undefined -->
      <button onClick={this.handleClick}>
        Click me
      </button>
    )
  }
}

​ 這是因爲onClick={this.handleClick}實際上是分爲兩步的:

const handleClick = this.handleClick;
...onClick = handleClick

​ 這就發生了上文所說的this指針丟失。

​ 所以必須得在一開始就確定死this指向,以保證即使執行了const handleClick = this.handleClick;`...onClick = handleClick`也不會發現this指針丟失的現象。

​ 具體方法有一下兩種:

class Foo extends React.Component {
  handleClick () {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      // 在綁定的時候再bind(this)。性能不是很好,多處地方調用同一
      // 函數的話,得重複bind
      <button onClick={this.handleClick.bind(this)}>
        Click me
      </button>
    )
  }
}
class Foo extends React.Component {
    constructor () {
    // 在構造函數裏bind(this)。缺點就是寫起來不順手......
    // 沒有人會習慣在類裏聲明瞭函數,再去構造函數裏去bind一次把
        this.handleClick = this.handleClick.bind(this);
    }
  handleClick () {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    )
  }
}

​ 除這兩種方法,還有一種方法:箭頭函數可以解決這一問題(箭頭函數的this指向完全繼承於上一作用域的this指向),箭頭函數又有兩種寫法:

class Foo extends React.Component {
    constructor () {
    // 在構造函數裏bind(this)。缺點就是寫起來不順手......
    // 沒有人會習慣在類裏聲明瞭函數,再去構造函數裏去bind一次把
        this.handleClick = this.handleClick.bind(this);
    }
  handleClick () {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
        // 相當於每次點擊事件的時候就聲明一個匿名函數,這個比第一種方法還費性能
      <button onClick={e => {this.handleClick}}>
        Click me
      </button>
    )
  }
}
class Foo extends React.Component {
  // 這是我最喜歡的寫法了,美觀而又省性能
  handleClick = () => {
    this.setState({ xxx: aaa })
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    )
  }
}
可能大家會想,爲什麼React不自己bind呢?因爲每次調用的時候,都bind一次,會影響性能,倒不如一開始就bind好,然後在調用時候直接調用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章