Hello!大家好啊!明明才第六篇而已,感覺卻快被榨乾了XD。
前兩天我們的例子都圍繞在計時器身上,但是真正的網頁可沒有那麼簡單,因為還有使用者這種生物在,身為一個正常的網頁,頁面上會有一兩個事件,也是再正常不過的事情了對吧?所以今天就來學習怎麼在組件中設定事件吧!
能夠跟我一起走到第六篇,應該都能稍微了解我的tempo了,那...就直接進入實做吧XD
class CheckButton extends React.Component{
//在`class`中宣告一個事件
writeConsole() {
console.log('點了點了點了')
}
render(){
//使用onClick指定觸發的事件
return <input type="button" onClick={this.writeConsole}
value="點我看console" />
}
}
ReactDOM.render(<CheckButton /> ,document.getElementById('root'))
結果如下:
這應該是觸發事件最基本的方式了,只是有些事情是需要注意的,包括讓我花了很多時間的小細節XD
<input type="button" onclick={this.writeConsole} value="點我看console" />
在React
中onclick
必須變為onClick
,如下:
<input type="button" onClick={this.writeConsole} value="點我看console" />
<input type="button" onClick="writeConsole()">
要使用花括號指定function
,並且指定this
:
<input type="button" onClick={this.writeConsole}>
state
的值上一篇有提到如果要修改state
必須使用setState()
,所以我們要寫一個計數器的話,可以很輕易地寫出下方的程式碼:
class AddButton extends React.Component{
constructor(props){
super(props)
this.state = ({clickCount : 0})
}
addCount(){
this.setState({clickCount:this.state.clickCount + 1})
console.log(`點了${this.state.clickCount}下`)
}
render(){
return <input type="button"
onClick={this.addCount} value="點我" />
}
}
ReactDOM.render(<AddButton />,document.getElementById('root'))
看起來一切沒問題對吧!我們打包執行並點擊按鍵觸發事件:
我滴天哪!想都沒想到居然噴出了一個華麗的錯誤,錯誤內容是Cannot read property 'setState' of undefined
,而我們呼叫setState
的地方只有在addCount
函式內的這一行this.setState({...})
,有句話說「羊毛出在羊身上」所以先讓我們確認一下this
在這時候讀到的值是什麼?因為setState
這個函式絕對不會有錯的,除非錯的是this
,那讓我們在出錯前把this
打印出來:
class AddButton extends React.Component{
constructor(props){
super(props)
this.state = ({clickCount : 0})
}
addCount(){
console.log(this)
this.setState({clickCount:this.state.clickCount + 1})
console.log(`點了${this.state.clickCount}下`)
}
render(){
return <input type="button"
onClick={this.addCount} value="點我" />
}
}
ReactDOM.render(<AddButton />,document.getElementById('root'))
結果this
果然沒有指向class
本身,而是undefined
:
所以問題來了,我們該怎麼去設定方法執行時的this
呢?欸嘿嘿!這個小問題就交給bind()
來處理吧!如果對bind
不太熟可以參考[筆記][JavaScript]使用call()、apply()、bind()設定函式中的「this」。
知道設定的方法後,還要在對的地方去傳入this
那才會是正確的,這裡簡單說明一下,一個使用class
建構器產生的物件,該物件的屬性絕對來自於constructor
建構子,所以當物件被建立出來後,我們可以把那個物件稱做constructor
建構子的實體,如果有興趣可以參考這裡[筆記][JavaScript]建構器及實體物件的constructor特性,雖然裡面不是使用ES6的class
語法說明,但是原理還是一樣的!
既然知道constructor
會指向class
本身,那我們可以把addCount
這個方法,宣告在constructor
中,讓組件被建構出來的時候一樣可以有該方法能用:
class AddButton extends React.Component{
constructor(props){
super(props)
this.state = ({clickCount : 0})
//在constructor指定呼叫addCount,並在呼叫時指定this為class本身
this.addCount = this.addCount.bind(this)
}
addCount(){
console.log(this)
this.setState({clickCount:this.state.clickCount + 1})
console.log(`點了${this.state.clickCount}下`)
}
render(){
return <input type="button"
onClick={this.addCount} value="點我" />
}
}
ReactDOM.render(<AddButton />,document.getElementById('root'))
結果就會如我們所願!成功修改了state
的值了!
但是我們發現明明在console.log
之前已經使用了setState()
去修改state
的值,但第一次點擊的時候計數器顯示的卻是0而不是1,原因是因為setState()
他是異步執行的關係,所以並不會等到修改後才執行console.log
,為了這個情況官方提供了兩種方式處理,相信其中一種方式已經浮現在大家的腦中了,沒錯!就是componentDidUpdate()
!
class AddButton extends React.Component{
constructor(props){
super(props)
this.state = ({clickCount : 0})
//在constructor指定呼叫addCount,並在呼叫時指定this為class本身
this.addCount = this.addCount.bind(this)
}
addCount(){
this.setState({clickCount:this.state.clickCount + 1})
}
componentDidUpdate(){
//把原本在addCount中的console.log移到componentDidUpdate()中
console.log(`點了${this.state.clickCount}下`)
}
render(){
return <input type="button"
onClick={this.addCount} value="點我" />
}
}
ReactDOM.render(<AddButton />,document.getElementById('root'))
結果就會和剛剛不一樣了!每一次取都是修改後的值。
第二個方法是直接在setState()
中傳入第二個參數,讓第二個參數做為修改完後執行的function
,如下:
class AddButton extends React.Component{
constructor(props){
super(props)
this.state = ({clickCount : 0})
//在constructor指定呼叫addCount,並在呼叫時指定this為class本身
this.addCount = this.addCount.bind(this)
}
addCount(){
this.setState({clickCount:this.state.clickCount + 1},()=>{console.log(`點了${this.state.clickCount}下`)})
}
render(){
return <input type="button"
onClick={this.addCount} value="點我" />
}
}
ReactDOM.render(<AddButton />,document.getElementById('root'))
結果也會和使用componentDidUpdate()
一樣,以下只附上第二種方式:
GitHub程式連結
GitPage頁面連結
傳入參數很簡單,只需要在設定事件時在指定function
名稱後添加bind(this)
,就可以在第一個this
後添加要傳入的參數了,如下:
class AddButton extends React.Component{
constructor(props){
super(props)
this.state = ({clickCount : 0})
this.addCount = this.addCount.bind(this)
}
//增加一個count的參數,用來表示每次增加多少
addCount(count){
console.log(`每次添加值:${count}`)
this.setState({clickCount:this.state.clickCount + count})
}
componentDidUpdate(){
console.log(`點了${this.state.clickCount}下`)
}
render(){
//在onClick中指定觸發的函式後面添加.bind(this)並填上傳入的第一個參數1
return <input type="button"
onClick={this.addCount.bind(this,1)} value="點我" />
}
}
ReactDOM.render(<AddButton />,document.getElementById('root'))
結果會如下:
在實務上常常會需要再觸發事件的時候,去取得當前觸發事件的DOM,例如當我選擇某個下拉選單,我必須根據該下拉選單的值做處理,這時候該怎麼辦呢?來做個小例子吧!
class InputGender extends React.Component{
constructor(props){
super(props)
this.state = ({gender : ''})
this.changeGender = this.changeGender.bind(this)
}
//宣告事件時傳入參數event取得觸發事件變數
changeGender(event){
//將觸發事件的DOM從event內的target屬性取出
console.log(event.target)
//指定選擇的性別給state.gender
this.setState({gender:event.target.value})
}
componentDidUpdate(){
console.log(`已將state.gender變動為:${this.state.gender}`)
}
render(){
return (<select onChange={this.changeGender.bind(this)}>
<option value="M">男</option>
<option value="W">女</option>
</select>)
}
}
ReactDOM.render(<InputGender />,document.getElementById('root'))
如此一來在選擇的時候就能夠取到觸發事件的DOM元件了:
但是如果我們有自定義參數要傳入function
中,就沒辦法直接設定event
了,但是還是可以在事件內用window.event.target
取得觸發事件的DOM:
class InputGender extends React.Component{
constructor(props){
super(props)
this.state = ({gender : ''})
this.changeGender = this.changeGender.bind(this)
}
//strA是傳入的參數
changeGender(strA){
console.log(`傳入參數${strA}`)
console.log(window.event.target)
this.setState({gender:event.target.value})
}
componentDidUpdate(){
console.log(`已將state.gender變動為:${this.state.gender}`)
}
render(){
//在onchange中增加一個自訂參數
return (<select onChange={this.changeGender.bind(this,'aaaa')}>
<option value="M">男</option>
<option value="W">女</option>
</select>)
}
}
ReactDOM.render(<InputGender />,document.getElementById('root'))
結果仍然沒有問題:
雖然一開始有點難懂,不過在好不容易搞懂class
的繼承和各種this
的傳來傳去後,就感覺清楚多了XD,希望小弟我的文章能夠減少各位卡關的時間!
最後如果文章中有任何問題或解釋不清楚的地方,還麻煩留言告訴我,小弟會盡快修改或補充文章內容的!謝謝大家
參考文章:
您好,請問在取得觸發事件的DOM的部分
如果我console.log(event)得到的東西SyntheticEvent是什麼呢?謝謝!
嗨,
我在事件中傳入參數這個練習區塊照著您的程式碼練習,但是console.log(每次添加值:${count}
);並沒有被印出來,想問問是為什麼呢?
Hi!不好意思那麼晚才注意到這則訊息!
你的程式碼和文章內的一模一樣嗎?
還是方不方便貼上你的 Code 讓我看一下:)
感謝分享
我找到event跑去那了
changeGender(strA){
console.log(`傳入參數${strA}`)
console.log(window.event.target)
this.setState({gender:event.target.value})
}
它跑到後面去排隊了
changeGender(strA,event){
console.log(`傳入參數${strA}`)
console.log(event)
this.setState({gender:target.value})
}