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阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容