規避 React 組件中的 bind(this)

来源:https://www.cnblogs.com/Wayou/archive/2018/09/15/get_rid_of_bind_within_react_component.html
-Advertisement-
Play Games

React 組件中處理 onClick 類似事件綁定的時候,是需要顯式給處理器綁定上下文(context)的,這一度使代碼變得冗餘和難看。 請看如下的示例: class App extends Component { constructor() { super(); this.state = { i ...


React 組件中處理 onClick 類似事件綁定的時候,是需要顯式給處理器綁定上下文(context)的,這一度使代碼變得冗餘和難看。

請看如下的示例:

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
      };
    });
  }
}

頁面上放了一個 checkbox 元素,點擊之後切換其選中狀態。這是很直觀的一段代碼,但並不會像你想的那樣正常工作。

事件處理器上下文丟失的報錯
事件處理器上下文丟失的報錯

因為 checkboxonChange 事件處理器中,找不到 React 組件的 setState 方法,這說明其執行時的上下文不是該組件,而是別的什麼東西,具體我們來調試下。

調試查看丟失上下文後 this 的值
調試查看丟失上下文後 this 的值

出乎意料,是 undefined,這個方法在一個完全野生的環境下執行,沒有任何上下文。

WHY

當然這並不是 React 的鍋,這是 JavaScript 中 this 的工作原理。具體可參見 Chapter 2: this All Makes Sense Now! 來追溯其底層原因,簡單來講 this 的值取決於函數調用的方式。

預設的綁定

function display(){
  console.log(this)
}

display() // 嚴格模式下為全局 `window`,非嚴格模式下為 `undefined`

隱式綁定

通過對象來調用,該函數的上下文被隱式地指定為該對象。

var obj = {
  name: 'Nobody',
  display: function(){
    console.log(this.name);
   }
 };
 obj.display(); // Nobody. 裡面取的是 obj 身上的 `name` 屬性 

但,如果把該對象上的方法賦值給其他變數,或通過參數傳遞的形式,再執行,那光景就又不一樣了。

var obj = {
  name: "Nobody",
  display: function() {
    console.log(this.name);
  }
};

var name = "global!";
var outerDisplay = obj.display;
outerDisplay(); // global! 這裡取到的 `name` 是全局中的內個

這裡賦值給 outerDisplay 後再調用,等同於調用一個普通函數,而不是對象中的那個,所以此時 this 為全局對象,剛好全局裡面有定義一個 name 變數。同樣地,如果是嚴格模式下,因為此時 thisundefined,所以訪問不到所謂的 undefiend.name,於是會拋錯。

function invoker(fn) {
  fn();
}

setTimeout( obj.display, 1000 ); // global!
invoker(obj.display); // global!

這裡 setTimeout 調用的時候,因為它的簽名實際上是 setTimeout(fn,delay),所以,可以理解為將 obj.display 賦值給了它的入參 fn,實際上執行的是 fn 而不再是對象上的方法了。對於 invoker 函數也是一樣的道理。

強制綁定

這個時候,bind 就成了那個拯救世界的英雄,任何時間我們都可以通過它來顯式地指定函數的執行上下文。

var name =global!”;
obj.display = obj.display.bind(obj); 
var outerDisplay = obj.display;
outerDisplay(); // Nobody

bind 將指定的上下文與函數綁定後返回一個新的函數,這個新函數再拿去賦值或傳參什麼的都不會對其上下文產生影響了,執行時始終是我們指定的那個。

現場還原

有了上面的背景,就可以還原文章開頭的問題了,即事件處理器的上下文 丟失的問題。

JSX 中的 HTML 標簽本質上對應 React 中創建該標簽的一個函數。比如你寫的 div 編譯會其實是 React.createElement(‘div’)。所以當你書寫 <Input> 時其實是調用了 React.createElement 來創建一個 <Input> 標簽。

React.createElement(
  type,
  [props],
  [...children]
)

標簽上的屬性會作為 props 參數傳遞給 createElement 函數。

<Input onChange={this.toggleCheck}> 表示將組件中的 toggleCheck 方法賦值給 createElement 的入參 propsprops 是個對象,接收所有書寫在標簽上的屬性,),實際調用的時候一如上面所說的,調用的已經不是組件中的 toggleCheck 方法了。

React.createElement(type, props){
  // 讓我們創建一個 <type> 併在 <type> 的值發生變化的時候調用一下 `props.onChange`
  ...
  props.onChange() // 它已經不是原來的方法了,丟失了上下文
  ...
}

因為 ES6 的 Class 是在嚴格模式下執行的,所以事件處理器中如果使用了 this 那它就是 undefined

