React状态 和 JavaScript箭头函数

发布时间 2023-03-29 18:21:16作者: 風栖祈鸢

React状态 和 JavaScript箭头函数

在看 React 的状态时见到了 JS 的箭头函数,一时间没看明白。

React 状态

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

在 React 中,当一个组件的状态发生改变时,React 会感知到状态的变化,并将其更新到 DOM 中。

先看一个界面上时钟的实现:

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

这个时钟只通过 React 实现了控件的绑定,但还没有实时刷新的功能,所以,需要在代码中设置一个定时器使其能够每秒刷新一次,此处使用生命周期方法(钩子)实现:

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>现在是 {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
 
ReactDOM.render(
  <Clock />,
  document.getElementById('example')
);

其中 componentDidMount()componentWillUnmount() 就是生命周期方法(钩子)。

当这个 Clock 组件输出到 DOM 后会执行 componentDidMount() 钩子,相当于组件的初始化,在上面,我们在这个方法中设置了一个定时器。

当这个 Clock 组件从 DOM 中移除后,会执行 componentWillUnmount() 钩子,通过初始化时设置的定时器的 ID this.timerID,可以卸载这个定时器。

在设置的定时器中执行了 tick() 方法,这个方法在执行时会设置 Clock 组件的状态,将 data 属性设置为当前的新的 Date 对象,React 可以感知到 State 的改变,会调用 render() 方法重新渲染输出,且 React 可以对比前后内容的差距,只重新渲染有区别的内容,这样就实现了画面的更新。

JavaScript箭头函数

在上面的例子中,可以注意到在 componentWillUnmount() 方法中使用了箭头函数:

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

其中,setInterval(func, time) 方法为每过 time 就执行一次 func,在上面的例子中,就是每过 1000 毫秒(1秒)就执行一次 () => this.tick() 方法。

那为什么这里要使用箭头函数呢?我一开始的想法是这里只是为了装B,于是把它换掉了:

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

打开页面,时间显示了,但不会动了。(后面发现,此时将 this.tick() 换为 console.log(this),此时的 this 确实是 Clock,但为什么时间不动,也还不是很懂了。)

于是尝试使用匿名函数 function() 将 tick() 包装起来:

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

这次报错了,问题是 tick() 不是一个方法:

Uncaught TypeError TypeError: this.tick is not a function

此时我就无法理解了。在研究了半天后,通过将 this.tick() 换为 console.log(this) 发现,此时的 this 居然是 Window:

  componentDidMount() {
    this.timerID = setInterval(function(){
      console.log(this);
      this.tick();
      },1000
    );
  }
  // 控制台输出:
  // Window {window: Window, self: Window, document: #document, name: '', location: Location, …}
  // Uncaught TypeError TypeError: this.tick is not a function

在这个方法中,this 指代的是 Window,而 tick() 是 Clock 的方法,所以这个报错就很合理了。

又研究了一会后发现,匿名函数中的 this 指代的就是 Window,所以直接使用匿名函数是行不通的,除非在外部保存 Clock 对象,再将对 this 的调用改为对 Clock 对象的调用:

  componentDidMount() {
    let that = this;
    this.timerID = setInterval(function(){
      console.log(that);
      that.tick();
      },1000
    );
  }
  // 控制台输出:
  // Clock {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}

这下就正常了,但这一会 that,一会 this 的,确实有点麻烦和乱了。所以这也是箭头函数存在的意义了(先撤了)。