react的事件處理機制基本用法和DOM類似,但還是有一定的區別。它的事件類型采用駝峰命名,直接將函數的聲明當成事件進行傳遞,是一個合成事件。如下所示:onClick={this.add} 這里的事件要加上on且Click首字母大寫,this.add需要被花括號包裹起來。
//html
<button onclick="add()">add</button>
//react
<button onClick={this.add}>add</button>
一、確定this指向
在react事件處理中尤為要注意this的指向問題,jsx中類的實例方法默認沒有綁定this,如果在調用時忘記綁定,那this的值就為undefined。在js中函數的作用域是由函數調用時決定的,而不是由函數聲明時決定的。第一次調用是作為對象中的函數調用,this是對象本身。第二次調用只是傳遞了一個函數名給變量並在變量后加上括號執行這個方法,這時它只是作為普通函數調用,this指向widow,在嚴格模式下this為undefined。
let obj = { name: 'davina', fn: function () { console.log(this.name); } } obj.fn();//davina
let newfn = obj.fn; newfn();//undefined
我們可以通過下面的例子看到,在綁定事件時,只是簡單的把當前的函數體給了onClick這個屬性,真正調用的話是onClick()而不是this.add()。
class App extends React.Component { state = { count: 100 } add() { console.log(this);//undefined //......
} render() { return <button onClick={this.add}>add</button> } } ReactDOM.render(<App />, document.getElementById('root'))
為了解決這個問題,我們需要將this的指向確定為這個實例本身,可以有以下4種方法:
方法一利用了屬性初始化語法,將方法初始化為箭頭函數,在創建時就綁定了this,不需要在類構造函數中再進行綁定。非常的方便,建議使用。
方法二三中,都是調用時再綁,每一次調用都會生成一個新的方法實例,如果這個回調函數作為一個屬性值傳入到低階組件時,這些組件會再次渲染,因為每一次都是新的方法實例作為新的屬性傳遞,如果要用到bind方法可以用方法四,在構造函數中用bind綁定this來避免性能不必要的消耗。
方法四在類的構造函數中綁定了this,調用時不需要再進行綁定,只會生成一個方法實例,綁定后多個地方可以使用。
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); // 方法四:在構造函數中用bind綁定this this.add = this.add.bind(this) this.state={ count:100 } }; // 方法一:使用屬性初始化語法綁定this add1 = () => { console.log(this); this.setState({ count: this.state.count + 1 }) } //方法二&三&四 add() { console.log(this); this.setState({ count: this.state.count + 1 }) } render() { let { count } = this.state; return <> {/* 方法一:使用屬性初始化器語法綁定this,this指向當前實例 */} <button onClick={this.add1}>方法一</button> {/* 方法二:在調用時用箭頭函數,箭頭函數中的this是上級的this,上級為render,render中的this是組件 */} <button onClick={() => this.add()}>方法二</button> {/* 方法三:調用時用bind綁定this, */} <button onClick={this.add.bind(this)}>方法三</button> {/* 方法四:在構造函數中用bind綁定this */} <button onClick={this.add}>方法四</button> <h4>當前數字是:{count}</h4> </> } } ReactDOM.render(<App />, document.getElementById('root'))
二、參數傳遞
我們可以給事件處理程序傳遞額外的參數,來進行操作。
import React from 'react' import ReactDOM from 'react-dom'
class App extends React.Component { state = {count: 100} minus(x, e, y) {//注意參數和e的位置,參數在前,e在后
console.log(e.nativeEvent, e.target) this.setState({ count: this.state.count - x + y }) } minus2(n, m, e) { console.log(e.nativeEvent, e.target) this.setState({ count: this.state.count - n + m }); } render() { let { count } = this.state; return <>
<button onClick={(e) => this.minus(10, e, 5)}>傳參一</button> {/* 使用bind時,事件對象e隱式傳遞 */} <button onClick={this.minus2.bind(this, 10, 5)}>傳參二</button>
<h4>當前數字是:{count}</h4>
</> } } ReactDOM.render(<App />, document.getElementById('root'))
三、鍵盤事件和自定義屬性
import React from 'react' import ReactDOM from 'react-dom'
class App extends React.Component { userKeyCode = (e) => { let keycode = e.which || e.keycode; console.log(keycode); //給input設置自定義屬性
let el = e.target || e.srcElement; console.log(el.getAttribute('my-color')) } render() { return( <>
<input type="text" onKeyPress={this.userKeyCode} my-color="red" />
</>) } } ReactDOM.render(<App />, document.getElementById('root'))
有時UI界面的內容會根據不同的情況顯示不同的內容,或者是決定是否渲染某部分內容,在vue中我們可以通過v-if/v-sow來控制,但在react中我們所有的條件判斷都是和普通js代碼是一致的。
// 需求:點擊按鈕讓其在二者中不斷的進行切換
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); this.state = { isLogin: true } }; render() { let { isLogin } = this.state; let welcome = null; if (isLogin) { welcome = <h3>歡迎回來~~</h3> } else { welcome = <h3>請先登錄~~</h3> } return <> {welcome} <button onClick={e => this.loginClick() }>{isLogin ? '退出' : '登錄'}</button>
</> } loginClick() { this.setState({ isLogin: !this.state.isLogin }) } } ReactDOM.render(<App />, document.getElementById('root'))
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props) this.state = { isLogin: true } } render() { return ( <div>
<button onClick={e => { this.loginClick() }}> {this.state.isLogin ? '退出' : '登錄'} </button> {/*這樣的話像是vue中的v-show它只是控制顯示與隱藏,如果經常的進行切換時可以用它,比較的節省性能*/} <h2 style={{ display: this.state.isLogin ? 'block' : 'none' }}> 你好啊,davina~~
</h2>
</div> ) } loginClick() { this.setState({ isLogin: !this.state.isLogin }) } } ReactDOM.render(<App />, document.getElementById('root'))
四、ref
什么是ref,ref是react提供用來操縱DOM元素或者是組件的接口。在react中,通常情況下是不需要也不建議直接操作原生DOM的,但有時會有一些特殊的情況存在,如管理焦點,觸發動畫,這時我們就要使用創建refs來獲取對應的DOM。
目前有三種方法來實現上面的要求:
1、當傳入字符串時,通過this.refs傳入字符串格式獲取對應的元素
2、當傳入一個函數,這個函數會在被掛載時進行回調,傳入一個元素對象,使用時直接拿到之前保存的元素對象就可
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { render() { return ( <>
<input ref='refInputOne' /> + <input ref='refInputTwo' /><button onClick={this.addOne}>=</button> <input ref='result' />
<br />
<input ref={num => this.refInputOne = num} /> +<input ref={num => this.refInputTwo = num} /><button onClick={this.addTwo}>=</button><input ref={num => this.result = num} />
</>
) } addOne=()=>{ let refInputOne = this.refs.refInputOne.value; let refInputTwo = this.refs.refInputTwo.value let result = parseFloat(refInputOne)+parseFloat(refInputTwo); this.refs.result.value = result } addTwo = () => { let refInputOne = this.refInputOne.value; let refInputTwo = this.refInputTwo.value let result = parseFloat(refInputOne) + parseFloat(refInputTwo); this.result.value = result } } ReactDOM.render(<App />, document.getElementById('root'))
以上兩種方法都不推薦,后續可能被廢棄,但是要知道。下面是推薦使用的方法
3、要使用必須先創建,refs是使用React.createRef函數創建的,通過ref屬性附加到react元素上,當ref被傳遞給render中的元素時,可以通過ref的current屬性來找到這個元素。react 會在組件掛載時給current屬性傳入DOM元素。
當 ref 屬性用於html元素時,構造函數中使用 React.createRef() 創建的 ref 接收底層 DOM 元素作為其 current 屬性。當 ref 屬性用於自定義 class 組件時,ref 對象接收組件的掛載實例作為其 current 屬性。我們不能在函數組件上使用ref屬性,因為它們沒有實例,無法通過ref獲取他們的實例,但某些時候我們可以通過React.forwardRef獲取到函數組件中的某個DOM元素或者是在hooks中學習如何使用ref。如果通過state實在做不到的事,再考慮使用ref,最好是不用。
import React from 'react' import ReactDOM from 'react-dom'
class App extends React.Component { constructor(props){ super(props) //創建一個ref來存儲dom元素
this.refInputOne = React.createRef(); this.refInputTwo = React.createRef(); this.result = React.createRef(); } add=()=>{ let numA = this.refInputOne.current.value; let numB = this.refInputTwo.current.value; this.result.current.value = parseFloat(numA)+parseFloat(numB); } componentDidMount(){ //直接使用原生的api,使第一個input框獲得焦點
this.refInputOne.current.focus(); } render() { return( <>
<input ref={this.refInputOne} / > + <input ref={this.refInputTwo} /> <button onClick={this.add}> = </button> <input ref={this.result} / >
</> ) } } ReactDOM.render(<App />, document.getElementById('root'))
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { constructor(props) { super(props) this.counterRef = React.createRef(); } render() { return ( <> {/* ref放到組件上 */} <Counter ref={this.counterRef} />
<button onClick={this.appBtnClick}>btn</button>
</>
) } appBtnClick = () => { console.log(this.counterRef) this.counterRef.current.increment(); } } class Counter extends React.Component { constructor(props) { super(props); this.state = { counter: 0 } } render() { return ( <div>
<h2>當前計數: {this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
</div>
) } increment() { this.setState({ counter: this.state.counter + 1 }) } } ReactDOM.render(<App />, document.getElementById('root'))
五、受控組件與非受控組件
在react中,html表單的處理方式和普通的DOM元素不一樣,表單元素通常會保存在一些內部的state中。DOM元素的值受到react狀態的控制,所以叫做受控組件。
在html中,表單元素通常是自己來維護state,並根據用戶的輸入來進行更新操作,我們在input元素中設置了value屬性,所以顯示的值將始終是this.state.value,這導致了react的state成為了唯一的數據源。由於handleChange在每次按鈕時都會執行更新react中的state所以顯示的值會隨着用戶的輸入而更新。
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props) this.state = {value: ''} } render() { return (<> 用戶:<input type='text' value={this.state.value} onChange={e => this.handleChange(e)} /> <button>提交</button> </>) }
//進行監聽 保持數據的更新一致 handleChange = (event) => { // console.log(event.target.value) this.setState({ value: event.target.value }) } } ReactDOM.render(<App />, document.getElementById('root'))
如果有多個input框需要同時進行處理,因為這時需要多處多個監聽,所以我們可以巧用es6中的計算屬性名(computed property names)。
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); this.state = { username: '', password: '', valid: '' }} render() { return (<> <from onSubmit={e => this.handleSubmit(e)}> <div>用戶:<input type="text" name="username" onChange={e => this.handleChange(e)} value={this.state.username} /></div> <div>密碼:<input type="text" name="password" onChange={e => this.handleChange(e)} value={this.state.password} /></div> <div>驗證碼:<input type="text" name="valid" onChange={e => this.handleChange(e)} value={this.state.valid} /></div> </from> <div><input type='submit' value='提交' /></div> </>) } // handleSubmit(event){ // event.preventDefault(); // } handleChange(event) { this.setState({ //屬性名可以不用寫死 [event.target.name]: event.target.value }) } } ReactDOM.render(<App />, document.getElementById('root'))
前面我們知道受控組件可以是看成DOM元素值受到react中state控制,那簡而言之非受控組件就是DOM元素值不受react中state控制,DOM元素的值是在DOM元素的內部。非受控組件它是通過直接操作DOM的方式來進行的。大多數情況下,我們使用受控組件來處理表單數據,如果要使用非受控組件通常使用defaultValue/defaultChecked/來設置默認值。
//使用ref獲取input元素
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); this.state={username:'hello'} this.usernameRef=React.createRef(); } render() { return (<> 用戶:<input type='text' defaultValue={this.state.username} ref={this.usernameRef} onChange={e => this.handleChange(e)} /> <button>提交</button> </>) } handleChange(){ console.log(this.usernameRef.current.value) } } ReactDOM.render(<App />, document.getElementById('root'))