想一下之前的章节时钟的例子。

目前为止我们只学习了一直方式去更新UI。

我们调用ReactDOM.render()方法去改变渲染的输出:

function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
} setInterval(tick, 1000);

在CodePen中试一试

在本章节,我们将学习怎样使Clock组件真正的可复用和封装起来。它将可以设置它自己的定时器并且每一秒更新自己。

我们可以先看看封装后的时钟是什么样子:

function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
} function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
} setInterval(tick, 1000);

在CodePen中试一试

然而,它遗漏了一个重要的条件:事实是Clock组件设置一个定时器并且每一秒更新UI这件事应该是Clock组件的一部分。

理想情况下,我们想要只写一次就可以让Clock更新自己:

ReactDOM.render(
<Clock />,
document.getElementById('root')
);

想要实现,需要添加state到Clock组件里。

状态state类似于属性,但是它是私有的并且完全由组件来控制。

我们前面提到过组件如果用类定义会有一些额外的特性。局部状态就是如此:一个功能只适用于类。

将函数转变成类

你可以把类似Clock的函数组件转变成类,只需五个步骤:

  1. 创建一个ES6类,名字和函数的名字相同,并且继承React.Component。
  2. 给这个类添加一个空的方法起名为render()。
  3. 把函数的主体内容移动到render()方法内。
  4. 在render()内把属性props替换成this.props。
  5. 删除遗留的空的函数声明。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

在CodePen里试一试

Clock组件现在被定义成了类而不止是函数。

这样我们可以使用额外特性就像state和生命周期钩子ligecycle hooks。

为类添加状态

我们将把date从属性props里移动到state里,需要三个步骤:

1)在render()方法里将this.props.date替换成this.state.date:

class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

2)添加一个class constructor 构造函数来分配初始的this.state值:

class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
} render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

注意我们如何传递props给基本构造函数:

  constructor(props) {
super(props);
this.state = {date: new Date()};
}

类组件应该一直使用props作为参数调用基本构造函数。

3)从<Clock />元素中去除date属性:

ReactDOM.render(
<Clock />,
document.getElementById('root')
);

我们在之后将重新添加定时器代码到组件内部。

结果会是这样:

class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
} render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
} ReactDOM.render(
<Clock />,
document.getElementById('root')
);

在CodePen里试一试

接下来,我们会让Clock组件设置自己的定时器并且每一秒都更新自己。

为类添加生命周期方法

在一个有很多组件的应用里,当组件被销毁的时候释放资源是一件很重要的事情。

每当Clock被第一次渲染到DOM里,我们想要设置一个定时器。这个时刻被叫做“mounting”。

每当Clock生成的这个DOM被移除的时候,我们也想清除定时器。这个时刻被叫做“unmounting”。

我们可以在组件类里声明特殊的方法,当mount和unmount的状态的时候去运行这些方法。

class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
} componentDidMount() { } componentWillUnmount() { } render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

这些方法被称为“生命周期钩子”lifecycle hooks。

当组件的输出被渲染到DOM中的时候,componentDidMount()钩子会执行。这里是最合适的地方设置一个定时器:

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

注意我们怎样正确保存定时器到this上。

this.props是React自己设置的,this.state有一种特殊的含义,如果你需要存储一些不需要视觉的输出,那么你可以随意手动添加额外的字段到类里面。

如果你不需要在render()使用什么东西,那那些东西就不会存在于state里。

在componentWillUnmount()生命周期钩子中我们将清除定时器:

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

最终,我们实现了tick()在每一秒都会运行。

this.setState()会安排更新组件的state。

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 (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
} ReactDOM.render(
<Clock />,
document.getElementById('root')
);

在CodePen中试一试

现在表每一秒都会滴答一下。

现在来快速地概括一下这段代码发生了什么以及调用方法的顺序:

1)当<Clock />被传递给ReactDOM.render(),React会调用Clock组件的构造函数。因为Clock组件需要显示当前时间,它就用一个包含当前时间的对象初始化this.state。之后我们会更新这个state。

