React 初探之 受控組件 VS 非受控組件

概述

React 中的受控組件和非受控組件都是針對于表單數據而言的。React 推薦使用受控組件來處理表單數據。

在受控組件中,表單數據由 React 組件的 state 管理。在非受控組件中,表單數據交由 DOM 節點處理,可以使用 ref 來從 DOM 中獲取表單數據。

受控組件

React 的 state 是表單的數據源,并且渲染表單的 React 組件還控制著用戶輸入過程中表單發生的操作。

function Form() {
  const [name, setName] = useState('');

  const onInputChange = event => setName(event.target.value);

  const onFormSubmit = event => {
    console.log('提交的數據 => ', name);
    event.preventDefault();
  };
  
  return (
    <form onSubmit={onFormSubmit}>
      <label>
        名字:
        <input type='text' value={name} onChange={onInputChange} />
      </label>
      <input type='submit' value='提交'/>
    </form>
  )
}

非受控組件

表單元素自己維護 state。可以使用 ref 獲取表單數據

function Form() {
  const inputElement = useRef(null);

  const onFormSubmit = event => {
    console.log('提交的數據 => ', inputElement.current.value);
    event.preventDefault();
  };

  return (
    <form onSubmit={onFormSubmit}>
      <label>
        名字:
        <input type='text' ref={inputElement} />
      </label>
      <input type='submit' value='提交'/>
    </form>
  )
}

默認值

在 React 渲染生命周期時,表單元素上的 value 將會覆蓋 DOM 節點中的值,在非受控組件中,React 可以通過 defaultValue 屬性賦予組件一個初始值。

return (
    <form onSubmit={onFormSubmit}>
      <label>
        名字:
        <input type='text' defaultValue='test' ref={inputElement} />
      </label>
      <input type='submit' value='提交'/>
    </form>
)
具有默認值(defaultValue)的表單元素,此時是否應該去控制后續的更新?

特殊的表單元素

<textarea>

在 React 中,<textarea> 使用 value 屬性代替 HTML 中的子元素定義其文本。

<textarea value={value} onChange={handleChange} />

<select>

React 使用根 <select> 標簽上的 value 屬性定義選中,而不使用 selected 屬性。

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

<input type='file'>

React 中 <input type='file'> 始終是一個非受控組件,因為它的值只能由用戶設定,而不能通過代碼控制。

受控組件與非受控組件的選擇

截取自文章

功能 受控組件 非受控組件
一次性的取值(例如:提交時) ? ?
提交時驗證 ? ?
實時驗證 ? ?
有條件的禁用提交按鈕 ? ?
強制輸入格式 ? ?
一個數據的多個輸入 ? ?
動態輸入 ? ?

實際應用場景

場景1

頁面有一些信息和一個編輯按鈕,點擊按鈕出來一個彈窗,彈窗的內容是對信息的更改。

// 我們使用 Redux 來進行狀態管理,數據來源為 props
const Information = props => {
  const [name, setName] = useState('');
  const [age, setAge] = useState(18);
  const nameInputElement = useRef(null);
  const ageInputElement = useRef(null);

  useEffect(() => {
    setName(props.name);
    setAge(props.age);
  }, [props]);

  return (
    <div>
      <div>
        <div>姓名:{name}</div>
        <div>年齡:{age}</div>
      </div>
      <Modal>
        <div>
          <label>
            姓名:
            {/* 方式1: 受控組件 */}
            <input type='text' value={name} onChange={e => setName(e.target.value)}/>

            {/* 方式2: 非受控組件 */}
            <input type='text' defaultValue={name} ref={nameInputElement}/>

            {/* 方式3: 非受控組件 */}
            <input type='text' defaultValue={name} onChange={e => setName(e.target.value)}/>

            {/* 方式4: 非受控組件 */}
            <input type='text' defaultValue={props.name} onChange={e => setName(e.target.value)}/>
            
          </label>
        </div>
        <div>
          <label>
            年齡:
            {/* 方式1: 受控組件 */}
            <input type='text' value={age} onChange={e => setAge(Number(e.target.value))}/>

            {/* 方式2: 非受控組件 */}
            <input type='text' defaultValue={age} ref={nameInputElement} />

            {/* 方式3: 非受控組件 */}
            <input type='text' defaultValue={age} onChange={e => setAge(Number(e.target.value))}/>

            {/* 方式4: 非受控組件 */}
            <input type='text' defaultValue={props.age} onChange={e => setAge(Number(e.target.value))}/>
            
          </label>
        </div>
      </Modal>
    </div>
  )
};

