IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React:Component组件 -> 正文阅读

[JavaScript知识库]React:Component组件

??????? 组件允许开发者将复杂的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,y
            this.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,y
            this.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,y
            this.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-1
    super(x, y);
    //step-2
    this.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.prototype
    console.log(point.__proto__ === Point.prototype);
    //打印point.__proto__
    console.log(point);
    //打印Point.prototype
    console.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__属性 完全等价于  A
    console.log(B.__proto__ === A);

    //子类B原型的constructor属性 完全等价于 B自身,这也证明了,class确实是ES5中function构造函数的语法糖,本质上还是一个函数
    console.log(B.prototype.constructor === B);

    //关系推导:子类B原型的__proto__属性  完全等价于A
    console.log(B.prototype.constructor.__proto__ === A);

    //关系延伸:子类B原型的__proto__属性 完全等价于 A的原型prototype
    console.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()函数
     */

    //左侧-Left
    class Left extends React.Component{
        constructor(props){
            super(props);
        }

        render(){
            const {props} = this;
            return <div style={{height:"100%",backgroundColor:"skyblue",flex:1}}>{props.name}</div>;
        }
    }

    //中间-Middle
    class Middle extends React.Component{
        constructor(props){
            super(props);
        }

        render(){
            const {props} = this;
            return <div style={{height:"100%",backgroundColor:"lightgreen",flex:8}}>{props.name}</div>;
        }
    }
    //右侧-Right
    class 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()函数
     */

    //左侧-Left
    class 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>;
        }
    }

    //中间-Middle
    class 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>;
        }
    }
    //右侧-Right
    class 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)再次测试,运行无误。

?

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:32:10  更:2022-10-08 20:32:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 15:32:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码