这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战
简单组件:无 state 复杂组件:状态 state
那么什么是状态呢?
graph LR
A(人) --> B(状态) -->C(影响)-->D(行为)
graph LR
A(组件) --> B(状态) -->C(驱动)-->D(页面)
标题深究其实是:组件(实例)的三大核心属性。 而 只有类组件才有实例,函数式组件根本没资格。为了解决函数式组件的这个问题 react 又推出了 hooks。
state 的使用 :我们做个例子点击改变天气 炎热还是凉爽
我们要创建类组件 还是 函数式 组件? 当然是 类组件。
// 1.创建类组件
class Weather extends React.Component{
render(){
console.log(this)
return <h2>今天天气很炎热</h2>
}
}
// 2. 渲染
ReactDOM.render(<Weather/>, document.getElementById('root'))
然后我们需要 定义一个变量 isHot 来 改变炎热还是凉爽。state 在类的实例上。 那我们想要往 state 中添加变量,我们要对类的实例进行初始化操作,那就需要我们写构造方法。
class Weather extends React.Component{
constructor(???){
}
render(){
console.log(this)
return <h2>今天天气很炎热</h2>
}
}
那么 构造器中需要传什么参数?这要取决于 实例对象传递的参数,然而,这是React创建的 ,我们并看不到。
我们去官网看,它传了props。那需要写super吗?需要,这是类规定的。
// 1.创建类组件
class Weather extends React.Component{
constructor(props){
super(props)
}
render(){
console.log(this)
return <h2>今天天气很炎热</h2>
}
}
state 要写成对象
constructor(props){
super(props)
this.state = {
isHot:true
}
}
现在实例对象上的 state 中已经有我们的 isHot 了
下面我们只需要取出来这个值,并渲染出来
// 1.创建类组件
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {
isHot:true
}
}
render(){
console.log(this)
return <div>
<h2>今天天气很
{
this.state.isHot ? '炎热':'凉爽'
}
</h2>
</div>
}
}
按钮点击改变值
给 h2 标签添加 id 属性 使用方法:addEventListener 或 onClick
// ES6 中模块化语法
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// 1.创建类组件
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {
isHot:true
}
}
render(){
console.log(this)
return <div>
<h2 id="title">今天天气很
{
this.state.isHot ? '炎热':'凉爽'
}
</h2>
</div>
}
}
// 2. 渲染
ReactDOM.render(<Weather/>, document.getElementById('root'))
// 原生方法 1
const title = document.getElementById("title")
title.addEventListener('click',()=>{
console.log("标题被点击了")
}
// 原生方法 2
title.onClick=()=>{
console.log("标题被点击了")
}
虽然两种方式可以实现点击事件,但是都用React了,尽量少写原生的js方法。
这种当然也是原生的方法。我们新写一个button标签在内部加入onclick。然后再写一个demo方法。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// 1.创建类组件
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {
isHot:true
}
}
render(){
console.log(this)
return <div>
<h2 id="title">今天天气很
{
this.state.isHot ? '炎热':'凉爽'
}
</h2>
<button onclick="demo()">change weather</button>
</div>
}
}
// 2. 渲染
ReactDOM.render(<Weather/>, document.getElementById('root'))
function demo(){
console.log("按钮被点击了")
}
我们会发现它报错了,因为不能使用onclick。那么我们用什么?用onClick,React把原生的事件都变成了驼峰规则的。 所以我们要改成
<button onClick="demo()">change weather</button>
我们发现还没有点击,就已经打印了 “按钮被点击了”
那么这是为什么呢?React帮我们创建了一个Weather实例,通过实例调用了 render。就执行了 <button onClick="demo()">change weather</button>
代码。要把函数的返回值赋过来,onClick="demo()"
是一个赋值语句,把右边的返回值赋值给onClick作为回调。demo函数的返回值是什么?是undefined。现在点击是没有效果的。
所以需要删掉小括号onClick="demo"
,这个含义是把右边的函数作为回调交给onClick事件,点击的时候才会调用函数
现在再点击按钮 达到了我们想要的效果。
我们解构赋值,拿到isHot
function demo(){
const {isHot} = this.state
console.log(isHot)
}
发现报错了。state没有被定义,那么究其根源是什么没有呢?是this。
为什么会没有this呢? 首先这个函数是我们自定义的函数,而Babel在将我们的jsx转为js的时候是严格模式。它不允许自定义的函数的this指向window。
🤔 在我们自定义的demo函数中根本拿不到组件的实例对象,怎么办? 我们在最外部定义一个that变量,然后在构造器中将this也就是实例对象赋值给that。最后,在函数中打印that
虽然这样是实现了,但是不是很完美。 我们把demo方法放入类中,发现function报错了,因为类里面不可以这么写。
去掉function就好了
现在的demo放在类的原型对象上了,供实例对象使用。 通过Weather实例调用demo时,demo中的this就是Weather实例。
此时就不需要that了。现在会报错demo函数undefined。因为demo是实例对象下的,所以需要this.demo
点击后还是会报错,因为此时的this是undefined
此时onClick={this.demo}
根本没有调用demo方法,只是通过类的实例对象沿着原型链找到了demo,然后把这个函数交给onClick作为回调了。直接从堆中将函数调用,根本不是从实例对象中调用。类中的方法默认开启了局部的严格模式。因此,此时的this是undefined。
使用bind
this.demo = this.demo.bind(this)
此时我们在函数中打印 this ,会发现自身也有demo方法了。那么每次点击调用执行的是自身的,还是原型上的呢🤔 ?按着原型链去找在自身上就已经找到了,就不会再去原型上去找了。
那原型上的demo方法可以删掉吗🤔 ?当然不可以,是因为原型上有demo方法,我们才可以生成一个新的挂在实例自身。
在demo函数中获取原来isHot的值。并将它取反再赋回去。
demo(){
const {isHot} = this.state
this.state.isHot = !isHot
}
}
怎么点击都没变化。那么们打印一下console.log(this.state.isHot)
发现值确实变化了
这个isHot值已经改变了,但是页面并不变化。我们看一下React开发者工具,无论我们怎么点击这个值都是不变的。React并不承认我们的操作。
⚠️ :状态不可以直接更改,需要API :setState this.state.isHot = !isHot 是 ❌ 的写法。下面的写法才是正确的。
demo(){
const {isHot} = this.state
this.setState({isHot:!isHot})
}
那么思考一下 🤔 这个setState是合并还是覆盖? 我们再在state中加一个 wind 变量 ,在改变 isHot时,wind这个值丢不丢,不丢,就是合并,否则是覆盖。
this.state = {
isHot:true,
wind:"风"
}
demo(){
const {isHot} = this.state
this.setState({isHot:!isHot})
console.log(isHot)
}
所以是一种合并。
为什么写构造器? 因为要做一些初始化的操作。感不感觉是没地方写了才写到构造器里的。 类中是可以直接写赋值语句的 。所以给state赋值,不需要非得写在构造器中。
state = {
isHot:true,
wind:"风"
}
可以 发现又 undefined 了。因为 demo 函数放在了Weather的原型对象上。
首先,我们自定义的方法大部分都是作为事件回调的。 那我们把这个函数改一下:现在是一个赋值语句。现在这个demo就放在Weather实例自身了,就不在原型上了。
demo = function(){
const {isHot} = this.state
this.setState({isHot:!isHot})
console.log(isHot)
}
我们再点击但是还是没有解决问题。 接下来,我们把函数换成箭头函数。发现好了。
demo=()=>{
const {isHot} = this.state
this.setState({isHot:!isHot})
console.log(isHot)
}
🤔 那么为什么那?
那么我们怎么看空白区域的 this ? 看不了了?我们刚才说过箭头函数中的 this 就是它外层的 this指向。所以我们在 箭头函数中 打印的 this 就是空白区域的 this。可以发现是组件的实例对象。
简化后 ,可以不需要写构造器了,自定义方法要用赋值语句的形式+箭头函数。
class Weather extends React.Component{
state = {
isHot:true,
wind:"风"
}
render(){
console.log(this)
return <div>
<h2>今天天气很
{
this.state.isHot ? '炎热':'凉爽'
}
</h2>
<button onClick={this.demo}>change weather</button>
</div>
}
demo=()=>{
const {isHot} = this.state
console.log(this,"this")
this.setState({isHot:!isHot})
console.log(isHot)
}
}
state是组件对象最重要的属性,值是对象(可以包含多个key value的组合)。
组件被称为“状态机”,通过更新组件的 state 来更新对应的页面显示(重新渲染组件)。
组件中的render方法中的 this 为组件的实例对象
组件自定义方法中的 this 为 undefined,如何解决?
(1)强制绑定 this :通过函数对象的bind()
(2)箭头函数
状态数据,不能直接修改或更新,用 setState()