第二节:React组件基本使用和事件处理

1. 对于组件的理解

官网定义:

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素,(虚拟DOM)。

组件通俗的理解就是JavaScript构造函数, 知识构造函数返回一个虚拟DOM


2. 组件的使用

2.1 组件分类

既然React组件就是构造函数,在JS中构造函数有两种写法,ES5的构造函数和ES6的类

因此组件也分为两种

  1. 函数组件,就是ES5的构造函数, 通常称为无状态组件
  2. class组件,就是ES6的类,通常继承React.Component, 为有状态的组件

至于什么是状态,状态就是state,后面再聊


2.2 组件的基本使用

注意,组件名必须大写,如果小写会被react认为是原生的DOM元素

<div id="box"></div>
<div id="box2"></div>

<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.js"></script>

<script type="text/babel">
    // 1. 定义组件
    // 方法一: 工厂函数组件(简单的组件:就是没有状态的组件)
    function MyComponent(){
      return <h2>工厂函数组件</h2>
    }

    // 方法二: ES6 类组件(复杂组件)
    class MyComponent2 extends React.Component{
      // 类组件中的渲染函数
      render (){
        return <h2>ES6 类组件</h2>
      }
    }

    // 2. 渲染组件标签
    // 函数组件在渲染时,Reach会触发函数的执行, 返回React元素
    ReactDOM.render(<MyComponent />, document.getElementById('box'));
    
    // class组件, React会先实例化一个组件对象, 通过组件对象调用render方法
    // render方法调用返回一React元素, 生成虚拟DOM 
    ReactDOM.render(<MyComponent2 />, document.getElementById('box2'))
</script>

示例说明:

  1. 函数组件的效率要高于Class组件,原因在于函数组件直接调用.class组件还要实例化
  2. 但是函数是无状态组件,如果需要定义有状态的组件(复杂组件),需要使用Class组件
  3. Class组件中,必须实现render函数, 返回React元素(虚拟DOM)


3. 组件的嵌套使用

说明:

  1. 组件的牵头使用, 就是在一个组件的React元素中,可以任意使用其他组件
  2. 组件在使用时可以多次复用

示例代码:

// 组件的嵌套使用
// 1. 定义子组件
function MyList(){
    return (
        <ul>
            <li>苹果</li>
            <li>香蕉</li>
            <li>梨子</li>
        </ul>
    )
}


// 2. 父组件
class MyCom extends React.Component{
    render(){
        return (
            <div>
                <h2>父组件</h2>
                {/* 组件复用: 注意JSX语法中注释写在{}里 */}
                <MyList />
                <MyList />
                <MyList />
            </div>
        )
    }
}

// 将组件渲染到页面上
ReactDOM.render(<MyCom/>, document.getElementById("app"))


4. 组件中的事件

React元素的事件处理和DOM元素很相似, 但是语法上有所不同


4.1 React元素事件与DOM元素的不同
  1. React事件命名采用小驼峰(camelCase),而不是纯小写.例如onClick
  2. 使用JSX语法时,需要传入一个函数作为事件处理函数,而不是一个字符串

示例代码如下

<!--
    html中DOM事件
    onclick事件名为纯小写
    handleClick() 为字符串, 浏览器会将字符串转成JS语句执行
-->
<div onclick="handleClick()">点击</div>



<!--
    React中JSX语法
    事件名为小驼峰onClick
    事件处理函数为普通的JS函数或方法
    JSX语法中 {}里是JS表达式, 因此handleClick是一个标识符
-->
<div onClick={ handleClick }>点击</div>


4.2 事件的使用
4.2.1 函数组件中绑定事件

示例代码如下:

let flag = false
function MyCom(){
    function handleClick(){
        console.log(11);
        flag = !flag
        
        // 调用render重新渲染
        render();
    }

    return <div onClick={handleClick}>{ flag ? "天王盖地虎" : "小鸡炖蘑菇"}</div>
}

