1. 简单组件和复杂组件
简单组件:无 state 复杂组件:状态 state
那么什么是状态呢?
2. state
标题深究其实是:组件(实例)的三大核心属性。 而 只有类组件才有实例,函数式组件根本没资格。为了解决函数式组件的这个问题 react 又推出了 hooks。
state 的使用 :我们做个例子点击改变天气 炎热还是凉爽
2.1 创建组件
我们要创建类组件 还是 函数式 组件? 当然是 类组件。
class Weather extends React.Component{
render(){
console.log(this)
return <h2>今天天气很炎热</h2>
}
}
ReactDOM.render(<Weather/>, document.getElementById('root'))
然后我们需要 定义一个变量 isHot 来 改变炎热还是凉爽。state 在类的实例上。 那我们想要往 state 中添加变量,我们要对类的实例进行初始化操作,那就需要我们写构造方法。
class Weather extends React.Component{
constructor(???){
}
render(){
console.log(this)
return <h2>今天天气很炎热</h2>
}
}
2.2 添加构造器
那么 构造器中需要传什么参数?这要取决于 实例对象传递的参数,然而,这是React创建的 ,我们并看不到。
我们去官网看,它传了props。那需要写super吗?需要,这是类规定的。
class Weather extends React.Component{
constructor(props){
super(props)
}
render(){
console.log(this)
return <h2>今天天气很炎热</h2>
}
}
2.3 添加变量/属性
state 要写成对象
constructor(props){
super(props)
this.state = {
isHot:true
}
}
现在实例对象上的 state 中已经有我们的 isHot 了 下面我们只需要取出来这个值,并渲染出来
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>
}
}
2.4 改变state值 setState
按钮点击改变值
2.4.1 原生写法
给 h2 标签添加 id 属性 使用方法:addEventListener 或 onClick
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
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>
}
}
ReactDOM.render(<Weather/>, document.getElementById('root'))
const title = document.getElementById("title")
title.addEventListener('click',()=>{
console.log("标题被点击了")
}
title.onClick=()=>{
console.log("标题被点击了")
}
虽然两种方式可以实现点击事件,但是都用React了,尽量少写原生的js方法。
2.4.2 写在标签里
这种当然也是原生的方法。我们新写一个button标签在内部加入onclick。然后再写一个demo方法。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
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>
}
}
ReactDOM.render(<Weather/>, document.getElementById('root'))
function demo(){
console.log("按钮被点击了")
}
我们会发现它报错了,因为不能使用onclick。那么我们用什么?用onClick,React把原生的事件都变成了驼峰规则的。 所以我们要改成
<button onClick="demo()">change weather</button>
2.4.3 函数是否需要小括号
我们发现还没有点击,就已经打印了 “按钮被点击了” 那么这是为什么呢?React帮我们创建了一个Weather实例,通过实例调用了 render。就执行了<button onClick="demo()">change weather</button> 代码。要把函数的返回值赋过来,onClick="demo()" 是一个赋值语句,把右边的返回值赋值给onClick作为回调。demo函数的返回值是什么?是undefined。现在点击是没有效果的。
所以需要删掉小括号onClick="demo" ,这个含义是把右边的函数作为回调交给onClick事件,点击的时候才会调用函数 现在再点击按钮 达到了我们想要的效果。
2.4.4 在demo函数中更改isHot的值
我们解构赋值,拿到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
2.4.5 自定义函数的this指向
此时onClick={this.demo} 根本没有调用demo方法,只是通过类的实例对象沿着原型链找到了demo,然后把这个函数交给onClick作为回调了。直接从堆中将函数调用,根本不是从实例对象中调用。类中的方法默认开启了局部的严格模式。因此,此时的this是undefined。
使用bind
this.demo = this.demo.bind(this)
- 本质上来说是一个赋值语句,先看右边,实例上其实是没有demo的,那么为什么不会报错呢?它会按着原型链找到原型上的,也就找到了我们定义的函数。
- 右边的代码一旦运行完,就会有了一个函数,而且这个函数的this成功的变成了Weather的实例对象。
- 再看左边,将这个函数放到了实例自身,还给这个函数起了个名字,this.demo。
此时我们在函数中打印 this ,会发现自身也有demo方法了。那么每次点击调用执行的是自身的,还是原型上的呢🤔 ?按着原型链去找在自身上就已经找到了,就不会再去原型上去找了。
那原型上的demo方法可以删掉吗🤔 ?当然不可以,是因为原型上有demo方法,我们才可以生成一个新的挂在实例自身。
2.4.6 setState?
在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)
}
所以是一种合并。
3. 精简代码
3.1 去掉构造器
为什么写构造器? 因为要做一些初始化的操作。感不感觉是没地方写了才写到构造器里的。 类中是可以直接写赋值语句的 。所以给state赋值,不需要非得写在构造器中。
state = {
isHot:true,
wind:"风"
}
可以 发现又 undefined 了。因为 demo 函数放在了Weather的原型对象上。
3.2 改造自定义函数
首先,我们自定义的方法大部分都是作为事件回调的。 那我们把这个函数改一下:现在是一个赋值语句。现在这个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 ? 看不了了?我们刚才说过箭头函数中的 this 就是它外层的 this指向。所以我们在 箭头函数中 打印的 this 就是空白区域的 this。可以发现是组件的实例对象。
3.3 完整代码
简化后 ,可以不需要写构造器了,自定义方法要用赋值语句的形式+箭头函数
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)
}
}
|