react 使用 state
前面几篇博文我们介绍了 函数式组件 和 类式组件,然后今天来看一下 state状态,很简单。
怎么区分函数式组件和类式组件
之前说函数式组件和类式组件的时候我们说过,函数式组件主要实现简单组件的定义,类式组件主要实现复杂组件的定义,那什么是简单组件,什么是复杂组件,其实很简单,就是我们今天要了解的 state。
简单点说:
- 如果你的组件是有状态的
state,就是复杂式组件。 - 如果你的组件没有状态
state,那他就是简单组件。
state 状态
好了,我们知道通过 state 的有无来判断你的组件是不是复杂组件,那新的问题又来了,什么状态 state ?
【理解一下】
打个比方:
比如说有一天,老师问小明,考试为啥没考好啊? 小明回答说,因为我
状态不好。
OK,仔细理解上面这个例子,我们可以得出一个结论:
人 是有 状态 的,人的 状态 会 影响 行为。
同样,组件 也是有 状态 的,状态 会 驱动 页面。
简单理解:组件的状态里面存放着数据,数据的改变就会驱动着页面的展示。
案例引入 state
接下来写一个案例,然后我们通过这个案例把 state 相关的基础东西都说一下。
一个页面,内容很简单,就是一个单纯的标签,点击标签更换标签显示的内容。
初始化状态
状态的初始化很简单,我们可以看 官网【穿梭门】 给的一个很基本的案例。
我们看到他是使用的 构造器函数,使用 this.state={ xxx : xxx } 完成初始化状态的,然后构造器函数传了一个 props 值,并且还继承了父类,这个 props 是啥我们先不要管,后边博客再说好了,然后我们先照着抄过来,实现我们这个案例的基本功能。
假设我们有一个状态值是 isWork, 如果是 true 就显示上班,如果是 false 就显示不上班。
<!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>11.state</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="app"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom 用于支持 react 操作 dom -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入 label,用于将 jsx 转化为 js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 此处必须写 text/babel -->
<script type="text/babel">
// 1. 创建类式组件
// 注意:创建类式组件必须继承 React.Component
class Demo extends React.Component {
constructor(props) {
super(props)
// 初始化状态 state
this.state = { isWork: true }
}
render() { // render 必须写,并且必须有返回值
let { isWork } = this.state
return <h1>老子今天{isWork ? '要' : '不'}上班!</h1>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('app'))
</script>
</body>
</html>
好的,保存刷新一下,我们看一下效果。
OK呀,因为是 true ,所以显示出来了。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
标签点击事件绑定
好的,上边一部分可以实现根据状态值 isWork 的不同,渲染去出上班不上班,所以说,现在我们需要一个步骤,就是触发,让上班不上班可以来回切换。
这个第一反应应该就是采用点击事件了对吧?然后我们先会议一下原生的点击事件有哪些好吧!
原生点击事件实现
原生实现点击事件一共有三种方式:
- addEventListener
- onclick
- 调用方法
下边关键代码写一下。
<body>
<button id="btn1">按钮一</button>
<button id="btn2">按钮二</button>
<button onclick="demo()">按钮三</button>
<script type="text/javascript">
const btn1 = document.getElementById('btn1')
btn1.addEventListener('click', () => {
alert('按钮一被点击了!')
})
const btn2 = document.getElementById('btn2')
btn2.onclick = () => {
alert('按钮二被点击了!')
}
function demo() {
alert('按钮三被点击了!')
}
</script>
</body>
我们看一下效果。
可以看到这三个方式都是可以实现点击事件的。
React 实现点击效果呢,按钮一 和 按钮二 的方式都是可以的,没有任何问题。我们在之前的代码上面稍微改一下,然后写一下这两种方式触发点击事件。
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isWork: true, day: '星期五' }
}
render() {
console.log(this)
let { isWork, day } = this.state
return <h1 id="title">老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('app'))
// 方式一
const title = document.getElementById('title')
title.addEventListener("click", () => {
console.log("标题被点击了")
})
// 方式二
const title = document.getElementById('title')
title.onclick = () => {
console.log("标题被点击了")
}
</script>
代码在上面了,这两种点击事件的方法都是可以的,但是 React 一般情况下不会这样使用,一般采用 按钮三 的方式编写点击事件。
React 绑定事件
接下来我们使用 调用函数 的方式的方式实现点击事件。
我们修改一下代码:
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isWork: true, day: '星期五' }
}
render() {
console.log(this)
let { isWork, day } = this.state
return <h1 id="title" onClick="demo()">老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('app'))
function demo() {
console.log("标题被点击了")
}
</script>
保存刷新,我们查看一下结果。
我们看到,我们啥都没有干,控制台直接报错了,报错的意思是 onClick 需要一个方法,但是现在传入的是字符串,学过 vue 的小伙伴可能灵光一下,觉得之前说过,如果调用要使用 { } 包裹起来。我们修改一下代码试一下。
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isWork: true, day: '星期五' }
}
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={demo()}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('app'))
function demo() {
console.log("标题被点击了")
}
</script>
保存刷新,查看结果。
一刷新页面,啥也没动发现控制台就已经打印出来数据了,而且当再次点击的时候,没反应了。这是为啥呢?因为 onClick 会把 { } 中包裹内容的返回值赋值给 onClick,恰巧 { } 中包裹的 demo() 是一个方法,里面的返回值是一个 undefined,在渲染的时候,React 会直接把 demo() 函数给执行了,啥也没返回,所以会直接打印,但是 onClick 啥也没绑定上,所以点击没反应。如何解决这个问题呢?很简单,只要不加括号就可以了。
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isWork: true, day: '星期五' }
}
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={demo}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('app'))
function demo() {
console.log("标题被点击了")
}
</script>
再次保存刷新,查看效果。
OK。现在一切都正常了,可以实现点击之后再控制台输出文本。这样点击事件就完成了。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
this 指向问题
上面部分我们实现了 React 绑定点击事件,接下来就是改变 isWork 的值就可以了。
首先为了标准我们把之前的代码修改一下:
<script type="text/babel">
// 1. 创建类式组件
class Work extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isWork: true, day: '星期五' }
}
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
changeWork() {
console.log("标签被点击了")
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Work />, document.getElementById('app'))
</script>
下面的代码案例,都基于上面x修改后的代码继续编写,逻辑没改,就修改了几个名称。 但是有一点要注意,我们把 changeWork 移入 Work 对象里面了,所以说 他在 Work 的原型对象上,供实例使用。通过 Work 实例调用 changeWork 时候,changeWork 中的 this 就是 weather 实例。注意这一点儿。
有很多小伙伴觉得,OK,后边的我熟啊!直接在点击方法里面写逻辑就可以了。比如先在点击方法里面打印一下 isWork 的值。
<script type="text/babel">
// 1. 创建类式组件
class Work extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isWork: true, day: '星期五' }
}
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
changeWork() {
console.log(this.state.isWork)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Work />, document.getElementById('app'))
</script>
保存刷新,点击标签,看一下打印的数据。
好家伙,我们控制台直接报错了。说找不到
isWork。不应该啊,我们直接打印一下 this。
我们看到,打印出的 this 不是 Work 实例。这就纳闷了,为啥 构造器函数,render 函数中的 this 是 Work 实例,但是到了 changeWork 函数就不是了? 明明都在同一级的!
要想明白这里,我们先去看一下 类的 this 指向问题。
类的 this 指向问题
看下面代码:
<script type="text/javascript">
class Boy {
constructor(name, age) {
this.name = name
this.age = age
}
speak() {
console.log(this)
}
}
const b1 = new Boy('我是ed.', 10)
b1.speak() // 通过实例调用
</script>
上面的代码我们创建了一个 boy 类,里面有一个 speak 方法,然后打印的是 speak 里面的 this。
然后我们实例化一下,然后调用一下 speak,看一下 this 指向是谁!
我们看到 this 的指向就是 Boy 的实例。
那我们修改一下代码:
<script type="text/javascript">
class Boy {
constructor(name, age) {
this.name = name
this.age = age
}
speak() {
console.log(this)
}
}
const b1 = new Boy('我是ed.', 10)
b1.speak() // 通过实例调用
const x = b1.speak
x()
</script>
上面的代码,我们把 b1 的 speak 方法赋值给了 x,也就是说 x 就是 speak 方法,我们调用 x ,看一下 this 打印的是啥。
x 打印出来是 undefined。为什么!因为哈,speak 函数 放在了类的原型对象上,他是给实例使用的,通过 Boy 实例调用 speak 的时候,speak 中的 this 就是 Boy 实例。这就是第一个能打印的原因。那第二个打印是 undefined 的,是因为,尽管给 x 赋值为 speak 函数,但是调用它的,已经不是 Boy 实例了,所以 speak 中的 this 是 undefined,他只能在往上一层找 window ,but!类中所有定义的方法,已经默认开启了严格模式,所以不会指向window,只能停止,打印 undefined。
思路转回案例
OK。我们回到上面那个打印 isWork 的是 undefined 的地方,为什么点击标签,他的 this 不是 Work 的实例对象? 这个地方和这个 x 的原因一模一样!!!
看下面代码:
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
changeWork() {
console.log(this.state.isWork)
}
我们的 changeWork 事件,是在 Work 的原型对象上,供实例使用。但是在标签上 <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1> 这句代码上,我们把 changeWork 赋值给了 onClick 的回调,所以不是通过实例调用的,是直接调用。所以说,this 的指向变了,不是 Work 实例了,所以直接去获得 isWork 的是后是 undefined。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
this 问题解决
上面我们弄清楚了 this 指向改变的问题,怎么解决?
so easy!
使用 bind。
bind 的作用:bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的 this 对象的值会被绑定到传入 bind() 第一个参数的值,当然这是绑定,不是执行。
<script type="text/babel">
// 1. 创建类式组件
class Work extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isWork: true, day: '星期五' }
// 解决 changeWork 中 this 指向问题
this.changeWork = this.changeWork.bind(this)
}
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
changeWork() {
// changeWork 放在哪里?—— Work 的原型对象上,供实例使用
// 由于 changeWork 是作为 onClick 的回调,所以不是通过实例调用的,是直接调用
// 类中的方法默认开启了局部的严格模式,所以 changeWork 中的 this 为 undefined
console.log(this.state.isWork)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Work />, document.getElementById('app'))
</script>
OK,实现完成!
所以说我们点击标签的 changeWork 是自身的 changeWork , 不是原型的 changeWork ,但是 自身的 changeWork 是通过 原型的 changeWork 生成的,只不过把 this 指向改掉了,目的是让点击事件可以获取想要的 this 指向。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
setState 的使用
this 指向问题解决了,可以直接获取到 isWork 状态了。然后就是点击标签,实现 isWork 状态的切换。
这个简单啊,无非就是点击标签的时候,isWork 取 !isWork 呗!
changeWork() {
// 获取原来的 isWork 值
const isWork = this.state.isWork
// 注意:状态 state 不可以直接更改,必须借助api修改
this.state.isWork = !isWork
let day = isWork?'星期天':'星期五'
console.log(this.state.isWork)
}
保存,刷新,看效果。
为什么!宽松点说,就是你改了,但是界面没有刷新,严格点说,写的不对。
因为 state 提供了 api 用来修改 state 状态,就是 serState。
<script type="text/babel">
// 1. 创建类式组件
class Work extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = { isWork: true, day: '星期五' }
// 解决 changeWork 中 this 指向问题
this.changeWork = this.changeWork.bind(this)
}
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
changeWork() {
// 获取原来的 isWork 值
const isWork = this.state.isWork
// 注意:状态 state 不可以直接更改,必须借助api修改
this.state.isWork = !isWork // 这是错误的写法
let day = isWork?'星期天':'星期五'
this.setState({ isWork: !isWork, day: day }) // 这是对的,必须使用 setState 修改,修改为合并操作
console.log(this.state.isWork)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Work />, document.getElementById('app'))
</script>
保存刷新,查看效果。
OK, 效果完成!非常 nice!!!
这部分最后想一个东西,我们的 构造体函数,在实现这个功能的时候执行了几次啊?对,一次,就是在实例化的时候执行的一次。那 render 函数执行了几次? 是的, n + 1 次,这个1 是页面刷新出来的一次,n 是点击标签的次数。那 changeWork 函数执行了几次呀? 嗯,执行了 n 次,点一次标签,执行一次。
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
state 简写
上边逼逼赖赖了那么就,就为了实现一个小小的功能,写了一页代码,能看到这儿的应该很少了吧。
关于 state 简写的很简单,省好多代码量,也不用解决 this 指向问题。
下面稍微说一下。
我们先说一下普通类,类中可以包含什么?
- 构造体函数
constructor(){ } - 方法
demo(){} - 赋值语句
a=1
其中赋值语句 a = 1 的含义是:给 Work 的实例对象添加一个属性,命名为a,值为1。
那我们的仔细看上一部分最后实现的代码,上面代码的构造体函数里面实现了什么功能啊?是不是只是初始化 state 状态 和 解决 this 指向问题,既然类中可以直接写赋值语句,那我们就可以把 state 直接提出来啊!
<script type="text/babel">
class Work extends React.Component {
constructor(props) {
super(props)
// this.state = { isWork: true, day: '星期五' }
this.changeWork = this.changeWork.bind(this)
}
// 初始化状态
state = { isWork: true, day: '星期五' }
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
// 自定义方法
changeWork() {
// 获取原来的 isWork 值
const isWork = this.state.isWork
// 注意:状态 state 不可以直接更改,必须借助api修改
this.state.isWork = !isWork // 这是错误的写法
let day = isWork?'星期天':'星期五'
this.setState({ isWork: !isWork, day: day }) // 这是对的,必须使用 setState 修改,修改为合并操作
console.log(this.state.isWork)
}
}
ReactDOM.render(<Work />, document.getElementById('app'))
</script>
上面代码保存,刷新,也是可以正常执行的。
然后现在 构造函数 中就只有 this 指向问题的解决代码了,那 this 指向问题有没有其他的解决办法?
有!
箭头函数!
先看下面代码,我们把 changeWork 改一下:
changeWork = function() {
const isWork = this.state.isWork
let day = isWork ? '星期天' : '星期五'
this.setState({ isWork: !isWork, day: day })
}
改成这样,是什么意思?这就不是一个方法了! 变成赋值语句了!Work原型就没有changeWork方法了,而是在 Work 创建的实例他自身添加了一个 changeWork 属性。
但是仅仅这样不行,因为尽管换成了 赋值语句,但是依旧没有解决 this 指向问题,只不过是换了个位置而已,把原型上的方法变成了自己身上的属性,只是把问题转移了,但是实际上并没有解决。
重点来咯!
再改!
changeWork = () => {
const isWork = this.state.isWork
let day = isWork ? '星期天' : '星期五'
this.setState({ isWork: !isWork, day: day })
}
我们把之前赋值给实例自身 changeWork 属性的 方法,修改成箭头函数。这样就可以了,this 问题解决!
为什么会解决!
因为,箭头函数有一个很大的特点,多大? 超级超级大!那个特点就是 没有自己的 this。
但是,如果你在箭头函数中,就是使用了 this 这个关键字!他不会报错!他会自动找寻外层的 this 作为自己的 this 去使用。
所以说,太他妈的秒~了
他找的外层的 this,正好就是 Work 实例的 this,正好解决了 this 只想问题,我勒个去!棒!
所以说 changeWork 修改成箭头函数之后,就没有了 this 指向问题,构造体函数直接下班!
<!-- 此处必须写 text/babel -->
<script type="text/babel">
class Work extends React.Component {
// constructor(props) {
// super(props)
// // this.state = { isWork: true, day: '星期五' }
// // this.changeWork = this.changeWork.bind(this)
// }
// 初始化状态
state = { isWork: true, day: '星期五' }
render() {
let { isWork, day } = this.state
return <h1 id="title" onClick={this.changeWork}>老子今天{isWork ? '要' : '不'}上班!因为今天{day}</h1>
}
// 自定义方法 —— 要用赋值语句的形式 + 箭头函数
changeWork = () => {
const isWork = this.state.isWork
let day = isWork ? '星期天' : '星期五'
this.setState({ isWork: !isWork, day: day })
}
}
ReactDOM.render(<Work />, document.getElementById('app'))
</script>
保存,刷新,查看!
OK,state 简化处理,完成!
总结
理解:
- state 是组件对象最重要的属性,值是对象(可以包含多个键值对)
- 组件被称为状态机,通过更新组件的 state 来更新对应的页面显示(重新渲染)
强烈注意:
- 组件中的 render 方法中的 this 为组件实例对象。
- 组件自定义的方法中 this 为 undefined,如何解决? 强制绑定this ,通过函数对象的 bind() 箭头函数
- 状态数据不能直接修改或更新
【本部分相关代码资料】:我是𝒆𝒅. 的 gitee
今天的内容就到这里了,东西有点多,能看到这里的几乎没人了吧?加油!宝子们,晚安~