组件允许开发者将复杂的UI页面拆分为独立可复用的代码片段,并对每个代码片段进行独立构思。React中的组件,在概念上类似于JavaScript函数,它接收任意的入参(对应当该组件的props属性),返回值为描述页面区块内容的React Virtual DOM-虚拟DOM元素或者元素的集合。

React框架允许开发者定义两种基本组件:①函数式组件,本质上就是一个JavaScript函数;②Class类式组件,基于ES6的class类语法,结合extends继承机制实现组件实例的定义。

目录

函数式组件

函数式组件:基本语法

函数式组件案例:三栏布局

JSX基本语法规则补充

混入变量

添加css样式选择器

添加内联样式

虚拟DOM的根标签个数

案例实现:三栏布局

函数式组件使用规则

函数式组件解析规则

class类式组件

ES6:Class类语法

预热:构造函数生成对象

Class类定义

prototype:实例属性|实例方法

私有属性定义与存值函数|取值函数

static:静态属性|静态方法

class:使用注意点

class:类中this指向问题与规避方法

extends:单继承语法与原型链

Minin:class类的多继承实现

React:Class类式组件定义

React:Class类式组件-绑定点击事件


函数式组件

函数式组件:基本语法

React官网提到:定义组件的最简单方式就是编写JavaScript函数。而React函数式组件的定义语法如下,

//参数:props是React组件实例的三大属性之一,用于接收外界使用组件时绑定到组件上的一些属性
function Welcome(props) {//返回值:基于JSX语法创建的虚拟DOM元素return <h1>Hello, {props.name}</h1>;
}

函数式组件案例:三栏布局

如何使用函数式组件呢?下面通过React中的函数式组件来实现一个三栏布局页面,最终效果如下所示,

基本思路为:将整个页面进行组件化拆分,即:将页面拆分为三个组件,左侧:Left,中间:Mid,右侧:Right,将其定义为三个函数式组件。

JSX基本语法规则补充

在基于React核心库和Babel语法转换库实现上述案例之前,我们先来了解一下JSX语法的其它规则,探讨一下如何在JSX语句中混入外部变量、添加样式选择器/内联样式等基本操作。

使用JSX语法定义虚拟DOM时,不能加引号,这在之前的《》一文中已有提及,下面看一些其它规则和使用示例,

混入变量

混入变量:在JSX语法创建的虚拟DOM中使用外部变量,需要使用{}-花括号包裹;

示例代码如下,

//1-获取React应用的外部DOM容器
const container = document.getElementById("app");//2-ReactDOM.render-虚拟DOM-${参数1:将要被添加的虚拟DOM;2-应用容器}const variable = 'Hello,React!';ReactDOM.render(<div>{variable}   </div>,container);

添加css样式选择器

上面干巴巴的文字显示略微丑陋,我们可以为其添加css样式选择器,进行修饰。

 混入样式选择器:为JSX语法创建的虚拟DOM添加样式时,例如:指定class选择器,需要为其className属性指定class-类选择器的名称。

