文章目录
封装真正可复用的 Clock 组件。它将设置自己的计时器并每秒更新一次。
我们可以从封装时钟的外观开始:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
    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);
</script>
</body>
</html>
然而,它忽略了一个关键的技术细节:Clock 组件需要设置一个计时器,并且需要每秒更新 UI。
理想情况下,我们希望只编写一次代码,便可以让 Clock 组件自我更新:
ReactDOM.render(
  <Clock />,  
  document.getElementById('root')
);
我们需要在 Clock 组件中添加 “state” 来实现这个功能。
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
将函数组件转换成class组件
通过以下五步将 Clock 的函数组件转成 class 组件:
- 创建一个同名的 ES6 class,并且继承于 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 组件被定义为 class,而不是函数。
每次组件更新时 render 方法都会被调用,但只要在相同的 DOM 节点中渲染<Clock />,就仅有一个 Clock 组件的 class 实例被创建使用。这就使得我们可以使用如 state 或生命周期方法等很多其他特性。
向class组件中添加局部的state
我们通过以下三步将 date 从 props 移动到 state 中:
- 把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>
            );
        }
    }
- 添加一个 class 构造函数,然后在该函数中为 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>
            );
        }
    }
- 移除<Clock/>元素中的date属性:
    function tick() {
        ReactDOM.render(
            <Clock/>,
            document.getElementById('root')
        );
    }
接下来,我们会设置 Clock 的计时器并每秒更新它。
将生命周期方法添加到class中
在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
当 Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为**“挂载(mount)”**。
同时,当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为**“卸载(unmount)”**。
我们可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法:
    class Clock extends React.Component {
        componentDidMount() {
        }
        componentWillUnmount() {
            
        }
        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>
            );
        }
    }
这些方法叫做**“生命周期方法”**。
componentDidMount() 方法会在组件已经被渲染到 DOM 中后运行,所以,最好在这里设置计时器:
        componentDidMount() {
            this.timerID = setInterval(
                () => this.tick(),
                1000
            );
        }
接下来把计时器的 ID 保存在 this 之中(this.timerID)。
尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流(比如计时器 ID)的额外字段。
我们会在 componentWillUnmount() 生命周期方法中清除计时器:
        componentWillUnmount() {
            clearInterval(this.timerID)
        }
最后,我们会实现一个叫 tick() 的方法,Clock 组件每秒都会调用它。
使用 this.setState() 来时刻更新组件 state:
    class Clock extends React.Component {
        tick() {
            this.setState({
                date: new Date()
            });
        }
        componentDidMount() {
            this.timerID = setInterval(
                () => this.tick(),
                1000
            );
        }
        componentWillUnmount() {
            clearInterval(this.timerID)
        }
        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>
            );
        }
    }
现在时钟每秒都会刷新。
让我们来快速概括一下发生了什么和这些方法的调用顺序:
- 当 <Clock />被传给ReactDOM.render()的时候,React 会调用Clock组件的构造函数。因为Clock需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化this.state。我们会在之后更新 state。
- 之后 React 会调用组件的 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()生命周期方法,这样计时器就停止了。
正确地使用State
不要直接修改State
不要直接修改State:
// Wrong
this.state.comment = 'Hello';
而是使用setState():
this.setState({comment: 'Hello'});
构造函数是唯一可以给this.state赋值的地方。
State的更新可能是异步的
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
例如,此代码可能会无法更新计数器:
// wrong
this.setState({
	counter:this.state.counter + this.props.increment,
});
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
this.setState((state, props) => ({
	counter: state.counter + props.increment
}));
===============================
this.setState(function(state, props) {
	return {
		counter: state.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 作为 props 向下传递到它的子组件中:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
这对于自定义组件同样适用:
<FormattedDate date={this.state.date} />
FormattedDate 组件会在其 props 中接收参数 date,但是组件本身无法知道它是来自于 Clock 的 state,或是 Clock 的 props,还是手动输入的:
function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
为了证明每个组件都是真正独立的,我们可以创建一个渲染三个 Clock 的 App 组件:
function App() {
  return (
    <div>
      <Clock />      
      <Clock />      
      <Clock />    
    </div>
  );
}
ReactDOM.render(
  <App />,
  document.getElementById('root')
);
每个 Clock 组件都会单独设置它自己的计时器并且更新它。
在 React 应用中,组件是有状态组件还是无状态组件属于组件实现的细节,它可能会随着时间的推移而改变。你可以在有状态的组件中使用无状态的组件,反之亦然。








