您好,登錄后才能下訂單哦!
這篇文章主要介紹“react使用實例分析”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“react使用實例分析”文章能幫助大家解決問題。
在寫 React 的時候,你可能會寫類似這樣的代碼:
import React from 'react' function A() { // ...other code return <h2>前端桃園</h2> }
你肯定疑惑過,下面的代碼都沒有用到 React,為什么要引入 React 呢?
如果你把 import React from ‘react’
刪掉,還會報錯
那么究竟是哪里用到了這個 React,導致我們引入 React 會報錯呢,不懂這個原因,那么就是 JSX 沒有搞得太明白。
你可以講上面的代碼(忽略導入語句)放到在線 babel 里進行轉化一下,發現 babel 會把上面的代碼轉化成:
function A() { // ...other code return React.createElement("h2", null, "前端桃園"); }
因為從本質上講,JSX 只是為 React.createElement(component, props, ...children)
函數提供的語法糖。
React 一開始的理念是想與瀏覽器的 DOM API 保持一直而不是 HTML,因為 JSX 是 JS 的擴展,而不是用來代替 HTML 的,這樣會和元素的創建更為接近。在元素上設置 class
需要使用 className
這個 API:
const element = document.createElement("div") element.className = "hello"
瀏覽器問題,ES5 之前,在對象中不能使用保留字。以下代碼在 IE8 中將會拋出錯誤:
const element = { attributes: { class: "hello" } }
解構問題,當你在解構屬性的時候,如果分配一個 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 屬性名稱的命名約定。
來自 JSX 簡介
這是官網的一段代碼,具體見:狀態(State) 和 生命周期
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h2>Hello, world!</h2> <h3>It is {this.state.date.toLocaleTimeString()}.</h3> </div> ); } }
而且有這么一段話,不僅讓我們調用 super
還要把 props
傳遞進去,但是沒有告訴我們為什么要這么做。
不知道你有沒有疑惑過為什么要調用 super
和傳遞 props
,接下來我們來解開謎題吧。
為什么要調用 super
其實這不是 React 的限制,這是 JavaScript 的限制,在構造函數里如果要調用 this,那么提前就要調用 super,在 React 里,我們常常會在構造函數里初始化 state,this.state = xxx
,所以需要調用 super。
為什么要傳遞 props
你可能以為必須給 super
傳入 props
,否則 React.Component
就沒法初始化this.props
:
class Component { constructor(props) { this.props = props; // ... } }
不過,如果你不小心漏傳了 props
,直接調用了 super()
,你仍然可以在 render
和其他方法中訪問 this.props
(不信的話可以試試嘛)。
為啥這樣也行?因為React 會在構造函數被調用之后,會把 props 賦值給剛剛創建的實例對象:
const instance = new YourComponent(props); instance.props = props;
props
不傳也能用,是有原因的。
但這意味著你在使用 React 時,可以用 super()
代替 super(props)
了么?
那還是不行的,不然官網也不會建議你調用 props 了,雖然 React 會在構造函數運行之后,為 this.props
賦值,但在 super()
調用之后與構造函數結束之前,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 } // ... }
要是構造函數中調用了某個訪問 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> <h2>Hello, world!</h2> <h3>It is {this.state.date.toLocaleTimeString()}.</h3> </div> ); } }
更詳細的內容可見Dan 的博客
前面以及說過了,JSX 是 React.createElement(component, props, …children)
提供的語法糖,component 的類型是:string/ReactClass type
,我們具體看一下在什么情況下會用到 string 類型,什么情況下用到 ReactClass type 類型
string 類型react會覺得他是一個原生dom節點
ReactClass type 類型 自定義組件
例如(string):在 jsx 中我們寫一個
<div></div>
轉換為js的時候就變成了
React.createElement("div", null)
例如(ReactClass type):在jsx中我們寫一個
function MyDiv() { return (<div><div>) }
轉換為js的時候就變成了
function MyDiv() { return React.createElement("div", null); } React.createElement(MyDiv, null);
上邊的例子中如果將MyDiv中的首字母小寫,如下
function myDiv() { return (<div><div>) }
轉換為 js 的時候就變成了
function MyDiv() { return React.createElement("div", null); } React.createElement("myDiv", null);
由于找不到 myDiv 這個 dom,所以就會報錯。
前提知識:深刻的理解 JavaScript 中的 this
相信剛寫 React 的時候,很多朋友可能會寫類似這樣的代碼:
class Foo extends React.Component { handleClick () { this.setState({ xxx: aaa }) } render() { return ( <button onClick={this.handleClick}> Click me </button> ) } }
發現會報 this
是 undefined
的錯,然后可能對事件處理比較疑惑,然后去看官網的事件處理有下面一段話:
你必須謹慎對待 JSX 回調函數中的
this
,在 JavaScript 中,class 的方法默認不會綁定this
。如果你忘記綁定this.handleClick
并把它傳入了onClick
,當你調用這個函數的時候this
的值為undefined
。
這并不是 React 特有的行為;這其實與 JavaScript 函數工作原理有關。通常情況下,如果你沒有在方法后面添加()
,例如onClick={this.handleClick}
,你應該為這個方法綁定this
。
然后你看了官網的例子和建議之后,知道需要為事件處理函數綁定 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 的基礎沒有打好。
React 是如何處理事件的?
咱們先來了解一下 React 是如何處理事件的。
React 的事件是合成事件, 內部原理非常復雜,我這里只把關鍵性,可以用來解答這個問題的原理部分進行介紹即可(后面應該會寫一篇 react 的事件原理,敬請期待)。
上篇文章已經說過,jsx 實際上是 React.createElement(component, props, …children)
函數提供的語法糖,那么這段 jsx 代碼:
<button onClick={this.handleClick}> Click me </button>
會被轉化為:
React.createElement("button", { onClick: this.handleClick }, "Click me")
了解了上面的,然后簡單的理解 react 如何處理事件的,React 在組件加載(mount
)和更新(update
)時,將事件通過 addEventListener
統一注冊到 document
上,然后會有一個事件池存儲了所有的事件,當事件觸發的時候,通過 dispatchEvent
進行事件分發。
所以你可以簡單的理解為,最終 this.handleClick
會作為一個回調函數調用。
理解了這個,然后再來看看回調函數為什么就會丟失 this
。
this 簡單回顧
在函數內部,
this
的值取決于函數被調用的方式。
如果你不能理解上面那句話,那么你可能需要停下來閱讀文章,去查一下相關資料,否則你可能看不懂下面的,如果你懶的話,就看為你準備好的 MDN 吧。
通過上面對事件處理的介紹,來模擬一下在類組件的 render 函數中, 有點類似于做了這樣的操作:
class Foo { sayThis () { console.log(this); // 這里的 `this` 指向誰? } exec (cb) { cb(); } render () { this.exec(this.sayThis); } } var foo = new Foo(); foo.render(); // 輸出結果是什么?
你會發現最終結果輸出的是 undefined
,如果你不理解為什么輸出的是 undefined
,那么還是上面說的,需要去深刻的理解 this 的原理。如果你能理解輸出的是undefined
,那么我覺得你就可以理解為什么需要 bind this 了。
那么你可能會問:為什么React沒有自動的把 bind 集成到 render 方法中呢?在 exec 調用回調的時候綁定進去,像這樣:
class Foo { sayThis () { console.log(this); // 這里的 `this` 指向誰? } exec (cb) { cb().bind(this); } render () { this.exec(this.sayThis); } } var foo = new Foo(); foo.render(); // 輸出結果是什么?
因為 render 多次調用每次都要 bind 會影響性能,所以官方建議你自己在 constructor 中手動 bind 達到性能優化。
對于事件處理的寫法也有好幾種,咱們來進行對比一下:
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> ) } }
優點:寫起來順手,一口氣就能把這個邏輯寫完,不用移動光標到其他地方。
缺點:性能不太好,這種方式跟 react 內部幫你 bind 一樣的,每次 render 都會進行 bind,而且如果有兩個元素的事件處理函數式同一個,也還是要進行 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> ) } }
優點:相比于第一種性能更好,因為構造函數只執行一次,那么只會 bind 一次,而且如果有多個元素都需要調用這個函數,也不需要重復 bind,基本上解決了第一種的兩個缺點。
缺點:沒有明顯缺點,硬要說的話就是太丑了,然后不順手(我覺得丑,你覺得不丑就這么寫就行了)。
3. 箭頭函數型
class Foo extends React.Component { handleClick () { this.setState({ xxx: aaa }) } render() { return ( <button onClick={(e) => this.handleClick(e)}> Click me </button> ) } }
優點:順手,好看。
缺點:每次 render 都會重復創建函數,性能會差一點。
4. public class fields 型
這種 class fields
還處于實驗階段,據我所知目前還沒有被納入標準,具體可見這里。
class Foo extends React.Component { handleClick = () => { this.setState({ xxx: aaa }) } render() { return ( <button onClick={this.handleClick}> Click me </button> ) } }
優點:好看,性能好。
缺點:沒有明顯缺點,如果硬要說可能就是要多裝一個 babel 插件來支持這種語法。
我平時用的就這四種寫法,我這邊從代碼的美觀性、性能以及是否順手方便對各種寫法做了簡單的對比。其實每種方法在項目里用都是沒什么問題的,性能方面基本上可以忽略,對于美觀性和順手比較主觀,所以總體來說就是看大家的偏好咯,如果硬要推薦的話,我還是比較推薦第四種寫法,美觀而且不影響性能。
這個問題是我們公司后端寫 React 的時候提出的問題,為啥不能直接修改 state,要 setState 一下。我在想,從 vue 轉到 React 可能也會有這種疑問,因為 vue 修改狀態都是直接改的。
如果我們了解 setState 的原理的話,可能就能解答這個問題了,setState 做的事情不僅僅只是修改了 this.state
的值,另外最重要的是它會觸發 React 的更新機制,會進行 diff ,然后將 patch 部分更新到真實 dom 里。
如果你直接 this.state.xx == oo
的話,state 的值確實會改,但是改了不會觸發 UI 的更新,那就不是數據驅動了。
那為什么 Vue 直接修改 data 可以觸發 UI 的更新呢?因為 Vue 在創建 UI 的時候會把這些 data 給收集起來,并且在這些 data 的訪問器屬性 setter 進行了重寫,在這個重寫的方法里會去觸發 UI 的更新。如果你想更多的了解 vue 的原理,可以去購買染陌大佬的剖析 Vue.js 內部運行機制。
不明白訪問器屬性的可以看這篇文章:深入理解JS里的對象
1. setState 是同步還是異步?
我的回答是執行過程代碼同步的,只是合成事件和鉤子函數的調用順序在更新之前,導致在合成事件和鉤子函數中沒法立馬拿到更新后的值,形式了所謂的“異步”,所以表現出來有時是同步,有時是“異步”。
2. 何時是同步,何時是異步呢?
只在合成事件和鉤子函數中是“異步”的,在原生事件和 setTimeout/setInterval等原生 API 中都是同步的。簡單的可以理解為被 React 控制的函數里面就會表現出“異步”,反之表現為同步。
3. 那為什么會出現異步的情況呢?
為了做性能優化,將 state 的更新延緩到最后批量合并再去渲染對于應用的性能優化是有極大好處的,如果每次的狀態改變都去重新渲染真實 dom,那么它將帶來巨大的性能消耗。
4. 那如何在表現出異步的函數里可以準確拿到更新后的 state 呢?
通過第二個參數 setState(partialState, callback)
中的 callback 拿到更新后的結果。
或者可以通過給 setState 傳遞函數來表現出同步的情況:
this.setState((state) => { return { val: newVal } })
5. 那表現出異步的原理是怎么樣的呢?
我這里還是用最簡單的語言讓你理解:在 React 的 setState 函數實現中,會根據 isBatchingUpdates(默認是 false) 變量判斷是否直接更新 this.state 還是放到隊列中稍后更新。然后有一個 batchedUpdate 函數,可以修改 isBatchingUpdates 為 true,當 React 調用事件處理函數之前,或者生命周期函數之前就會調用 batchedUpdate 函數,這樣的話,setState 就不會同步更新 this.state,而是放到更新隊列里面后續更新。
這樣你就可以理解為什么原生事件和 setTimeout/setinterval 里面調用 this.state 會同步更新了吧,因為通過這些函數調用的 React 沒辦法去調用 batchedUpdate 函數將 isBatchingUpdates 設置為 true,那么這個時候 setState 的時候默認就是 false,那么就會同步更新。
關于“react使用實例分析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。