// 渲染方式一
// ReactDOM.render(<MyCom/>, document.getElementById("app"))


// 渲染方式二
// 将React.render渲染函数封装
function render(){
  ReactDOM.render(<MyCom/>, document.getElementById("app"))
}
render()

示例代码说明:

  1. 其实函数组件没有状态,我们只能利用JS变量模拟状态
  2. 当示例中点击事件发生后,flag变量值会被改变,
  3. 但是如果采用渲染方式一,页面不会发生改变, 因为变量的改变不会触发React重新渲染
  4. 因此我们就需要采用第二种方法,将渲染函数封装, 在事件触发后手动的触发封装函数,重新渲染


4.2.2 class组件中绑定事件

示例代码如下:

class MyCom extends React.Component{
    constructor(){
        super();
        this.state = {
            flag : true
        }
    }

    handleClick(){
        console.log(111);

        this.setState({
            flag: !this.state.flag
        })

    }

    render(){
        return <div onClick={ this.handleClick.bind(this) }>{ this.state.flag ? "天王盖地虎" : "小鸡炖蘑菇"}</div>
    }

}

ReactDOM.render(<MyCom/>, document.getElementById("app"))

示例代码说明:

  1. class组件中定义了state状态
  2. 状态中的flag在通过setState方法改变后,会自动触发React重新渲染
  3. 因此这里我们就需要在将ReactDOM.render方法封装了,因为状态的改变会触发React重新渲染
  4. 在class组件中会看到大量使用this,因为React会实例化组件对象,通过组件对象调用内部方法


4.3 class组件中事件处理函数的this指向问题

采用普通定义事件处理函数的方式,并且普通的调用,来查看this指向问题

实例代码:

class MyCom extends React.Component{
    constructor(){
        super();
    }

    handleClick(){
        console.log(this);  // window
    }

    render(){
        return <div onClick={ this.handleClick }>点击</div>
    }

}

ReactDOM.render(<MyCom/>, document.getElementById("app"))

实例说明:

  1. 如果你测试过代码就会了解,此时的事件处理函数中的this是window
  2. 丢失this原因在于React元素会被编译为React.createElement虚拟DOM,就丢失了this作用域
  3. 如果在函数中不使用任何组件内部的状态或其他方法,这样写没有任何问题
  4. 可是如果你希望在事件处理函数中能够获取到组件实例中的方法或状态,就需要使用到this


4.4 如何解决this指向

解决this指向主要在两大方向思考对策

  1. 在事件处理函数绑定时
  2. 在事件处理函数定义时

无论是在绑定事件处理函数还是在定义事件处理函数都有多个方法


4.4.1 绑定事件函数时通过bind修改this

示例代码如下

class MyCom extends React.Component{
    constructor(){
        super();
    }

    handleClick(){
        console.log(this);
    }

    render(){
        /* bind强制this绑定 */
        return <div onClick={ this.handleClick.bind(this) }>点击</div>
    }

}

ReactDOM.render(<MyCom/>, document.getElementById("app"))

示例说明:

  1. 在绑定事件处理函数时通过bind方法强制修改函数内部this指向,为当前的this.
  2. 当前绑定的this就是组件实例


4.4.2 绑定箭头函数返回事件处理函数的自执行

示例代码如下:

class MyCom extends React.Component{
    constructor(){
        super();
    }

    handleClick(){
        console.log(this);
    }

    render(){
        /*  箭头函数 */
        return <div onClick={ () => this.handleClick() }>点击</div>
    }

}

ReactDOM.render(<MyCom/>, document.getElementById("app"))

示例说明:

  1. 其实这里绑定的事件处理函数是箭头函数
  2. 在箭头函数中执行需要处理逻辑的函数,如this.handleClick()
  3. 此时函数handleClick的this 就是指向当前调用的this
  4. 当前调用handleClick的this就是组件实例对象,


4.4.3 将事件处理函数绑定给实例对象自己的属性

