听风是风

学或不学,知识都在那里,只增不减。

导航

从零开始的react入门教程(三),了解react事件与使用注意项

壹 ❀ 引

从零开始的react入门教程(二),从react组件说到props/state的联系与区别一文中,我们介绍了react组件的基本用法以及props与state的区别。其中react组件分为函数组件class组件,函数组件一般比较简单,它的函数名首字母需要大写,接受外部传入的props并返回react元素,这是一个简单的函数组件。

function App(props) {
    return <div>{props.name}</div>
}

除此之外就是功能性更强的Class组件,如下是一个简单的react Class组件。

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: '听风是风'
        }
    }
    componentDidMount() {
        console.log('dom渲染完成');
    }
    render() {
        console.log('此时开始渲染dom');
        return <h1>我是{this.state.name}</h1>
    }
}

在上述例子中,我们在constructor中初始化了State并继承父组件传入的props(props与state将共同构成组件App的数据),在render方法中我们定义了组件App需要渲染的dom结构,而在componentDidMount方法中我们可以做一些渲染完成后的操作,比如发起接口请求。这三个方法都属于react生命周期钩子函数,关于更详细的生命周期介绍,我会在后续单独拿一篇文章展开介绍。

除了介绍组件外,我们还介绍了react中非常重要的概念propsState,react是单向数据流,对于一个组件而言,它能接收外部传递的数据props,需要注意的是props仅限只读,不可修改,如果要修改可以在父级专门提供修改数据的方法一并传递过来,或者子组件拷贝一份自己玩。而State则是组件内部定义的私有属性,react提供了专门的方法用于修改State,同样State也能作为props传递给自己的子组件,props与State一同构成了react的数据流。

贰 ❀ 奇怪的事件绑定

在上篇文章中,我们定义了一个简单的让数字自增的组件,但在方法绑定上至少是让我非常难受,比如下面这个例子:

// 这是一个子组件,它会调用父组件传递过来的方法
class Son extends React.Component {
    render() {
        return <button onClick={() => this.props.click()} >click me</button>
    }

}
// 这是一个父组件,它会传递一个方法给子组件调用
class Parent extends React.Component {
    state = {
        name: '听风是风'
    }
    handleClick() {
        console.log(this.state.name);
    }
    render() {
        return <Son click={() => this.handleClick()} />
    }
}

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

不知道大家有没有对于上述例子中的事件绑定/事件传递感到别扭,按我们印象中理解事件绑定如vue或者小程序,大多数应该是如下这样,但react硬是写成了一个箭头函数,方法后还带了圆括号,非常反人类:

<button onClick={事件名} >click me</button>

如果我们将上面的例子的事件传递去除掉外层的箭头函数以及圆括号,点击按钮就会报错:

<Son click={this.handleClick} />

但如果我们保留父组件箭头函数式的事件传递,只是将子组件调用处改为如下这样,你会发现又能正常执行:

<Son click={() => this.handleClick()} />
<button onClick={this.props.click} >click me</button>

这说明问题就出在事件传递上,而不是使用上。我们再看个奇怪的例子:

// 同样是调用父组件方法的子组件
class Son extends React.Component {
    render() {
      	// 注意,这里的方法调用方法被修改了
        return <button onClick={this.props.click} >click me</button>
    }

}
// 同样是传递方法给子组件使用的父组件
class Parent extends React.Component {
  	// 注意,这里方法没用state的数据。
    handleClick() {
        console.log(1);
    }
    render() {
      	// 注意,这里方法传递被修改了
        return <Son click={this.handleClick} />
    }
}

很神奇,当父组件传递过去的方法没用state数据,我们在方法传递与使用上似乎都可以按照传统的写法了。不卖关子,其实当我们修改上面例子事件传递出错时,错误已经说的很清楚,访问不到name,本质上就是因为this丢失了,this都没了,自然访问不到state数据或者props,这才出了错。而前文click={() => this.handleClick()这种写法的目的就是为了将this与方法绑定在一起。

而第二个例子中,传递方法内部并没有与state数据或props有联系,你只要把方法给我调用就好了,所以才可以按照我们理解的去改写后还能正常执行。

那么问题就来了,动动小脑瓜子想想项目开发中大部分方法肯定会与state挂钩,难道我们在方法传递上难道就一定要忍受这份屈辱吗?其实并不是,对于事件的this绑定上react提供了好几种方式,我们接着说。

叁 ❀ react事件绑定

叁 ❀ 事件命名

与传统事件命名习惯一样,react对于事件命名同样推荐小驼峰,比如getUserNameverifyEmail,在了解这些之后,让我们从最简单的点击事件说起。

传统JS实现点击依赖的是onclick事件,比如:

<button onclick="eventName()">
  click me
</button>

其中onclick响应用户点击行为,点击之后会触发eventName中的的代码逻辑。同理,在react中也封装了类似的交互事件,比如点击事件在react中为onClick,监听表单输入变化的事件onChange等等;react在事件监听名与事件响应方法命名上官方也有推荐,一般为on[Event]handle[event],我们看个例子:

class Parent extends React.Component {
    state = {
        num: 1,
    }
    handleClick() {
        this.setState(state => ({
            num: ++state.num
        }));
    }

    render() {
        return (
            <div className="demo">
                <div>{this.state.num}</div>
                <button onClick={() => this.handleClick()} >click me</button>
            </div>
        )
    }
}

(注意,由于class在react中属于关键字,所以得使用className,经过react封装效果与class一样)

但一个组件中可能存在多个点击响应方法,大家都叫handle[event]就难以区分了,所以开发中可以用handle[语义化方法名]来命名我们的响应函数,比如handleGetUserName

这里我们只是举例说明了事件其一的onClick,类似经过react封装的合成事件还有很多,比如复制粘贴onCopy、onCut、 onPaste,滚动事件onScroll等等,若对于是否有这个事件可用的疑问时,可以扫一遍官方事件列举表react合成事件,用法这里就不一一介绍了。

叁 ❀ 贰 事件的this绑定

我们在上文中描述了react对于事件绑定的奇怪做法,这么做的原因是由于在JavaScript中,class默认不会帮你绑定this,如果我们不做绑定,在事件函数传递时会导致this丢失,从而为undefined。

react一共提供了三种为函数绑定this的做法,第一种我们已经在上文中有举例了,第二种做法是在组件constructor中利用bind直接绑定this,比如:

class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            num: 1
        }
        // 在constructor中为方法绑定this
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        this.setState(state => ({
            num: ++state.num
        }));
    }

    render() {
        return (
            <div className="demo">
                <div>{this.state.num}</div>
                {/* 注意,这里就不用箭头函数返回 */}
                <button onClick={this.handleClick} >click me</button>
            </div>
        )
    }
}