2)之后React会调用Clock组件的render()方法。React就是这样得知应该把什么显示在屏幕上。之后React会更新DOM匹配Clock组件的渲染输出。

3)当Clock组件的输出被插入了DOM,React会调用componentDisMount()生命周期钩子函数。在钩子函数里,Clock组件会询问浏览器去设置一个定时器每秒调用一次tick()。

4)每一秒浏览器调用tick()方法。在tick里,Clock组件安排了UI的更新,通过调用setState()方法和一个参数,参数是一个包含当前时间的对象。多亏了setState()的调用,React知道了state被改变了,然后就会重新调用render()方法来得知什么应该被显示到屏幕上。在这个时刻,render()方法里的this.state.date会发生变化,一次渲染输出会包含更新了的时间。React会相应地更新DOM。

5)如果Clock组件在某时从DOM中移除,React会调用componentWillUnmount()生命周期钩子来停止定时器。

正确使用state

关于setState()你需要知道三件事:

不要直接改变state

举个例子,这样不会重新渲染DOM:

// Wrong
this.state.comment = 'Hello';

应该使用setState():

// Correct
this.setState({comment: 'Hello'});

你只能在构造函数里初始化this.state。

state的更新可能是异步的

React也许会批量将很多setState()调用放进一次更新里。

因为this.props和this.state也许会异步更新,所以你不应该依据它们的值来计算下一次state。

举个例子,以下代码更新counter也许会失败:

// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});

要修改这个问题,使用一个第二种形态的setState(),它接受一个函数为参数而不是对象。那个函数会接受先前的state为第一个参数,第二个参数是当更新被应用的那个时候的props:

// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));

上面我们用了箭头函数,但是也可以使用常规的函数:

// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});

state更新是合并了的

当你调用setState()的时候,React会将你提供的对象合并到当前state。
举个例子,你的state也许包含了好几个独立的变量:
 constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}

然后你可以分别调用setState()来独立地更新它们:

 componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
}); fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}

合并是很浅的,所以this.setState({comments})没有改变this.state.posts,但是完全替换了this.state.comments。

数据自顶向下流动

父组件和子组件都不能知道某一个组件是有状态的还是无状态的,并且它们也不应该关心组件是函数的还是类的。

这就是为什么state经常被叫做本地的或者封装的。组件不能设置别人的state只能设置自己的。

一个组件也许会选择去传递它的state作为props给它的子组件:

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
 这在用户自定义组件也同样适用:
<FormattedDate date={this.state.date} />

FormattedDate组件会从它的props里接受到date并且不会知道date是否来自于Clock的state,来自于Clock的props,或者是手动添加的:

function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

在CodePen里试一试

这样情况都被叫做“自顶向下”或者“单向的”数据流动。任何state都总是被一些特定的组件拥有,并且从state导出的任何数据或者UI只能影响比自己低的组件。

如果你想象一个组件树是一个props的瀑布,每一个组件的state就像一股附加的水流,会在任意点加入到瀑布中往下游流动。

为了表示所有的组件都是被隔离的,我们可以创建一个APP组件,用它来渲染三个<Clock>:

function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
} ReactDOM.render(
<App />,
document.getElementById('root')
);
每一个Clock会独立设置自己的定时器并更新。
在 React 应用程序中,组件是有状态还是无状态被认为是可能随时间而变化的组件的实现细节。 可以在有状态组件中使用无状态组件,反之亦然。

