我需要加载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最难的部分...
答案 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.DictionaryLength
和this.RandomNumber
之后执行。因此快速修复
loadUpDictionary() {
loadDictionary().then(d => {
this.setState(() => ({
dictionary: d,
isLoaded: true
}), () => {
this.DictionaryLength();
});
// console.log(this.state.dictionary);
});
}
这个想法是让this.loadUpDictionary
和this.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()
是异步的,则上面的模式有一个竞争条件,在您尝试使用它时,不能保证您的数据已准备就绪。一种解决方案是将异步调用的结果链接到then
或await
,以保证承诺已解决。
话虽如此,实际上没有必要将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>