React 组件状态(State)

简介: 10月更文挑战第8天

组件可以拥有状态(state),它是组件数据的私有部分,可以用来管理动态数据。

状态仅适用于类组件,或者使用 React 的 Hook 时可以在函数组件中使用。

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。

以下实例创建一个名称扩展为 React.Component 的 ES6 类,在 render() 方法中使用 this.state 来修改当前的时间。

添加一个类构造函数来初始化状态 this.state,类组件应始终使用 props 调用基础构造函数。

类组件中的状态管理
创建一个有状态的类组件:

Counter.js 文件
import React, { Component } from 'react';

class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

increment = () => {
this.setState({ count: this.state.count + 1 });
}

render() {
return (


Count: {this.state.count}




);
}
}

export default Counter;
在 src/index.js 中渲染该组件:

实例
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Counter from './Counter';

const root = ReactDOM.createRoot(document.getElementById("root"));
// 渲染 Counter 组件
root.render();
函数组件中的状态管理(使用 Hook)
使用 React Hook 可以在函数组件中使用状态。最常用的 Hook 是 useState。

创建一个有状态的函数组件:

Counter.js 文件
import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

return (


Count: {count}




);
}

export default Counter;
在 src/index.js 中渲染该组件:

实例
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Counter from './Counter';

const root = ReactDOM.createRoot(document.getElementById("root"));
// 渲染 Counter 组件
root.render();
实例
创建一个时间点实例来理解组件状态:

React 实例
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

render() {
return (


Hello, world!


现在是 {this.state.date.toLocaleTimeString()}.



);
}
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(

);

尝试一下 »
接下来,我们将使Clock设置自己的计时器并每秒更新一次。

将生命周期方法添加到类中
在具有许多组件的应用程序中,在销毁时释放组件所占用的资源非常重要。

每当 Clock 组件第一次加载到 DOM 中的时候,我们都想生成定时器,这在 React 中被称为挂载。

同样,每当 Clock 生成的这个 DOM 被移除的时候,我们也会想要清除定时器,这在 React 中被称为卸载。

我们可以在组件类上声明特殊的方法,当组件挂载或卸载时,来运行一些代码:

React 实例
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
date: new Date()
});
}

render() {
return (


Hello, world!


现在是 {this.state.date.toLocaleTimeString()}.



);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(

);

尝试一下 »
实例解析:

componentDidMount() 与 componentWillUnmount() 方法被称作生命周期钩子。

在组件输出到 DOM 后会执行 componentDidMount() 钩子,我们就可以在这个钩子上设置一个定时器。

this.timerID 为定时器的 ID,我们可以在 componentWillUnmount() 钩子中卸载定时器。

代码执行顺序:

当 被传递给 ReactDOM.render() 时,React 调用 Clock 组件的构造函数。 由于 Clock 需要显示当前时间,所以使用包含当前时间的对象来初始化 this.state 。 我们稍后会更新此状态。

React 然后调用 Clock 组件的 render() 方法。这是 React 了解屏幕上应该显示什么内容,然后 React 更新 DOM 以匹配 Clock 的渲染输出。

当 Clock 的输出插入到 DOM 中时,React 调用 componentDidMount() 生命周期钩子。 在其中,Clock 组件要求浏览器设置一个定时器,每秒钟调用一次 tick()。

浏览器每秒钟调用 tick() 方法。 在其中,Clock 组件通过使用包含当前时间的对象调用 setState() 来调度UI更新。 通过调用 setState() ,React 知道状态已经改变,并再次调用 render() 方法来确定屏幕上应当显示什么。 这一次,render() 方法中的 this.state.date 将不同,所以渲染输出将包含更新的时间,并相应地更新 DOM。

一旦 Clock 组件被从 DOM 中移除,React 会调用 componentWillUnmount() 这个钩子函数,定时器也就会被清除。

数据自顶向下流动
父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。

这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。

以下实例中 FormattedDate 组件将在其属性中接收到 date 值,并且不知道它是来自 Clock 状态、还是来自 Clock 的属性、亦或手工输入:

React 实例
function FormattedDate(props) {
return

现在是 {props.date.toLocaleTimeString()}.

;
}

class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
date: new Date()
});
}

