React 绑定事件的四种方式 表单事件 受控组件与非受控组件

1. 简介

React 元素的事件处理和 DOM 元素类似。但是有一点语法上的不同:

  • React 事件绑定属性的命名采用驼峰式写法,而不是小写,例如原生的事件全是小写 onclick,React 里的事件是驼峰 onClick。
  • 如果采用 JSX 的语法需要传入一个函数作为事件处理函数,而不是一个字符串(DOM 元素的写法)。
  • React的事件并不是原生事件,而是合成事件。
  • 在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为, 你必须明确的使用 e.preventDefault()。

HTML 通常写法是:

<button onclick="clickHandle()">提交</button>

<script>
function clickHandle(e){
	e.preventDefault();	//阻止默认事件
	//或者使用如下的写法
	return false;
}
</script>

React 中写法为:

<button onClick={clickHandle}>提交</button>

<script>
function clickHandle(e){
	e.preventDefault();	//阻止默认事件,不能使用return false
}
</script>

2. React 绑定事件的几种方式

  • 当使用 ES6 class 语法来定义一个组件的时候,事件处理器会成为类的一个方法。
  • 必须谨慎对待 JSX 回调函数中的 this,类的方法默认是不会绑定 this 的。如果你忘记绑定 this.handleClick 并把它传入 onClick, 当你调用这个函数的时候 this 的值会是 undefined。
  • 通常情况下,如果你没有在方法后面添加 () ,例如 onClick={this.handleClick},你应该为这个方法绑定 this。

2.1 方式一

在回调函数中使用箭头函数,直接在 render() 里写行内的箭头函数(不推荐),这种写法存在的问题就是,当每次执行render() 的时候都会创建一个不同的回调函数。在大多数情况下,这没有问题。然而如果这个回调函数作为一个属性值传入低阶组件,这些组件可能会进行额外的重新渲染。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Box extends Component {
  handleClick(e,val){
  	console.log(e,val);
  }
  render() {
    return (
      <button onClick={(e)=>this.handleClick(e,'aa')}>添加</button>
    );
  }
}

ReactDOM.render(
  <Box />,
  document.getElementById('root')
);

2.2 方式二

在 render() 方法中,使用 bind 绑定 this(不推荐)。直接在组件内定义一个非箭头函数的方法,然后在render里直接使用onClick={this.handleClick.bind(this)},这种方式的缺点是,每次都使用 bind 绑定 this,代码会冗余:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Box extends Component {
	handleClick(val,e) {	//事件对象e要放在最后
		console.log(val,e);
	}
	render() {
		return (
			<button onClick={this.handleClick.bind(this, 'aa')}>添加</button>
		);
	}
}

ReactDOM.render(
  <Box />,
  document.getElementById('root')
);

2.3 方式三

使用属性初始化器来正确的绑定回调函数(推荐),在组件内使用箭头函数定义一个方法,这种方式的缺点是不能自定义传参:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Box extends Component {
	handleClick =(e)=>{
		console.log(e);
	}
	render() {
		return (
			<button onClick={this.handleClick}>添加</button>
		);
	}
}

ReactDOM.render(
  <Box />,
  document.getElementById('root')
);

2.4 方式四

直接在组件内定义一个非箭头函数的方法,然后在constructor里bind(this)(推荐),这种方式的优点是性能比较好,不管render()执行多少次,最终都指向同一个引用。这种方式的缺点是不能自定义传参。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Box extends Component {
	constructor(){
		super();
		this.myhandleClick = this.handleClick.bind(this);
	}
	handleClick(e){
		console.log(e);
	}
	render() {
		return (
			<button onClick={this.myhandleClick}>添加</button>
		);
	}
}

ReactDOM.render(
  <Box />,
  document.getElementById('root')
);

3. React 事件传参

和普通浏览器一样,事件 handleClick 会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。不同的是 React中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagationevent.preventDefault 这种常用的方法。

为事件处理程序传递额外的参数,通常使用上面前两种(2.1、2.2)方式,即:

<button onClick={(e)=>this.handleClick(e, 'aa')}>添加</button>
<button onClick={this.handleClick.bind(this, 'aa')}>添加</button>

值得注意的是,通过 bind 方式向监听函数传参,在类组件中定义的监听函数,事件对象 e 要排在所传递参数的后面。

3.1 子组件中修改参数

在子组件中修改父组件传过来的参数,比较推荐的方法是, 在父组件中定义方法,子组件中调用父组件的方法,通过props传递到子组件中,然后在子组件中通过this.props.method来调用。看下面的例子:

子组件中代码:

class Button extends Component {
  handleClick(){
  	//执行DOM元素的 change属性
    this.props.change();
  }
  render() {
    return (
     <button onClick={()=>this.handleClick()}>
       {this.props.children}
     </button>
    );
  }
}

父组件中代码:

