我正在使用异步 API 调用来获取父记录中的一些模板数据。(子组件将根据在其他子组件中所做的选择进行切换。
模板数据很大,所以我只想获取一次。此外,我试图只传递每个孩子所需的最少道具。有些只需要模板的名称,有些则需要适当的组数组。因此,我正在尝试在父元素中完成工作。我似乎在设置状态方面遇到了一些困惑。
编辑
我调用没有参数的useEffect
,以便它在加载时执行。我希望第一个setTemplates()
将设置templates
状态变量,以便我可以在下一次调用中使用它来同时setTemplateChoices()
和设置templateChoices
状态。
// working version
import React, { useState, useEffect } from 'react'
export default function ParentComponent() {
const [templates, setTemplates] = useState([])
const [templateChoices, setTemplateChoices] = useState([])
// this will actually be fetched from within the `useEffect()` call -
// I'm just putting it here to show structure and make it testable
const defaultTemplates = [
{ id: 1, name: 'Form 1', }, // plus - groups: [ {}, {} ... ], etc.
{ id: 2, name: 'Form 2', }, // plus - groups: [ {}, {} ... ], etc.
{ id: 3, name: 'Form 3', }, // plus - groups: [ {}, {} ... ], etc.
]
useEffect(() => {
setTemplates(defaultTemplates)
}, [])
return (
<div>
<h2>Templates</h2>
{templates.map(template => (
<p>{template.name}</p>
))}
</div>
)
}
但这不会:
// non-working version
import React, { useState, useEffect } from 'react'
export default function ParentComponent() {
const [templates, setTemplates] = useState([])
const [templateChoices, setTemplateChoices] = useState([])
const defaultTemplates = [
{ id: 1, name: 'Form 1' },
{ id: 2, name: 'Form 2' },
{ id: 3, name: 'Form 3' },
]
useEffect(() => {
setTemplates(defaultTemplates)
setTemplateChoices(templates.map(template => template.name))
}, [])
return (
<div>
<h2>Template Choices</h2>
{templateChoices.map(choice => (
<p>{choice}</p>
))}
<p>Children go here depending on step</p>
</div>
)
}
我尝试使用异步和立即执行无济于事:
useEffect(() => {
async function doWork() {
await setTemplates(defaultTemplates)
await setTemplateChoices(templates.map(template => template.name))
}
doWork()
}, [])
我甚至尝试将它们分成两个useEffect调用:
useEffect(() => {
setTemplates(defaultTemplates)
}, [])
useEffect(() => {
setTemplateChoices(templates.map(template => template.name))
}, [templates])
还是没有喜悦。为什么没有设置templateChoices
?
我假设您正在使用useEffect
,因为您正在从其他地方加载数据,并且由于变量更改,查询中的某些因素可能需要自动刷新。
如果没有,您应该直接设置状态(即useState(defaultQuery) or useState(() => defaultQuery)
(。这只会在组件加载时调用它一次。
第一个示例不起作用的原因是模板未立即更新。它需要刷新。你可以使用 useEffect 两次,但它效率低下。更简单的解决方案是
useEffect(() => {
setTemplates(defaultTemplates);
setTemplateChoices(defaultTemplates.map(template => template.name));
}, [defaultTemplates]);
将默认值置于使用状态:
import React, { useState, useEffect } from 'react'
export default function ParentComponent() {
const [templates, setTemplates] = useState([
{ id: 1, name: 'Form 1', }, // plus - groups: [ {}, {} ... ], etc.
{ id: 2, name: 'Form 2', }, // plus - groups: [ {}, {} ... ], etc.
{ id: 3, name: 'Form 3', }, // plus - groups: [ {}, {} ... ], etc.
])
const [templateChoices, setTemplateChoices] = useState([])
return (
<div>
<h2>Templates</h2>
{templates.map(template => (
<p>{template.name}</p>
))}
</div>
)
}
对于其他任何人,将它们分成两个 useEffect 调用实际上确实有效:
useEffect(() => {
setTemplates(defaultTemplates)
}, [])
useEffect(() => {
setTemplateChoices(templates.map(template => template.name))
}, [templates])
以下是我如何使用 React 实现这一点,仅使用 1 个useEffect
,其中包含两个setState
调用。
PS:我没有使用异步,因为该代码段不太支持它。但是你可以使用它。
function mockAPI() {
const defaultTemplates = [
{ id: 1, name: 'Form 1' },
{ id: 2, name: 'Form 2' },
{ id: 3, name: 'Form 3' },
];
return new Promise((resolve,reject) => {
setTimeout(()=>resolve(defaultTemplates),1000);
});
}
function App() {
const [templates, setTemplates] = React.useState(null);
const [templateChoices, setTemplateChoices] = React.useState([]);
console.log("Rendering App...");
console.log("templates: " + templates);
console.log("templateChoices: " + templateChoices);
React.useEffect(() => {
console.log("Running useEffect...");
if (!templates) {
console.log("Fetching templates...");
mockAPI().then((result) => setTemplates(result));
}
else {
console.log("Setting templateChoices...");
setTemplateChoices(templates.map(template => template.name));
}
},[templates]);
const choiceItems = templateChoices.map((choice,index) =>
<p key={index}>{choice}</p>
);
return(
templates ?
templateChoices ?
<div>
{choiceItems}
</div>
: null
: <div>Loading templates...</div>
);
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
就个人而言,我会使用一个useEffect来获取模板。并且,如果可能的话,直接在函数体内使用它构建您需要的任何内容。或者使用不同的useEffect
来做到这一点。
像这样:
function mockAPI() {
const defaultTemplates = [
{ id: 1, name: 'Form 1' },
{ id: 2, name: 'Form 2' },
{ id: 3, name: 'Form 3' },
];
return new Promise((resolve,reject) => {
setTimeout(()=>resolve(defaultTemplates),1000);
});
}
function App() {
const [templates, setTemplates] = React.useState(null);
console.log("Rendering App...");
console.log("templates: " + templates);
React.useEffect(() => {
console.log("Running useEffect...");
console.log("Fetching templates...");
mockAPI().then((result) => setTemplates(result));
},[]);
let choiceItems = null;
if (templates) {
console.log("Building choiceItems..");
choiceItems = templates.map((template,index) =>
<p key={index}>{template.name}</p>
);
}
return(
templates ?
<div>
{choiceItems}
</div>
: <div>Loading templates...</div>
);
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>