1

网页中使用的form表单大家肯定都再熟悉不过了,它主要作用是用来收集和提交信息。
React中的表单组件与我们普通的Html中的表单及其表现形式没有什么不同,所以如何使用表单我觉得再拿出来说可能是画蛇添足、毫无意义。不过再怎么样也不能辜负大家对标题的期待吧,本篇内容笔者将以控件为主体进行记录。

那么问题来了,何为控件?
在React中,将form组件(<form></form>)下的所有子组件统称为控件,比如常用的input:text, Select, input:radio等等。

对于一个表单来说,<form/>只是这个表单的载体,它具体能做哪些事还是由其内部的控件决定的。所以如何将这些控件有效得组织起来形成一个表单体系就显得尤为重要。同时React也将控件分为受控组件(Controlled Components)和非受控组件(uncontrolled components)。不过有一点需要明确下这个分类依据并不是以组件的类别来分类而是以使用方式来分类的,具体咱们下面接着说。

受控组件

官方对受控组件的定义是:

An input form element whose value is controlled by React in this way is called a “controlled component”

简而言之就是用户输入型的表单控件是由React体系来管理。比如一个输入框,我们可以通过其value对其赋值进而实现改变输入框中的值的目的(当然不只限于输入框,还有下拉框、文本域等)。而这也是受控组件可以被React控制的关键所在。先举个例子:

import React, {Component} from 'react';

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

    render() {
        return (
            <input type="text" value={this.state.inputValue}/>
        )
    }
}
export default ControlledInput;

通过例子我们能看出在这个组件的State中我们定义了inputValue这个字段并且将它以value的形式赋给了一个输入框,此时这个组件的表现如下:
clipboard.png

不过拥有雪亮眼睛的朋友一眼就能看出来两个"问题":

  1. 此时的输入框不管怎么敲击键盘,输入框中的值都是不变的。熟悉原生输入框的朋友都知道,一旦它的value属性被赋了值那么相当于输入框的内容被写死了,如果想改变输入框的内容那就得改变value属性的值
  2. 输入框的value属性的值是组件State中的inputValue字段。

根据上面两个"问题"我们可以预见到:如果通过setState方法去改变inputValue的值,这样React会重新渲染组件节点中的inputValue值,不就相当于改变了输入框的value属性值了么?
那就动手实践下,我们在这个组件中添加一个按钮用它来改变inputValue值:

import React, {Component} from 'react';

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

    changeHandler = () => {
        this.setState({
            inputValue: 'React Form'
        })
    };

    render() {
        return (
            <div>
                <input type="text" value={this.state.inputValue}/>
                <input type="button" value="变" onClick={this.changeHandler}/>
            </div>

        )
    }
}
export default ControlledInput;

clipboard.png

效果与我们所遇见的一样。不过实际开发中我们可不能通过点击按钮去改变输入框的值,因为输入框需要用户的实际输入,所以我们需要在输入框DOM节点上绑定onChange实践,将用户实际输入的值set给组件的State。修改方式如下:

import React, {Component} from 'react';

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

    inputHandler = (e) => {
        const content = e.target.value;
        this.setState({
            inputValue: content
        })
    };

    render() {
        return (
            <div>
                <input type="text" value={this.state.inputValue} onChange={this.inputHandler}/>
            </div>

        )
    }
}
export default ControlledInput;

每当输入框中的内容发生变动我们就将变动后的内容通过State重新渲染给输入框的value属性。说到这里,可能就有人认为这不是脱裤子放屁吗?为什么要在用户输入完后再绕一圈?且不急,后面会有具体说明。我们继续往下说!!!
上面的代码就是将输入框作为一个受控组件在使用。具体流程我们可以画一个图,这样更为直观也更容易理解。
clipboard.png

这就是受控组件的实现方式流程图。这里仅仅是用输入框作为演示主体,其实还有下拉框、文本域这类可以产生更多交互的Html标签(或者说是可以通过value属性改变其外在表现的Html标签)。不过这里笔者想特地排除下单选框和复选框,因为它们的状态太过单一。

非受控组件

说到非受控组件我相信大家都能意会它的是什么意思,就是它的状态由自己维护,实际上这也是Html标签原来的样子。比如输入框被输入了什么值,那这个值就被输入框自己管理,不由外来因素管理。或许这么做会显得更为简单,笔者一直也这么认为(仅限一般情况下)。
这里我们继续以输入框为主要演示对象继续往下说。对于一个输入框如果我们想获取它的输入内容,一般有有两个方法,一个是绑定事件在其输入期间同步的获取输入值,还有一个办法是通过原生DOM对象的value获取输入值。不过对于非受控组件我们应该最大限度得保证其纯净,如果给其绑定一个事件来获取输入值那么相当于用受控组件的实现方式去处理它会显得很冗余,所以通过原生DOM去获取输入值是最Nice的办法当然React也提供了访问原生DOM的方法: React.createRef()
这是React提供的新的访问原生DOM的方法,墙裂建议这样使用;
同样我们也使用它改变一下原来的代码:

import React, {Component} from 'react';

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

    inputHandler = () => {
        const originalDom = this.input.current;//current代表原生DOM对象
        const content = originalDom.value;
        alert(content);
    };

    render() {
        return (
            <div>
                <input type="text" ref={this.input}/>
                <input type="button" value="取值" onClick={this.inputHandler}/>
            </div>

        )
    }
}
export default ControlledInput;

