react文档——state和生命周期

State 和生命周期

考虑前面章节中时钟的例子。

到目前位置,我们仅学习了一种更新 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')
);

为了实现它,我们需要给Clock组件添加“state”。

State 类似于 props,但它是私有的并且完全由组件控制。

我们之前提到的,组件定义为类时有一些额外的特性。本地 state 正是这样:一个只有类可用的特性。

由函数转换到类

你可以通过五步将函数式组件转换成类组件,比如Clock

  1. 使用相同的名字创建一个继承自React.Component的 ES6 类。
  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>
    );
  }
}

CodePen上试试。

Clock现在被定义为类而不是函数。

这让我们可以使用额外的特性比如:本地 state 和生命周期钩子。

添加本地 State 到一个类

我们将通过三步将dateprops移到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>
    );
  }
}

2)添加一个构造函数来初始化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时设置一个计时器。这在 React 中称为“挂载”。

我们也想每当通过Clock生成的 DOM 被移除时清除这个计时器。这在 React 总称为“卸载”。

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

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>
    );
  }
}

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

componentDidMount()钩子在组件输出已经被渲染到 DOM 之后运行。这是设置一个计时器的好地方:

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

注意我们如何在this的右边保存计时器的 ID。

虽然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 调用componentDidMount()生命周期钩子。在其内部,Clock组件要求浏览器设置一个计时器来一秒钟调用一次tick()
  4. 每一秒浏览器调用一次tick()方法。在其内部,Clock组件通过调用一个使用包含当前时间的对象作为参数的setState()方法来安排 UI 的更新。由于setState()的调用,React 知道了 state 已经改变,并且再次调用render()方法获取应该在屏幕上显示的内容。这一次,render()方法中的this.state.date是不同的,所以渲染输出会包含更新的时间。React 相应的更新 DOM。
  5. 如果Clock组件永久的从 DOM 移除,React 调用componentWillUnmount()生命周期钩子,因此计时器停止了。

正确的使用 State

关于setState()有三件事你应该知道。

不要直接修改 State

例如,这将不会重新渲染一个组件:

// 错误
this.state.comment = 'Hello';

相反,使用setState():

// 改正后
this.setState({comment: 'Hello'});

唯一可以给this.state赋值的地方是在构造函数中。

State 更新可能是异步的

出于性能考虑,React 可能在一次更新中调用多次setState()

由于this.propsthis.state的更新可能是异步的,你不应该依赖它们的值来计算下一个 state。

例如,下面这段代码更新counter会失败:

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

我们使用setState()的第二种形式:接收一个函数而不是对象,来修复它。这个函数会接收之前的 state 作为第一个参数,并且在当时应用于更新的 props 作为第二个参数:

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

上面我们使用了箭头函数,使用普通的函数也是有效的:

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 作为 props 向下传递给它的子组件。

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

这也适用于用户自定义组件:

<FormattedDate date={this.state.date} />

FormattedDate组件会接收date作为它的 props,并且不会知道它是否来自Clock的 state、props,还是手动输入的。

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

CodePen上试试。

这通常被称为“自上而下”的或“单向”数据流。任何 state 始终隶属于一些特定的组件,并且任何来源于这个 state 的数据或 UI 只能影响到组件树“下面”的组件。

如果你把组件树想象成一个由 props 构成的瀑布,每一个组件的 sate 就像额外的水源,会在任意的节点汇入这个瀑布并且随之向下流。

为了展示所有的组件都是真正的独立的,我们可以创建一个App组件,渲染三个<Clock>

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

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

CodePen上试试。

每个Clock设置它自己的定时器,并独立更新。

在 React 应用中,不管组件是有状态的还是无状态的,都被认为组件的实现细节可以随时间发生改变的。你可以在有状态的组件内使用无状态的组件,反之亦然。

下一步

事件处理

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

推荐阅读更多精彩内容