zishu's blog

zishu's blog

一个热爱生活的博主。https://zishu.me

React 學習筆記(三)

來源於:https://react.docschina.org/docs/forms.html

一、表單#

在 React 中,html 表單元素的工作方式和其他的 DOM 元素不太一樣,因為表單內部通常會保持一些內部的 state,比如下面這個純 html 表單只接受一個名稱:

<form>
  <label>
    名字:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="提交" />
</form>

這個表單具有默認的 html 表單行為,即在用戶提交表單之後自動刷新頁面。如果你在 React 中執行相同的代碼,它仍然有效。

但大多數情況下,使用 JavaScript 函數可以很方便的處理表單的提交,同時還可以訪問用戶填寫的表單數據。實現這種效果的標準方式是使用 “受控組件”。

受控組件#

在 html 中,表單元素比如<input><textarea><select>,這些表單元素通常自己維護 state,並根據用戶輸入進行更新。而在 React 中,可變狀態(mutable state)通常保存在組件的 state 屬性中,並且只能通過使用 setState() 來更新。

我們可以把兩者結合起來,使 React 的 state 成為 “唯一數據源”。渲染表單的 React 組件還控制著用戶輸入過程中表單發生的操作。被 React 以這種方式控制取值的表單輸入元素就叫做 “受控組件”。

例如,如果我們想讓前一個示例在提交時打印出名稱,我們可以將表單寫為受控組件:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

由於在表單元素上設置了 value 屬性,因此顯示的值將始終為 this.state.value,這使得 React 的 state 成為唯一數據源。由於 handlechange 在每次按鍵時都會執行並更新 React 的 state,因此顯示的值將隨著用戶輸入而更新。

對於受控組件來說,輸入的值始終由 React 的 state 驅動。你也可以將 value 傳遞給其他 UI 元素,或者通過其他事件處理函數重置,但這意味著你需要編寫更多的代碼。

textarea 標籤#

在 html 中,<textarea> 元素通過其子元素定義其文本:

<textarea>
  測試!
</textarea>

而在 React 中,<textarea> 使用 value 屬性代替。這樣,可以使得使用 <textarea> 的表單和使用單行 input 的表單非常類似:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '請撰寫一篇關於你喜歡的 DOM 元素的文章.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('提交的文章: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          文章:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

請注意,this.state.value 初始化於構造函數中,因此文本區域默認有初值。

select 標籤#

在 HTML 中,<select> 創建下拉列表標籤。例如,如下 html 創建了水果相關的下拉列表:

<select>
  <option value="grapefruit">葡萄柚</option>
  <option value="lime">酸橙</option>
  <option selected value="coconut">椰子</option>
  <option value="mango">芒果</option>
</select>

請注意,由於 selected 屬性的緣故,椰子選項默認被選中。React 並不會使用 selected 屬性,而是在根 select 標籤上使用 value 屬性。這在受控組件中更便捷,因為您只需要在根標籤中更新它。例如:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('你喜歡的風味是: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          選擇你喜歡的風味:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
            <option value="mango">芒果</option>
          </select>
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

總的來說,這使得 <input type="text">, <textarea><select> 之類的標籤都非常相似 — 它們都接受一個 value 屬性,你可以使用它來實現受控組件。

注意:你可以將數組傳遞到 value 屬性中,以支持在 select 標籤中選擇多個選項:

<select multiple={true} value={['B', 'C']}>

文件 input 標籤#

在 HTML 中,<input type="file"> 允許用戶從存儲設備中選擇一個或多個文件,將其上傳到服務器,或通過使用 JavaScript 的 File API 進行控制。

<input type="file" />

因為它的 value 只讀,所以它是 React 中的一個非受控組件。

處理多個輸入#

當需要處理多個 input 元素時,我們可以給每個元素添加 name 屬性,並讓處理函數根據 event.target.name 的值選擇要執行的操作。

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.name === 'isGoing' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          參與:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          來賓人數:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

這裡使用了 ES6 計算屬性名稱的語法更新給定輸入名稱對應的 state 值:

例如:

this.setState({
  [name]: value
});

等同於 es5:

var partialState = {};
partialState[name] = value;
this.setState(partialState);

另外,由於 setState() 自動將部分 state 合併到當前 state, 只需調用它更改部分 state 即可。

受控輸入空值#

在受控組件上指定 value 的 prop 會阻止用戶更改輸入。如果你指定了 value,但輸入仍可編輯,則可能是你意外地將 value 設置為 undefinednull

下面的代碼演示了這一點。(輸入最初被鎖定,但在短時間延遲後變為可編輯。)

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

受控組件的替代品#

有時使用受控組件會很麻煩,因為你需要為數據變化的每種方式都編寫事件處理函數,並通過一個 React 組件傳遞所有的輸入 state。當你將之前的代碼庫轉換為 React 或將 React 應用程序與非 React 庫集成時,這可能會令人厭煩。在這些情況下,你可能希望使用非受控組件,這是實現輸入表單的另一種方式。

成熟的解決方案#

如果你想尋找包含驗證、追蹤訪問字段以及處理表單提交的完整解決方案,使用 Formik 是不錯的選擇。然而,它也是建立在受控組件和管理 state 的基礎之上 —— 所以不要忽視學習它們。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。