上面有4種方式:請注意:這四種方式都是在 <Modal> 中使用的

  • 方式1:在 <Modal> 中使用方式1的時候,彈窗出來之后對內容進行修改,之后點擊取消關閉彈窗后,再點擊編輯按鈕,此時彈窗內容還是修改后的內容。所以如果使用方式1:受控組件來進行 <Modal> 時,需要在 closeModal 的方法中對內容進行重置
  • 方式2:這種方式使用 ref 獲取表單的值,但是有一個問題就是,獲取用戶的 age 是異步的,當拿到用戶的 age 后,頁面重新 render,但是此方式不會進行 age 的更新,故不使用此方式
  • 方式3:這種方式使用 onChange 獲取表單的值,但是這種方式和方式2有同樣的問題,故不使用此方式
  • 方式4:這種方式彈窗出來之后對內容的進行修改,之后點擊取消關閉彈窗,再點擊編輯按鈕,內容是原有的內容,并且關閉時不需要重置。

注:方式2與方式3的區別在于方式3會 render,而方式2不會 render

總結:方式1(受控組件)與方式4(非受控組件)都可以進行使用,方式1 closeModal 時需要重置修改內容。

場景2

頁面中有一個單選框(使用 ant Design 的 <Radio>) + 一個保存按鈕,單選框選擇性別,保存按鈕可以對性別進行保存

const Page = props => {
  const [sex, setSex] = setState('');
  const sexElement = useRef(null);

  useEffect(() => {
    setSex(props.sex);
  }, [props]);

  const save = () => {
    console.log('你的性別 -> ', sex);
  };

  return (
    <div>
      性別:

      {/* 方式1: 受控組件 */}
      <Radio.Group value={sex} onChange={e => setSex(e.target.value)}>
        <Radio.Button value='male'>男</Radio.Button>
        <Radio.Button value='female'>女</Radio.Button>
      </Radio.Group>

      {/* 方式2: 非受控組件 */}
      <Radio.Group defaultValue={sex} ref={sexElement}>
        <Radio.Button value='male'>男</Radio.Button>
        <Radio.Button value='female'>女</Radio.Button>
      </Radio.Group>

      {/* 方式3: 非受控組件 */}
      <Radio.Group defaultValue={sex} onChange={e => setSex(e.target.value)}>
        <Radio.Button value='male'>男</Radio.Button>
        <Radio.Button value='female'>女</Radio.Button>
      </Radio.Group>

      {/* 方式4: 非受控組件 */}
      <Radio.Group defaultValue={props.sex} onChange={e => setSex(e.target.value)}>
        <Radio.Button value='male'>男</Radio.Button>
        <Radio.Button value='female'>女</Radio.Button>
      </Radio.Group>
      
      <button onClick={save}>保存</button>
    </div>
  )
};

上面有4種方式:請注意:這四種方式都是在頁面中使用的

  • 方式1:受控組件,沒有任何問題
  • 方式2:異步獲取 sex 之后頁面沒有進行更改,故不使用此方式
  • 方式3:和方式2相同的問題,故不使用此方式
  • 方式4:實踐下來之后和方式2具有相同的問題

總結:在頁面中使用的時候乖乖用受控組件

總結

我遇到了場景2中方式4的問題,所以我谷歌了一下,答案就是去研究受控組件,所有就誕生了這篇文章。另外千萬不要在受控組件中使用 debounce,因為受控組件的一個特點就是實時。在非受控組件中,個人感覺 debounce 還是有必要使用一下的,從而優化了一下 render 的次數。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容