iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 7
3
Modern Web

React 30天系列 第 7

Day 07-來點互動吧(Handling Events)

前情提要:
昨天講了component複雜的生命週期,其實主要概念只有可以在componentDidMount、
componentDidUpdate和componentWillUnmount做一些非同步處理和事件監聽行為(如:resize或scroll)。

今天我們就來點互動吧!
https://media.giphy.com/media/bTzFnjHPuVvva/giphy.gif
說是這麼說,但其實昨天的codepen範例和前天的計數器,我都默默偷渡click event了。ˊ_>ˋ
不過如果不用event去更改state,那怎麼介紹state更新還有生命週期的變換,筆述說明的話也太無趣了吧!

在知道使用方式後,我們今天就來了解還有哪些細節需要注意吧!
使用React element處理事件(event)和在DOM element處理event的行為其實是非常相似的(難怪用起來沒有什麼不適感(?!),以下列出差異:

  • React events使用camelCase命名,不是lowercase。如:
    • onclick 需轉為 onClick
    • onchange需轉為 onChange
  • 使用JSX,我們可以傳遞function作為事件處理,而不是使用string
    舉例來說:
    在HTML我們會這麼用:
    <button onclick="handleClick()">
      Activate Lasers
    </button>
    
    在React的細微差異
    <button onClick={handleClick}>
      Activate Lasers
    </button>
    
  • 不能在react使用return false來避免默認行為,需要明確的調用preventDefault()
    一般HTML使用狀況
    <a href="#" onclick="console.log('The link was clicked.'); return false">
      Click me
    </a>
    
    React使用情況
    function ActionLink() {
      function handleClick(e) {
        e.preventDefault();
        console.log('The link was clicked.');
      }
    
      return (
        <a href="#" onClick={handleClick}>
          Click me
        </a>
      );
    }
    
    在上述的例子裡,e是一個合成事件(synthetic event)。React根據W3C規範定義了這些合成事件(synthetic event),所以我們不用擔心跨瀏覽器相容性。(想知道更多嗎?左轉SyntheticEvent)。

接著是處理event的method綁定(bind)this的二三事。等等,這個不也是前天的內容嗎?果然component一家親,圈子很小相見容易。我們再來複習一下有哪些綁定方法吧! ╮( ̄▽ ̄"")╭
Bind in Constructor (ES2015)

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

Class Properties (Stage 3 Proposal)

class Foo extends Component {
  // Note: 此語法還是實驗性,尚未標準化
  handleClick = () => {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

以下兩種bind方法會在每次render的時候就建立一個新的function,有效能上的影響
Bind in Render

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
  }
}

Arrow Function in Render

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={() => this.handleClick()}>Click Me</button>;
  }
}

傳遞額外的參數到事件處理器
接著,情境題來了。如果我有一個待辦事項列表(To-Do List),每項資料都有button讓我刪除該筆資料,我想透過id刪掉該筆資料,該怎麼做?
雖然在上面的幾種方法有提到如果在render method內bind this,會在每次re-render的時候都建立一個新function,但如果我們想要多傳其他資料給function內使用,也只能仰賴它們了,實際用法如下:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

那我們來練習用這個方法刪除清單上的項目吧!

試做一個項目可刪除的List Component

執行步驟如下:

  1. 使用JSON GENERATOR產生假資料,這邊我用到id, name, 和greeting,產生的資料長得像這樣:
    https://ithelp.ithome.com.tw/upload/images/20181014/20111595v3JLlCZV3C.png
  2. 打開開發環境,在目錄資料夾新增一個名為data.json的檔案,把假資料放進來
    https://ithelp.ithome.com.tw/upload/images/20181014/201115951tgBACU4TT.png
  3. 在src/components新增list.js檔案,接著:
    • 建立class component
    • 初始化state,把data匯入至state備用
    • 使用map來取得array內的每一個元素內容,並整理成我們所需的react element返回,這邊可能會遇到console的警告訊息Warning: Each child in an array or iterator should have a unique "key" prop.,提醒我們array內的每個react element都需要設定key
    • 加上click事件,使用arrow function綁定this,在function內呼叫deleteItem method並傳入id
    • 使用filer濾掉該筆資料,並透過setState更新state資料
    • export List component
import React, { Component } from "react";
import data from "../../data";

class List extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data
    };
  }

  deleteItem(id) {
    this.setState({
      data: this.state.data.filter(data => data.id !== id)
    });
  }

  render() {
    return this.state.data.map(({ id, greeting }) => (
      <li key={id}>
        {greeting}
        <button onClick={() => this.deleteItem(id)}>刪除</button>
      </li>
    ));
  }
}

export default List;
  1. 在index.js import List component
import React from "react";
import ReactDOM from "react-dom";

import List from "./src/components/list";

const App = () => {
  return (
    <ul>
      <List />
    </ul>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));

在terminal上執行yarn start
操作畫面如下:
https://i.imgur.com/CGXaeR2.gif
完整程式碼請參考github


總結今日:

  • React events使用camelCase命名
  • 使用JSX,我們可以傳遞function作為事件處理
  • 不能在react使用return false來避免默認行為,需要明確的調用preventDefault()
  • event method綁定(bind)this的各種方法複習
  • 傳遞額外參數到事件處理器(event handlers)的使用方法

本日完結,大家週末愉快!


上一篇
Day 06-生命有限好好把握(Lifecycle)
下一篇
Day 08-淚水交織的表單(Form)
系列文
React 30天30

1 則留言

0
神Q超人
iT邦新手 2 級 ‧ 2018-10-14 20:22:03

果然component一家親,圈子很小相見容易。

太認同這句話了,每次寫寫都會覺得一直在講差不多的東西/images/emoticon/emoticon37.gif

Yvonne iT邦新手 5 級‧ 2018-10-15 00:52:02 檢舉

哈哈!常常寫一寫寫到自我迷失(欸 /images/emoticon/emoticon56.gif
一起加油!

我要留言

立即登入留言