例如,为其添加如下预先定义好的样式选择器,

    <style>.font-class{font-size: 24px;color: #fff;font-weight: 700;background-color: skyblue;}</style>

示例代码如下,

 //1-获取React应用的外部DOM容器  const container = document.getElementById("app");//2-ReactDOM.render-虚拟DOM-${参数1:将要被添加的虚拟DOM;2-应用容器}const variable = 'Hello,React!';ReactDOM.render(<div className='font-class'>{variable}   </div>,container);

添加内联样式

当然,如果不想额外通过style标签定义外部样式,直接添加内联样式,React的虚拟DOM元素也是支持的。规则如下,

混入内联样式:style={{key:value,...,key:value}}; -表示在{}中用JSON对象来指定样式

我们尝试通过添加内联样式实现和上面相同的效果,示例代码如下,

//1-获取React应用的外部DOM容器
const container = document.getElementById("app");
//2-ReactDOM.render-虚拟DOM-${参数1:将要被添加的虚拟DOM;2-应用容器}
const variable = 'Hello,React!';
ReactDOM.render(<div style={{fontSize:"24px",color:"#fff",backgroundColor:"skyblue",fontWeight:700}}>{variable}   </div>,container);

虚拟DOM的根标签个数

VDOM-虚拟DOM的根标签个数:只能有1个。如果不唯一就会报错,这一点和Vue2的单位件组件是一致的。

案例实现:三栏布局

方便起见,我们直接为单个组件添加内联样式,整体采用flex布局,左中右的宽度比例为1:8:1。示例代码如下,

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>react-函数式组件</title><style>*{padding:0;margin: 0;box-sizing: border-box;}</style>
</head><body><!-- DOM容器 --><div id="app"></div>
</body>
<!-- 引入React开发库 -->
<script src="./js/libs/react.development.js"></script>
<script src="./js/libs/react-dom.development.js"></script>
<!-- 引入浏览器适用的babel,作用:将jsx语法代码转换为js代码 -->
<script src="./js/libs/babel.min.js"></script>
<!-- type='text/babel'-表示script标签中的代码将要通过babel进行语法转换 -->
<script type="text/babel">/*** 创建函数式组件-{VDOM+CSS+JS}-使用JSX语法创建* props-组件的属性,Object对象类型* *///左侧function Left(props){return <div style={{height:"100%",backgroundColor:"skyblue",flex:1}}>{props.name}</div>;}//中间function Middle(props){return <div style={{height:"100%",backgroundColor:"lightgreen",flex:8}}>{props.name}</div>}//右侧function Right(props){return <div style={{height:"100%",backgroundColor:"pink",flex:1}}>{props.name}</div>}const container = document.getElementById("app");//2-ReactDOM.render-虚拟DOM-${参数1:将要被添加的虚拟DOM;2-应用容器}ReactDOM.render(<div style={{display:"flex",flexDirection:"row",justifyContent:"space-around",height:'100vh',backgroundColor:"#ccc"}}><Left name='Left'/><Middle name='Middle'/><Right name='Right'/></div>,container);
</script></html>

函数式组件使用规则

以上,基于函数式组件实现了三栏布局,但是还有一些细节点需要注意,

[1] JSX中的标签-(函数式组件标签)
            必须闭合
            若开头字母为小写,则将其转换为html标签;若不存在,则会报错;
            若开头字母为大写,react则将其作为子组件进行渲染;若为定义,则会报错;
[2] JSX中可以嵌套

第一点,由于是函数式组件,因此,从组件定义到标签使用,开头字母都必须大写,否则会报错,报错信息如下,

第二点,JSX中可以进行虚拟DOM结点的嵌套,以上案例通过div元素嵌套三个函数式组件,以虚拟DOM形式作为参数传递到ReactDOM.render()函数内部。

函数式组件解析规则

函数式组件本质上就是一个JavaScript函数,只不过是整合了JSX的写法,返回值固定式VDOM类型的对象。这一点通过上面的案例相信你一定有所体会,至于其中使用到的props属性,将在之后的博文中进行介绍。

        函数式组件解析规则可描述为:当执行了ReactDOM.render()语句之后,React会尝试去解析<Left/>、<Middle/>、<Right/>三个组件,找到组件的定义代码——判断为函数式组件,就直接去调用Left、Middle、Right函数,并将返回的VDOM转化为真实DOM,随后呈现在页面之中显示。

class类式组件

React框架允许开发者基于ES6的Class类和extends继承语法定义类式组件,只需要继承React核心库react.js中暴露出来的React.Component父类即可。

ES6:Class类语法

在正式开始介绍React的类式组件之前,有必要回顾一下ES6的Class类定义和使用的语法规则,主要内容为:

【1】Class类定义;

【2】Class类实例属性和实例方法定义;

【3】实例属性新写法与取值|存值函数;

【4】静态属性和静态方法定义;

【5】私有属性定义;

【6】class类的使用的注意点

【7】class类的this指向问题

【8】extends继承语法与原型链

【9】MixIn多继承语法

针对以上条目,您可以有选择性的阅读感兴趣的部分。

预热:构造函数生成对象

JavaScript中,生成实例对象的传统方法是通过构造函数,当然也还有一些其它方案,如:通过字面量直接创建、通过new Object的形式进行创建。对于构造函数的形式,其实就是在定义一个可复用的对象模板,举个简单的例子如下,

  //3-构造函数定义对象模板-可复用的function Point(x, y) {//成员属性this.x = x;this.y = y;}//挂载实例属性Point.prototype.name = "Point-Function";//挂载实例方法Point.prototype.getName = function (){return this.name;}Point.prototype.getX = function() {return this.x;}Point.prototype.getY = function (){return this.y;}Point.prototype.getLocation = function (){return `(${this.x},${this.y})`;}Point.prototype.toString = function() {return "x=" + this.x + ",y=" + this.y;};//挂载静态成员到构造函数身上//静态属性-{对象不可访问,构造函数名.msg可访问}Point.msg = "Point_class";//静态方法-{对象不可访问,构造函数名.showMsg()可访问}Point.showMsg = function() {console.log(Point.msg);};//创建实例对象var point = new Point(100,150);//通过Point类的对象调用实例成员console.log(point.name);console.log(point.getLocation());//通过构造函数调用静态成员console.log(Point.msg);Point.showMsg();

Class类定义

通过构造函数生成JavaScript对象实例的方式,固然可以满足实际开发需求,但是,这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。所以,ES6引入了新的Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类,然后通过new class类名的方式创建对象。

那么,如何定义一个类呢?语法其实非常简单,

class 类名{//构造器-固定写法constructor(参数列表){//todo:做一些实例属性初始化的操作this.propertyName = 参数1;...this.propertyName = 参数n;}    //实例成员methodName(参数列表){//toDo:实现一些功能}    }

prototype:实例属性|实例方法

以下通过class类的语法,将上面回顾部分中的Point对象模板重构为ES6-Class风格的类。示例代码如下,

    class Point{//构造器constructor(x,y) {//初始化实例属性x,ythis.x = x;this.y = y;this.name = "Point-Function";}//定义实例方法getName(){return this.name;}getX(){return this.x;}getY(){return this.y;}getLocation(){return `(${this.x},${this.y})`;}toString(){return `x=${this.x},y=${this.y}`;}}//定义Point类的对象const point = new Point(100,150);console.log(point.name);console.log(point.getName());console.log(point.getLocation());

看到这里,你可能有些疑惑。ES5中,我们通过将对象的方法挂载到prototype原型对象上的方式,来避免在每次创建对象时,都在堆内存中重复性的开辟额外空间,用于存储实例方法的问题。

那么,ES6中的class类定义形式,如何解决上述问题呢?答案是:无需做特殊处理。因为:构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,ES6-class类中的所有方法默认都被定义在类的prototype属性上面。

私有属性定义与存值函数|取值函数

熟悉Java后端编程语言的朋友应当十分了解,每当我们使用Java去定义一个类时,总会将成员属性私有化,并为类内部的成员属性添加getter/setter方法,体现的是类定义的一种封装思想,可以提升变量访问的安全性。

ES6的class-类语法规则中包含了如何实现类实例属性的私有化——在属性名之前通过#符号修饰,声明为一个私有属性,同时也可以提供接口供外部对象访问、修改私有属性——这就是存值函数(set函数)和取值函数(get函数)

以下,我们将为Point类定义一个私有属性version,并借助get/set函数来获取、修改它。

   class Point{//私有属性#version = 'v1.0';//存值函数set version(version){//设置#version私有属性的值之前,可以做一些过滤、预处理操作if(typeof version === "undefined" || version == null) return;this.#version = version;}//取值函数get version(){return this.#version;}//静态属性static msg = "Point_class";//静态方法static showMsg(){console.log(this);console.log(Point.msg);}//构造器constructor(x,y) {//初始化实例属性x,ythis.x = x;this.y = y;this.name = "Point-Function";}//定义实例方法getName(){return this.name;}getX(){return this.x;}getY(){return this.y;}getLocation(){return `(${this.x},${this.y})`;}toString(){return `x=${this.x},y=${this.y}`;}}//定义Point类的对象const point = new Point(100,150);//修改私有成员属性的值point.version = 'v2.0';console.log(point.version);

可以看到,存值函数(set函数)和取值函数(get函数)在定义之后,就可以像Point类实例自身的属性一样,通过=等号赋值,或者获取对应属性的值。

static:静态属性|静态方法

那么,如何在ES6-Class类中定义静态成员呢?

在静态方法的定义上,ES6引入了新的关键字static。(static关键字用于表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。)

在静态属性的定义上,ES6规范中,可以直接将静态属性挂载到Class类上面,也可以通过static关键字直接在类内部定义。

下面,我们借助static关键字,来对上面的Point类做进一步完善。

    class Point{//静态属性static msg = "Point_class";//静态方法static showMsg(){console.log(this);console.log(Point.msg);}//构造器constructor(x,y) {//初始化实例属性x,ythis.x = x;this.y = y;this.name = "Point-Function";}//定义实例方法getName(){return this.name;}getX(){return this.x;}getY(){return this.y;}getLocation(){return `(${this.x},${this.y})`;}toString(){return `x=${this.x},y=${this.y}`;}}//调用静态成员Point.showMsg();

值得一提的是:ES6-Class类内部的静态方法中,this代表当前类-Point本身,也就意味着,通过静态方法中的this,也可以拿到静态属性msg。但是,为了和成员方法进行区分,我们通常使用Point.msg,即:“类名.静态属性名”的方式进行调用。

class:使用注意点

为简化类型判断、类初始化操作,ES6新增了in关键字、static静态代码块之类的内容,具体可见:ES6 入门教程。下面,我们来看一些class类使用时的一些注意点,因为这可能影响到之后我们编写React的class类式组件。

①严格模式:JavaScript 严格模式(strict mode)即在严格的条件下运行。ES5中经常是通过"use strict" 指令实现,例如:JQuery脚本库源码中也开启了严格模式。

严格模式会对原来的代码产生什么影响呢?首要一点就是,我们无法使用未声明的变量,这意味着JavaScript会脱离以往“懒散”的代码结构,以更高的标准运行在浏览器上。

②不存在变量提升。意味着,Class类的使用,必须在定义之后,如下的写法是不被允许的。

③name属性。本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。name属性总是对应紧跟在class关键字后面的类名,除非你对它进行修改操作。

还有一些其它注意点或者技巧,如:Generator方法等,我们这里用不太到,暂时先跳过。

class:类中this指向问题与规避方法

还有一个重要的问题就是:Class类中的this指向问题。这将会影响到后续:在定义React类式组件过程中,尤其是事件回调方法的使用。

我们知道,如果类的成员方法内部含有this,那么它默认指向当前类的实例。但是,一旦我们开始尝试单独去调用类的成员方法,那么就很可能报错——原因:会导致成员方法的this指向发生改变。举个例子,

 class Point{constructor(x,y) {this.x = x;this.y = y;}getX(){console.log(this);return this.x;}getY(){console.log(this);return this.y;}}const point = new Point(100,150);console.log(point.getX());//100//下面做一个对象解构const { getY } = point;console.log(getY());//expect value:150,real value:直接报错

我们看一下输出结果,很明显,在解构之后,getY()方法在独立调用时,this的指向变了,已经变为undefined。

为什么是undefined呢?至少我们预期的,不是Point类的对象point,那它也应该是window全局对象啊。我们可能忽略了一点,上面有提到,ES6的class是在严格模式下执行的,我们无法去访问未定义的变量。而恰好,class内部的这个全局范围内,this变量是未定义的,并非指向window对象,所以是undefined。

那么,如何避免上述问题,让this指向我们想要指的那个地方呢?

有以下两种解决方法:

方法①:在constructor()构造器中显示调用bind()方法,修改this的指向。修改后的代码如下,

    class Point{constructor(x,y) {this.x = x;this.y = y;//bind-显示绑定this指向this.getY = this.getY.bind(this);}getX(){console.log(this);return this.x;}getY(){console.log(this);return this.y;}}const point = new Point(100,150);console.log(point.getX());//100//下面做一个对象解构const { getY } = point;console.log(getY());//expect value:150,real value:150.

此时,已经可以正常运行。

        方法②:使用箭头函数,箭头函数内部的this总是指向定义时所在的对象。且不看如何理解,我们先看下如何编写代码。

    class Point{constructor(x,y) {this.x = x;this.y = y;}getX(){console.log(this);return this.x;}getY = ()=>{console.log(this);return this.y;}}const point = new Point(100,150);console.log(point.getX());//100//下面做一个对象解构const { getY } = point;console.log(getY());//expect value:150,real value:150.

可以看到,现在确实是预期的输出结果。那么是什么原因呢?先明确一点,箭头函数中的this,是在函数定义时候绑定的,并且是固定不可变的之前会报错的形式,其this是在执行函数的时候绑定的。箭头函数在什么时候被定义呢?显然是和类一起定义的,因此,箭头函数内部的this会总是固定指向实例对象

extends:单继承语法与原型链

ES5中的传统继承方式比较复杂,方式种类内容也很多,包括:原型链继承、组合继承、寄生组合式继承等等,可以自行搜索学习。下面看ES6中的继承语法。

ES6的class继承语法借助extends关键字实现,规则十分之简单,规则如下,

class 父类{constructor(){}
}class 子类 extends 父类{constructor(){super();//必须显示调用父类构造函数}
}

注意到,子类通过extends关键字继承父类时,在构造函数内部,必须首先通过super关键字,调用父类的构造方法,这一动作的目的是:用来新建一个父类的实例对象。如果不做,那么程序就会报错。为什么呢?

原因在于:ES6规定,子类自己的this对象,必须要先通过父类的构造函数完成塑造,得到与父类相同的实例属性和方法,然后在对其加工,添加子类拓展出来的新的实例属性和方法。换言之,父类实例的创建必须在子类实例之前,或者说子类实例构建的原材料就是父类实例。所以,必须先得到这个原材料——父类实例,否则编译器它“巧妇难为无米之炊”啊,你想让它凭空捏造出来一个子类实例,它自然做不到,就会报错了。

在之后,通过extends关键字继承React.Component父类时,也一定要注意这一点。

另一点是,在子类的构造函数中,只有调用super()之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。

遵循如上规则的示例代码如下,

class Point {constructor(x, y) {this.x = x;this.y = y;}
}class ColorPoint extends Point {constructor(x, y, color) {//step-1super(x, y);//step-2this.color = color; this.color = color;
}

关于继承的特点,概括起来为:

【1】父类的私有属性子类无法继承;

【2】父类的实例属性、实例方法、静态属性、静态方法都会被继承。

除上面的内容之外,我们再谈一谈`原型链`。

我们知道,大多数浏览的ES5语法实现过程中,每个对象都有一个__proto__属性,指向对应的构造函数的prototype属性。其中:__proto__属性,是对象才有的;而prototype,是函数才有的。从网上摘录的一句比较清楚的解释如下,

举个例子,我们来探索一下prototype和__proto__之间的关系,示例代码如下,

function Point(x, y) {//成员属性this.x = x;this.y = y;}//挂载实例属性Point.prototype.name = "Point-Function";//挂载实例方法Point.prototype.getName = function (){return this.name;}Point.prototype.getX = function() {return this.x;}Point.prototype.getY = function (){return this.y;}Point.prototype.getLocation = function (){return `(${this.x},${this.y})`;}Point.prototype.toString = function() {return "x=" + this.x + ",y=" + this.y;};const point = new Point(100,150);//比较point.__proto__ === Point.prototypeconsole.log(point.__proto__ === Point.prototype);//打印point.__proto__console.log(point);//打印Point.prototypeconsole.log(Point.prototype);console.log(Point.prototype.prototype);

打印结果如下,

可以看到,

①Point.prototype完全等价于point.__proto__属性,表示的是同一个对象。

②当对Point构造函数取两次prototype,打印的结果为undefined。

我们来结合之前的描述,尝试通过画图来对上面的打印结果进行解释。

如上图所示,即为Point构造函数、Point原型对象、Point对象三者之间的存在的关系。

之前有提到:ES6的class类可以视为ES5的function构造函数的一种语法糖,其prototype属性在class上依旧存在。那么,在ES6的extends语法之下,如何描述上述这3者之间的关系呢?从网上摘录出来的一个解释如下,

不方编写如下示例代码,来对这种关系进行验证。

    class  A{}class B extends A{}//子类B的__proto__属性 完全等价于  Aconsole.log(B.__proto__ === A);//子类B原型的constructor属性 完全等价于 B自身,这也证明了,class确实是ES5中function构造函数的语法糖,本质上还是一个函数console.log(B.prototype.constructor === B);//关系推导:子类B原型的__proto__属性  完全等价于Aconsole.log(B.prototype.constructor.__proto__ === A);//关系延伸:子类B原型的__proto__属性 完全等价于 A的原型prototypeconsole.log(B.prototype.__proto__ === A.prototype);

输出结果如下,

在此,我依然想尝试使用画图的方式,对上面的输出结果进行解释。

(1)首先,先通过如下代码证明,ES5中推导出来的关系图完全适用于ES6-class关键字定义的类。图示和代码示例如下,

ES6-class类、prototype原型对象、类对象关系图解
    class  A{}class B extends A{}//创建A类的对象const  a = new A();console.log(A.prototype.constructor === A);console.log(a.__proto__ === A.prototype);console.log(a.__proto__.constructor === A);const b = new B();console.log(B.prototype.constructor === B);console.log(b.__proto__ === B.prototype);console.log(b.__proto__.constructor === B);

输出结果全部为true,这验证了上面说法。

(2)分析类A本身与类B之间的继承关系,主要是两条线:①构造函数的继承线;②方法的继承线。之所以这样区分,是因为(1)中,我们已经验证,Class A和Class B,就相当于是ES5中的构造函数function A和function B,本质上数据类型还是function;此外,类的实例方法是挂载到构造函数的原型对象prototype上的,所以要分两条线进行分析。其关系如下图所示,

ES6-继承关系路线分析

至此,关于extends类的继承和原型链关系分析部分的内容就结束了。除此之外,ES6还允许我们去继承原生内置构造函数,像:Error、Date、Array等(ES5中是不可以的,无法实现的),其基本语法规则和上述jextends继承语法规则一致,就不在此赘述了。

Minin:class类的多继承实现

经过上面的内容,我们不禁疑惑,ES6支持多继承吗?不管三七二十一,先写了代码再说。

    class  A{}class Base {}class B extends A,Base{}

运行之后,果然报红了,

那么,有没有什么办法可以实现多继承呢?即:让一个子类同时继承多个父类。办法是有的,ECMAScript 6入门教程-阮一峰的博客中给出了如下的解决方案。

①定义了mix函数,用于将多个对象合成为一个类,并返回这个类;

function mix(...mixins) {class Mix {constructor() {for (let mixin of mixins) {copyProperties(this, new mixin()); // 拷贝实例属性}}}for (let mixin of mixins) {copyProperties(Mix, mixin); // 拷贝静态属性copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性}return Mix;
}function copyProperties(target, source) {for (let key of Reflect.ownKeys(source)) {if ( key !== 'constructor'&& key !== 'prototype'&& key !== 'name') {let desc = Object.getOwnPropertyDescriptor(source, key);Object.defineProperty(target, key, desc);}}
}

②调用的时候,只需要让子类继承这个类即可。

class DistributedEdit extends mix(Loggable, Serializable) {// ...
}

React:Class类式组件定义

至此,关于ES6中class类语法规则的主要内容已经回顾完毕,下面叙述如何基于class类语法规则,定义一个React的类式组件。

React官网给出了一个简单例子如下,

class Welcome extends React.Component {render() {return <h1>Hello, {this.props.name}</h1>;}
}

主要思路就是:继承React核心库react.js中开发出来的类类型React.Component父类,并重写render()实例方法,基于JSX语法规则返回一个Virtual DOM-虚拟DOM元素。

下面,我们尝试基于class类式组件,实现前面的三栏布局效果,最终样式基本如下,

示例代码如下,

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>react-类式组件-三栏布局</title><style>*{padding:0;margin: 0;box-sizing: border-box;}</style>
</head><body><!-- DOM容器 --><div id="app"></div>
</body>
<!-- 引入React开发库 -->
<script src="./js/libs/react.development.js"></script>
<script src="./js/libs/react-dom.development.js"></script>
<!-- 引入浏览器适用的babel,作用:将jsx语法代码转换为js代码 -->
<script src="./js/libs/babel.min.js"></script>
<!-- type='text/babel'-表示script标签中的代码将要通过babel进行语法转换 -->
<script type="text/babel">/*** 定义 class 组件,需要继承 React.Component* [1] 类名即为React的组件名* [2] React.Component的子类必须覆写render()函数*///左侧-Leftclass Left extends React.Component{constructor(props){super(props);}render(){const {props} = this;return <div style={{height:"100%",backgroundColor:"skyblue",flex:1}}>{props.name}</div>;}}//中间-Middleclass Middle extends React.Component{constructor(props){super(props);}render(){const {props} = this;return <div style={{height:"100%",backgroundColor:"lightgreen",flex:8}}>{props.name}</div>;}}//右侧-Rightclass Right extends React.Component{constructor(props){super(props);}render(){const {props} = this;return <div style={{height:"100%",backgroundColor:"pink",flex:1}}>{props.name}</div>;}}const container = document.getElementById("app");//2-ReactDOM.render-虚拟DOM-${参数1:将要被添加的虚拟DOM;2-应用容器}ReactDOM.render(  <div style={{display:"flex",flexDirection:"row",justifyContent:"space-around",height:'100vh',backgroundColor:"#ccc"}}><Left name='Left'/><Middle name='Middle'/><Right name='Right'/></div>,container);
</script></html>

显示效果如下,

React:Class类式组件-绑定点击事件

严格来讲,此部分数据React事件处理部分的内容。我们在此处简单使用一下,来对应上面有讲到的关于Class-类中this指向的问题,在React类式组件定义中的应用。先看一下,官网关于使用部分的介绍,如下图所示,

那么,我们在使用时,只需要遵循上述规则,并绑定好事件,提供事件处理函数即可。先截个图,回忆一下原生DOM中的事件属性和描述信息。

需求描述:为上面的三栏布局,每一栏添加一个点击事件,点击事件触发之后,打印对应的组件显示文字内容。

实现思路:由于是JSX创建的虚拟DOM对象,那么,我们将onclick转换为小驼峰命名格式,并绑定到对应的JSX-VDOM节点上即可;此外,我们将事件回调函数写到React的class类式组件内部。

分步实现

(1)尝试为Left组件,绑定点击事件,示例代码如下,

(2)尝试点击Left子组件,但是发现控制台报错,显示当前this为undefined。

(3)立即想到,之前有介绍关于ES6-class类内部的this指向问题。由于:①函数的定义形式,规定了它内部的this指向是在运行时,由调用者确定的;②此处是在点击事件内部触发的回调函数,导致当前实例函数并不是由组件对象发起调用的,因此,在严格模式下,对应的this就变成了undefined。

那么,我们借助之前的思路,将handlerClick()实例方法的定义形式转换为箭头函数的形式,使其内部的this在handlerClick定义时就确定为当前类的对象——组件实例。修改代码如下所示,

(4)再次点击左侧的Left组件区域,控制台打印如下内容,即:当前方法内部的this指向当前Left组件自身,正确无误。

(5)为其它组件的绑定点击事件,完整的示例代码如下,

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>react-类式组件-三栏布局</title><style>*{padding:0;margin: 0;box-sizing: border-box;}</style>
</head><body><!-- DOM容器 --><div id="app"></div>
</body>
<!-- 引入React开发库 -->
<script src="./js/libs/react.development.js"></script>
<script src="./js/libs/react-dom.development.js"></script>
<!-- 引入浏览器适用的babel,作用:将jsx语法代码转换为js代码 -->
<script src="./js/libs/babel.min.js"></script>
<!-- type='text/babel'-表示script标签中的代码将要通过babel进行语法转换 -->
<script type="text/babel">/*** 定义 class 组件,需要继承 React.Component* [1] 类名即为React的组件名* [2] React.Component的子类必须覆写render()函数*///左侧-Leftclass Left extends React.Component{constructor(props){super(props);}handlerClick = () =>{console.log(this);const {props} = this;console.log(props.name);}render(){const {props} = this;return <div style={{height:"100%",backgroundColor:"skyblue",flex:1}} onClick={this.handlerClick}>{props.name}</div>;}}//中间-Middleclass Middle extends React.Component{constructor(props){super(props);}handlerClick = () =>{console.log(this);const {props} = this;console.log(props.name);}render(){const {props} = this;return <div style={{height:"100%",backgroundColor:"lightgreen",flex:8}} onClick={this.handlerClick}>{props.name}</div>;}}//右侧-Rightclass Right extends React.Component{constructor(props){super(props);}handlerClick = () =>{console.log(this);const {props} = this;console.log(props.name);}render(){const {props} = this;return <div style={{height:"100%",backgroundColor:"pink",flex:1}} onClick={this.handlerClick}>{props.name}</div>;}}const container = document.getElementById("app");//2-ReactDOM.render-虚拟DOM-${参数1:将要被添加的虚拟DOM;2-应用容器}ReactDOM.render(  <div style={{display:"flex",flexDirection:"row",justifyContent:"space-around",height:'100vh',backgroundColor:"#ccc"}}><Left name='Left'/><Middle name='Middle'/><Right name='Right'/></div>,container);
</script></html>

(6)再次测试,运行无误。

React:Component组件相关推荐

  1. React学习(6)-React中组件的数据-state

    前言 组件中的state具体是什么?怎么更改state的数据? setState函数分别接收对象以及函数有什么区别? 如何划分组件的状态数据,进行自我的灵魂拷问,以及props与state的灵魂对比 ...

  2. React学习(六)-React中组件的数据-state

    虽互不曾谋面,但希望能和你成为笔尖下的朋友 以读书,技术,生活为主,偶尔撒点鸡汤 不作,不敷衍,意在真诚吐露,用心分享 点击左上方,可关注本刊 撰文 | 川川 ID:suibichuanji 点击下方 ...

  3. react父子组件,兄弟组件,爷爷到孙子组件笔记

    import React from 'react' import App from './App' // 1.父传子. // 2.子传父 class ComCent extends React.Com ...

  4. 第一章:Reac入门 与 第二章:React面向组件编程

    目录 一.jsx语法规则 二.React中定义组件 1.函数式组件: 2.类式组件:*有关类复习的知识点前往React知识铺垫查看https://blog.csdn.net/m0_61927991/a ...

  5. React子组件给父组件传值, 父组件引用子组件并给子组件传值

    本博客代码是 React 父组件和子组件相互传值的 demo:实现封装一个折线图,折线图选择下拉框,获取下拉框点击的值并且传给父组件根据下拉框筛选的条件更新视图:效果图如下: 父组件代码: 代码解析: ...

  6. react测试组件_测试驱动的开发,功能和React组件

    react测试组件 This article is part of my studies on how to build sustainable and consistent software. In ...

  7. react创建组件_如何使用React创建时间轴组件

    react创建组件 These days I've been working on a new page for my website. I wanted to have a Timeline to ...

  8. React Component vs React Element

    React Component vs React Element 有这样的一个问题: // 方法定义 function add(x, y) {return x + y }// 方法调用 add(1, ...

  9. React创建组件的三种方式及其区别

    React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归:具体的三种方式: 函数式定义的无状态组件 es5原生方式React.createClass定义的组件 es6形式的ext ...

最新文章

  1. 分享一首诗歌关于人生 时间 成就 得失的
  2. linux编译安装网卡驱动详解(网卡丢包)
  3. csdn java社区_java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET
  4. 人脸识别损失函数笔记
  5. Linux下的shell语言编程入门
  6. JVM:如何分析线程堆栈
  7. thinkphp内置标签简单讲解
  8. QT5新建工程错误-无法打开源文件QtWidgets/QApplication
  9. 2014蓝桥杯C++B:啤酒和饮料;切面条(思维分析)
  10. Spring Boot 中实现定时任务的常用方式@Scheduled
  11. 谁抢光了你的火车票?
  12. 如何测试webservice接口
  13. https 抓包解密
  14. 【渝粤题库】陕西师范大学164113 电子支付 作业(专升本)
  15. 大数据-浅谈hive优化
  16. 如果你突然打了个喷嚏,那
  17. 用verilog实现串行信号转8bit并行信号
  18. PhotoShop一键修改4的倍数图片工具
  19. catkin_make 编译包无效(没反应)
  20. C# 判断本机是否安装Excel及多版本安装?获取Excel进程信息和打开Excel应用软件

热门文章

  1. Pycharm中光标变粗 光标进入改写状态
  2. bitmap的六种压缩方式,Android图片压缩
  3. C++的STL中accumulate函数用法
  4. Android开发应该用什么语言
  5. windows查看tomcat版本信息
  6. 零基础Unity做一个中秋诗词鉴赏网页,提前祝您中秋快乐!(DoTween动画 | WebGL视频 | 大文件上传GitHub)
  7. Python IO编程详解
  8. java开发自我介绍范文(合集)
  9. MMAction2学习笔记 使用C3D训练测试自己的数据集
  10. WIFI模块RTL8723BU驱动移植