React - 方法通过状态运行正确的时间,但在父组件更改状态

时间:2017-08-16 11:41:04

标签: javascript reactjs settimeout

我正在尝试构建一个页面,其中一些数据在首次安装时已初始化,并在websocket服务器在触发某个按钮点击事件时给出响应消息时进行更新,同时我还需要禁用该按钮。禁用,并告诉用户再次点击按钮的秒数。

我的第一个想法是,单个组件,通过状态更新,给计数器一个状态,然后使用setTimeout每1000ms倒数1,结果是计数器“banCount”工作正常,直到我添加websocket.send (),然后每次倒计时2。

我认为那是因为当websocket服务器响应时,状态是变化的,所以整个组件都被更新,计数器搞砸了。

所以,我有一个想法,将它分离成一个子组件,具有自己的状态,但在componentWillReceiveProps的生命周期中什么都不做,并且它不会接收道具,所以它只会使用它自己的状态。但结果是,无论是否将计数器分成子组件,它们都是一样的。

父组件:

import React from 'react';
import ReactDOM from 'react-dom';

import TestChild from './testChild/testChild';

class TestParent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
     wsData: null,
   };
 }

 componentWillMount() {
   this.wsClient = new WebSocket("ws://localhost:9000/server", 'echo-protocol');
   this.wsClient.onmessage = msg => {
     if (msg) {
       this.setState({
         wsData: msg.data
       });
     }
   };
 }

 render() {
   const data = () => {
     if (this.state.wsData) {
       return this.state.wsData;
     } else {
       return "waiting data";
     }
   };
   return (
     <div>
       <div>{data()}</div>
       <TestChild wsClient={this.wsClient}/>
     </div>
   );
 }    
}

ReactDOM.render(
   <TestParent />,
   document.getElementById('reactWrapper')
);

和子组件:

import React from 'react';

class TestChild extends React.Component {
  constructor(props) {
    super(props);
    this.count = null;
    this.state = {
      banCount: this.count
    };
    this.wsClient = this.props.wsClient;
    this.countupdate = 0;
  }

  banCount() {
    this.setState({
      banCount: this.count
    });
  }

  callNext(n) {
    this.wsClient.send('can you hear me');
    this.count = n;
    this.banCount();
  }

  componentDidUpdate() {
    if (this.count > 0) {
      setTimeout(() => {
        this.count -= 1;
        this.banCount();
      }, 1000);
    } else if (this.count === 0) {
      this.count = null;
      this.banCount();
    }
  }

  render() {
    return <button onClick={() => this.callNext(3)}>click me {this.state.banCount}</button>;
  }    
}

export default TestChild;

请忽略'服务器和websocket连接是否有效,它们没问题。

我不知道为什么,我甚至没有更新Child组件,我是React的新手,我真的不知道如何调试这个,我读了几个小时的代码,但对我来说太复杂了

为什么每次都倒数2?而且我肯定是错的,这是正确的方法。

请帮我使用React和vanilla Javascript,我没有使用Redux或Flux,甚至不知道它们是什么,谢谢。

2 个答案:

答案 0 :(得分:0)

这不是经过测试的代码,但是应该可以帮助你构建你想要的东西,我没有测试你的组件,但我怀疑你的setTimeout()被多次调用。

import React from 'react';

class TestChild extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: null,
    };
  }

  startCountDown() {
    var newCount = this.state.count -1;
    if(newCount === 0){
       clearTimeout(this.timer);
    }
    this.setState({
      count: newCount,
    });

  }

  callNext(n) {
    this.wsClient.send('can you hear me');
    this.setState({
      count: n,
    });
    this.timer = setTimeout(() => {
      startCountDown();
    }, 1000);
  }
  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  render() {
    return <button disabled={this.state.count>0} onClick={() => 
       this.callNext(3)}>click me {this.state.count}</button>;
  }    
}

export default TestChild;

答案 1 :(得分:0)

最后我把它解决了。

这是因为React将重新渲染所有子组件,无论是否设置子级的新状态。阻止它重新渲染的唯一方法是使用ShouldComponentUpdate,所以:

shouldComponentUpdate() {
  return this.state.banCount !== null;
}

将起作用,因为当子组件在websocket.send()之后接收道具时,this.count仍为null,但在websocket.send()之后,this.count设置为3,因此子组件将自从更新。

另一个工作:

callNext(n) {
  this.wsClient.send('can you hear me');
  this.count = n;
}

componentWillReceiveProps(nextProps) {
  this.data = nextProps.datas;
  this.setState({
    banCount: this.count
  });
}

在这个workround中,如果没有shouldComponentUpdate(),子组件将在其父组件接收websocket数据时始终重新呈现,因此在单击处理函数中,停止调用bancount(),因此它不会自行更新,而是设置状态当接收到nextProps时,将触发重新渲染。

总结以上所有:

子组件将始终使用或不通过新的道具设置状态重新渲染,除非shouldComponentUpdate返回false,我在click处理函数中称为bancount(),触发子组件更新状态本身,但在父组件接收之后websocket数据,它再次触发状态更新,这就是它运行两次的原因。