【React】表单应用

表单说明

  • 有以下示例代码:
import React, { Component } from "react";

class App extends Component {
    state = {
        msg: "hello world",
    };
    render() {
        return (
            <div>
                <input type="text" value={this.state.msg} />
            </div>
        );
    }
}

export default App;
  • 通过运行后我们可以在浏览器的consle控制台找到React给予我们的提示:
    在这里插入图片描述
  • 通过上述的警告提示,我们可以得知,在React中并不存在类似于Vue的双向数据绑定操作。此处需要注意以下几点:
1. Vue中的`v-model`是语法糖
2. 在React里使用的是**单向**数据流
  • 由于在React里数据流是单向的,所以我们就必须得考虑一个问题:怎么获取用户在表单中输入的数据呢?解决办法:
3. 给表单项添加`onChange`事件【受控组件】
4. 给表单项的value/checked,设置成defaultValue/defaultChecked【非受控组件】
  • React推荐我们使用受控组件。

受控组件(推荐)

1.HTML中:
- 表单元素(如`input``textarea``select`)通常自己维护state,并且根据用户输入进行更新
2. 在React中:
- 可改变状态通常保存在组件的state属性中,并且只能通过使用`setState()`来更新
3. 两者结合:
- 使React的state成为唯一数据源,React组件控制着用户输入过程中表单发送的操作
  • state与表单项中的value值绑定在一起,由state的值来控制表单元素的值,称为受控组件。
绑定步骤:
1. 在state中添加一个状态,作为表单元素的value值
2. 给表单元素绑定change事件,将表单元素的值设置为state的值

所谓受控组件,顾名思义就是受React控制的组件。其特点是将state在表单中的更新,通过onChange事件来进行操作。

  • 受控组件必须要求表单项中要有俩个组合的情况:
1. 针对普通的文本框、多行文本框等:
- value属性+onChange事件类型
2. 针对单选按钮组/复选框:
- checked属性+onChange事件类型
  • 每次onChange事件的触发都会重新渲染页面

  • 在受控组件上指定 value 的 prop 会阻止用户更改输入。
  • 如果你指定了 value,但输入仍可编辑,则可能是你意外地将value 设置为 undefined 或 null
<input value={null} />

案例分析

state中约定一个用户的基本信息,要求将信息展示在表单中,允许被用户修改,点击提交按钮获取用户修改后的最新的信息(state中的)

import React, { Component } from "react";

class App extends Component {
    // 初始化用户信息(项目中的话信息是来自于ajax)
    state = {
        userInfo: {
            uname: "zhangsan",
            mail: "zhangsan@1000phone.com",
            // 社交属性性别
            gender: "女",
            hobbies: ["1", "2"],
        },
    };
    render() {
        // 先结构用户信息,用起来方便
        let { uname, mail, gender, hobbies } = this.state.userInfo;
        return (
            <div>
                <div>
                    昵称:
                    <input type="text" name="uname" value={uname} onChange={this.vModel.bind(this)} />
                </div>
                <div>
                    邮箱:
                    <input type="text" name="mail" value={mail} onChange={this.vModel.bind(this)} />
                </div>
                <div>
                    性别:
                    <input type="radio" name="gender" value="男" checked={gender === "男" ? true : false} onChange={this.vModel.bind(this)} /><input type="radio" name="gender" value="女" checked={gender === "女" ? true : false} onChange={this.vModel.bind(this)} /></div>
                <div>
                    爱好:
                    <input type="checkbox" name="hobbies" value="1" checked={hobbies.includes("1") ? true : false} onChange={this.vModel.bind(this)} />
                    吃饭
                    <input type="checkbox" name="hobbies" value="2" checked={hobbies.includes("2") ? true : false} onChange={this.vModel.bind(this)} />
                    睡觉
                    <input type="checkbox" name="hobbies" value="3" checked={hobbies.includes("3") ? true : false} onChange={this.vModel.bind(this)} />
                    撸代码
                </div>
                <button onClick={this.submit.bind(this)}>提交</button>
            </div>
        );
    }