React文档(六)state和生命周期的更多相关文章

  1. React源码剖析系列 - 生命周期的管理艺术

    目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理.本系列文章希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期(C ...

  2. React 源码剖析系列 - 生命周期的管理艺术

    目前,前端领域中 React 势头正盛,很少能够深入剖析内部实现机制和原理. 本系列文章 希望通过剖析 React 源码,理解其内部的实现原理,知其然更要知其所以然. 对于 React,其组件生命周期 ...

  3. React文档(十三)思考React

    在我们的看来,React是使用js创建大型快速网站应用的首要方法.它在Facebook和Instagram的使用已经为我们展现了它自己. React的一个很好的地方就在于当你创建应用的时候它使你思考如 ...

  4. React Native组件的结构和生命周期

    React Native组件的结构和生命周期 一.组件的结构 1.导入引用 可以理解为C++编程中的头文件. 导入引用包括导入react native定义的组件.API,以及自定义的组件. 1.1 导 ...

  5. React文档(十六)refs和DOM

    Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素. 在标准的React数据流中,props是使得父组件和子组件之间交互的唯一方式.你通过props重新 ...

  6. 初识React,Virutal DOM, State以及生命周期

    这是React分类下的第一篇文章,是在了解了一些基本面后,看Tyler文章,边看边理解边写的. React可以看做是MVC中的V,关注的是视图层.React的组件就像Angular的Directive ...

  7. React文档(二十四)高阶组件

    高阶组件(HOC)是React里的高级技术为了应对重用组件的逻辑.HOCs本质上不是React API的一部分.它是从React的组合性质中显露出来的模式. 具体来说,一个高阶组件就是一个获取一个组件 ...

  8. React第二篇:组件的生命周期

    前言:因为生命周期是必须要掌握的,所以React的第二篇咱就写这. (版本:16.3.2) React的生命周期大致分为四个状态:分别是Mouting.Updating.Unmounting.Erro ...

  9. React基础篇 (3)-- 生命周期

    生命周期是react中的重要部分,理解它有助于我们更合理的书写逻辑. 组件的生命周期可分成三个状态: Mounting:已插入真实 DOM Updating:正在被重新渲染 Unmounting:已移 ...

随机推荐

  1. C# DataTable.Compute()用法

    DataTable.Compute()用法 2010-04-07 11:28 一.DataTable.Compute()方法說明如下 作用:          计算用来传递筛选条件的当前行上的给定表达 ...

  2. (转载)MySQl数据库-批量添加数据的两种方法

    方法一:使用excel表格 方法二:使用insert语句(FileWriter批量写入) 使用excel表格 1.打开数据表,按照表的字段在excel中添加数据.注意:表中字段名必须和excel中的名 ...

  3. Luncene学习二《搜索索引》

    搜索索引的流程 第一步:创建一个Directory对象,也就是索引库存放的位置 第二步:创建一个IndexReader对象,需要指定Directory对象 第三步:创建一个indexsearcher对 ...

  4. .Net Core项目在Docker上运行,内存占用过多导致pods重启的问题

    默认情况下,.NET Core应用的内存回收模式是Server模式,这种情况下,内存占用和服务器核心数量有关,一半占用量比较大. 我们的应用目前吞吐量都不大,可以采用Workstation模式,这种模 ...

  5. 17秋 SDN课程 第二次上机作业

    1.控制器floodlight所示可视化图形拓扑的截图,及主机拓扑连通性检测截图 拓扑 连通性 2.利用字符界面下发流表,使得'h1'和'h2' ping 不通 流表截图 连通性 3.利用字符界面下发 ...

  6. android获取屏幕宽度和高度

    1. WindowManager wm = (WindowManager) getContext() .getSystemService(Context.WINDOW_SERVICE); int wi ...

  7. CSS垂直居中查询宝典

    一.垂直居中的用处 设计稿需求 当我们抱怨设计反复不定的时候,试着理解一下.每一位开发者也会是一位用户,请多多用'用户'的角色去开发.就比如下面这图,你会更稀饭哪种格式呢? 如果我们使用一个webap ...

  8. activity 运行流程图

  9. 转载 R语言颜色基础设置

    原文链接:http://www.biostatistic.net/thread-5065-1-1.html R语言在画图形的时候,经常遇到颜色设定问题,用户可以根据color.rgb值和hsv值来设定 ...

  10. 学习笔记46—如何使Word和EndNote关联

    1)打开Word文件项目中的选项,然后点击加载项, 2)找到Endnote安装目录,选择目录中的Configure EndNote.exe,选中configuration endnote compon ...