4.State and Lifecycle(状态和生命周期)

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 ** :

  1. 用相同的名字创建一个ES6类,并且继承React.Component
  2. 将一个叫做 render() 的空方法添加到类中。
  3. 将函数体移动到render()方法中。
  4. render()方法体中用this.props替换 props。
  5. 删除空函数余下的声明。
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>
    );
  }
}
  1. 添加一个 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。

  1. <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')
);

现在时钟每秒滴答。
让我们快速回顾一下发生了什么以及方法的调用顺序:

  1. <Clock />被传递到ReactDOM.render(), React 调用Clock组件的constructor。 由于Clock需要展示当前的时间,它便用一个包含当前时间的对象初始化this.state。我们稍后会更新this state.

  2. 然后 React 调用 Clock组件的render()方法。 这就是React如何知道什么应该展现在屏幕上。然后 React 更新 DOM 去匹配Clock 的渲染输出。

  3. 当 Clock输出已被插入到 DOM时, React 调用componentDidMount()生命周期钩子函数。在该函数中,Clock组件让浏览器创建一个定时器,并每秒调用 tick() 一次。

  4. 浏览器每秒调用一次tick()方法。在该方法中,Clock组件通过传入一个包含当前时间的对象调用setState(),以此来安排UI更新。多亏了setState()调用, React 知道 state 已发生改变,并且再次调用 render()方法来了解什么应该出现在屏幕上。这次,render()方法中的this.state.date将会不同,并且输出的渲染将包含更新过的时间。React 因此更新了 DOM 。

  5. 如果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.propsthis.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组件,反之亦然。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 想一下上一节中那个滴答计时的例子。迄今为止,我们只学到一种更新UI的方法。我们通过调用ReactDOM.rende...
    莫铭阅读 484评论 0 0
  • State 和生命周期 考虑前面章节中时钟的例子。 到目前位置,我们仅学习了一种更新 UI 的方式。 我们调用Re...
    soojade阅读 1,187评论 0 1
  • 考虑之前的例子,我们只学会了一种方法去更新UI,我们调用ReactDOM.render()去改变输出渲染: 在这个...
    编码的哲哲阅读 549评论 0 0
  • 学习使用Clock组件,来重用和封装。并设置定时器。封装了一个定时器,如: Try it on CodePen. ...
    ZMJun阅读 440评论 0 0
  • 本笔记基于React官方文档,当前React版本号为15.4.0。 1. 安装 1.1 尝试 开始之前可以先去co...
    Awey阅读 7,532评论 14 128