• 从零开始的react入门教程(五),了解react中的表单,何为受控组件与非受控组件


    壹 ❀ 引

    我们在从零开始的react入门教程(四),了解常用的条件渲染、列表渲染与独一无二的key一文中介绍了react中常用的条件渲染操作,比如三元运算符,逻辑运算符等,结合react组件或者react元素,我们能做到很多视图层的切换效果。

    除此之外我们也介绍了react中的渲染操作,不同于vue类似框架使用循环指令,react中直接使用数组API达到渲染元素元素块的操作;说到循环我们不得不提到关键属性key,为循环的元素提供独一无二的key有利于react后续的DOM更新,当然,尽可能不要使用数组索引作为key,会造成什么问题我们也有在上文举例说明。

    那么这篇文章,我们将围绕react表单展开,除了介绍常用表单用法,我们还会介绍react中受控组件与非受控组件的概念,这对于我们在项目开发中,如何设计自己的组件会有一定启发,本文开始。

    贰 ❀ 从表单说起

    对于传统HTML所提供的比如inputselect等表单元素,它们都会自己维护内部的state。说通俗点,我们修改input的值,再获取这个input的value,你会发现value已经被同步更新了,这个操作由表单元素内部自己完成,我们并没有干涉。

    <input type="text" class="echoInput">
    输入的内容是:
    <span class="echoSpan"></span>
    
    const input = document.querySelector('.echoInput');
    const span = document.querySelector('.echoSpan');
    input.onchange = function () {
        span.innerHTML = input.value;
    }
    

    比如上述例子,我们使用onchange事件单纯监听了input值的变化,并未对input做赋值操作,修改value的行为由input自身完成。

    (注意:原生change事件并不是改了值就会触发,而是修改值并失去焦点才会触发)

    如果你有了解过vue或者angularjs类似的框架你会发现实现做法上有所不同。说到表单,你可能第一想到的是双向绑定,以及类似v-modelng-model类似的指令;以vue为例,相对传统表单表vue自行做了部分指令封装,通过为input添加v-model指令并提供一个变量作为初始值,之后不管我们修改变量还是input都会影响另一方,input修改相对原生change还要失焦才能感知变化这点上也人性化了很多。

    <input v-model="message" placeholder="edit me">
    <p>Message is: {{ message }}</p>
    

    叁 ❀ react中的表单

    在前面的文章中我们已经得知react属于单向数据流,props与state组成的数据像瀑布的水流一样从上往下传递,而类似v-model的指令是接受一个变量同时,也拥有反向修改变量的权力,这与react中props应该只读,state只能通过setState修改的特性有所违背。

    不过不用担心,对此问题我们已经介绍过了对应的解决方案,如果你想在子组件中修改上层传递的props,请在传递数据的同时一并提供修改数据的方法,如果你想更新内部state请使用setState方法,所以到这里你应该知晓react中如何操控表单元素了。

    叁 ❀ 壹 input-text

    让我们来看个例子,从最简单的input说起:

    class EchoInput extends React.PureComponent {
        constructor(props) {
            super(props);
            this.state = { value: '' };
        }
    
        handleChange = (event) => {
            // 还记得e对象吗,这里就通过e获取当前元素的value
            this.setState({ value: event.target.value });
        }
    
        render() {
            return (
                <form className="echo">
                    <label>
                        名字:
                        <input type="text" value={this.state.value} onChange={this.handleChange} />
                    </label>
                    <div>{'提交的名字: ' + this.state.value}</div>
                </form>
            );
        }
    }
    
    ReactDOM.render(<EchoInput />, document.getElementById('root'))
    

    在上述例子中,对于input而言state是它唯一的数据来源,由于onChange监听了input的值变化,只要用户修改input值就会触发handleChange中的setState去更新state中的value属性。

    叁 ❀ 贰 input-radio

    还记得原生的input如何做单选吗吗?如果你想让多个input表示单选,你需要将input的type类型设置为radio,同时它们需要拥有相同的name属性。现在让我们看看在react表单中如何做单选,直接上例子:

    class EchoInput extends React.PureComponent {
        constructor(props) {
            super(props);
            this.state = { value: '' };
        }
    
        handleChange = (event) => {
            // 还记得e对象吗,这里就通过e获取当前元素的value
            this.setState({ value: event.target.value });
        }
    
        render() {
            return (
                <form className="echo">
                    请选择你的性别:
                    <label>
                       男
                       <input
                           type="radio"
                           value="男性"
                           checked={this.state.value==='男性'}
                           onChange={this.handleChange}/>
                   </label>
                    <label>
                        女
                        <input
                            type="radio"
                            value="女性"
                            checked={this.state.value==='女性'}
                            onChange={this.handleChange}/>
                    </label>
                    <div>{'选择的性别为: ' + this.state.value}</div>
                </form>
            );
        }
    }
    
    ReactDOM.render(<EchoInput />, document.getElementById('root'))
    

    与普通的input text类型不同,这里我们为每个input直接提供了value值,并通过checked属性判断当前input是否应该被选中。当用户点击不同单选按钮时,通过change事件去更新state中的value,上述代码中,由于state的值总是只能为其中一个,所以我们没加name属性也达到了单选的目的。

    叁 ❀ 叁 input-checkbox

    既然都说了单选,我们顺便说说多选checkbox如何使用,直接上代码:

    class EchoInput extends React.PureComponent {
        constructor(props) {
            super(props);
            this.state = { heroes: [{ name: '曜', id: 0, checked: false }, { name: '澜', id: 1, checked: false }, { name: '盾山', id: 3, checked: false }] };
        }
    
        handleChange = (index,e) => {
            let heroes = [...this.state.heroes];
            heroes[index].checked = !heroes[index].checked;
            this.setState({ heroes:heroes });
        }
    
        render() {
            return (
                <form className="echo">
                    选择你喜欢的英雄:
                    {
                        this.state.heroes.map((hero, index) => {
                            return (
                                <label key={hero.id}>
                                    {hero.name}
                                    <input type="checkbox" checked={hero.checked} onChange={this.handleChange.bind(this,index)} />
                                </label>
                            )
                        })
                    }
                </form>
            );
        }
    }
    
    ReactDOM.render(<EchoInput />, document.getElementById('root'))
    

    我们通过遍历,得到了一个checkbox的列表,在点击选择时,我们通过index找到对应的数据,将其checked设置为当前反值,从而达到了选中与反选的效果。

    叁 ❀ 肆 textarea

    我们知道原生的textarea标签有两种设置值的做法,因为textarea有结束标签,所以可以通过innerHTML设置,其次也支持像input那样通过value设置,一般我们推荐通过value管理textarea的值,因为这样我们就能像input-text一样管理状态变化了,直接上例子:

    class EchoInput extends React.PureComponent {
        constructor(props) {
            super(props);
            this.state = { value: '请输入内容' };
        }
    
        handleChange = (event) => {
            this.setState({ value: event.target.value });
        }
    
        render() {
            return (
                <form className="echo">
                    <label>
                        <textarea type="text" value={this.state.value} onChange={this.handleChange} />
                    </label>
                    <div>{'输入的内容为: ' + this.state.value}</div>
                </form>
            );
        }
    }
    
    ReactDOM.render(<EchoInput />, document.getElementById('root'))
    

    叁 ❀ 伍 select

    让我们再来看一看select的例子,比如这是一个听风是风曾用网名的列表:

    const names = [{ id: 0, value: '听风是风'}, { id: 1, value: '时间跳跃'}, { id: 2, value: '行星飞行'}]
    class EchoList extends React.PureComponent {
        constructor(props) {
            super(props);
            this.state = { name: '' };
        }
    
        handleChange = (event) => {
            this.setState({ name: event.target.value });
        }
    
        render() {
            const list = this.props.names.map(name => (
                <option value={name.value} key={name.id}>{name.value}</option>
            ))
            return (
                <form className='echo'>
                    <label>
                        听风是风的名字列表:
                        <select value={this.state.name} onChange={this.handleChange}>
                            {list}
                        </select>
                    </label>
                    <div>当前选择的是:{this.state.name}</div>
                </form>
            );
        }
    }
    
    ReactDOM.render(<EchoList names={names} />, document.getElementById('root'))
    

    在上述例子中,我们借用了循环生成了option选项列表,value属性与onChange属性均添加在select标签上。

    有同学可能就要问,如果我要给select添加默认值怎么办,传统select默认值通过selected属性实现,不过这里很简单,我们只需要在constructor中定义state时给予一个默认值即可,修改上述代码中的state初始化为:

    constructor(props) {
        super(props);
        this.state = { name: '行星飞行' };
    }
    

    刷新页面,你会发现select的选项已经默认选中了行星飞行。当然,select可以通过multiple属性让其支持多选,我们可以在select上添加该属性,并修改state的值为数组,这里只列出需要修改的部分:

    constructor(props) {
        super(props);
        this.state = { name: ['听风是风', '行星飞行'] };
    }
    
    <select value={this.state.name} onChange={this.handleChange} multiple={true}>
        {list}
    </select>
    

    叁 ❀ 受控组件与非受控组件

    在上文介绍的表单中有一个通性,所有表单元素的value都由state提供,同时当值变化时也是通过setState去更新state中对应的值,对于这些组件它们,它们就像工具人一般存在,自身的值与行为都受外部控制,所以这类组件在react中被称为受控组件。总结来说,这类组件的value是受控制的,它接受外部传递的state属性作为值,以及一个操控该值的方法。

    那么既然有受控组件,是不是也有非受控组件呢?那必须有,与受控组件最直接的区别是,非受控组件可以接受外部传递的一个默认值,但除此之外,非受控组件会自己管理值的状态变化(就像文章开头我们说的原生input标签),获取非受控组件的value不再是通过state,而是由DOM节点提供,我们来看个例子:

    class NameForm extends React.Component {
        constructor(props) {
            super(props);
            this.input = React.createRef();
        }
        handleClick = (event) => {
            console.log('输入的内容为: ' + this.input.current.value);
        }
    
        render() {
            return (
                <label>
                    Name:
                    <input type="text" defaultValue={'我是默认值'} ref={this.input} />
                    <input type="button" value="Submit" onClick={this.handleClick} />
                </label>
            )
        }
    }
    
    ReactDOM.render(<NameForm />, document.getElementById('root'))
    

    在上述例子中,我们通过defaultValue属性为组件提供默认值,除此之外,你会发现input的变化与state没有任何关系,input的vaule如何变化由input组件自身管理,那这就是一个非受控组件的例子。

    上文中我们通过ref直接操作dom并获取了dom的值,如果你有了解的vue,应该不会对此属性陌生。事实上,不管是vue还是react,子组件获取父组件的属性可以通过属性传递,有些特殊的场景,父组件可能也要获取子组件的属性或者方法,那么ref就能解决这类问题。这里只是简单提及,具体用法我们会在后面具体解释,先别急。

    回归正题,对于非受控组件而言,你会发现对比受控组件,你不需要额外提供控制state变化的监听方法,因为value变化组件自身就已经帮你做了。

    在前面介绍input的类型中,其实我们少介绍了一种类型,也就是file类型,它用于上传文件,而这种类型的input也是典型的非受控组件。因为说直白点,你根本控制不了它的value,用户上传什么那么它的vaule就是什么,你无法通过方法手动去修改它的值,因为file类型的input的值为只读。

    class FileInput extends React.Component {
      constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
        this.fileInput = React.createRef();
      }
      handleSubmit(event) {
        event.preventDefault();
        console.log(
          `选择的文件为 - ${this.fileInput.current.files[0].name}`
        );
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Upload file:
              <input type="file" ref={this.fileInput} />
            </label>
            <br />
            <button type="submit">Submit</button>
          </form>
        );
      }
    }
    
    ReactDOM.render(
      <FileInput />,
      document.getElementById('root')
    );
    

    这是官网提供的一个例子,我们并未为input绑定value或者提供defaultValue,input内部值的变化完全由自身管理。

    准备来说,使用受控组件或者受控组件都是合理的,但至少你在设计组件之前,应该明确你的组件应该设计成哪种形式,因为在我目前的公司,就有人定义非受控组件,但又提供监听并改变值的方法,这会让人混乱,至少这种做法是非常不好的。

    肆 ❀ 总

    好了,那么到这里,我们介绍了react表单中几种常见的表单元素操作,但事实上,你需要手写表单组件的场景可能并不会很多,因为往往我们可能会使用到其它三方UI组件库,但即便如此,了解react中的原生做法也是有必要的,至少在日常开发中,遇到一些场景你能明白这些组件的value大致是如何运作的。

    初次之外,我们简单提及了react中受控组件与非受控组件的概念,任何东西都没有绝对意义上的好与坏,在合适的场景使用合适的做法那就是优秀的,当然我们也说了,至少别写出受控与非受控的混合体,这极不优雅。

    下周就是圣诞节了,时间过得很快,不知不觉2020年都到尾声了,心情还是有点激动啊,期待下周的来临,那么本文结束。

  • 相关阅读:
    服务器和虚拟机怎么安装Kaldi?
    SHSSS丨从人设到音色——基于说话人属性特征的语音合成
    Vue生命周期钩子
    快速入门React(基础)
    [Vue warn]: Failed to resolve filter: xxxxxxx
    Node.js express 连接数据库
    项目本地预览
    vue配置启动项目自动打开浏览器
    css 实现div内显示两行或三行,超出部分用省略号显示
    VSCode格式化代码配置
  • 原文地址:https://www.cnblogs.com/echolun/p/14165531.html
Copyright © 2020-2023  润新知