react总结之事件,this指向与ref
react的事件处理机制基本用法和DOM类似,但还是有一定的区别。它的事件类型采用驼峰命名,直接将函数的声明当成事件进行传递,是一个合成事件。如下所示:onClick={this.add} 这里的事件要加上on且Click首字母大写,this.add需要被花括号包裹起来。
//html <button onclick="add()">add</button> //react <button onClick={this.add}>add</button>
一、确定this指向
在react事件处理中尤为要注意this的指向问题,jsx中类的实例方法默认没有绑定this,如果在调用时忘记绑定,那this的值就为undefined。在js中函数的作用域是由函数调用时决定的,而不是由函数声明时决定的。第一次调用是作为对象中的函数调用,this是对象本身。第二次调用只是传递了一个函数名给变量并在变量后加上括号执行这个方法,这时它只是作为普通函数调用,this指向widow,在严格模式下this为undefined。
let obj = { name: 'davina', fn: function () { console.log(this.name); } } obj.fn();//davina let newfn = obj.fn; newfn();//undefined
我们可以通过下面的例子看到,在绑定事件时,只是简单的把当前的函数体给了onClick这个属性,真正调用的话是onClick()而不是this.add()。
class App extends React.Component { state = { count: 100 } add() { console.log(this);//undefined //...... } render() { return <button onClick={this.add}>add</button> } } ReactDOM.render(<App />, document.getElementById('root'))
为了解决这个问题,我们需要将this的指向确定为这个实例本身,可以有以下4种方法:
方法一利用了属性初始化语法,将方法初始化为箭头函数,在创建时就绑定了this,不需要在类构造函数中再进行绑定。非常的方便,建议使用。
方法二三中,都是调用时再绑,每一次调用都会生成一个新的方法实例,如果这个回调函数作为一个属性值传入到低阶组件时,这些组件会再次渲染,因为每一次都是新的方法实例作为新的属性传递,如果要用到bind方法可以用方法四,在构造函数中用bind绑定this来避免性能不必要的消耗。
方法四在类的构造函数中绑定了this,调用时不需要再进行绑定,只会生成一个方法实例,绑定后多个地方可以使用。
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); // 方法四:在构造函数中用bind绑定this this.add = this.add.bind(this) this.state={ count:100 } }; // 方法一:使用属性初始化语法绑定this add1 = () => { console.log(this); this.setState({ count: this.state.count + 1 }) } //方法二&三&四 add() { console.log(this); this.setState({ count: this.state.count + 1 }) } render() { let { count } = this.state; return <> {/* 方法一:使用属性初始化器语法绑定this,this指向当前实例 */} <button onClick={this.add1}>方法一</button> {/* 方法二:在调用时用箭头函数,箭头函数中的this是上级的this,上级为render,render中的this是组件 */} <button onClick={() => this.add()}>方法二</button> {/* 方法三:调用时用bind绑定this, */} <button onClick={this.add.bind(this)}>方法三</button> {/* 方法四:在构造函数中用bind绑定this */} <button onClick={this.add}>方法四</button> <h4>当前数字是:{count}</h4> </> } } ReactDOM.render(<App />, document.getElementById('root'))
二、参数传递
我们可以给事件处理程序传递额外的参数,来进行操作。
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { state = {count: 100} minus(x, e, y) {//注意参数和e的位置,参数在前,e在后 console.log(e.nativeEvent, e.target) this.setState({ count: this.state.count - x + y }) } minus2(n, m, e) { console.log(e.nativeEvent, e.target) this.setState({ count: this.state.count - n + m }); } render() { let { count } = this.state; return <> <button onClick={(e) => this.minus(10, e, 5)}>传参一</button> {/* 使用bind时,事件对象e隐式传递 */} <button onClick={this.minus2.bind(this, 10, 5)}>传参二</button> <h4>当前数字是:{count}</h4> </> } } ReactDOM.render(<App />, document.getElementById('root'))
三、键盘事件和自定义属性
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { userKeyCode = (e) => { let keycode = e.which || e.keycode; console.log(keycode); //给input设置自定义属性 let el = e.target || e.srcElement; console.log(el.getAttribute('my-color')) } render() { return( <> <input type="text" onKeyPress={this.userKeyCode} my-color="red" /> </>) } } ReactDOM.render(<App />, document.getElementById('root'))
有时UI界面的内容会根据不同的情况显示不同的内容,或者是决定是否渲染某部分内容,在vue中我们可以通过v-if/v-sow来控制,但在react中我们所有的条件判断都是和普通js代码是一致的。
// 需求:点击按钮让其在二者中不断的进行切换 import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); this.state = { isLogin: true } }; render() { let { isLogin } = this.state; let welcome = null; if (isLogin) { welcome = <h3>欢迎回来~~</h3> } else { welcome = <h3>请先登录~~</h3> } return <> {welcome} <button onClick={e => this.loginClick() }>{isLogin ? '退出' : '登录'}</button> </> } loginClick() { this.setState({ isLogin: !this.state.isLogin }) } } ReactDOM.render(<App />, document.getElementById('root'))
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props) this.state = { isLogin: true } } render() { return ( <div> <button onClick={e => { this.loginClick() }}> {this.state.isLogin ? '退出' : '登录'} </button> {/*这样的话像是vue中的v-show它只是控制显示与隐藏,如果经常的进行切换时可以用它,比较的节省性能*/} <h2 block' : 'none' }}> 你好啊,davina~~ </h2> </div> ) } loginClick() { this.setState({ isLogin: !this.state.isLogin }) } } ReactDOM.render(<App />, document.getElementById('root'))
四、ref
什么是ref,ref是react提供用来操纵DOM元素或者是组件的接口。在react中,通常情况下是不需要也不建议直接操作原生DOM的,但有时会有一些特殊的情况存在,如管理焦点,触发动画,这时我们就要使用创建refs来获取对应的DOM。
目前有三种方法来实现上面的要求:
1、当传入字符串时,通过this.refs传入字符串格式获取对应的元素
2、当传入一个函数,这个函数会在被挂载时进行回调,传入一个元素对象,使用时直接拿到之前保存的元素对象就可
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { render() { return ( <> <input ref='refInputOne' /> + <input ref='refInputTwo' /><button onClick={this.addOne}>=</button> <input ref='result' /> <br /> <input ref={num => this.refInputOne = num} /> +<input ref={num => this.refInputTwo = num} /><button onClick={this.addTwo}>=</button><input ref={num => this.result = num} /> </> ) } addOne=()=>{ let refInputOne = this.refs.refInputOne.value; let refInputTwo = this.refs.refInputTwo.value let result = parseFloat(refInputOne)+parseFloat(refInputTwo); this.refs.result.value = result } addTwo = () => { let refInputOne = this.refInputOne.value; let refInputTwo = this.refInputTwo.value let result = parseFloat(refInputOne) + parseFloat(refInputTwo); this.result.value = result } } ReactDOM.render(<App />, document.getElementById('root'))
以上两种方法都不推荐,后续可能被废弃,但是要知道。下面是推荐使用的方法
3、要使用必须先创建,refs是使用React.createRef函数创建的,通过ref属性附加到react元素上,当ref被传递给render中的元素时,可以通过ref的current属性来找到这个元素。react 会在组件挂载时给current属性传入DOM元素。
当 ref 属性用于html元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。我们不能在函数组件上使用ref属性,因为它们没有实例,无法通过ref获取他们的实例,但某些时候我们可以通过React.forwardRef获取到函数组件中的某个DOM元素或者是在hooks中学习如何使用ref。如果通过state实在做不到的事,再考虑使用ref,最好是不用。
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { constructor(props){ super(props) //创建一个ref来存储dom元素 this.refInputOne = React.createRef(); this.refInputTwo = React.createRef(); this.result = React.createRef(); } add=()=>{ let numA = this.refInputOne.current.value; let numB = this.refInputTwo.current.value; this.result.current.value = parseFloat(numA)+parseFloat(numB); } componentDidMount(){ //直接使用原生的api,使第一个input框获得焦点 this.refInputOne.current.focus(); } render() { return( <> <input ref={this.refInputOne} / > + <input ref={this.refInputTwo} /> <button onClick={this.add}> = </button> <input ref={this.result} / > </> ) } } ReactDOM.render(<App />, document.getElementById('root'))
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { constructor(props) { super(props) this.counterRef = React.createRef(); } render() { return ( <> {/* ref放到组件上 */} <Counter ref={this.counterRef} /> <button onClick={this.appBtnClick}>btn</button> </> ) } appBtnClick = () => { console.log(this.counterRef) this.counterRef.current.increment(); } } class Counter extends React.Component { constructor(props) { super(props); this.state = { counter: 0 } } render() { return ( <div> <h2>当前计数: {this.state.counter}</h2> <button onClick={e => this.increment()}>+1</button> </div> ) } increment() { this.setState({ counter: this.state.counter + 1 }) } } ReactDOM.render(<App />, document.getElementById('root'))
五、受控组件与非受控组件
在react中,html表单的处理方式和普通的DOM元素不一样,表单元素通常会保存在一些内部的state中。DOM元素的值受到react状态的控制,所以叫做受控组件。
在html中,表单元素通常是自己来维护state,并根据用户的输入来进行更新操作,我们在input元素中设置了value属性,所以显示的值将始终是this.state.value,这导致了react的state成为了唯一的数据源。由于handleChange在每次按钮时都会执行更新react中的state所以显示的值会随着用户的输入而更新。
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props) this.state = {value: ''} } render() { return (<> 用户:<input type='text' value={this.state.value} onChange={e => this.handleChange(e)} /> <button>提交</button> </>) }
//进行监听 保持数据的更新一致 handleChange = (event) => { // console.log(event.target.value) this.setState({ value: event.target.value }) } } ReactDOM.render(<App />, document.getElementById('root'))
如果有多个input框需要同时进行处理,因为这时需要多处多个监听,所以我们可以巧用es6中的计算属性名(computed property names)。
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); this.state = { username: '', password: '', valid: '' }} render() { return (<> <from onSubmit={e => this.handleSubmit(e)}> <div>用户:<input type="text" name="username" onChange={e => this.handleChange(e)} value={this.state.username} /></div> <div>密码:<input type="text" name="password" onChange={e => this.handleChange(e)} value={this.state.password} /></div> <div>验证码:<input type="text" name="valid" onChange={e => this.handleChange(e)} value={this.state.valid} /></div> </from> <div><input type='submit' value='提交' /></div> </>) } // handleSubmit(event){ // event.preventDefault(); // } handleChange(event) { this.setState({ //属性名可以不用写死 [event.target.name]: event.target.value }) } } ReactDOM.render(<App />, document.getElementById('root'))
前面我们知道受控组件可以是看成DOM元素值受到react中state控制,那简而言之非受控组件就是DOM元素值不受react中state控制,DOM元素的值是在DOM元素的内部。非受控组件它是通过直接操作DOM的方式来进行的。大多数情况下,我们使用受控组件来处理表单数据,如果要使用非受控组件通常使用defaultValue/defaultChecked/来设置默认值。
//使用ref获取input元素
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); this.state={username:'hello'} this.usernameRef=React.createRef(); } render() { return (<> 用户:<input type='text' defaultValue={this.state.username} ref={this.usernameRef} onChange={e => this.handleChange(e)} /> <button>提交</button> </>) } handleChange(){ console.log(this.usernameRef.current.value) } } ReactDOM.render(<App />, document.getElementById('root'))