網(wǎng)上各種言論說 React 上手比 Vue 難,可能難就難不能深刻理解 JSX,或者對 ES6 的一些特性理解得不夠深刻,導致覺得有些點難以理解,然后說 React 比較難上手,還反人類啥的,所以我打算寫兩篇文章來講新手學習 React 的時候容易迷惑的點寫出來,如果你還以其他的對于學習 React 很迷惑的點,可以在留言區(qū)里給我留言。
為什么要引入 React
在寫 React 的時候,你可能會寫類似這樣的代碼:
import React from 'react'
function A() {
// ...other code
return <h1>前端桃園</h1>
}
你肯定疑惑過,下面的代碼都沒有用到 React,為什么要引入 React 呢?
如果你把 import React from ‘react’ 刪掉,還會報下面這樣的錯誤:

那么究竟是哪里用到了這個 React,導致我們引入 React 會報錯呢,不懂這個原因,那么就是 JSX 沒有搞得太明白。
你可以講上面的代碼(忽略導入語句)放到在線 babel 里進行轉(zhuǎn)化一下,發(fā)現(xiàn) babel 會把上面的代碼轉(zhuǎn)化成:
function A() {
// ...other code
return React.createElement("h1", null, "前端桃園");
}
因為從本質(zhì)上講,JSX 只是為 React.createElement(component, props, ...children) 函數(shù)提供的語法糖。
為什么要用 className 而不用 class
React 一開始的理念是想與瀏覽器的 DOM API 保持一直而不是 HTML,因為 JSX 是 JS 的擴展,而不是用來代替 HTML 的,這樣會和元素的創(chuàng)建更為接近。在元素上設(shè)置
class需要使用className這個 API:
const element = document.createElement("div")
element.className = "hello"瀏覽器問題,ES5 之前,在對象中不能使用保留字。以下代碼在 IE8 中將會拋出錯誤:
const element = {
attributes: {
class: "hello"
}
}解構(gòu)問題,當你在解構(gòu)屬性的時候,如果分配一個
class變量會出問題:
const { class } = { class: 'foo' } // Uncaught SyntaxError: Unexpected token }
const { className } = { className: 'foo' }
const { class: className } = { class: 'foo' }
其他討論可見:有趣的話題,為什么jsx用className而不是class
為什么屬性要用小駝峰
因為 JSX 語法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用camelCase(小駝峰命名)來定義屬性的名稱,而不使用 HTML 屬性名稱的命名約定。
為什么 constructor 里要調(diào)用 super 和傳遞 props
這是官網(wǎng)的一段代碼,具體見:狀態(tài)(State) 和 生命周期
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
而且有這么一段話,不僅讓我們調(diào)用 super 還要把 props 傳遞進去,但是沒有告訴我們?yōu)槭裁匆@么做。

