- 原文地址:React Forms: Using Refs
- 原文作者:Loren Stewart
- 譯者:萌萌
- 校對者:小 boy
React 提供了兩種從 <form>
元素中獲取值的標準方法。第一種方法是實現所謂的受控元件 (可以看我部落格裡發表的文章) ,第二種方法是使用 React 的 ref
屬性。
受控元件很重,被展示的值和元件的 state 繫結是它的特性。我們通過執行一個附著在 form 元素上的 onChange
事件控制程式碼,來更新被展示的值。onChange
函式更新 state 屬性,進而更新 form 元素的值。
(在看到下面的文章之前,如果你只是想看相應的示例程式碼:請移步這裡)
受控元件示例:
import React, { Component } from 'react';
class ControlledCompExample extends Component {
constructor() {
super();
this.state = {
fullName: ''
}
}
handleFullNameChange = (e) => {
this.setState({
fullName: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
console.log(this.state.fullName)
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor="fullName">Full Name</label>
<input
type="text"
value={this.state.fullName}
onChange={this.handleFullNameChange}
name="fullName" />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default ControlledCompExample;複製程式碼
input 的值是 this.state.fullName
(在第7行和第26行)。 onChange
函式是 handleFullNameChange
(第 10 - 14 行和第 27 行)。
受控元件最主要的優勢是:
1、便於驗證使用者的輸入
2、可以根據受控元件的值動態地渲染其他元件。例如:一個使用者在下拉選單中選擇的值(如“dog” 或者 “cat” )可以控制在 form 中渲染的其他 form 元件(例如:一個設定品種的核取方塊)
受控元件的缺點是要寫大量的程式碼。你需要通過 props 把 state 屬性傳遞給 form 元素,還需要一個函式來更新這個屬性的值。
對於單一表單元素來說這真的不是什麼問題 —— 但是如果你需要一個龐大並且複雜的表單(不需要動態渲染或者實時驗證),過度使用受控表單會讓你書寫成噸的程式碼。
從 form 元素取值的簡便的方法是使用 ref
屬性。我們用不同的方式來應對不同的 form 元素和元件結構,所以這篇文章剩下的內容分為以下幾個部分。
1、文字輸入框、數字輸入框和選擇框
使用 ref
的最簡單的例子是文字和數字 input 元素。我們在 input 的 ref
屬性裡新增一個把 input 本身作為引數的箭頭函式。我喜歡把引數命名為和元素本身一樣的的名字,就像下面的第三行那個樣子:
<input
type="text"
ref={input => this.fullName = input} />複製程式碼
由於該引數是 input 元素本身的別名,你可以隨心所欲地為它命名:
<input
type="number"
ref={cashMoney => this.amount = cashMoney} />複製程式碼
接著你可以拿到該引數,並將它賦值給當前 class 內 this
關鍵字上掛載的屬性(譯者注:這裡的 class 指的是 JSX 所處的 React 元件 class)。input(例如: DOM 節點)可以通過 this.fullName
和 this.amount
來讀取。它的值可以通過 this.fullName.value
和 this.amount.value
來讀取。
選擇元素也可以用相同的方法(例如:下拉選單)。
<select
ref={select => this.petType = select}
name="petType">
<option value="cat">Cat</option>
<option value="dog">Dog</option>
<option value="ferret">Ferret</option>
</select>複製程式碼
選擇元素的值可以通過 this.petType.value
獲取。
2、子元件通過 props 傳值給父元件
通過受控元件,父元件獲取子元件的值十分簡單 —— 父元件中已經有這個值了(譯者注:在父元件中定義)!它被傳遞給子元件。同時 onChange
方法也被傳給子元件,使用者通過與 UI 互動(譯者注:觸發 onChange
)來更新該值。
你可以在我上篇文章的受控元件示例中看到它是如何執行的。
雖然該值已經存在於受控元件的父元件中,但是當使用 ref
的時候卻不是這樣。使用 ref
的時候,該值存在於 DOM 節點自身當中,必須向上與父元件通訊。
要將該值從子元件傳給父元件,父元件需要向子元件傳遞一個 鉤子
。然後子元件將節點掛載到 鉤子
上, 以便父元件讀取。
在我們更深入的探討之前先來看一些程式碼。
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
console.log('first name:', this.firstName.value);
this.firstName.value = 'Got ya!';
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<CustomInput
label={'Name'}
firstName={input => this.firstName = input} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
function CustomInput(props) {
return (
<div>
<label>{props.label}:</label>
<input type="text" ref={props.firstName}/>
</div>
);
}
export default RefsForm;複製程式碼
通過上面的程式碼,可以看到一個 form 元件 RefForm
和一個叫做 CustomInput
的 input 元件。通常,箭頭函式都是在 input 自身上面,但是從這(15 - 27 行)可以看到它是通過 props 傳遞的。由於箭頭函式存在於父元件中,所以 this.firstName
中的 this
指向父元件。
input 子元件的值被賦給父元件的 this.firstName
屬性,所以父元件可以獲得子元件的值。現在,父元件中的 this.firstName
指的是子元件中的 DOM 節點(例如: CustomInput
中的 input)。
父元件不僅可以訪問 input 中的 DOM 節點,還可以在父元件內給節點的值賦值。在上文的第 7 行可以看到例子。一旦表單被提交, input 的值就被設定為 “Got ya!” 。
這種方式有點讓人摸不著頭腦,所以請仔細揣摩並敲程式碼實踐一下,直至完全理解。
你可能會寫出來更好的 radio 和 checkbox 受控元件,但是如果你真的想要用 `ref` ,那麼接下來的兩部分會幫到你。複製程式碼
3、 Radio 標籤集合
不像 text 和 number 這類 input 元素,radio 元素是成組出現的。每組中的元素都有相同的 name
屬性,就像這樣:
<form>
<label>
Cat
<input type="radio" value="cat" name="pet" />
</label>
<label>
Dog
<input type="radio" value="dog" name="pet" />
</label>
<label>
Ferret
<input type="radio" value="ferret" name="pet" />
</label>
<input type="submit" value="Submit" />
</form>複製程式碼
在 “pet” radio 標籤集合中有三個選項 —— “cat”、“dog” 和 “ferret”。
由於我們關心的是整個集合的元素,所以給每個單選框設定 ref
並不是一個好主意。遺憾的是,沒有 DOM 節點是包含了 radio 集合的。
可以通過下面的三步來檢索出 radio 集合的值:
1、在 form
標籤上設定 ref (下面的第20行)。
2、從 form 中取出這個 radio 集合。然後它應該是 pet
集合(下面的第9行)。
- 此處返回一個節點列表和一個值。在這種情況下,這個節點列表包含三個 input 節點和被選中的值。
- 需要注意的是這個節點列表是個類陣列,它沒有陣列的方法。在下一部分中還有更多關於這個話題的內容。
3、使用.
方法來獲取這個集合的值(下面的第13行)。
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
// 從 form 中取出節點列表
// 它是一個類陣列,沒有陣列的方法
const { pet } = this.form;
// radio 標籤集合有 value 屬性
// 檢視列印出來的資料
console.log(pet, pet.value);
}
render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<label>
Cat
<input type="radio" value="cat" name="pet" />
</label>
<label>
Dog
<input type="radio" value="dog" name="pet" />
</label>
<label>
Ferret
<input type="radio" value="ferret" name="pet" />
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default RefsForm;複製程式碼
如果你正在用子元件寫一個表單也是可行的。儘管元件中會有更多的邏輯,但是從 radio 集合中獲取值的方法是不變的。
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
// 從 form 中取出節點列表
// 它是一個類陣列,沒有陣列的方法
const { pet } = this.form;
// radio 標籤集合有 value 屬性
// 檢視列印出來的資料
console.log(pet, pet.value);
}
render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<RadioSet
setName={'pet'}
setOptions={['cat', 'dog', 'ferret']} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
function RadioSet(props) {
return (
<div>
{props.setOptions.map(option => {
return (
<label
key={option}
style={{textTransform: 'capitalize'}}>
{option}
<input
type="radio"
value={option}
name={props.setName} />
</label>
)
})}
</div>
);
}
export default RefsForm;複製程式碼
4、 Checkbox 標籤集合
和 radio 標籤集合不一樣, Checkbox 標籤集合可能有多個值。導致獲取這些值會比獲取 radio 標籤集合的值難一些。
可以通過下面的五步來檢索出 checkbox 標籤集合被選中的值:
1、在 form
標籤上設定 ref (下面的第27行)。
2、從 form 中取出這個checkbox 集合。然後它應該是 pet
集合(第9行)。
- 此處返回一個節點列表和一個值
- 需要注意的是這個節點列表是一個類陣列,它沒有陣列的方法,然後我們就需要進行下面的這一步 ...
3、把這個節點列表轉換成一個陣列,然後就可以使用陣列的方法了(第 12 行的checkboxArray
)。
4、使用Array.filter()
獲取選中的 checkbox (第 15 行的checkedCheckboxes
)。
5、使用Array.map()
獲取選中的 checkbox 的唯一的值(第 19 行的checkedCheckboxesValues
)
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
// 從 form 中取出節點列表
// 它是一個類陣列,沒有陣列的方法
const { pet } = this.form;
// 把節點列表轉換成一個陣列
const checkboxArray = Array.prototype.slice.call(pet);
// 僅取出被選中的 checkbox
const checkedCheckboxes = checkboxArray.filter(input => input.checked);
console.log('checked array:', checkedCheckboxes);
// 使用 .map() 方法從每個被選中的 checkbox 中把值取出來
const checkedCheckboxesValues = checkedCheckboxes.map(input => input.value);
console.log('checked array values:', checkedCheckboxesValues);
}
render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<label>
Cat
<input type="checkbox" value="cat" name="pet" />
</label>
<label>
Dog
<input type="checkbox" value="dog" name="pet" />
</label>
<label>
Ferret
<input type="checkbox" value="ferret" name="pet" />
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default RefsForm;複製程式碼
使用子元件寫 checkbox 的方法和上一部分中寫 radio 的方法是一樣的。
import React, { Component } from 'react';
class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
// 從 form 中取出節點列表
// 它是一個類陣列,沒有陣列的方法
const { pet } = this.form;
// 把節點列表轉換成一個陣列
const checkboxArray = Array.prototype.slice.call(pet);
// 僅取出被選中的 checkbox
const checkedCheckboxes = checkboxArray.filter(input => input.checked);
console.log('checked array:', checkedCheckboxes);
// 使用 .map() 方法從每個被選中的 checkbox 中把值取出來
const checkedCheckboxesValues = checkedCheckboxes.map(input => input.value);
console.log('checked array values:', checkedCheckboxesValues);
}
render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<CheckboxSet
setName={'pet'}
setOptions={['cat', 'dog', 'ferret']} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
function CheckboxSet(props) {
return (
<div>
{props.setOptions.map(option => {
return (
<label
key={option}
style={{textTransform: 'capitalize'}}>
{option}
<input
type="checkbox"
value={option}
name={props.setName} />
</label>
)
})}
</div>
);
}
export default RefsForm;複製程式碼
結論
如果你不需要:
1、實時監視 form 元素的值(例如:為了基於使用者的輸入渲染之後的元件)
2、實時執行自定義驗證方法
那麼使用 ref
方法獲取 form 元素的值是一個很好的方法。
大多數情況下,越過受控元件使用 ref
最主要的價值是會寫更少的程式碼。 checkbox ( radio 其次)是一個特例。對於 checkbox ,使用 ref
省下的程式碼量是很少的,所以無法說是使用受控元件好還是 ref
好。
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。