所以你看到 React 官方的示例中,constructor 里有 bind(this) 的語句就不奇怪了,就是為了糾正這個事件處理器歪了的執行上下文。

  constructor() {
    super();
    this.state = {
      isChecked: false
    };
+  this.toggleCheck = this.toggleCheck.bind(this);
  }

這樣是能正常工作了,但是,這句代碼的存在真的很彆扭,因為,

  • 對於業務來說,毫無意義,徒增代碼量
  • 很醜陋,每加一個處理器就要加一條這樣的綁定
  • 冗餘,這樣重覆的代碼大量冗餘在項目中,在搜索中混淆了原本的方法

避免的方式有很多,就看哪種最對味。下麵來看看如何避免寫這些綁定方法。

#0行內的綁定

最簡單的可以在行內進行綁定操作,這樣不用單獨寫一句出來。

   <input
            type="checkbox"
            checked={this.state.isChecked}
-             onChange={this.toggleCheck}
+            onChange={this.toggleCheck.bind(this)}
      />

#1箭頭函數

因為箭頭函數不會創建新的作用域,其上下文是語義上的(lexically)上下文。所以在綁定事件處理器時,直接使用剪頭函數是很方便的一種規避方法。

          <input
            type="checkbox"
            checked={this.state.isChecked}
-             onChange={this.toggleCheck}
+            onChange={() => this.toggleCheck()}
          />

#2將類的方法改成屬性

如果將這個處理器作為該組件的一個屬性,這個屬性作為事件的處理器以箭頭函數的形式存在,執行的時候也是能正常獲取到上下文的。

-  toggleCheck() {
+  toggleCheck = () => {
    this.setState(currentState => {
      return {
        isChecked: !currentState.isChecked
      };
    });
  }

總結

React 組件中,其實跟 React 沒多大關係,傳遞事件處理器,或方法作為回調時,其上下文會丟失。為了修複,我們需要顯式地給這個方法綁定一下上下文。除了常用的在構造器中進行外,還可通過箭頭函數,公有屬性等方式來避免冗餘的綁定語句。

相關資源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡介紹一個可以快速創建 React UI 組件庫使用、演示文檔的項目:Docz ...
  • 下麵是在實現瀑布流中的一小段代碼(當滾動條滾動距離+可視區高度>文檔高度,請求數據) 下麵複習一下知識點: 一、查看滾動條的滾動距離 js中有兩套方法可以查看滾動條的滾動距離 (1)window.pageXOffset/window.pageYOffset 查看滾動條橫軸和縱軸的滾動距離;但IE8以 ...
  • 局部變數前面要加var 如 var name = "jiahuai" 全局變數 name = "jiahuai" 寫完每一行JavaScript代碼用;號隔開 註釋: 單行 // 多行 /* */ ...
  • 如果當前路徑是迴圈的,或者包含多個相交的子路徑,那麼Canvas的繪圖環境變數就必須判斷,當fill()方法被調用時,應該如何對當前路徑進行填充。 Canvas在填充互相有交叉的路徑時,使用 非零環繞規則 非零環繞 對於路徑中的任意給定區域,從該區域內部畫一條足夠長的線段,使此線段的終點完全落在路徑 ...
  • 實現效果:楊輝三角 即: 在這裡我將用到js中數組的知識來完成,我將用二維數組來儲存這個序列,其中外層數組儲存所有的值,裡層數組將儲存每一行的值。 我的思路是: 1.獲取用戶輸入要的行數。 2.創建二維數組併進行計算,優先計算出所需要的數值,並按行儲存,輸出之後效果如下: 3.對以上的數據,按照楊輝 ...
  • 《一統江湖的大前端》系列是自己的前端學習筆記,旨在介紹javascript在非網頁開發領域的應用案例和發現各類好玩的js庫,不定期更新。如果你對前端的理解還是寫寫頁面綁綁事件,那你真的是有點OUT了,前端能做的事情已經太多了, , , , , , 甚至 ,什麼火就搞什麼,活脫脫一個 蹭熱點小能手 。 ...
  • 這是第一次使用html寫一個簡單的註冊表 <!DOCTYPE html><html><head> <title>木木音樂網第一次註冊表</title></head><body><h2>使用手機號碼註冊</h2><form action="" method="get"> <p>手機號碼: <input ...
  • 今天在使用scroll-view組件的時候發現結果跟預想的不一樣。其實也不是第一次用了,同樣的寫法卻出現了意料之外的效果,所以認定是bug了。博主使用的是2.3.0版本,所以之前的版本應該也會有這個bug。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...