如何让setState在Reactjs中同步运行?

时间:2019-11-11 02:03:11

标签: reactjs

我需要加载26731个元素的JSON文件,然后获取长度,然后生成一个随机元素,并使用代码中的值。但是这种异步的Reactjs让我发疯,我花了很多时间试图让简单的东西同步

问题是,尽管我可以将JSON加载到字典中:[]我不能使用它来生成随机数的长度。

我尝试了Spread运算符... this.state,但这似乎不合适,也没有任何效果。

我的状态

this.state = {
  dictionary: [],
  isLoaded: false,
  dictLength: 0,
  rnd: 0
};

componentDidMount() {
  this.loadUpDictionary();
  this.DictionaryLength();
  this.RandomNumber();
}

这行得通,因为我可以在“渲染器”中看到一个字典。该代码还可以,除了componentDidMount不能按顺序运行这些函数。

loadUpDictionary() {
  loadDictionary().then(d => {
    this.setState(() => ({
      dictionary: d,
      isLoaded: true
    }));
    //  console.log(this.state.dictionary);
  });
}

但是这些函数不包含任何值,因为在呈现字典之前它不包含任何值

DictionaryLength() {
  const Length = this.state.dictionary.length;
   console.log("Length of dictionary " + Length); //outputs Length of dictionary 0
  this.setState(() => ({
    dictLength: Length
  }));
}

RandomNumber() {
const min = 0;
const max = this.state.dictLength - 1;
const rnd = Math.floor(Math.random() * (max - min + 1) + min);
this.setState(() => ({
    rnd: rnd
  }));
}

渲染

render() {
  const { isLoaded, dictionary, dictLength, rnd } = this.state; //pass across the state

  if (!isLoaded) {
    return <div>Loading ....</div>;
  } else {
    return (
      <div>
        <h1>
          Dictionary loaded {dictionary.Entries.length} RND {rnd} dictLength{" "}
          {dictLength}
        </h1>
        <h2>Get a single entry {dictionary.Entries[3].Word} </h2> means{" "}
        {dictionary.Entries[3].Definition}
      </div>
    );
  }
}

输出

已加载字典的字典26731 RND 0 dictLength 0 一次性输入Abaft 意思是坚果。副词adv。在船尾的一半。准备比船尾更近。 [摘自* a2,-baft:请参阅* aft]

对我来说,这是React最难的部分...

4 个答案:

答案 0 :(得分:2)

通常,除非有充分的理由,否则您不希望在一个函数中多次调用setState,因为那样会降低性能。您想要整理所有数据,然后使用 all 数据一次调用setState。因此,加载字典,计算您的随机数,然后调用setState一次,同时更新所有4个状态值。像这样的东西(还没有测试过):

componentDidMount() {
  this.loadUpDictionary()
    .then(d => {
      const min = 0;
      const max = d.length - 1;
      const rnd = Math.floor(Math.random() * (max - min + 1) + min);
      this.setState({
        dictionary: d,
        isLoaded: true
        dictLength: d.length,
        rnd
      });
    });
}

loadUpDictionary() {
  return loadDictionary();
}

后续调用将嵌套在加载的.then中,但这很好,因为它们始终依赖于此调用。

如果您希望自己计算字典长度和随机数,那么同步函数仍然可以执行并调用它们,例如

