先前我們在 【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;

基礎使用 - 以onChange取得輸入值

<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 (
            <input type="text" onChange={(e)=>{setAccount(e.target.value)}}/>
                目前account:{account}
export default LoginForm;

用defaultValue給予初始值

以前在使用純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 (
            <input type="text" defaultValue={account} onChange={(e)=>{setAccount(e.target.value)}}/>
                目前account:{account}
export default LoginForm;

value、state、控制組件

那value被拿去幹嘛了呢? 幹嘛還要多一個defaultValue ?

value在JSX中是「目前input中的值」的意思。這樣講可能不太懂,所以來看一下這個範例。我們很常遇到一個狀況: 在不移除input時,需要在程式中的其他地方改變input值(例如:當使用者輸入不合規定,清空input內容)。 現在,我們在最底下再新增一個button來模擬這個狀況。

    return (
            <input type="text" defaultValue={account} onChange={(e)=>{setAccount(e.target.value)}}/>
                目前account:{account}
            <button onClick={()=>{setAccount("")}}>用按鍵取得新的account</button>

執行看看,你會發現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 (
            <input type="text" defaultValue={account} value={account} onChange={(e)=>{setAccount(e.target.value)}}/>
                目前account:{account}
            <button onClick={()=>{setAccount("")}}>用按鍵取得新的account</button>
export default LoginForm;

接著在這個例子我們就可以把defaultValue移除了。因為value會始終跟著我們的state,且這個例子中state一開始就有給定初始值,所以defaultValue有沒有都一樣。

disabled - 把input關起來

如果你要讓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搭配時容易發生的錯誤使用

如果你想要在componentDidMount中去取得初始input值(一般發生在用fetch去取得該資料),那麼你不該使用defaultValue來設定。以下是用componentDidMount+defaultValue的狀況:

import React,{useState,useEffect} from "react";
const LoginForm=()=>{
    const [account,setAccount]=useState("快來輸入我");
    useEffect(()=>{
        setTimeout(()=>{setAccount("用fetch拿到的資料")},2000);
    },[])
    return (
            <input type="text" defaultValue={account} onChange={(e)=>{setAccount(e.target.value)}}/>
                目前account:{account}
            <button onClick={()=>{setAccount("")}}>用按鍵取得新的account</button>
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來綁定。

如果綁了state在value上而沒有綁onChange?

在這個狀況下,input會鎖死變成無法修改的狀態。你只能透過在從其他地方更改該state來修改input中的值。

其他的輸入元素

textarea

<textarea></textarea><input type="text"/>的用法是一模一樣的。

 <textarea value={account} onChange={(e)=>{setAccount(e.target.value)}}></textarea>

select

select和option是要在select中設定value、onChange、defaultValue。特別的地方是當value、defaultValue的值被指定為不是存在任一option中的值時,就不會顯示該值,而是顯示第一個option的值

import React,{useState} from "react";
const LoginForm=()=>{
    const [nowSelect,setNowSelect]=useState("789");
    return (
            <select value={nowSelect} onChange={(e)=>{setNowSelect(e.target.value)}}>
                <option value="123">123</option>
                <option value="456">456</option>
            </select>
                目前select:{nowSelect}
            <button onClick={(e)=>{setNowSelect("789")}}>改變為789</button>
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>

選取input、核取input

這兩個比較特別,它們是用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 & submit

form的subimt要觸發的事件是用onSubmit去綁定函式

 <form onSubmit={this.handleSubmit}>
      <input type="submit" value="Submit" />
 </form>

input的基礎互動也是我猶豫很久應該要在哪裡講的一個主題,因為想讓前面講hook的時候比較順暢,所以就拉到這裡才講。另外相對於控制組件,就有非控制組件。這邊不會特別提,有興趣可以查看看,後面也會稍微提一下什麼時候會用非控制組件。

下一篇會開始講react-router-dom。