render() {
return (


Hello, world!




);
}
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(

);

尝试一下 »
这通常被称为自顶向下或单向数据流。 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。

如果你想象一个组件树作为属性的瀑布,每个组件的状态就像一个额外的水源,它连接在一个任意点,但也流下来。

为了表明所有组件都是真正隔离的,我们可以创建一个 App 组件,它渲染三个Clock:

React 实例
function FormattedDate(props) {
return

现在是 {props.date.toLocaleTimeString()}.

;
}

class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
date: new Date()
});
}

render() {
return (


Hello, world!




);
}
}

function App() {
return (






);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render();

尝试一下 »
以上实例中每个 Clock 组件都建立了自己的定时器并且独立更新。

在 React 应用程序中,组件是有状态还是无状态被认为是可能随时间而变化的组件的实现细节。

我们可以在有状态组件中使用无状态组件,也可以在无状态组件中使用有状态组件。

React 组件React Props
2 篇笔记 写笔记
ch4o5

don*nli619@gmail.com

参考地址

410
关于挂载时的 setInterval 中调用 tick() 的方式 ()=>this.tick():

1、()=>this.tick()

()=>this.tick() 是 ES6 中声明函数的一种方式,叫做箭头函数表达式,引入箭头函数有两个方面的作用:更简短的函数并且不绑定 this。

var f = ([参数]) => 表达式(单一)
// 等价于以下写法
var f = function([参数]){
return 表达式;
}
箭头函数的基本语法如下:

(参数1, 参数2, …, 参数N) => { 函数声明 }
(参数1, 参数2, …, 参数N) => 表达式(单一)
//相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }

// 当只有一个参数时,圆括号是可选的:
(单一参数) => {函数声明}
单一参数 => {函数声明}

// 没有参数的函数应该写成一对圆括号。
() => {函数声明}
根据以上概念,尝试将 setInterval 中调用 tick() 的方式改为通常声明方式:

this.timerID = setInterval(function(){
return this.tick();
},1000
);
但是会报错,tick() 不是一个方法。

2、this.tick()

this.tick() 中的 this 指代的是 function,而不是我们想要的指代所在的组件类 Clock,所以我们要想办法让 this 能被正常指代。这里我们采用围魏救赵的办法:

let that = this;
this.timerID = setInterval(function () {
return that.tick();
},1000);
在闭包函数的外部先用 that 引用组件 Clock 中挂载组件方法 componentDidMount() 中 this 的值,然后在 setInterval 中闭包函数中使用that,that 无法找到声明,就会根据作用域链去上级(上次层)中继承 that,也就是我们引用的组件类 Clock 中的 this。

到此为止,将 () => this.tick()等价代换为了我们熟悉的形式。
ch4o5
ch4o5

don*nli619@gmail.com

参考地址

6年前 (2018-09-07)
曹胖子

239*0360@qq.com

165
ch4o5童鞋 所列举将箭头函数用法恢复成常规写法,报错:tick() 不是一个方法的原因在于 setInterval 是 window 的方法,内闭包使用 this 当然也指向 window,window 没有 tick() 方法故报错。

this.timerID = setInterval(function(){ //setInterval是window的方法
return this.tick(); //this指的是window
},1000
);
所以需要更改指向,在 setInterval 外部定义变量,let that/_this = this,才能正常使用 _this.tick()。

componentDidMount(){
let _this = this;
this.timerID = setInterval(function(){
_this.tick();
}, 1000);
}

目录
相关文章
|
1天前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
3天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1538 5
|
1月前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
7天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
564 22
|
3天前
|
存储 SQL 关系型数据库
彻底搞懂InnoDB的MVCC多版本并发控制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
198 3
|
10天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
10天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
543 5
|
22天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
6天前
|
XML 安全 Java
【Maven】依赖管理,Maven仓库,Maven核心功能
【Maven】依赖管理,Maven仓库,Maven核心功能
219 3
|
9天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
323 2