iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 11
0
Modern Web

激戰 ReactJS 30天系列 第 11

【Day11】 發生什麼事 - Events

Events

開發網頁程式會需要很多不同的互動事件(Events)
React 是開發網頁程式的工具之一
所以勢必也可以使用這些 events
今天就來稍微介紹一下 React 中 Events 的用法啦~

SyntheticEvent

React 的事件函式參數event不是本地事件(Native Events)
而是一個叫做合成事件(SyntheticEvent)的物件
它具備跨瀏覽器(cross-browser)以及可重複使用(reusable)的特性
可以重複使用是什麼意思呢?
簡單來說
所有回調函式的event參數(亦可簡寫e)就像是 同一個合成事件參數
React 只使用一個事件監聽器(EventListener)
且沒有把事件處理直接和 DOM 節點連接

合成事件具有下面這些屬性

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type

在回調函式被調用之後
event中的這些屬性都將會失效
因此可以被重複使用
這麼做可以節省記憶體
提升網站效能
除此之外
SyntheticEvent 的這個event參數
若是切換到其他的執行緒
也會找不到原先具備的那些屬性

下面有一段程式碼:

import React from 'react';

class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         clickEvent:"",
         eventType:""
      }
      this.onClick = this.onClick.bind(this);
   };
   onClick(event) {
     console.log(event); // => nullified object.
     console.log(event.type); // => "click"
     const eventType = event.type; // => "click"

     setTimeout(function() {
       console.log(event.type); // => null
       console.log(eventType); // => "click"
     }, 0);

     // Won't work. this.state.clickEvent will only contain null values.
     this.setState({clickEvent: event});

     // You can still export event properties.
     this.setState({eventType: event.type});
   }
   render() {
      return (
         <div>
            <button onClick = {this.onClick}>Click</button>
         </div>
      );
   }
}
export default App;

透過setTimeout函式在另一條執行緒上試著讓他紀錄event的屬性資料
可以看到event一旦換到不同的執行緒
他的屬性資料event.type就會被清除變成null
只有透過變數eventType額外儲存的屬性資料才被保留下來
如果出現需要保留這些屬性資料的情況
可以在回調函式中呼叫event.persist()
透過event.persist()就可以讓合成事件(synthetic event)脫離事件池(pool)
並且讓使用者的程式碼可以對其進行存取

這是官方文件中的一段程式碼
執行結果:

這段程式碼說明了合成事件重複使用的特性
也可以在onClick這個回調函式中呼叫event.persist()執行看看
會發現原本消失的資料又重新回到眼前了
SyntheticEvent 是 React 在事件處理上很不一樣的一個地方

Handling Events

除了上面事件參數是 React 特有的合成參數以外
事件處理還有幾個比較不一樣的地方
下面我們用這段程式碼來說明:

import React from 'react';

class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         value: 0,
      }
      this.updateState = this.updateState.bind(this);
   };
   updateState(event) {
      var score = this.state.value;
      score += 1; 
      this.setState({value: score});
   }
   render() {
      return (
         <div>
            <button onClick = {this.updateState}>Add</button>
            <h4>{this.state.value}</h4>
         </div>
      );
   }
}
export default App;

事件監聽

在我們習慣的網頁程式中
我們會對 DOM 物件加上監聽器(EventListener)來讓網頁元素產生反應
但是在 React 我們不用對每個網頁元素這麼做
因為 React 採用單一事件處理器
所以可以在render的時候直接加入事件處理:

render() {
   return (
      <div>
         <button onClick = {this.updateState}>Add</button>
      </div>
   );
}

寫法上和 html 很像
但是有一些些的不一樣
在 html 上的寫法是這樣:

<button onclick = "updateState()">Add</button>

但是在 React 的寫法則是:

<button onClick = {this.updateState}>Add</button>

React 的事件(onclick)要使用駝峰式寫法來表示而不是小寫字母
事件觸發也不是直接給它事件函式
取而代之的是給他一個函式名稱的字串

事件無效化

在 html 我們可以透過return false來讓事件失效

<button onclick = "console.log("clicked"); return false">Add</button>

但是在 React 這樣的寫法是無效的
想要讓事件不被觸發
要用這樣的寫法:

   updateState(event) {
      event.preventDefault(); // 讓事件無效化
      var score = this.state.value;
      score += 1; 
      this.setState({value: score});
   }