不知道你有沒有疑惑過為什么要調(diào)用 super 和傳遞 props,接下來我們來解開謎題吧。
為什么要調(diào)用 super
其實這不是 React 的限制,這是 JavaScript 的限制,在構(gòu)造函數(shù)里如果要調(diào)用 this,那么提前就要調(diào)用 super,在 React 里,我們常常會在構(gòu)造函數(shù)里初始化 state,this.state = xxx ,所以需要調(diào)用 super。
為什么要傳遞 props
你可能以為必須給 super 傳入 props,否則 React.Component 就沒法初始化this.props:
class Component {
constructor(props) {
this.props = props;
// ...
}
}
不過,如果你不小心漏傳了 props,直接調(diào)用了 super(),你仍然可以在 render和其他方法中訪問 this.props(不信的話可以試試嘛)。
為啥這樣也行?因為React 會在構(gòu)造函數(shù)被調(diào)用之后,會把 props 賦值給剛剛創(chuàng)建的實例對象:
const instance = new YourComponent(props);
instance.props = props;
props 不傳也能用,是有原因的。
但這意味著你在使用 React 時,可以用 super() 代替 super(props) 了么?
那還是不行的,不然官網(wǎng)也不會建議你調(diào)用 props 了,雖然 React 會在構(gòu)造函數(shù)運行之后,為 this.props 賦值,但在 super() 調(diào)用之后與構(gòu)造函數(shù)結(jié)束之前,this.props 仍然是沒法用的。
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
// Inside your code
class Button extends React.Component {
constructor(props) {
super(); // 忘了傳入 props
console.log(props); // ? {}
console.log(this.props); // undefined
}
// ...
}
要是構(gòu)造函數(shù)中調(diào)用了某個訪問 props 的方法,那這個 bug 就更難定位了。因此我強烈建議始終使用super(props),即使這不是必須的:
class Button extends React.Component {
constructor(props) {
super(props); // ? We passed props
console.log(props); // ? {}
console.log(this.props); // ? {}
}
// ...
}
上面的代碼確保 this.props 始終是有值的。
如果你想避免以上的問題,你可以通過class 屬性提案 來簡化代碼:
class Clock extends React.Component {
state = {date: new Date()};
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
更詳細的內(nèi)容可見Dan 的博客
為什么組件用大寫開頭
前面以及說過了,JSX 是 React.createElement(component, props, …children) 提供的語法糖,component 的類型是:string/ReactClass type,我們具體看一下在什么情況下會用到 string 類型,什么情況下用到 ReactClass type 類型
- string 類型react會覺得他是一個原生dom節(jié)點
- ReactClass type 類型 自定義組件
例如(string):在 jsx 中我們寫一個
<div></div>
轉(zhuǎn)換為js的時候就變成了
React.createElement("div", null)
例如(ReactClass type):在jsx中我們寫一個
function MyDiv() {
return (<div><div>)
}
<MyDiv></MyDiv>
轉(zhuǎn)換為js的時候就變成了
function MyDiv() {
return React.createElement("div", null);
}
React.createElement(MyDiv, null);
上邊的例子中如果將MyDiv中的首字母小寫,如下
function myDiv() {
return (<div><div>)
}
<myDiv></myDiv>
轉(zhuǎn)換為 js 的時候就變成了
function MyDiv() {
return React.createElement("div", null);
}
React.createElement("myDiv", null);
由于找不到 myDiv 這個 dom,所以就會報錯。
為什么調(diào)用方法要 bind this
前提知識:深刻的理解 JavaScript 中的 this
相信剛寫 React 的時候,很多朋友可能會寫類似這樣的代碼:
class Foo extends React.Component {
handleClick () {
this.setState({ xxx: aaa })
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
發(fā)現(xiàn)會報 this 是 undefined 的錯,然后可能對事件處理比較疑惑,然后去看官網(wǎng)的事件處理有下面一段話:
你必須謹慎對待 JSX 回調(diào)函數(shù)中的
this,在 JavaScript 中,class 的方法默認不會綁定this。如果你忘記綁定this.handleClick并把它傳入了onClick,當你調(diào)用這個函數(shù)的時候this的值為undefined。
這并不是 React 特有的行為;這其實與 JavaScript 函數(shù)工作原理有關(guān)。通常情況下,如果你沒有在方法后面添加(),例如onClick={this.handleClick},你應(yīng)該為這個方法綁定this。
然后你看了官網(wǎng)的例子和建議之后,知道需要為事件處理函數(shù)綁定 this就能解決,想下面這樣:
class Foo extends React.Component {
handleClick () {
this.setState({ xxx: aaa })
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>
Click me
</button>
)
}
}
但是可能你沒有去思考過為什么需要 bind this?如果你不能理解的話,還是 js 的基礎(chǔ)沒有打好。
React 是如何處理事件的?
咱們先來了解一下 React 是如何處理事件的。
React 的事件是合成事件, 內(nèi)部原理非常復(fù)雜,我這里只把關(guān)鍵性,可以用來解答這個問題的原理部分進行介紹即可(后面應(yīng)該會寫一篇 react 的事件原理,敬請期待)。
上篇文章已經(jīng)說過,jsx 實際上是 React.createElement(component, props, …children) 函數(shù)提供的語法糖,那么這段 jsx 代碼:
<button onClick={this.handleClick}>
Click me
</button>
會被轉(zhuǎn)化為:
React.createElement("button", {
onClick: this.handleClick
}, "Click me")
了解了上面的,然后簡單的理解 react 如何處理事件的,React 在組件加載(mount)和更新(update)時,將事件通過 addEventListener 統(tǒng)一注冊到 document 上,然后會有一個事件池存儲了所有的事件,當事件觸發(fā)的時候,通過 dispatchEvent 進行事件分發(fā)。
所以你可以簡單的理解為,最終 this.handleClick 會作為一個回調(diào)函數(shù)調(diào)用。
理解了這個,然后再來看看回調(diào)函數(shù)為什么就會丟失 this。
this 簡單回顧
在函數(shù)內(nèi)部,
this的值取決于函數(shù)被調(diào)用的方式。
如果你不能理解上面那句話,那么你可能需要停下來閱讀文章,去查一下相關(guān)資料,否則你可能看不懂下面的,如果你懶的話,就看為你準備好的 MDN 吧。
通過上面對事件處理的介紹,來模擬一下在類組件的 render 函數(shù)中, 有點類似于做了這樣的操作:
class Foo {
sayThis () {
console.log(this); // 這里的 `this` 指向誰?
}
exec (cb) {
cb();
}
render () {
this.exec(this.sayThis);
}
}
var foo = new Foo();
foo.render(); // 輸出結(jié)果是什么?
你會發(fā)現(xiàn)最終結(jié)果輸出的是 undefined,如果你不理解為什么輸出的是 undefined,那么還是上面說的,需要去深刻的理解 this 的原理。如果你能理解輸出的是undefined,那么我覺得你就可以理解為什么需要 bind this 了。
那么你可能會問:為什么React沒有自動的把 bind 集成到 render 方法中呢?在 exec 調(diào)用回調(diào)的時候綁定進去,像這樣:
class Foo {
sayThis () {
console.log(this); // 這里的 `this` 指向誰?
}
exec (cb) {
cb().bind(this);
}
render () {
this.exec(this.sayThis);
}
}
var foo = new Foo();
foo.render(); // 輸出結(jié)果是什么?
因為 render 多次調(diào)用每次都要 bind 會影響性能,所以官方建議你自己在 constructor 中手動 bind 達到性能優(yōu)化。
四種事件處理對比
對于事件處理的寫法也有好幾種,咱們來進行對比一下:
1. 直接 bind this 型
就是像文章開始的那樣,直接在事件那里 bind this
class Foo extends React.Component {
handleClick () {
this.setState({ xxx: aaa })
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>
Click me
</button>
)
}
}
優(yōu)點:寫起來順手,一口氣就能把這個邏輯寫完,不用移動光標到其他地方。
缺點:性能不太好,這種方式跟 react 內(nèi)部幫你 bind 一樣的,每次 render 都會進行 bind,而且如果有兩個元素的事件處理函數(shù)式同一個,也還是要進行 bind,這樣會多寫點代碼,而且進行兩次 bind,性能不是太好。(其實這點性能往往不會是性能瓶頸的地方,如果你覺得順手,這樣寫完全沒問題)
2. constuctor 手動 bind 型
class Foo extends React.Component {
constuctor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick () {
this.setState({ xxx: aaa })
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
優(yōu)點:相比于第一種性能更好,因為構(gòu)造函數(shù)只執(zhí)行一次,那么只會 bind 一次,而且如果有多個元素都需要調(diào)用這個函數(shù),也不需要重復(fù) bind,基本上解決了第一種的兩個缺點。
缺點:沒有明顯缺點,硬要說的話就是太丑了,然后不順手(我覺得丑,你覺得不丑就這么寫就行了)。
3. 箭頭函數(shù)型
class Foo extends React.Component {
handleClick () {
this.setState({ xxx: aaa })
}
render() {
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
)
}
}
優(yōu)點:順手,好看。
缺點:每次 render 都會重復(fù)創(chuàng)建函數(shù),性能會差一點。
4. public class fields 型
這種 class fields還處于實驗階段,據(jù)我所知目前還沒有被納入標準,具體可見這里。
class Foo extends React.Component {
handleClick = () => {
this.setState({ xxx: aaa })
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
優(yōu)點:好看,性能好。
缺點:沒有明顯缺點,如果硬要說可能就是要多裝一個 babel 插件來支持這種語法。
總結(jié)
我平時用的就這四種寫法,我這邊從代碼的美觀性、性能以及是否順手方便對各種寫法做了簡單的對比。其實每種方法在項目里用都是沒什么問題的,性能方面基本上可以忽略,對于美觀性和順手比較主觀,所以總體來說就是看大家的偏好咯,如果硬要推薦的話,我還是比較推薦第四種寫法,美觀而且不影響性能。
覺得寫的很好,轉(zhuǎn)載至:https://zhuanlan.zhihu.com/p/83079398