(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
先前我們在【React.js入門 - 06】 JSX (下)有稍微帶過說<input/>
類的元件在取得使用者的輸入值時,必須透過e.target.value
的方式取得,但是並沒有仔細講取得輸入值的範例。這是因為我們經常把<input/>
和state一起使用,有的時候還會搭配生命週期,所以才拖到現在講。
現在,我們創建一個新的檔案LoginForm.js,並在裡面宣告、輸出同名的function component,並在App.js使用。
import React from "react";
const LoginForm=()=>{
return (
)
}
export default LoginForm;
<input/>
取得輸入值的方法不是onClick,而是onChange。但是在綁定的方法和onClick一模一樣,通常會這樣搭配setState取得輸入值:
<input type="text" onChange={(e)=>{ /* 用e.target.value去setState */ }} />
現在,請利用useState在LoginForm.js建立一個名為Account的state,初始值為空字串。引入部份不再多提。
import React,{useState} from "react";
const LoginForm=()=>{
const [account,setAccount]=useState("");
return (
)
}
export default LoginForm;
接著,在return值中加入text type的<input/>
,並綁定setAccount
,丟入e.target.value。
<input type="text" onChange={(e)=>{ setAccount(e.target.value) }}/>
最後我們在return值那邊加入一行用來觀察輸入值的<div>
。
import React,{useState} from "react";
const LoginForm=()=>{
const [account,setAccount]=useState("");
return (
<div>
<input type="text" onChange={(e)=>{setAccount(e.target.value)}}/>
<div>
目前account:{account}
</div>
</div>
)
}
export default LoginForm;
以前在使用純html時,如果我們要讓input有初始值,是像這樣直接在value中賦予:
<input type="text" value="account"/>
然而在JSX的input中value的定義並不是「初始值」,初始值是用另外一個屬性 - defaultValue來設定。現在,我們先給我們的account一個初始值"快來輸入我"
:
const [account,setAccount]=useState("快來輸入我");
接著你重新整理網頁,發現只有下面顯示目前account值的<div>
初始值被改變。
這是因為我們還沒有綁定defaultValue上去。所以現在就來綁:
import React,{useState} from "react";
const LoginForm=()=>{
const [account,setAccount]=useState("快來輸入我");
const [password,setPassword]=useState("");
return (
<div>
<input type="text" defaultValue={account} onChange={(e)=>{setAccount(e.target.value)}}/>
<div>
目前account:{account}
</div>
</div>
)
}
export default LoginForm;
那value被拿去幹嘛了呢? 幹嘛還要多一個defaultValue ?
value在JSX中是「目前input中的值」的意思。這樣講可能不太懂,所以來看一下這個範例。我們很常遇到一個狀況: 在不移除input時,需要在程式中的其他地方改變input值(例如:當使用者輸入不合規定,清空input內容)。 現在,我們在最底下再新增一個button來模擬這個狀況。
return (
<div>
<input type="text" defaultValue={account} onChange={(e)=>{setAccount(e.target.value)}}/>
<div>
目前account:{account}
</div>
<button onClick={()=>{setAccount("")}}>用按鍵取得新的account</button>
</div>
)
執行看看,你會發現input中的值並沒有跟著account一起被清空
這是因為defaultValue只是初始值,元件被建立後它就不會影響輸入值。而onChange只有在使用者改變input時才會觸發,和它用來改變的state無關。
如果你希望「input中的值只在一開始受state影響」,就要用該state去綁定defaultValue;相反的,如果你希望「input中的值始終跟著state」,就要用該state去綁定value。「input中的值始終跟著state,state的值也隨input值改變而更動」這樣的狀況我們會稱為控制組件(or 受控組件):
import React,{useState} from "react";
const LoginForm=()=>{
const [account,setAccount]=useState("快來輸入我");
return (
<div>
<input type="text" defaultValue={account} value={account} onChange={(e)=>{setAccount(e.target.value)}}/>
<div>
目前account:{account}
</div>
<button onClick={()=>{setAccount("")}}>用按鍵取得新的account</button>
</div>
)
}
export default LoginForm;
接著在這個例子我們就可以把defaultValue移除了。因為value會始終跟著我們的state,且這個例子中state一開始就有給定初始值,所以defaultValue有沒有都一樣。
如果你要讓input暫時不能更動,可以透過disabled
這個props來控制:
<input type="text" disabled={true} defaultValue={account} onChange={(e)=>{setAccount(e.target.value)}}/>
另外,因為這個props要求的是一個布林值,你也可以只寫關鍵字,input一樣會收到disabled=true
<input type="text" disabled defaultValue={account} onChange={(e)=>{setAccount(e.target.value)}}/>
如果你想要在componentDidMount中去取得初始input值(一般發生在用fetch去取得該資料),那麼你不該使用defaultValue來設定。以下是用componentDidMount+defaultValue的狀況:
import React,{useState,useEffect} from "react";
const LoginForm=()=>{
const [account,setAccount]=useState("快來輸入我");
useEffect(()=>{
setTimeout(()=>{setAccount("用fetch拿到的資料")},2000);
},[])
return (
<div>
<input type="text" defaultValue={account} onChange={(e)=>{setAccount(e.target.value)}}/>
<div>
目前account:{account}
</div>
<button onClick={()=>{setAccount("")}}>用按鍵取得新的account</button>
</div>
)
}
export default LoginForm;
你會發現input值並沒有跟隨fetch到的值
這是因為defaultValue在render前就決定了。也就是用state綁定defaultValue的整個流程為:
在render return前決定state初始值 -> 在render return時決定defaultValue值 -> 渲染畫面 -> 執行componentDidMount,在其中改變state值 -> defaultValue值不變
因為在componentDidMount中設定state等同於我們在非input處修改state的狀況,所以如果你要讓input值等同從server取得的值,應該要用value來綁定。
在這個狀況下,input會鎖死變成無法修改的狀態。你只能透過在從其他地方更改該state來修改input中的值。
<textarea></textarea>
和<input type="text"/>
的用法是一模一樣的。
<textarea value={account} onChange={(e)=>{setAccount(e.target.value)}}></textarea>
select和option是要在select中設定value、onChange、defaultValue。特別的地方是當value、defaultValue的值被指定為不是存在任一option中的值時,就不會顯示該值,而是顯示第一個option的值。
import React,{useState} from "react";
const LoginForm=()=>{
const [nowSelect,setNowSelect]=useState("789");
return (
<div>
<select value={nowSelect} onChange={(e)=>{setNowSelect(e.target.value)}}>
<option value="123">123</option>
<option value="456">456</option>
</select>
<div>
目前select:{nowSelect}
</div>
<button onClick={(e)=>{setNowSelect("789")}}>改變為789</button>
</div>
)
}
export default LoginForm;
另外,你也可以在option透過selected
這個props來控制預選取的option,但是當select標籤有設定value或defaultValue時,以select標籤的設定值為主。
<select onChange={(e)=>{setNowSelect(e.target.value)}}>
<option value="123">123</option>
<option selected={true} value="456" >456</option>
</select>
和上面disabled一樣,可以這樣寫:
<option selected value="456" >456</option>
這兩個比較特別,它們是用checked
這個props去控制是否被選取。
const [isCheck,setIsCheck]=useState(false);
<input type="radio" value="123" checked={isCheck} onChange={(e)=>{setIsCheck(true)}} />123<br/>
<input type="radio" value="456" checked={!isCheck} onChange={(e)=>{setIsCheck(false)}} />456
因為checked是用布林值去控制,我們如果要取得value值,用「比較是否和value相同」的方式來設定會比較方便,就不需要多用一個state去存目前的value。
const [nowSelect,setNowSelect]=useState("789");
<input type="radio" value="123" checked={nowSelect==="123"} onChange={(e)=>{setNowSelect("123")}} />123<br/>
<input type="radio" value="456" checked={nowSelect==="456"} onChange={(e)=>{setNowSelect("456")}}/>456
form的subimt要觸發的事件是用onSubmit去綁定函式
<form onSubmit={this.handleSubmit}>
<input type="submit" value="Submit" />
</form>
input的基礎互動也是我猶豫很久應該要在哪裡講的一個主題,因為想讓前面講hook的時候比較順暢,所以就拉到這裡才講。另外相對於控制組件,就有非控制組件。這邊不會特別提,有興趣可以查看看,後面也會稍微提一下什麼時候會用非控制組件。
下一篇會開始講react-router-dom。