React版本:15.4.2
**翻译:xiyoki **
考虑前一节中滴答作响的时钟的例子。
到目前为止,我们只学习了一种更新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);
在本节中,我们将学习如何使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);
但是,它漏掉了一项重要要求:Clock设置计时器和每秒更新UI都应当作为Clock自身的实现细节。
理想情况下,我们希望再写一次上述代码,并且Clock要更新它自身:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
要实现这一点,我们需要将‘state’添加到Clock组件。
State和props比较相似,但state是组件私有的,并且完全由组件控制。
我们之前提到了定义为类的组件有一些额外的功能。
Local State是指:一个只对类组件有效的功能。
Converting a Function to a Class(将一个函数转换为类)
你只需5步就能将一个** functional component ** 转换成一个 ** class component ** :
- 用相同的名字创建一个ES6类,并且继承
React.Component
。 - 将一个叫做
render()
的空方法添加到类中。 - 将函数体移动到
render()
方法中。 - 在
render()
方法体中用this.props
替换 props。 - 删除空函数余下的声明。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Clock现在被定义成了一个类而不是函数。
这使我们可以使用额外的功能,如局部状态和生命周期钩子。
Adding Local State to a Class(将本地状态添加到类)
只需三步,我们就将date从props移动到state:
1)在render()
方法中用 this.state.date
替换 this.props.date
:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- 添加一个 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到base constructor的:
constructor(props) {
super(props);
this.state = {date: new Date()};
}
类组件在调用base constructor时应当总是传递props。
- 从
<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')
);
下面,我们将使Clock
设置它自己的计时器,并且每秒钟更新自己。
Adding Lifecycle Methods to a Class(向类添加生命周期方法)
在具有许多组件的应用程序中,当组件被销毁时释放组件所占用的资源非常重要。
每当Clock被首次渲染到DOM时,我们都要建立一个定时器。这在React中被称作 "mounting(安装)"。
每当由Clock产生的DOM被移除时,我们也希望清除定时器。这在React中被称为 "unmounting(卸载)"。
我们可以在组件类上声明特殊的方法,以便在组件安装和卸载时运行一些代码:
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(生命周期钩子)。
在组件的输出(组件返回的React元素)已被渲染到DOM上后,componentDidMount()
hook 运行。这是设置计时器的好地方:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
注意我们是如何保存timerID到this上的。
虽然this.props
由React元素自身设置,this.state
也有其特殊的意义,然而如果你需要存储东西不用于可视化输出,那么你也可以自由地手动添加其他字段到类。
如果一些数据你没有在render()
中使用,那么这些数据就不应该出现在state中。
我们将在componentWillUnmount()
生命周期钩子中清除计时器:
componentWillUnmount() {
clearInterval(this.timerID);
}
最后,我们将要实现每秒运行一次的tick()
方法。
它将使用this.setState()
去排定组件local 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')
);
现在时钟每秒滴答。
让我们快速回顾一下发生了什么以及方法的调用顺序:
-
当
<Clock />
被传递到ReactDOM.render()
, React 调用Clock
组件的constructor
。 由于Clock
需要展示当前的时间,它便用一个包含当前时间的对象初始化this.state
。我们稍后会更新this state
. -
然后 React 调用
Clock
组件的render()
方法。 这就是React如何知道什么应该展现在屏幕上。然后 React 更新 DOM 去匹配Clock
的渲染输出。 -
当 Clock输出已被插入到 DOM时, React 调用
componentDidMount()
生命周期钩子函数。在该函数中,Clock
组件让浏览器创建一个定时器,并每秒调用 tick() 一次。 -
浏览器每秒调用一次
tick()
方法。在该方法中,Clock组件通过传入一个包含当前时间的对象调用setState()
,以此来安排UI更新。多亏了setState()
调用, React 知道 state 已发生改变,并且再次调用render()
方法来了解什么应该出现在屏幕上。这次,render()
方法中的this.state.date
将会不同,并且输出的渲染将包含更新过的时间。React 因此更新了 DOM 。 -
如果
Clock
组件被从DOM中移除, React 就调用componentWillUnmount()
生命周期钩子函数,因此定时器就被停止了。
Using State Correctly(正确使用State)
关于setState(),你应该知道三件事。
Do Not Modify State Directly(不要直接修改state)
例如:这将不会重新渲染组件:
// Wrong
this.state.comment = 'Hello';
相反,使用setState()代替:
// Correct
this.setState({comment: 'Hello'});
唯一你可以指派this.state
的地方是constructor
。
State Updates May Be Asynchronous (状态更新可能异步)
为了性能,React会将多个setState()
调用放置到单一的更新中。
由于this.props
和this.state
可以被异步更新,因此你不应该依赖它们的值来计算下一个状态。
例如,此代码可能无法更新计数器:
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 Updates are Merged(状态更新被合并)
当你调用setState()
时,React将你提供的对象合并到当前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
。
The Data Flows Down(数据向下流动)
不论是父组件,还是子组件都不知道某个组件是stateful还是stateless,并且它们不应该关心该组件是否被定义为函数或类。
这就是为什么状态通常被称为局部或封装。组件的状态除了拥有和设置它的组件外,其它任何组件都不能访问它。
组件可以选择将其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>;
}
这通常被称为‘自顶向下’或‘单向’数据流。任何state总是由一些特定组件所拥有,并且从该state派生的任何数据或UI只能影响树中位于它们下面的组件。
如果你将组件树想象成由props构成的瀑布,那么每个组件的state就像一个额外的水源,它在任意点连接瀑布,并且也向下流。
为了展示所有组件是真正孤立的,我们可以创建一个App组件,该组件渲染三个<Clock />
:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每个Clock独立地设置其自己的计时器,独立地更新。
在React应用程序中,组件是stateful还是stateless被视为可能随时间更改的组件的实现细节。你可以在stateful组件内使用stateless组件,反之亦然。
网友评论