    // 修改用户名
    // chgUname(e) {
    //     // 修改state中的数据
    //     // 获取最新的数据
    //     let val = e.target.value;
    //     // 获取要改的属性名
    //     let name = e.target.name;
    //     // 修改数据
    //     this.setState((state) => {
    //         // 对象有一个特点:引用传值
    //         state.userInfo[name] = val;
    //         return state;
    //     });
    // }

    // // 修改邮箱
    // chgMail(e) {
    //     let val = e.target.value;
    //     // 获取要改的属性名
    //     let name = e.target.name;
    //     // 修改数据
    //     this.setState((state) => {
    //         // 对象有一个特点:引用传值
    //         state.userInfo[name] = val;
    //         return state;
    //     });
    // }

    // 封装受控组件的表单数据与state的绑定关系
    vModel(e) {
        // 修改state中的数据
        // 获取最新的数据
        let val = e.target.value;
        // 获取要改的属性名
        let name = e.target.name;
        // 修改数据
        this.setState((state) => {
            // 判断是否是复选框的处理
            if (e.target.type === "checkbox") {
                // 多选
                // console.log('走了多选');
                // 思路:先去判断当前的值在原来的数据中是否存在,存在则移除,不存在则添加
                let index = state.userInfo[name].indexOf(val);
                if (index > -1) {
                    state.userInfo[name].splice(index, 1);
                } else {
                    state.userInfo[name].push(val);
                }
            } else {
                // 对象有一个特点:引用传值
                state.userInfo[name] = val;
            }
            return state;
        });
    }

    // 提交表单
    submit() {
        console.log(this.state.userInfo);
    }
}

export default App;
  • 运行结果:
    在这里插入图片描述
  • 注意点:
如果需要将原来应该分别处理的事件合在一起去写的话,
一定需要多传递一个参数用于记录当前修改的是state中的哪个值,
此时可以选择一些方案的任意一种:
1. 通过相同 name 属性值去辨别,给每个表单项设置与 state 中相同的 key 名的 name 值,然后通过事件对象去获取
2. 可以直接在事件绑定的位置传递标记,例如:
onChange = {this.changeHandler.bind(this,'username')}
------------------------------------------
表单项中表单类型为 checkbox 的比较特殊,与其他类型的不同,需要特殊处理(取反操作,而其余的表单项是来什么值用什么值)

input 标签

  • 由于在表单元素上设置了value属性,因此显示的值将始终为this.state.value,这使得react的state成为唯一数据源。
  • 由于handlechange在每次按键时都会执行并更新React的state,因此显示的值将随用户输入而更新
  • 对于受控组件来说,输入的值始终由React的state驱动。
  • 你也可以将value传递给其他UI元素,或者通过其他事件处理函数重置,但这意味着你需要写更多的代码
  • 例如:在表单提交是打印出名字
class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
  }

  handleChange=(event)=> {
    this.setState({value: event.target.value});
  }

  handleSubmit=(event)=> {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}
  • 当需要处理多个 input 元素时,
  • 我们可以给每个元素添加 name 属性,
  • 并让处理函数根据 event.target.name 的值选择要执行的操作。
class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };
  }

  handleInputChange=(event)=> {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          参与:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          来宾人数:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}
  • 运行结果:
    在这里插入图片描述

textare 标签

  • 在HTML中,textarea元素通过其子元素定义其文本
<textarea>
  你好, 这是在 text area 里的文本
</textarea>
  • 而在React中,textarea使用value属性替代。
  • 这样使得使用textarea的表单和使用单行input的表单非常类似
