React 組件中的 bind(this)示例代碼
React 組件中處理 onClick 類似事件綁定的時(shí)候,是需要顯式給處理器綁定上下文(context)的,這一度使代碼變得冗余和難看。
請(qǐng)看如下的示例:
class App extends Component { constructor() { super(); this.state = { isChecked: false }; } render() { return ( <div className="App"> <label > check me: <input type="checkbox" checked={this.state.isChecked} onChange={this.toggleCheck} /> </label> </div> ); } toggleCheck() { this.setState(currentState => { return { isChecked: !currentState.isChecked }; }); } }
頁(yè)面上放了一個(gè) checkbox 元素,點(diǎn)擊之后切換其選中狀態(tài)。這是很直觀的一段代碼,但并不會(huì)像你想的那樣正常工作。
事件處理器上下文丟失的報(bào)錯(cuò)
因?yàn)?checkbox 的 onChange 事件處理器中,找不到 React 組件的 setState 方法,這說明其執(zhí)行時(shí)的上下文不是該組件,而是別的什么東西,具體我們來調(diào)試下。
調(diào)試查看丟失上下文后 this 的值
出乎意料,是 undefined,這個(gè)方法在一個(gè)完全野生的環(huán)境下執(zhí)行,沒有任何上下文。
WHY
當(dāng)然這并不是 React 的鍋,這是 JavaScript 中 this 的工作原理。具體可參見 Chapter 2: this All Makes Sense Now! 來追溯其底層原因,簡(jiǎn)單來講 this 的值取決于函數(shù)調(diào)用的方式。
默認(rèn)的綁定
function display(){ console.log(this) }
display() // 嚴(yán)格模式下為全局 `window`,非嚴(yán)格模式下為 `undefined`
隱式綁定
通過對(duì)象來調(diào)用,該函數(shù)的上下文被隱式地指定為該對(duì)象。
var obj = { name: 'Nobody', display: function(){ console.log(this.name); } }; obj.display(); // Nobody. 里面取的是 obj 身上的 `name` 屬性
但,如果把該對(duì)象上的方法賦值給其他變量,或通過參數(shù)傳遞的形式,再執(zhí)行,那光景就又不一樣了。
var obj = { name: "Nobody", display: function() { console.log(this.name); } }; var name = "global!"; var outerDisplay = obj.display; outerDisplay(); // global! 這里取到的 `name` 是全局中的內(nèi)個(gè)
這里賦值給 outerDisplay 后再調(diào)用,等同于調(diào)用一個(gè)普通函數(shù),而不是對(duì)象中的那個(gè),所以此時(shí) this 為全局對(duì)象,剛好全局里面有定義一個(gè) name 變量。同樣地,如果是嚴(yán)格模式下,因?yàn)榇藭r(shí) this 為 undefined,所以訪問不到所謂的 undefiend.name,于是會(huì)拋錯(cuò)。
function invoker(fn) { fn(); } setTimeout( obj.display, 1000 ); // global! invoker(obj.display); // global!
這里 setTimeout 調(diào)用的時(shí)候,因?yàn)樗暮灻麑?shí)際上是 setTimeout(fn,delay),所以,可以理解為將 obj.display 賦值給了它的入?yún)?fn,實(shí)際上執(zhí)行的是 fn 而不再是對(duì)象上的方法了。對(duì)于 invoker 函數(shù)也是一樣的道理。
強(qiáng)制綁定
這個(gè)時(shí)候,bind 就成了那個(gè)拯救世界的英雄,任何時(shí)間我們都可以通過它來顯式地指定函數(shù)的執(zhí)行上下文。
var name = “global!”; obj.display = obj.display.bind(obj); var outerDisplay = obj.display; outerDisplay(); // Nobody
bind 將指定的上下文與函數(shù)綁定后返回一個(gè)新的函數(shù),這個(gè)新函數(shù)再拿去賦值或傳參什么的都不會(huì)對(duì)其上下文產(chǎn)生影響了,執(zhí)行時(shí)始終是我們指定的那個(gè)。
現(xiàn)場(chǎng)還原
有了上面的背景,就可以還原文章開頭的問題了,即事件處理器的上下文 丟失的問題。
JSX 中的 HTML 標(biāo)簽本質(zhì)上對(duì)應(yīng) React 中創(chuàng)建該標(biāo)簽的一個(gè)函數(shù)。比如你寫的 div 編譯會(huì)其實(shí)是 React.createElement(‘div')。所以當(dāng)你書寫 <Input> 時(shí)其實(shí)是調(diào)用了 React.createElement 來創(chuàng)建一個(gè) <Input> 標(biāo)簽。
React.createElement( type, [props], [...children] )
標(biāo)簽上的屬性會(huì)作為 props 參數(shù)傳遞給 createElement 函數(shù)。
<Input onChange={this.toggleCheck}>
表示將組件中的 toggleCheck 方法賦值給 createElement 的入?yún)?props(props 是個(gè)對(duì)象,接收所有書寫在標(biāo)簽上的屬性,),實(shí)際調(diào)用的時(shí)候一如上面所說的,調(diào)用的已經(jīng)不是組件中的 toggleCheck 方法了。
React.createElement(type, props){ // 讓我們創(chuàng)建一個(gè) <type> 并在 <type> 的值發(fā)生變化的時(shí)候調(diào)用一下 `props.onChange` ... props.onChange() // 它已經(jīng)不是原來的方法了,丟失了上下文 ... }
因?yàn)?ES6 的 Class 是在嚴(yán)格模式下執(zhí)行的,所以事件處理器中如果使用了 this 那它就是 undefined。
所以你看到 React 官方的示例中,constructor 里有 bind(this) 的語(yǔ)句就不奇怪了,就是為了糾正這個(gè)事件處理器歪了的執(zhí)行上下文。
constructor() { super(); this.state = { isChecked: false }; + this.toggleCheck = this.toggleCheck.bind(this); }
這樣是能正常工作了,但是,這句代碼的存在真的很別扭,因?yàn)椋?br />
•對(duì)于業(yè)務(wù)來說,毫無意義,徒增代碼量
•很丑陋,每加一個(gè)處理器就要加一條這樣的綁定
•冗余,這樣重復(fù)的代碼大量冗余在項(xiàng)目中,在搜索中混淆了原本的方法
避免的方式有很多,就看哪種最對(duì)味。下面來看看如何避免寫這些綁定方法。
#0行內(nèi)的綁定
最簡(jiǎn)單的可以在行內(nèi)進(jìn)行綁定操作,這樣不用單獨(dú)寫一句出來。
<input type="checkbox" checked={this.state.isChecked} - onChange={this.toggleCheck} + onChange={this.toggleCheck.bind(this)} />
#1箭頭函數(shù)
因?yàn)榧^函數(shù)不會(huì)創(chuàng)建新的作用域,其上下文是語(yǔ)義上的(lexically)上下文。所以在綁定事件處理器時(shí),直接使用剪頭函數(shù)是很方便的一種規(guī)避方法。
<input type="checkbox" checked={this.state.isChecked} - onChange={this.toggleCheck} + onChange={() => this.toggleCheck()} />
#2將類的方法改成屬性
如果將這個(gè)處理器作為該組件的一個(gè)屬性,這個(gè)屬性作為事件的處理器以箭頭函數(shù)的形式存在,執(zhí)行的時(shí)候也是能正常獲取到上下文的。
- toggleCheck() { + toggleCheck = () => { this.setState(currentState => { return { isChecked: !currentState.isChecked }; }); }
總結(jié)
React 組件中,其實(shí)跟 React 沒多大關(guān)系,傳遞事件處理器,或方法作為回調(diào)時(shí),其上下文會(huì)丟失。為了修復(fù),我們需要顯式地給這個(gè)方法綁定一下上下文。除了常用的在構(gòu)造器中進(jìn)行外,還可通過箭頭函數(shù),公有屬性等方式來避免冗余的綁定語(yǔ)句。
相關(guān)文章
React?antd中setFieldsValu的簡(jiǎn)便使用示例代碼
form.setFieldsValue是antd?Form組件中的一個(gè)方法,用于動(dòng)態(tài)設(shè)置表單字段的值,它接受一個(gè)對(duì)象作為參數(shù),對(duì)象的鍵是表單字段的名稱,值是要設(shè)置的字段值,這篇文章主要介紹了React?antd中setFieldsValu的簡(jiǎn)便使用,需要的朋友可以參考下2023-08-08在create-react-app中使用css modules的示例代碼
這篇文章主要介紹了在create-react-app中使用css modules的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07React中useEffect 與 useLayoutEffect的區(qū)別
本文主要介紹了React中useEffect與useLayoutEffect的區(qū)別,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07react如何實(shí)現(xiàn)一個(gè)密碼強(qiáng)度檢測(cè)器詳解
這篇文章主要給大家介紹了關(guān)于react如何實(shí)現(xiàn)一個(gè)密碼強(qiáng)度檢測(cè)器的相關(guān)資料,使用這個(gè)密碼強(qiáng)度器后可以幫助大家提高在線帳號(hào)、個(gè)人信息的安全性,需要的朋友可以參考下2021-06-06詳解React中的useMemo和useCallback的區(qū)別
React中的useMemo和useCallback是兩個(gè)重要的Hooks。常常被用于優(yōu)化組件的性能。雖然這兩個(gè)Hooks看起來很相似,但它們彼此之間還是有很大的區(qū)別的,隨著小編一起來學(xué)習(xí)吧2023-04-04react native基于FlatList下拉刷新上拉加載實(shí)現(xiàn)代碼示例
這篇文章主要介紹了react native基于FlatList下拉刷新上拉加載實(shí)現(xiàn)代碼示例2018-09-09