实例代码如下:

class MyCom extends React.Component{
    constructor(){
        super();
        // 这句代码的意思,是将原型上的方法强制绑定this以后赋值给实例对象自己
        this.handleClick = this.handleClick.bind(this)
    }

    // 这个方法是定义在实例原型对象上的
    handleClick(){
        console.log(this);
    }

    render(){
        //  这里this.handleClick 会优先绑定组件实例自己的方法
        return <div onClick={ this.handleClick }>点击</div>
    }

}

ReactDOM.render(<MyCom/>, document.getElementById("app"))

实例说明:

  1. 原理绑定是事件是原型上的方法
  2. 通过将原型方法赋值给实例自己的属性并强制修改方法内部this以后
  3. 在绑定事件处理函数时,就会优先绑定组件实例自己的方法,
  4. 那么this也就重新执行组件实例
  5. 但是也有不好的地方,就是组件实例和原型上都有相同方法,原型上还不用


4.4.4 使用箭头函数定义事件处理函数

实例代码:

class MyCom extends React.Component{
    constructor(){
        super();
    }

    // 使用箭头函数定义事件处理函数
    handleClick = () => {
        console.log(this);
    }

    render(){

        return <div onClick={ this.handleClick }>点击</div>
    }

}

ReactDOM.render(<MyCom/>, document.getElementById("app"))

实例说明:

  1. 采用箭头函数定义事件处理函数,
  2. 这样事件处理函数本事将没有this
  3. 事件处理函数的this将只想父作用域的this


4.5 事件对象

事件对象主要看是如何绑定的, 分为两种

  1. 直接绑定事件处理函数
  2. 通过bind绑定事件处理函数
  3. 通过绑定箭头函数,在箭头函数中执行之前需要处理的函数


4.5.1 直接绑定事件处理函数

示例代码:

class MyCom extends React.Component{
    //...
    handleClick = (ev) => {
        console.log(ev);    // class事件对象
        console.log(event)  // 原生事件对象
    }
    render(){
        //  这里this.handleClick 会优先绑定组件实例自己的方法
        return <div onClick={  this.handleClick }>点击</div>
    }
}

示例说明:

  1. 直接绑定事件处理函数, 那么事件对象默认会是第一个参数
  2. 这种绑定方式没办法给事件处理函数传递额外的参数


4.5.2 通过bind绑定事件处理函数

示例代码:

class MyCom extends React.Component{
    constructor(){
        super();
    }

    // 这个方法是定义在实例原型对象上的
    handleClick(num,ev){
        console.log(ev);  // class事件对象
        console.log(event); // 原生事件对象
        console.log(num);   // 20
        console.log(this);  // 组件实例对象

    }

    render(){
      
        return <div onClick={  this.handleClick.bind(this,20) }>点击</div>
    }

}

示例说明:

  1. 通过bind绑定的事件对象,可以传递额外的参数
  2. 在事件处理函数中,所有的额外参数接受完毕以后,最后还有一个形参就是事件对象


4.5.3 箭头函数绑定事件处理函数

示例代码如下

class MyCom extends React.Component{
    constructor(){
        super();
        // 这句代码的意思,是将原型上的方法强制绑定this以后赋值给实例对象自己

    }

    // 这个方法是定义在实例原型对象上的
    handleClick(ev,num){
        console.log(ev);  // class事件对象
        console.log(event); // 原生事件对象
        console.log(num);   // 20
        console.log(this);  // 组件实例对象
    }

    render(){
        //  这里this.handleClick 会优先绑定组件实例自己的方法
        return <div onClick={  (ev) => {
                this.handleClick(ev,20)
            }}>点击</div>
    }

}

示例说明:

  1. 此时的事件处理函数严格的来说应该是手动绑定的箭头函数
  2. 因此事件对象就是箭头函数的第一个参数
  3. 真正需要执行逻辑的函数需要手动调用
  4. 因此事件对象也需要手动的传递
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341