第三种做法其实与第一种做法类似,只是箭头函数不是写在事件绑定后,而是在方法声明时就已经使用了箭头函数,比如:

class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            num: 1
        }
    }
    // 声明时使用箭头函数
    handleClick = () => {
        this.setState(state => ({
            num: ++state.num
        }));
    }

    render() {
        return (
            <div className="demo">
                <div>{this.state.num}</div>
                {/* 注意,这里就不用箭头函数返回 */}
                <button onClick={this.handleClick} >click me</button>
            </div>
        )
    }
}

那么关于事件的this绑定就说到这。

叁 ❀ 叁 事件中的e对象

在事件处理时,我们往往会有需要知道点击的是哪一个元素的场景,比如我们通过一段模板遍历生成了多个元素,它们都绑定的为同一个事件响应函数,我们希望点击这个元素时获取它的专属class名怎么做到呢?与angularjs或vue一样,react同样提供了e对象,比如:

class Parent extends React.Component {
    handleClick = (e) => {
        console.dir(e.target)
    }

    render() {
        return (
            <div className="demo">
                <button onClick={(e) =>{this.handleClick(e)}} >click me</button>
            </div>
        )
    }
}

点击按钮,我们在控制台打印了这个dom对象,就像js一样,我们可以访问到很多我们熟悉的属性,比如下面的例子,我们点击元素获取当前元素的class名:

class Parent extends React.Component {
    state = {
        name: ''
    }
    handleClick(e) {
        this.setState({ name: e.target.className });
    }

    render() {
        const numbers = [1, 2, 3, 4, 5];
        const listItems = numbers.map((numbers, index) =>
            <li className={'li-' + numbers} onClick={(e) => { this.handleClick(e) }} key={index}>{numbers}</li>
        );
        return (
            <div className="demo">
                <ul>
                    {listItems}
                </ul>
                <div>当前点击的li的class名为:{this.state.name}</div>
            </div>
        )
    }
}

除此之外,我们可能还会有阻止元素默认行为或者阻止事件冒泡等类似的需求,e对象也能帮我们做到这一点,比如e.preventDefault()

传递e对象也有两种行为,上述例子中我们提供的是显示,很直接的将e作为参数传递了进度,第二种是通过bind隐式传递,它们效果是一致的,像这样:

handleClick = (id, e) => {
	// do something...
}
// 显示,e直接卸载参数上
<button onClick = { (e)=> this.handleClick( id, e ) }></button>
// 隐式,通过bind绑定,但还是可以在方法中通过e获取
button onClick = { this.handleClick.bind( this, id ) }></button>

另外,当我们同时需要获取event以及传递自定义参数时,还支持函数柯里化的写法,下面写法等同于上面写法:

handleClick = ( id ) => {
	return (event) => {
    console.log( event, id )
  }
}

//函数合理化写法 
button onClick = { this.handleClick( id ) }></button>

肆 ❀ 总

为什么这么久没更新呢,仔细想想理由,应该是懒散 > 工作饱和人比较疲惫 > 家里的电脑坏了懒得用公司笔记本,为了恢复博客更新频率,现在已经在强迫自己下班后留在公司花2小时左右时间学习与写博客了,所以不出意外(一定)这个月会恢复以往频率,毕竟还是还是得努力学习才行。

这篇文章只是简单介绍了react事件的用法以及部分注意项,由于react合成事件还是挺多,一个个说篇幅过长,大部分事件用法与我们以往所用框架相同,所以我的观点是需要用某个事件了,去扫一遍官网提供的事件,用几次自然就熟悉了。

另外由于react官方文档说的比较简短,为了避免部分知识点官网介绍不全,我也在读相关react书籍,尽可能对于一个知识点多搜集一些信息,下篇文章应该是周末更新,那么本文就先写到这里了。十点了,下班去坐地铁!!!

posted on 2020-12-03 21:56  听风是风  阅读(569)  评论(4编辑  收藏  举报