为什么我的组件在 setState 之前渲染?
Why does my component render before setState?
编辑 - 添加了 render
函数
我已经和这个问题抗争了几个星期了。我想创建两个显示在仪表板中的列表,都使用来自 API 的数据。如果我用虚拟数据创建一个 const,列表将根据需要显示。但是,当我尝试使用相同的代码来显示来自我的 API 的信息时,我在设置调用渲染的状态条件之前显示了一个列表。有一次我没有看到任何一个列表,所以我很高兴至少取得了一些进展。如果有人能协助指出我做错了什么,我将不胜感激!
async componentDidMount() {
let data = {
firstName: sessionStorage.getItem('cwsFirstName'),
surname: sessionStorage.getItem('cwsSurname'),
email: sessionStorage.getItem('cwsUser'),
role: sessionStorage.getItem('cwsRole'),
type: sessionStorage.getItem('cwsType'),
storeId: sessionStorage.getItem('cwsStoreId'),
clientId: sessionStorage.getItem('cwsClient')
};
let user = [];
user.push(data);
await this.setState({ user: user });
const client = data.clientId;
// start with extracting the services from the db
const clientservices = await this.mysqlLayer.Get(`/admin/clientservices/${client}`);
//console.log('clientservices: ', clientservices);
let workzones = [
{
worklist: 'Queues',
task: 'list_all'
},
{
worklist: 'Today',
task: 'list_today'
}
];
// loop through services to populate worklists
let loopCount = 0;
clientservices.forEach(async service => {
let workspace = service.service;
let type = service.type;
let workspaces = [];
// loop through the workzones
workzones.forEach(async workzone => {
//console.log('workzone: ', workzone);
let worklists = [];
let task = workzone.task;
let records = await this.mysqlLayer.Get(`/${type}/${workspace}/${task}/${client}`);
//console.log('records: ', records);
let statusArr = [];
records.forEach(async record => {
//console.log('currentStatus: ', record.currentStatus);
statusArr.push(record.currentStatus);
});
let completeWorklist = statusArr.filter(this.onlyUnique);
let statusList = [];
if (completeWorklist.length > 0) statusList = this.filterWorklists(workspace, completeWorklist);
let items = [];
statusList.forEach(async element => {
//console.log('element: ', element);
let count = 0;
records.forEach(async record => {
if (record.currentStatus === element) {
++count;
}
});
items.push({
item: element,
count: count
});
//console.log('items: ', items);
}); // end of worklist loop *******************************
worklists.push({
worklist: workzone.worklist,
items: items
});
//console.log('worklists: ', workzone, worklists, moment(new Date()).format('HH:mm:sss'));
//console.log('worklists: ', workzone, worklists, moment(new Date()).milliseconds());
await this.setState({
records: records,
type: type,
worklists: [...this.state.worklists, ...worklists]
});
++loopCount;
console.log('loopCount: ', loopCount);
//console.log('this.state.worklists: ', this.state.worklists, moment(new Date()).milliseconds());
let tempWorklists = this.state.worklists;
//console.log('tempWorklists: ', tempWorklists, moment(new Date()).milliseconds());
workspaces.push({
workspace: workspace,
worklists: tempWorklists
});
}); // end of workzone loop *******************************
//console.log('2 workspaces: ', workspaces);
await this.setState({
//loading: false,
//workspaces: [...this.state.workspaces, ...workspaces]
workspaces: workspaces
//workspaces: workspacesConst
});
//console.log('this.state.workspaces: ', this.state.workspaces, moment(new Date()).milliseconds());
await this.setState({
//loading: false,
//workspaces: [...this.state.workspaces, ...workspaces]
workspaces: workspaces
//workspaces: workspacesConst
});
//console.log('this.state.workspaces: ', this.state.workspaces, moment(new Date()).milliseconds());
}); // end of workspace loop *******************************
// Should be good to render now
//if (this.state.worklists.length > 1)
await this.setState({ loading: false });
//this.forceUpate();
}
render() {
if (this.state.loading) {
return (
<div>Loading...</div>
);
} else {
const workspace = this.state.workspaces.map((workspace, idx) =>
//const workspace = workspaces.map((workspace, idx) =>
<div key={idx} className="card border-light mb-3" style={{padding: "20px"}}>
{console.log('render workspace: ', workspace.worklists, moment(new Date()).milliseconds())}
<Workspace
key={idx}
records={this.state.records}
workspaces={workspace}
type={this.state.type}
user={this.state.user}
/>
</div>
);
return (
<div className="container">
<div className="cols-12">
<Welcome user={this.state.user}/>
{workspace}
</div>
</div>
);
}
}
如您所见,渲染发生在 this.state.loading
设置为 'false' 之前。当然它应该只在那之后渲染?
不,这不是 setState
和 React 生命周期的工作方式。
实际上每次你调用 setState
react 都会自动调用 render
方法。
因此,在您的 componentDidMount
中,您多次调用 setState。所以它会多次渲染你的组件。并呈现您的工作区。
它不会停止,直到您最后一次调用 setState
来设置 loading false。
我建议你使用 progressbar
的 spinner
或类似的东西来显示 loading
是 true
一旦它变成 false
你可以显示 workspaces
.
这里还有一个注意事项是 setState
是 asynchronous
但 await
不能与 setState
一起使用,因为它不 return 任何 promise
.
有关 React 生命周期的更多信息 -
这里有两个问题导致了我的问题:
正如 Sagar 所指出的,await
不适用于 setState
- 我之前不知道这一点
await
具有 returns Promise 确实有效的功能(例如 API 调用),但代码将继续 运行 直到 Promise 完成。我知道这一点,但还没有完全意识到它对我尝试创建的序列意味着什么。
我通过向代码中的每一行添加一个 console.log 来了解这一点,以查看何时发生了什么。我开始意识到要求 API 提取的行是导致问题的原因:
let records = await this.mysqlLayer.Get(`/${type}/${workspace}/${task}/${client}`);
我的代码一直 运行 并更新状态以表明它已完成,因为 await
意味着它是并行的 运行 个部分。
我还没有修复代码,但想让其他人知道,以防他们遇到类似问题。
编辑 - 添加了 render
函数
我已经和这个问题抗争了几个星期了。我想创建两个显示在仪表板中的列表,都使用来自 API 的数据。如果我用虚拟数据创建一个 const,列表将根据需要显示。但是,当我尝试使用相同的代码来显示来自我的 API 的信息时,我在设置调用渲染的状态条件之前显示了一个列表。有一次我没有看到任何一个列表,所以我很高兴至少取得了一些进展。如果有人能协助指出我做错了什么,我将不胜感激!
async componentDidMount() {
let data = {
firstName: sessionStorage.getItem('cwsFirstName'),
surname: sessionStorage.getItem('cwsSurname'),
email: sessionStorage.getItem('cwsUser'),
role: sessionStorage.getItem('cwsRole'),
type: sessionStorage.getItem('cwsType'),
storeId: sessionStorage.getItem('cwsStoreId'),
clientId: sessionStorage.getItem('cwsClient')
};
let user = [];
user.push(data);
await this.setState({ user: user });
const client = data.clientId;
// start with extracting the services from the db
const clientservices = await this.mysqlLayer.Get(`/admin/clientservices/${client}`);
//console.log('clientservices: ', clientservices);
let workzones = [
{
worklist: 'Queues',
task: 'list_all'
},
{
worklist: 'Today',
task: 'list_today'
}
];
// loop through services to populate worklists
let loopCount = 0;
clientservices.forEach(async service => {
let workspace = service.service;
let type = service.type;
let workspaces = [];
// loop through the workzones
workzones.forEach(async workzone => {
//console.log('workzone: ', workzone);
let worklists = [];
let task = workzone.task;
let records = await this.mysqlLayer.Get(`/${type}/${workspace}/${task}/${client}`);
//console.log('records: ', records);
let statusArr = [];
records.forEach(async record => {
//console.log('currentStatus: ', record.currentStatus);
statusArr.push(record.currentStatus);
});
let completeWorklist = statusArr.filter(this.onlyUnique);
let statusList = [];
if (completeWorklist.length > 0) statusList = this.filterWorklists(workspace, completeWorklist);
let items = [];
statusList.forEach(async element => {
//console.log('element: ', element);
let count = 0;
records.forEach(async record => {
if (record.currentStatus === element) {
++count;
}
});
items.push({
item: element,
count: count
});
//console.log('items: ', items);
}); // end of worklist loop *******************************
worklists.push({
worklist: workzone.worklist,
items: items
});
//console.log('worklists: ', workzone, worklists, moment(new Date()).format('HH:mm:sss'));
//console.log('worklists: ', workzone, worklists, moment(new Date()).milliseconds());
await this.setState({
records: records,
type: type,
worklists: [...this.state.worklists, ...worklists]
});
++loopCount;
console.log('loopCount: ', loopCount);
//console.log('this.state.worklists: ', this.state.worklists, moment(new Date()).milliseconds());
let tempWorklists = this.state.worklists;
//console.log('tempWorklists: ', tempWorklists, moment(new Date()).milliseconds());
workspaces.push({
workspace: workspace,
worklists: tempWorklists
});
}); // end of workzone loop *******************************
//console.log('2 workspaces: ', workspaces);
await this.setState({
//loading: false,
//workspaces: [...this.state.workspaces, ...workspaces]
workspaces: workspaces
//workspaces: workspacesConst
});
//console.log('this.state.workspaces: ', this.state.workspaces, moment(new Date()).milliseconds());
await this.setState({
//loading: false,
//workspaces: [...this.state.workspaces, ...workspaces]
workspaces: workspaces
//workspaces: workspacesConst
});
//console.log('this.state.workspaces: ', this.state.workspaces, moment(new Date()).milliseconds());
}); // end of workspace loop *******************************
// Should be good to render now
//if (this.state.worklists.length > 1)
await this.setState({ loading: false });
//this.forceUpate();
}
render() {
if (this.state.loading) {
return (
<div>Loading...</div>
);
} else {
const workspace = this.state.workspaces.map((workspace, idx) =>
//const workspace = workspaces.map((workspace, idx) =>
<div key={idx} className="card border-light mb-3" style={{padding: "20px"}}>
{console.log('render workspace: ', workspace.worklists, moment(new Date()).milliseconds())}
<Workspace
key={idx}
records={this.state.records}
workspaces={workspace}
type={this.state.type}
user={this.state.user}
/>
</div>
);
return (
<div className="container">
<div className="cols-12">
<Welcome user={this.state.user}/>
{workspace}
</div>
</div>
);
}
}
this.state.loading
设置为 'false' 之前。当然它应该只在那之后渲染?
不,这不是 setState
和 React 生命周期的工作方式。
实际上每次你调用 setState
react 都会自动调用 render
方法。
因此,在您的 componentDidMount
中,您多次调用 setState。所以它会多次渲染你的组件。并呈现您的工作区。
它不会停止,直到您最后一次调用 setState
来设置 loading false。
我建议你使用 progressbar
的 spinner
或类似的东西来显示 loading
是 true
一旦它变成 false
你可以显示 workspaces
.
这里还有一个注意事项是 setState
是 asynchronous
但 await
不能与 setState
一起使用,因为它不 return 任何 promise
.
有关 React 生命周期的更多信息 -
这里有两个问题导致了我的问题:
-
正如 Sagar 所指出的,
await
不适用于setState
- 我之前不知道这一点await
具有 returns Promise 确实有效的功能(例如 API 调用),但代码将继续 运行 直到 Promise 完成。我知道这一点,但还没有完全意识到它对我尝试创建的序列意味着什么。
我通过向代码中的每一行添加一个 console.log 来了解这一点,以查看何时发生了什么。我开始意识到要求 API 提取的行是导致问题的原因:
let records = await this.mysqlLayer.Get(`/${type}/${workspace}/${task}/${client}`);
我的代码一直 运行 并更新状态以表明它已完成,因为 await
意味着它是并行的 运行 个部分。
我还没有修复代码,但想让其他人知道,以防他们遇到类似问题。