class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
    };
  }

  handleChange=(event)=> {
    this.setState({value: event.target.value});
  }

  handleSubmit=(event)=> {
    alert('提交的文章: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          文章:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}
  • 运行结果:
    在这里插入图片描述

  • 注意:this.state.value初始化与构造函数中,因此文本域默认有初始值

select 标签

  • 在HTML中,select创建下拉列表标签。
  • 例如:
<select>
  <option value="grapefruit">葡萄柚</option>
  <option value="lime">酸橙</option>
  <option selected value="coconut">椰子</option>
  <option value="mango">芒果</option>
</select>
  • 由于selected属性的缘故,椰子选项默认别选中
  • React并不会使用selected属性,而是在根select标签上使用value属性。
  • 这在受控组件中更便捷,因为你只需要在根标签中更新他
class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};
  }

  handleChange=(event)=> {
    this.setState({value: event.target.value});
  }

  handleSubmit=(event)=> {
    alert('你喜欢的风味是: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          选择你喜欢的风味:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
            <option value="mango">芒果</option>
          </select>
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}
  • 运行结果:
    在这里插入图片描述
    在这里插入图片描述

  • 总的来说,这使得, <input type="text">, <textarea><select>之类的标签都非常相似

  • 它们都接受一个 value 属性,你可以使用它来实现受控组件。


  • 你可以在数组传递到value属性中,以支持在select标签中选择多个选项
  • <select multiple={true} value={['B', 'C']}>
class MulFlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: "coconut",
      arr: [],
      options: [
        { value: "grapefruit", label: "葡萄柚" },
        { value: "lime", label: "酸橙" },
        { value: "coconut", label: "椰子" },
        { value: "mango", label: "芒果" }
      ]
    };
  }

  handleChange=(e)=>{
    let idx = this.state.arr.findIndex(item=>{
      return item === e.target.value
    })
    if (idx >= 0) {
      this.state.arr.splice(idx,1);
    } else {
      this.state.arr.push(e.target.value);
    }
    let arr = this.state.arr;
    this.setState({arr});
  }

  render() {
    return (
      <div>
        <select multiple={true} value={this.state.arr} onChange={this.handleChange}>
          {this.state.options.map((item,index) => {
            return <option value={item.value} key={index}>{item.label}</option>;
          })}
        </select>
      </div>
    );
  }
}
  • 运行结果
    在这里插入图片描述

非受控组件

  • 非受控组件:指的是不受react控制的组件
  • 注意点:
1. 关于ref:
	它是react中用户获取组件/元素对象的一个对象,
	在使用时需要通过 createRef() 进行创建
	然后将创建好可变数据绑定到组件/元素上
	后续根据元素的类型获取到对应信息:
		- 如果元素是html标签的话,则获取到的是dom信息
		- 如果元素是组件标签的话,则获取到的是该组件对应的组件实例(整个实例,是一个对象)
2. 非受控组件在实际表单处理应用中,获取到的都是dom对象,也就是说非受控组件基本使用的都是dom操作
3. 非受控组件操作的时候需要配合:
	defaultValue 和 defaultChecked 属性来实现,
	该操作不会像受控组件一样,每次改变数据导致页面组件重新渲染
4. 在使用ref的时候如果一个ref要对应多个表单项(如单选按钮组/复选框),
	则不能给每个元素都加ref,需要给其公共父去加

因为非受控组件将真实数据储存在 DOM 节点中,这时表单数据将交由 DOM 节点来处理。需要借助 ref 对象,来实现对表单项的操作,来从 DOM 节点中获取表单数据。

使用步骤:
1. 调用React.createRef()方法创建ref对象
2. 将创建好的ref对象添加到文本框中
3. 通过ref对象获取到文本框的值
  • 一般表单项少的时候可以考虑使用非受控组件。