clipboard.png

运行结果很完美!!!非受控组件的实现及使用方法就是这么简单,我们只管取值就行了,其它由它们自己管理就好了。

受控组件与非受控组件的不同及启示

通过上面的介绍我们了解了受控组件与非受控组件及其实现、使用方式。它们的不同之处笔者这里用一句话总结下:

受控组件的value值受React管理;非受控组件的value值由自身管理;

那这句话能给我们什么样的启示?
首先看非受控组件的value值由自身管理,对于非受控组件我们好像除了拿值其它的什么都干不了,实际情况的却如此,pass!!
再看受控组件的value值受React管理,前面我们知道受控组件对于用户输入的值需要从State中绕一下再到组件本身。那么我们可不可以在这个“绕”的过程中做点什么?比如在setState之前对用户输入的内容做点手脚什么的...带着这个想法,我们往下看!

如何选择受控组件与非受控组件

既然React将form控件分为受控组件与非受控组件自然有它自己的道理,那么我们实际开发中应该如何优雅得选择它们?假如我们只是单纯得获取用户的输入,那我们会本能的选择非受控组件,因为它使用很方便不需要写那么多复杂的逻辑;假如我们想对用户的输入做点手脚,比如字母转大小写等等...因为原生input标签不具备此功能,或许我们就得使用受控组件。当然一位外国友人对如何选择受控组件与非受控组件发表了自己的看法,笔者这里做个搬运。不想看或者没法看的朋友可以看下面的搬运内容,跳过废话直接上结论:

clipboard.png

  1. 一次性数据获取可以任意使用其中一种。不过笔者觉得选择非受控组件会更为便捷,不需要写一大堆逻辑
  2. 提交时进行验证可以任意使用其中一种。这个仁者见仁智者见智,不过笔者还是觉得非受控组件会更好
  3. 实时验证使用受控组件。因为我们在用户输入过程中对输入值进行实时验证,而非受控组件不支持这么做,初非你用第三方库;
  4. 有条件地禁用提交按钮使用受控组件。禁用提交按钮的前提是输入值不合法,参考第三条。
  5. 对输入值进行格式化使用受控组件。非受控组件输入什么就是什么,我们无法去改变;而受控组件我们可以在获取到输入值的时候对输入值进行转换,比如大小写获取货币转换等等,最后render回控件中。
  6. 对一条数据尽心多次输入使用受控组件。同样的非受控组件的状态由自身管理,而对于受控组件我们可以对输入值做更多的处理
  7. 动态输入使用受控组件。假如我们从后台拉取一个数据要填入输入框,那么必须得使用受控组件,因为非受控组件只能被用户输入。

心得:在表单中使用单选框和复选框

为什么单独将单选框和复选框拉出来军训?是因为它们的状态单一不需要用户输入且它们的value属性并不和自身表现有直接关系。例如我们无法通过对单选框的value设置为true从而让它变为选中的状态。而且单选框和复选框基本都是成群结队出现的,所以从受控组件与非受控组件角度来处理它们显然不适合。这里笔者推荐两种处理方式:

事件代理

比如这样写:

import React, {Component} from 'react';

class ControlledInput extends Component {

    constructor(props) {
        super(props);
        this.parent = React.createRef();
    }

    componentDidMount() {
        this.parent.current.addEventListener('change', (event) => {
            //do something
            console.log(event);
        })
    }

    componentWillUnmount() {
        this.parent.current.removeEventListener('change');

    }

    render() {
        return (
            <div ref={this.parent}>
                <input type="radio" name='sex'/>
                <input type="radio" name='sex'/>
            </div>

        )
    }
}
export default ControlledInput;

在一群单选框或者复选框外层包一个div并且绑定事件监听。当我们点击它们的时候就会获取到对于的事件对象,然后就可以做你想做!

动态生产

所谓动态生产就是用一个function生成所需要的一群单选框或者复选框,同时给它们绑定相关事件,比如:

import React, {Component} from 'react';

class ControlledInput extends Component {

    constructor(props) {
        super(props);
        this.checkBoxs = ['A', 'B', 'C'];
    }

    //Process checkbox group
    createCheckBoxes = () => this.checkBoxs.map(checkbox => (
        <label key={checkbox}>
            {checkbox}: <input type="checkbox" name="hobby" value={checkbox} onChange={this.checkBoxChange}/>
        </label>
    ));

    checkBoxChange = (e) => {
        //do something
        console.log(e);
    };

    render() {
        return (
            <div>
                Checkboxes: {this.createCheckBoxes()}
            </div>

        )
    }
}
export default ControlledInput;

对于这两种方法,大家仁者见仁智者见智。不过如果有其他的方法欢迎留言 :)

写在最后

其实也没啥写的,就想再提一下输入框的三个属性帮助不熟悉的朋友多了解下。对于Html输入框很熟悉的朋友此处可以跳过了。

  1. placeholder: 主要用来提示用户这个输入框需要输入什么样的内容。不影响正常输入!
  2. defaultValue: 填充该输入框的默认值,此时不显示placeholder内容。不影响正常输入!
  3. value: 起到填充输入框内容的作用;与defaultValue不能共存;且一旦设置了value属性(即使为空或者无值)那么该输入框将无法进行输入。

风吹过的夏夜
295 声望31 粉丝

前端工程师