.then(d => {
  this.setState({
     dictionary: d,
     isLoaded: true
     dictLength: this.DictionaryLength(d),
     rnd: this.RandomNumber(d)
  });
}

只需具有这些函数返回值,而不是将其设置为状态即可。

答案 1 :(得分:0)

问题不在于React,而是JavaScript的工作方式

this.loadUpDictionary();
this.DictionaryLength();
this.RandomNumber();

loadUpDictionary() {
loadDictionary().then(d => {
  this.setState(() => ({
    dictionary: d,
    isLoaded: true
  }));
  //  console.log(this.state.dictionary);
});
}

this.loadUpDictionary被执行时,this.DictionaryLength()将立即被执行。您在.then中有一个loadUpDictionary,但是它只会在this.DictionaryLengththis.RandomNumber之后执行。因此快速修复

loadUpDictionary() {
loadDictionary().then(d => {
  this.setState(() => ({
    dictionary: d,
    isLoaded: true
  }), () => {
    this.DictionaryLength();
  });
  //  console.log(this.state.dictionary);
});
}

这个想法是让this.loadUpDictionarythis.setState首先运行,并且由于setState允许进行回调,因此您可以让this.DictionaryLength()做到这一点

答案 2 :(得分:0)

您需要了解异步的工作原理。该代码将执行并在上一个功能完成之前之前继续到下一行。因此,在您的情况下,每个功能都会立即运行,而不必等待上一个功能完成。

this.loadUpDictionary();
this.DictionaryLength();
this.RandomNumber(); 

这就是承诺存在的原因。某些选项-根据您的目标浏览器,使用诸如Bluebird之类的Promise库并从您的函数中返回Promise,或使用javascript async/await,以便在您尝试下一步处理结果之前完成调用

但是,建议您看一下上面的代码,建议将字典的处理组合到单个函数中,然后从那里设置状态。除非需要,否则我将避免将整个字典加载到状态中。像这样:

initializeDictionary = async () => {
    // wait for the data to return
    const dictionary = await loadDictionary();

    const min = 0;
    const max = dictionary.Entries.length - 1;
    const rnd = Math.floor(Math.random() * (max - min + 1) + min);

    // get a random entry
    const entry = dictionary.Entries[rnd];

    // set the entry (length if needed)
    this.setState({
        entry: entry,
        dictionaryLength: dictionary.Entries.length
    });
}; 

答案 3 :(得分:0)

拥抱异步编程!早餐的例子可以说明它的好处。如果要烤面包,您是否宁愿在开始烤面包机或开始烤面包机之后什么也不做,然后在等待烤面包准备就绪时拿出橙汁和谷类食品?

与异步编程相同:您可以触发HTTP请求并与用户保持交互,直到数据返回为止,而不用挂起浏览器。如果您需要发出20个独立的HTTP请求,那么async可以让您一次完成所有请求,而无需顺序执行。

您提供的代码似乎与可以正常工作的流程非常接近,但是主要问题似乎是此功能:

componentDidMount() {
  this.loadUpDictionary();
  this.DictionaryLength(); // depends on `this.loadUpDictionary` resolving
  this.RandomNumber();     // also depends on `this.loadUpDictionary` resolving
}

如果this.loadUpDictionary()是异步的,则上面的模式有一个竞争条件,在您尝试使用它时,不能保证您的数据已准备就绪。一种解决方案是将异步调用的结果链接到thenawait,以保证承诺已解决。

话虽如此,实际上没有必要将dictionary的长度存储在其自己的变量中。您已经具有dictionary.length属性,因此这是担心保持一致性的更多状态。

有可能,isLoaded也没有必要。由于状态更改,React自动知道加载已完成,并会触发重新渲染。只需对字典进行HTTP请求,并在请求解析后选择一个随机词即可。

这是一个最小的完整示例:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {dictionary: []};
  }

  async loadDictionary() {
    try {
      const url = "https://raw.githubusercontent.com/dwyl/english-words/master/words_dictionary.json"; // 6.51 MB
      const response = await fetch(url);
      const data = await response.json();
      this.setState({dictionary: Object.keys(data)});    
    }
    catch (err) {
      console.log(err);
    }
  }

  componentWillMount() {
    this.loadDictionary();
  }

  render() {
    const {dictionary} = this.state;
    return (
      <div>
        {dictionary.length
          ? <div>
              <p>Random word: {dictionary[~~(Math.random()*dictionary.length)]}</p> 
              <p>dictionary length: {dictionary.length}</p>
            </div>
          : <p>loading...</p>
        }
      </div>
    );
  }
}

ReactDOM.render(<Example />, document.body);

这是一个带有钩子和伪造请求的可运行代码段,该伪造请求还在请求到达时而不是在渲染时选择随机单词:

<script type="text/babel" defer>
const {useState, useEffect} = React;

const Example = () => {
  const [dictionary, setDictionary] = useState([]);
  const [randomWord, setRandomWord] = useState("");
  
  const loadDictionary = async () => {
    try {
      const response = await new Promise(resolve => 
        setTimeout(resolve, 2000, {data: ["apples", "bananas", "cherries"]})
      );
      setDictionary(response.data);
      setRandomWord(response.data[~~(Math.random()*response.data.length)]);
    }
    catch (err) {
      console.log(err);
    }
  };
  
  useEffect(() => {
    loadDictionary();
  }, []);

  return (
    <div>
      {dictionary.length
        ? <div>
            <p>Random word: {randomWord}</p> 
            <p>dictionary length: {dictionary.length}</p>
          </div>
        : <p>loading...</p>
      }
    </div>
  );
}

ReactDOM.render(<Example />, document.body);

</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>