class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.input = React.createRef();
  }

  handleSubmit=(event) =>{
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

案例分析

约定一个用户的基本信息,要求将信息展示在表单中,允许被用户修改,点击提交按钮获取用户修改后的最新的信息

import React, { Component, createRef } from "react";

class App extends Component {
    constructor(props) {
        super(props);
        // 创建ref对象
        this.ref_uname = createRef();
        this.ref_mail = createRef();
        this.ref_gender = createRef();
        this.ref_hobbies = createRef();
        // ref对象是后续获取表单最新数据的唯一途径
        // 建议性操作,将所有的ref丢到一个数组中(后续可以循环取值)
        this.refArr = [this.ref_uname, this.ref_mail, this.ref_gender, this.ref_hobbies];
    }
    // 初始化用户信息(项目中的话信息是来自于ajax)
    state = {
        userInfo: {
            uname: "zhangsan",
            mail: "zhangsan@1000phone.com",
            // 社交属性性别
            gender: "女",
            hobbies: ["1", "2"],
        },
    };
    render() {
        console.log("走了render");
        // 先结构用户信息,用起来方便
        let { uname, mail, gender, hobbies } = this.state.userInfo;
        return (
            <div>
                <div>
                    昵称:
                    <input type="text" ref={this.ref_uname} name="uname" defaultValue={uname} />
                </div>
                <div>
                    邮箱:
                    <input type="text" ref={this.ref_mail} name="mail" defaultValue={mail} />
                </div>
                <div ref={this.ref_gender}>
                    性别:
                    <input type="radio" name="gender" value="男" defaultChecked={gender === "男" ? true : false} /><input type="radio" name="gender" value="女" defaultChecked={gender === "女" ? true : false} /></div>
                <div ref={this.ref_hobbies}>
                    爱好:
                    <input type="checkbox" name="hobbies" value="1" defaultChecked={hobbies.includes("1") ? true : false} />
                    吃饭
                    <input type="checkbox" name="hobbies" value="2" defaultChecked={hobbies.includes("2") ? true : false} />
                    睡觉
                    <input type="checkbox" name="hobbies" value="3" defaultChecked={hobbies.includes("3") ? true : false} />
                    撸代码
                </div>
                <button onClick={this.submit.bind(this)}>提交</button>
            </div>
        );
    }

    // 提交:在点击之后获取最新的表单数据
    submit() {
        // 用于保存所有的数据,此处不能使用state及setState(一旦使用就是受控组件)
        let data = { userInfo: {} };
        // console.log(this.ref_uname.current);
        // 循环所有的ref逐个进行处理
        this.refArr.forEach((ref) => {
            // 获取到当前的dom对象
            let obj = ref.current;
            // 判断是否存在多个子
            if (obj.children.length > 0) {
                // 多个元素(还需要判断是单选还是复选)
                for (var i = 0; i < obj.children.length; i++) {
                    if (obj.children[i].type === "radio" && obj.children[i].checked) {
                        // 将单选按钮组中选中的那个值赋给data
                        data.userInfo[obj.children[i].name] = obj.children[i].value;
                    }
                    if (obj.children[i].type === "checkbox" && obj.children[i].checked) {
                        // 将多选框中选中的值保留
                        if (!data.userInfo[obj.children[i].name]) {
                            data.userInfo[obj.children[i].name] = [];
                        }
                        data.userInfo[obj.children[i].name].push(obj.children[i].value);
                    }
                }
            } else {
                // 单个元素
                let name = obj.name;
                let val = obj.value;
                data.userInfo[name] = val;
            }
        });
        // 最终的data就是需要提交的数据
        console.log(data);
    }
}

export default App;

在这里插入图片描述

默认值

非受控组件不能给表单项加value属性,一旦加了它就不是非受控组件了,它就成了受控组件了。如果有默认值请通过defaultValue属性进行输出。

  • 在 React 渲染生命周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,
  • 在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新
  • 在这种情况下, 你可以指定一个 defaultValue 属性,而不是 value
render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input
          defaultValue="Bob"
          type="text"
          ref={this.input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}
  • 同样,<input type="checkbox"><input type="radio"> 支持 defaultChecked<select><textarea> 支持 defaultValue

文件输入

  • 在 HTML 中,<input type="file"> 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。
  • 在 React 中,<input type="file"> 始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。
  • 您应该使用 File API 与文件进行交互
import React, { Component } from "react";

class App extends Component {
    fileRef = React.createRef()
    imgRef = React.createRef()
    getData = () => {
      const file = this.fileRef.current.files[0]
      const reader = new FileReader()
      reader.readAsDataURL(file)
      const _this = this
      // 注意this的指向
      reader.onload = function () {
        _this.imgRef.current.src = this.result
      }
    }
    render() {
      return (
        <div>
          <input type="file" ref={ this.fileRef }/>
          <button onClick = { this.getData }>预览图片</button>
          <img src="" ref={ this.imgRef } alt=""/>
        </div>
      )
    }
  }

export default App;
  • 运行结果:
    在这里插入图片描述
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一颗不甘坠落的流星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值