React Controlled vs Uncontrolled Component

在 React 的世界裡,表單(Form)元件與其他 HTML DOM 元件的運作方式不一樣,這是因為表單會儲存自身的內部狀態,而其他的元件並沒有自身狀態要存的緣故。例如:當使用者在欄位輸入時,表單會記住使用者輸入的資料以更新其值。這在開發的時候所出現的不同點就是,當我們希望能實作更細緻的 UI 時,就需要掌握更多的資訊,因此會希望將表單狀態的儲存,轉由開發人員掌控,因此就有了 controlled component 與 uncontrolled component 選擇上的差異。

先分別來看什麼是 controlled component?看什麼是 uncontrolled component?

Controlled Component

在一般的 HTML 中,表單元件像是 <input><textarea><select> 會自動儲存自身的狀態並在使用者輸入時更新其值,而在 React 中,如上面提到的,若希望處理更複雜的狀況,那麼欄位值的變更就必須由開發者處理。

表單元件 <input> 會自動儲存自身的狀態並在使用者輸入時更新其值。

<form>
  <input type="text" name="name" />
</form>

在 React 中,表單元件 <input> 值的更新必須由開發者處理,如下,欄位 name 將初始值放在 state 中,當有變更時再用 setState 更新。

class App extends Component {
  constructor(props) {
    super(props);

    this.state = { name: '' };
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault(); // prevent browsing to a new page when the user submits the form
    console.log(JSON.stringify(this.state));
  }

  handleChange(value) {
    this.setState({ name: value });
  }

  render() {
    return (
      <div className='App'>
        <h1>Controlled Component</h1>
        <form onSubmit={this.handleSubmit}>
          <label>
            Name
            <input
              type='text'
              name='name'
              value={name}
              autoComplete='off'
              onChange={(e) => this.handleChange(e.target.value)}
            />
          </label>
          <input type='submit' value='Submit' />
        </form>
      </div>
    );
  }
}

完整程式碼與 Demo

同理,<textarea><select> 也是這樣處理,但 <input type="file"> 屬於 uncontrolled component 的範疇,稍後討論。

Uncontrolled Component

在 React 的世界中,通常我們會使用 controlled component 來實作表單元件以處理表單資料,但還是有些狀況是以 uncontrolled component,也就是我們常用一般的 HTML DOM element 來處理的。優點是若要整合 React 與非 React 元件是比較容易的,或在實作簡易表單時,不需要考慮太多細節,就很適合使用 uncontrolled component。

範例如下,簡易表單。

class Form extends Component {
  render() {
    return (
      <form>
        <input type='text' />
      </form>
    );
  }
}

另一個範例如下,不再使用 state 設定 <input> 的值,但若要取用則可使用 ref 來取值,並且可用 defaultValue 來設定初始值。

class App extends Component {
  constructor(props) {
    super(props);
    this.input = React.createRef();
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault(); // prevent browsing to a new page when the user submits the form
    console.log(JSON.stringify(`name: ${this.input.current.value}`));
  }

  render() {
    return (
      <div className='App'>
        <h1>Controlled Component</h1>
        <form onSubmit={this.handleSubmit}>
          <label>
            Name
            <input
              type='text'
              name='name'
              autoComplete='off'
              defaultValue='Jack'
              ref={this.input}
            />
          </label>
          <input type='submit' value='Submit' />
        </form>
      </div>
    );
  }
}

完整程式碼與 Demo

在 React 的世界中,<input type="file"> 一定是 uncontrolled component,這是因為其值只能由使用者上傳的檔案來設定,而無法經由程式設定。但我們可以經由 File API 取值。

class App extends Component {
  constructor(props) {
    super(props);
    this.fileInput = React.createRef();
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault(); // prevent browsing to a new page when the user submits the form
    console.log(this.fileInput.current.files[0].name);
  }

  render() {
    return (
      <div className='App'>
        <h1>Uncontrolled Component: File Upload</h1>
        <form onSubmit={this.handleSubmit}>
          <input type='file' ref={this.fileInput} />
          <input type='submit' value='Submit' />
        </form>
      </div>
    );
  }
}

完整程式碼與 Demo

Controlled vs Uncontrolled Component

React Controlled vs Uncontrolled Component

上圖是一個 HTML 標籤的示意圖,在實作中,uncontrolled component 與一般的 HTML DOM element 無異,也就是由表單管理自身的狀態;controlled component 必須要先將的值存在元件的狀態中(也可以存在其他地方,例如另一個元件的狀態或 store,先前提到 Redux Form 就是存在 reudx store 的),而當使用者輸入資料時就呼叫 callback 更新 state,接著元件會重新渲染而取得新值。

下圖即是描述這個過程。

Controlled Flow

圖片來源:Controlled and uncontrolled form inputs in React don’t have to be complicated

疑?controlled component 的複雜度似乎遠高過 uncontrolled component!為什麼我們要用這麼麻煩的東西???

我們來看 controlled component 與 uncontrolled component 在實作功能上的差異。

React Controlled vs Uncontrolled Component

備註:笑臉表示實作時難度不高,哭臉表示硬做可以但很辛苦。

一般來說,controlled component 與 uncontrolled component 都可以做到以上表格提到的功能,像是在送出表單時取得使用者輸入的資料,甚至是當使用者輸入欄位值或 blur 時驗證資料是否有誤,但差異在於,uncontrolled component 在做這些功能時比較難,也就是硬做也是可以的(請見範例),只是比較不優雅、可能會有效能問題罷了。

參考資料


comments powered by Disqus