class Counter extends Component {
  constructor() {
    super();
    this.state = {
      count: 0
    }
  }
  handleChange(type) {
    this.setState((preState, props) => {
          count: preState.count + 1
      } 
    }, () => { })
  }

  render() {
    return (
      <div >
        <Button change={() => this.handleChange()}>-</Button>
      </div>
    );
  }
}

4. 表单事件

HTML 表单元素与 React 中的其他 DOM 元素有所不同,因为表单元素生来就保留一些内部状态。

在 HTML 当中,像 <input>, <textarea>, 和 <select> 这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。

4.1 受控组件

对于受控组件来说,输入的值始终由 React 的 state 驱动。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

例如,如果想让用户输入的小写字母全部转成大写字母,我们可以将表单写为受控组件:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Form extends Component {
  constructor() {
    super();
    this.state = {name: ""}
  }
  handleChange(event) {
    this.setState({
      name: event.target.value.toUpperCase() //	将输入的字母转成大写
    })
  }
  render() {
    return (
      <div>名称:<input type="text" value={this.state.name} onChange={this.handleChange.bind(this)} /></div>
    );
  }
}

ReactDOM.render(
  <Form />,
  document.getElementById('root')
);

由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。使用 onChange 事件来监听 input 的变化,并修改 state。由于 handlechange 在每次按键时都会执行并更新 React 的 state,将字母转成大写,因此显示的值将随着用户输入而更新。

4.2 多个 input 元素

  • 当有多个 input 元素需要处理时,你可以通过给每个元素添加一个 name 属性,来让处理函数根据 event.target.name的值来选择做什么。
  • 在 React 中,<textarea> 使用 value 属性代替。这样,可以使得使用 <textarea> 的表单和使用单行 input 的表单非常类似。
  • 在 React 中,Select 下拉菜单,不使用 selected 属性,而在根 select 标签上用 value 属性来表示选中项,当 Select 为多选时,对应的 state 应该为一个数组。
  • 注意:<input type=“file”> 的 value 只读,所以它是 React 中的一个非受控组件。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Form extends Component {
  constructor() {
    super();
    this.state = {
      name: "",
      desc: "",
      city: [1,2]
    }
  }
  handleChange(event) {
    if(event.target.name==='city'){
      this.state.city.push(event.target.value);
      this.setState({})
      return;
    }
    this.setState({
      [event.target.name]: event.target.value
    })
  }
  render() {
    return (
      <div>
        名称:<input type="text" value={this.state.name} name="name" onChange={this.handleChange.bind(this)} />
        描述:<textarea name="desc" value={this.state.desc} onChange={this.handleChange.bind(this)}></textarea>
        城市:<select name="city" multiple 
        value={this.state.city}
        onChange={this.handleChange.bind(this)}>
          <option value="1">北京</option>
          <option value="2">上海</option>
          <option value="3">天津</option>
        </select>
      </div>
    );
  }
}

ReactDOM.render(
  <Form />,
  document.getElementById('root')
);

总的来说,这使得 <input type=“text”>, <textarea> 和 <select> 之类的标签都非常相似—它们都接受一个 value 属性,你可以使用它来实现受控组件。

4.3 受控组件应用场景

受控组件的应用场景:可以监听用户输入,改变用户输入的数据。例如上面将输入的字母转成大写、判断用户是否输入为空等。

如果你想寻找包含验证、追踪访问字段以及处理表单提交的完整解决方案,使用 Formik 是不错的选择。然而,它也是建立在受控组件和管理 state 的基础之上

有时使用受控组件会很麻烦,因为你需要为数据变化的每种方式都编写事件处理函数,并通过一个 React 组件传递所有的输入 state。在这些情况下,你可能希望使用非受控组件, 这是实现输入表单的另一种方式。

4.4 非受控组件

非受控组件,组件展示的值,完全不受 state 的控制。 使用 ref 属性,用来绑定到 render() 输出的任何组件上。通过 ref 属性获取绑定的 DOM 元素,跟 vue 中 ref 的使用类似。

ref 使用方法:

当 ref 属性值为静态内容时:
1、绑定一个 ref 属性到 render 的返回值上;
2、通过 this.refs 获取绑定的 DOM 元素;

当 ref 属性值为 state 时:
1、使用 import 导入 react.createRef ;
2、将 state 作为 ref 的属性值,绑定到 render 的返回值上;
3、通过 state.current 获取绑定的 DOM 元素;

import React, { Component,createRef } from 'react';
import ReactDOM from 'react-dom';

class Form extends Component {
  constructor() {
    super();
    this.title = createRef()
  }
  handleSubmit = () => {
    console.log(this.title.current.value)
    console.log(this.refs.myInput.value)
  }
  render() {
    return (
      <div>
        <input type="text" ref={this.title}/>
        <input type="text" ref='myInput'/>
        <button onClick={this.handleSubmit}>提交</button>
      </div>
    );
  }
}

ReactDOM.render(
  <Form />,
  document.getElementById('root')
);
发布了139 篇原创文章 · 获赞 51 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Charissa2017/article/details/105642021