官方文件表示 React 要在對應的回調函式中呼叫event.preventDefault()
這裡的event也是上面所提過的合成事件
不過這個函式貌似目前有些問題(?
實際使用我發現它並不能真的阻止事件觸發
我去四處追尋答案並自己實驗後

   updateState(event) {
      // event.preventDefault();                    // 無效
      // event.stopPropagation();                   // 無效
      // e.nativeEvent.stopImmediatePropagation();  // 無效
      // return false;                              // 有效
      var score = this.state.value;
      score += 1; 
      this.setState({value: score});
   }

上面的寫法都有人說
但是裡面只有return false成功讓事件不被觸發
甚至有人回報這是 React 的 bug
不過
通常網頁開發要阻止事件觸發的方式有非常多種
也可以透過標籤屬性(tag attribute)或樣式表(style list)來達到目的
這邊就讓我暫時掛一個問號吧!

函式綁定

在 React 中
回調函式有一個必須要注意的地方
那就是 函式綁定

React 中會用到非常多的this
其中函式回調(callback)時
若是沒有事先綁定好
JSX 回調的this就會變成未定義
導致錯誤而沒有辦法正常運作
綁定的寫法是這樣:

constructor(props) {
   super(props);
   this.state = {
      value: 0,
   }
   // 綁定函式寫法
   this.updateState = this.updateState.bind(this);
};

如果無論如何都不想綁定這個函式
可以在呼叫函式的地方使用箭頭函數(=>)
這個函數可以綁定this變數
解決this未定義的情況

import React from 'react';
class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         value: 0
      }
   };
   updateState(e) {
      var score = this.state.value;
      score += 1; 
      this.setState({value: score});
   }
   render() {
      return (
         <div>
            <button onClick = {(e) => this.updateState(e)}>Add</button>
            <h4>{this.state.value}</h4>
         </div>
      );
   }
}
export default App;

還有一個官方提供的解決辦法是

updateState = () => {
      var score = this.state.value;
      score += 1; 
      this.setState({value: score});
}
...
render() {
   return (
      <div>
         <button onClick = {this.updateState}>Add</button>
         <h4>{this.state.value}</h4>
      </div>
   );
}

不過官方表示這是個試驗性的寫法
具備使用實驗性的public class fields syntax條件
才可以透過這個辦法解決回調this未定義的問題

不得不說事件這裡可能是第一個碰到比較難以理解的部分
不僅用法不同
想從根本理解所以嘗試追朔原因
有些地方還是會讓人無法釐清
不過至少透過實際寫code並且執行學會了怎麼使用
歡迎大家提出不一樣的看法來討論 =D

如果想知道有哪些事件可以使用
可以去 這個連結 看看
這是官方提供事件的支援清單

參考資料

  1. tutorialspoint-ReactJS Tutorial
  2. React 官方文件
  3. React中文 - 處理事件

>>> 隊友任意門 <<<


Day11 end
by 瑞Ray (๑´ㅂ`๑)


上一篇
【Day10】 三個願望一次滿足 - 組件API
下一篇
【Day12】 Rendering
系列文
激戰 ReactJS 30天31

1 則留言

0
ShawnGood
iT邦新手 5 級 ‧ 2019-02-14 13:00:48

event.stopPropagation()應該是阻止接下來有其他handler去繼續使用這個event

f(e) {
    e.stopPropagation();// 不加這個的話,點擊b2就會執行2次 f()
    console.log(e.currentTarget);
}
<div onClick={this.f.bind(this)}>
  b1
  <button onClick={this.f.bind(this)}>b2</button>
</div>

我之前也遇過一個需要使用event.preventDefault()的情況
https://stackoverflow.com/questions/50230048/react-ondrop-is-not-firing

洪啟瑞 iT邦新手 5 級 ‧ 2019-02-19 16:37:26 檢舉

喔喔喔 我明白了
因為不是終止執行,而是避免重複執行,所以才會有失效的感覺。
// PS. 剛剛查了一下函式名稱 Propogation 是「傳播」的意思,「停止他的傳遞」換句話說就是確保唯一,英文不是那麼好 =P。
長知識了 感謝大大分享!

ShawnGood iT邦新手 5 級 ‧ 2019-02-23 10:38:51 檢舉

ㄟ只是分享一下經驗值,我也是javascript菜雞1隻 :)

洪啟瑞 iT邦新手 5 級 ‧ 2019-02-24 21:49:16 檢舉

OK 菜雞互相扶持走向大大之路XD

我要留言

立即登入留言