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

nhjlsmyf  于 2022-10-15  发布在  React
关注(0)|答案(4)|浏览(186)

我需要加载一个包含26731个元素的JSON文件,然后获取长度,然后生成一个随机元素并使用代码中的值。但这种异步React让我抓狂,我一直在努力让简单的东西同步
问题是,尽管我可以将JSON加载到DICTIONARY:[],但我不能使用它来生成随机数的长度。
我尝试了扩散运算符...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口述长度0获取单个条目,表示空闲。高级在船的尾部半部。准备好了。比船尾更靠近船尾。[来自a2,-baft:参见aft]
对我来说,这是最难的React部分。

n8ghc7c1

n8ghc7c11#

通常,除非有充分的理由,否则您不希望在一个函数中多次调用setState,因为这会降低性能。您需要整理所有数据,然后使用所有数据调用一次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)
  });
}

只要让这些函数“返回”值,而不是将其设置为状态即可。

yqhsw0fo

yqhsw0fo2#

问题不在于Reaction,而在于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()将立即被执行。您在loadUpDictionary中有一个.then,但它将仅在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()来执行此操作

dba5bblo

dba5bblo3#

您需要了解异步是如何工作的。代码将执行并继续到下一行**,然后**上一个函数完成。因此,在您的场景中,每个函数都会立即运行,而不是等待前一个函数完成。

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

这就是承诺存在的原因。一些选项--根据您的目标浏览器,使用像Bluebird这样的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
    });
};
uurity8g

uurity8g4#

拥抱异步编程!它的好处可以用一个早餐例子来说明。如果你在做烤面包,你是愿意在烤面包机后什么都不做,还是先打开烤面包机,然后拿出橙汁和麦片来等烤面包做好?
与异步编程相同:您可以发出一个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也不是必需的。由于状态更改,Reaction会自动知道加载已完成,并将触发重新渲染。只需对词典发出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.querySelector("#app"));

下面是一个带有钩子和假请求的可运行代码片段,该请求还会在请求到达时而不是在呈现时选取随机单词:

<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.createRoot(document.querySelector("#app"))
  .render(<Example />);

</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

相关问题