??????? 组件允许开发者将复杂的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)再次测试,运行无误。
?