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.stopPropagation
、event.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')
);