如何定义复杂组件(类组件)与简单组件(函数组件)?
- 是否具有状态(state)
引出问题,什么是状态?
举个例子,今天考试,考砸了,因为我状态不好,是状态影响了我的行为。
组件中的状态,驱动了页面更新,换句话说,组件状态中存着数据,数据的改变,驱动页面的更新。
这里要了解,state状态是谁身上的状态?
:state状态是组件实例对象身上的状态,不是组件类本身身上的,而是由这个类缔造的实例身上的。
了解一下实例对象:
c1就是Car类的实例对象。
<script>
class Car {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
const c1 = new Car("宝马", 100);
console.log("c1", c1);
</script>
类本身身上:
重点:(class)组件实例上三大属性之一:state
看一段代码和打印的结果
class Weather extends React.Component {
render() {
console.log("this:", this);
return <h1>我是用类定义的组件(适用于复杂组件的定义)</h1>;
}
}
ReactDOM.render(<Weather />, document.getElementById("test"));
然后实现一个需求来深入了解state:通过点击页面,炎热/凉爽切换。
我们发现,初始化后的state,值为null(为什么是null,可以看一下初始化数据时的注释)。
初始化数据
<!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>
</head>
<body>
<div id="test"></div>
<!-- 引入核心库 -->
<script src="../js/react.development.js"></script>
<!-- 扩展库 -->
<script src="../js/react-dom.development.js"></script>
<!-- 转换jsx转为js -->
<script src="../js/babel.min.js"></script>
<script type="text/babel">
// 1.创建组件
class Weather extends React.Component {
/**
* 构造器中能收到什么数据,取决于new的时候,传的是什么数据
* new Weather并不是我们操作的,而是react操作的
*/
constructor(props) {
// 还没学到props,但得用着,模仿官网写
// 类本身语法
super(props);
// 构造函数中this指向构造函数实例对象
// 16.8之前,state是{},16.8及之后,是null
this.state = {
isHot: true,
};
}
render() {
console.log("this:", this);
return <h1>今天天气很炎热</h1>;
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
</body>
</html>
通过tools手动切换
接下来写点击事件,注意,先做一个错误示范
<script type="text/babel">
// 1.创建组件
class Weather extends React.Component {
/**
* 构造器中能收到什么数据,取决于new的时候,传的是什么数据
* new Weather并不是我们操作的,而是react操作的
*/
constructor(props) {
// 还没学到props,但得用着,模仿官网写
// 类本身语法
super(props);
// 构造函数中this指向构造函数实例对象
// 16.8之前,state是{},16.8及之后,是null
this.state = {
isHot: true,
};
}
// state在Weather的实例对象身上
render() {
console.log("this:", this);
return (
<h1 onClick={demo()}>
今天天气很{this.state.isHot ? "炎热" : "凉爽"}
</h1>
);
}
}
function demo() {
console.log("demo被调用");
}
// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
我在调用点击事件时,写的是 onClick={demo()}
在控制台会发现,函数被立即执行了
这是因为:
react在new Weather时,通过实例调用了render方法,想拿到return的值,就要执行<h1 onClick={demo()}>今天天气很{this.state.isHot ? “炎热” : “凉爽”}</h1>,这段代码,当执行到onClick赋值语句时,就要将demo()函数调用的返回值交给onClick作为回调,demo()的返回值是undifend(因为没有retrun任何数据),也就是把undifend交给onClick作为回调,**在demo后加(),这是错误的做法,这种做法将导致demo函数调用。**等到点击时,就调用了undifend,react处理了这一过程,如果是undifend,就没有多余动作。
演示其他常见错误写法
render() {
console.log("this:", this);
return (
<h1 onClick='demo()'>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</h1>
);
}
或者:
render() {
console.log("this:", this);
return (
<h1 onclick='demo'>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</h1>
);
}
正确写法
<!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>
</head>
<body>
<div id="test"></div>
<!-- 引入核心库 -->
<script src="../js/react.development.js"></script>
<!-- 扩展库 -->
<script src="../js/react-dom.development.js"></script>
<!-- 转换jsx转为js -->
<script src="../js/babel.min.js"></script>
<script type="text/babel">
// 1.创建组件
class Weather extends React.Component {
/**
* 构造器中能收到什么数据,取决于new的时候,传的是什么数据
* new Weather并不是我们操作的,而是react操作的
*/
constructor(props) {
// 还没学到props,但得用着,模仿官网写
// 类本身语法
super(props);
// 构造函数中this指向构造函数实例对象
// 16.8之前,state是{},16.8及之后,是null
this.state = {
isHot: true,
};
}
// state在Weather的实例对象身上
render() {
console.log("this:", this);
return (
<h1 onClick={demo}>
今天天气很{this.state.isHot ? "炎热" : "凉爽"}
</h1>
);
}
}
function demo() {
console.log("demo被调用");
}
// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById("test"));
</script>
</body>
</html>
实现数据的修改
上文已经将数据渲染到页面中,现在想要修改页面的数据。想要修改数据,首先要拿到state中的isHot
,先看一段错误写法:
function demo() {
console.log("demo被调用");
// 错误示范
const { isHot } = this.state;
console.log("isHot", isHot);
}
提示xxx of undefined(reading ‘state’),也就是state of undefined,当xxx为undefined时,undefined.state 就会报这个错误。这里的xxx指的就是this
。我们来验证一下:
打印一下this
function demo() {
console.log("this", this);
}
为什么react在调用demo方法时,this是undefined而不是window?
先来复习一下this的指向问题:
- 全局作用域或者普通函数中 this 指向全局对象 window
- 方法调用中谁调用 this 指向谁
- 在构造函数或者构造函数原型对象中 this 指向构造函数的实例
- 箭头函数中指向外层作用域的 this
当前的场景是1,我们再来看一下代码结构和注释:
解释一下以上两张图:
图1:
js在执行<h1 onClick={demo()}></h1>时,调用了自定义函数demo,由于babel在编译时,默认开启了严格模式,禁止自定义函数指向window,所以是undefined。
图2:
react中的render方法指向的是Weather实例,所以在render中能够正确打印this,react在执行<ReactDOM.render(, document.getElementById(“test”));< 这段代码时,做了new Weather的操作,虽然能够正确调用demo方法,但由于state在Weather实例对象上,demo中无法拿到state。也就是将自定义函数放到类的外边,页面虽然能够正确渲染,但并不能拿到/修改state中的数据。
接下来将demo方法放在Weather类中:
class Weather extends React.Component {
/**
* 构造器中能收到什么数据,取决于new的时候,传的是什么数据
* new Weather并不是我们操作的,而是react操作的
*/
constructor(props) {
// 还没学到props,但得用着,模仿官网写
// 类本身语法
super(props);
/**
* 构造函数中this指向构造函数实例对象
* 16.8之前,state是{},16.8及之后,是null
* state在Weather的实例对象身上
*/
this.state = {
isHot: true,
};
}
// 切换天气
demo() {
console.log("this", this);
const { isHot } = this.state;
console.log("isHot", isHot);
}
// 渲染
render() {
console.log("this:", this);
return (
<h1 onClick={demo}>
今天天气很{this.state.isHot ? "炎热" : "凉爽"}
</h1>
);
}
}
注意,类不是函数体,不需要写function
刷新打印后,我们会发现,控制台报错。提示我们demo is not defined
来看一下代码和注释:
上图的意思是:
demo在Weatcher类的原型对象上,只有实例Weatcher才能调用demo,所以得写成this.demo,搞定demo is not defined 以后,点击h1,控制台报错,state of undefined,说明this is undefined。
来思考一下,为什么demo中的this,是undefined?(这非常重要,思考的步骤,橘色,红色,黄色)
为什么这里的demo,不是通过Weather调用的?
通过一个例子,来分析
<!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>Document</title>
</head>
<body>
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
study() {
// study方法放在哪里?类的原型对象上,供实例使用
// 通过Person实例调用study方法时,study中的this就是Person实例
console.log("this:", this);
}
}
const p1 = new Person("tom", 18);
p1.study();
</script>
</body>
</html>
这是没有问题的,然后我们修改一下,并打印
const p1 = new Person("tom", 18);
p1.study();
const x = p1.study;
x();
分析完,再来回头看原来的问题
这里只是通过类的实例对象,通过原型链查找,找到了demo方法,然后将demo方法交给了onClick作为回调,当点击h1时,是直接从堆里把函数拉出来直接执行,并不是通过实例调用
demo() {
// demo放在哪里?---Weatcher的原型对象上,供实例使用
// 通过Weatcher实例调用demo时,demo中的this就是Weatcher实例
// --------------
/**
* 为什么this是undefined?
* 1.由于demo是作为onClick的回调,所以不是通过实例调用的,是直接调用
* 2.类中的方法默认开启了局部严格模式,所以demo中的this为undefined
*/
console.log("this", this);
}