React 【事件處理】React事件和DOM事件、this關鍵字的處理、向事件處理程式傳遞引數、向父元件傳遞引數、React事件機制
目錄:
3. 事件處理如何傳遞引數
4. 向父元件傳遞引數
5. React 事件機制
React 事件其實跟 DOM 事件一樣,遵循 JS 事件模型,可以通過回撥函式去獲取事件物件,只是在 JS 語法方面與 DOM 事件有些不同。
例 子
初始檔案(只關注 listItem.jsx 即可 ):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <title>React App</title> </head> <body> <div id="root"></index.htmldiv> </body> </html>
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } .themed-grid-col { padding-top: 15px; padding-bottom: 15px; background-color: rgba(255, 255, 255); border: 1px solid rgba(86, 61, 124, 0.2); } .themed-grid-col-s { padding-top: 15px; padding-bottom: 15px; background-color: rgba(86, 61, 124, 0.2); border: 1px solid rgba(86, 61, 124, 0.2); }index.css(樣式檔案)
.common{
text-decoration: underline;
text-indent: 2em;
}
common.module.css(樣式檔案)
.title{
composes: common from 'common.module.css';
color: red;
font-size: 20px;
}
.list-title{
color: blue;
font-weight: bold;
}
.digital{
border-radius: 5px;
margin: 0 20px;
padding: 10px;
border: #eaeaea solid 1px;
background-color: #fff;
box-shadow: 0 0 4px rgba(100, 100, 100, .5);
}
listItem.module.css(樣式檔案)
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css' import App from './App'; import 'bootstrap/dist/css/bootstrap.css'; ReactDOM.render( <App />, document.getElementById('root'));index.js(React入口檔案)
import React, { Component } from 'react'; import ListItem from './components/listItem' const listData = [ { id: 1, name: '紅蘋果', price: 2 }, { id: 2, name: '青蘋果', price: 3 }, ] class App extends Component { renderList(){ return listData.map( item => { return <ListItem key={item.id} data={ item }/> }) } render() { return( <div className="container"> { listData.length === 0 && <div className="text-center">購物車是空的</div> } { this.renderList() } </div> ) } } export default App;App.js(listItem.jsx的父元件)
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:
修改 listItem.jsx,在 listItem.jsx 中新增事件。所有的事件命名都遵循小駝峰命名法。
在 “-” 按鈕上新增點選事件,表示式處呼叫事件回撥函式 handleDecrease。
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { handleDecrease(){ console.log('---') } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:
點選 “-” 按鈕後,呼叫了 handleDecrease 方法,控制檯輸出了 “ --- ”。
可以給事件回撥函式 handleDecrese 傳入引數,引數是事件物件。
頁面表現:
點選 “-” 按鈕後,呼叫了 handleDecrease 方法,控制檯輸出了 “ --- ”跟事件物件。
點開事件物件看看,可以看到很多引數。所以,我們可以通過在事件回撥函式裡使用事件物件獲取當前事件需要的一些引數,比如螢幕點選的位置、當前繫結物件的位置等。
在寫法上,React 事件採用駝峰命名法,原生事件採用小寫寫法。
在 React 事件中,不需要新增 “()” 執行事件,而原生事件需要新增 “()”。
React 事件使用 preventDefault 阻止預設行為,在原生事件中,可以直接使用 “javascript:;” 返回空值去阻止事件,而在 React 事件中不能這樣做。
可以使用 3 種方法處理 this 關鍵字:1. 在 JSX 中使用 bind 方法;2. 在建構函式中使用 bind 方法;3. 使用箭頭函式。
先舉個例子演示使用 this 關鍵字可能出現問題:
在 “-” 按鈕上新增點選事件,點選該按鈕之後呼叫事件回撥函式 handleDecrease,執行 handleDecrease 之後 count 減 1 ,呼叫執行 this.doSomethingWithCount()。
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease(){ count --; this.doSomethingWithCount(); } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:
點選 “-” 按鈕之後報錯。提示 doSomethingWithCount 是 undefined 狀態。
說明:
在 JS 中,this 的使用方法會隨著引用物件的差別而不同。下面舉幾個例子:
下面舉例說明怎麼解決 this 關鍵字的問題。
在 button 元素上新增點選事件的時候使用 bind 方法,this 作為引數傳入,也就是將 render 作用域的 this 傳遞進去,這樣的話,在 handleDecrease 方法裡面就可以使用 this 關鍵字了。
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease(){ console.log(this); this.doSomethingWithCount(); } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.handleDecrease.bind(this) } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
點選“-”按鈕後,可以看到 this 被正確地定義了,this 指向的是 ListItem 這個物件。
顯示地定義建構函式,傳入 props 作為引數,呼叫 super 方法傳入 props。一定要在呼叫了 super 方法後才使用 this,使用 bind 方法繫結 this 關鍵字後會返回一個新的函式例項,將新的函式例項賦給 this.handleDecrease,這就完成了 this 關鍵字的繫結。
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { constructor( props ){ super( props ) this.handleDecrease = this.handleDecrease.bind(this) } doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease(){ console.log(this); this.doSomethingWithCount(); } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:同上
箭頭函式的 this 總是定義時所在的物件
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease = () => { console.log(this); this.doSomethingWithCount(); } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:同上
對比方法1、2、3
建議使用方法 3 。之所以不推薦方法1、2,是因為使用方法1、2會在 JSX 元素中寫很多 bind 或者在 contructor 中寫很多 bind,如果這個元件或者 app 有很多事件要處理的話,使用方法1、2就不優雅。
例 子
使用幾種方法,實現當點選 “-” 按鈕時,將當前商品的 id 傳遞給事件處理函式。
bind 的第一個引數是 this,第二個引數是需要傳遞的引數 this.props.data.id。在事件處理函式傳入 id 引數,打印出 id。
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease = (id) => { count --; this.doSomethingWithCount(); console.log( "id:", id ); } handleIncrease = () => { count ++; } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.handleDecrease.bind(this, this.props.data.id) } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
定義一個箭頭函式 doHandle 去呼叫事件處理函式 handleDecrease,在 handleDecrease 傳入需要的引數 id。在button 元素上的點選事件呼叫箭頭函式 doHandle。
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease = (id) => { count --; this.doSomethingWithCount(); console.log( "id:", id ); } handleIncrease = () => { count ++; } doHandle = () => { this.handleDecrease( this.props.data.id ) } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.doHandle } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:同上
在事件表示式中直接定義匿名函式,傳遞需要的引數。更推薦使用這種方式。
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease = (id) => { count --; this.doSomethingWithCount(); console.log( "id:", id ); } handleIncrease = () => { count ++; } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ () => {this.handleDecrease( this.props.data.id )} } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:同上
在方法 1 中不需要顯式定義事件物件,但是,使用方法 2 在匿名箭頭函式裡面如果需要使用事件物件的話,必須要顯式定義事件物件。
例子:使用方法 2 時使用事件物件需要顯式定義
如果需要在 handleDecrease 中使用事件處理函式,需要在事件處理函式 handleDecrease 跟 在 button 元素新增事件的時候都顯示定義事件物件 ev。
沒有顯式呼叫事件程式時:
在事件處理程式輸出事件物件 ev,如果沒有顯式定義事件物件,觸發點選事件的時候輸出的 ev 是 undefined。
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease = (id, ev) => { count --; this.doSomethingWithCount(); console.log( "id:", id ); console.log( "ev:", ev ); } handleIncrease = () => { count ++; } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ () => {this.handleDecrease( this.props.data.id )} } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:
顯式呼叫事件程式時:
給匿名函式傳入事件物件 ev ,並給事件處理程式傳入事件物件 ev。
例子:使用方法 1 時使用事件物件不需要顯式定義
無需顯式定義事件物件
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease = (id, ev) => { count --; this.doSomethingWithCount(); console.log( "id:", id ); console.log( ev ); } handleIncrease = () => { count ++; } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.handleDecrease.bind(this, this.props.data.id) } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:同上
向父元件傳遞引數分為 2 步:
1. 在父元件定義好事件處理函式,並通過 props 向子元件傳遞
2. 在子元件 react 元素上,繫結 props 傳入的函式並帶入引數
例 子
實現點選 “刪除” 按鈕時,將商品 id 傳給父元件。
從父元件傳給子元件一個方法,讓子元件來呼叫這個方法,並將引數傳遞到這個方法裡,這樣,就可以在父元件得到子元件傳遞過來的引數了。
在父元件 App.js 裡面定義一個 handleDelete 箭頭函式,將子元件 listItem.jsx 傳遞過來的 id 值打印出來。
import React, { Component } from 'react'; import ListItem from './components/listItem' const listData = [ { id: 1, name: '紅蘋果', price: 2 }, { id: 2, name: '青蘋果', price: 3 }, ] class App extends Component { renderList(){ return listData.map( item => { return <ListItem key={item.id} data={ item } onDelete={this.handleDelete}/> }) } handleDelete = (id) => { console.log( 'id:', id ); } render() { return( <div className="container"> { listData.length === 0 && <div className="text-center">購物車是空的</div> } { this.renderList() } </div> ) } } export default App;App.js
import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); class ListItem extends Component { render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col"> <button onClick={()=>{this.props.onDelete(this.props.data.id)}} className="btn btn-danger btn-sm" type="button" >刪除 </button> </div> </div> ); } } export default ListItem;listItem.jsx
頁面表現:
點選任一 “刪除” 按鈕,在控制檯輸出相應的商品 id 。
(事件流相關筆記:https://www.cnblogs.com/xiaoxuStudy/p/13126722.html#three )
在 JS 中,"DOM2級事件" 規定的事件流包括3個階段:1. 事件捕獲階段 ;2. 處於目標階段 ;3. 事件冒泡階段。
如下圖,假設在 text 中觸發了 click 事件,首先是事件捕獲階段,事件從父級元素傳播到發生事件的元素,事件按 window->document->body 順序傳播,然後是處於目標階段,執行事件,然後處於事件冒泡階段,事件從子元素向父元素傳播。
因為 DOM 從頁面中接收事件的順序,行為委託成為了可能。通俗地將,行為委託的實質就是將子元素的事件處理委託給父級元素進行處理。
React 會將所有事件都繫結在 document 上而不是某一元素上,統一地使用事件監聽,並在冒泡階段處理事件。所以,當掛載或者解除安裝元件的時候,只需要在統一的事件監聽位置增加或刪除物件。因此,會極大地提高效率。當事件觸發的時候,元件會生成一個合成事件,然後傳遞到 document 中,document 會通過 Dispatch Event 回撥函式依次執行 Dispatch Listener 中同類型事件的監聽函式,事件註冊在元件生成的時候將 Virtual DOM 中所有事件對應的原生事件都註冊在 document 當中的一個監聽器當中,也就是所有的事件處理函式都存放在 ListenerBank 中,並以 key 作為索引,這樣的好處就是將可能要觸發的事件分門別類。
(React 機制抽象圖)
1. React 事件是合成事件,不是 DOM 原生事件
2. React 在 document 監聽所有支援事件
3. 使用統一的分發函式 dispatchEvent 去指定事件函式執行