【发布时间】:2020-06-15 15:24:20
【问题描述】:
我正在做一些研发工作,并决定学习 React 并在此过程中构建应用程序。
我遇到了一个奇怪的问题,这让我摸不着头脑。我已经在 Stackoverflow 上找不到与我的问题完全匹配的问题,所以我想发个帖子。
问题的真相:在我的组件外部进行 Axios 调用然后设置状态不会触发重新渲染,但在组件内进行 Axios 调用会触发。
我已经重命名了部分代码。
应用总结
我已经创建了 MVC 应用程序的前端部分,它将与我同事的 Spring Boot 服务器对话以检索“配置”数据,这些数据将从 SQL 数据库中检索。我的前端将显示此配置数据并允许对其进行编辑。
我正在使用 Axios 进行 REST 调用。
我在我的一个组件的 componentDidMount() 中有一个 Axios 调用,它对服务器进行初始调用,然后将这些结果添加到该组件的状态。然后页面重新呈现,并用结果填充表格。该表是它自己的组件,每个条目也是它自己的组件。数据通过 props 传递给这些组件。
问题
如果我将 Axios 调用保留在 componentDidMount() 中,那么我的应用程序将按预期工作。
但是,我有多个进行 REST 调用的组件,因此存在重复的代码,所以我想在我的应用程序中创建一个任何组件都可以调用的 REST 服务。我通过遵循命令设计模式来做到这一点,我什至用 Typescript 编写了它。我知道矫枉过正,但这是为了研发,我只是想把它作为一个学习练习。
所以要使用它,我的组件中有一个函数,它设置命令、带有命令和 url 的调用程序,然后在调用程序上调用执行并将结果添加到变量中。
所以问题来了:如果我使用我的 REST 服务,它在我的组件之外的一个类中调用了 Axios,那么不会发生重新渲染并且我的表中没有填充数据。但是,如果我将 Axios 调用保留在我的 componentDidMount() 中,那么一切正常。
我的尝试
在玩了很多 console.logs 之后,我看到 setState 在设置状态之前没有获取数据,所以我的状态中的“项目”是空白的。我通过在函数中添加“async”和在 setState 调用中添加“await”来解决这个问题。现在我可以通过 console.log 看到状态有数据,但是重新渲染仍然没有触发。
我也尝试过移动一些东西 - 所以我在 componentDidMount() 中拥有了函数和所有内容,然后我将所有内容移出并简单地在 componentDidMount() 中调用了该函数 - 这没有奏效。
我的代码
maincomponent.js 这是发起 Axios 调用的组件。你会看到我原来的 Axios 调用被注释掉了。
class ParameterList extends React.Component {
constructor(props) {
super(props)
this.state = {
error: null,
isLoaded: false,
items: [],
}
}
componentDidMount() {
// Axios.get(URLS.GET_CONFIG_PARAMETERS).then((results) => {
// console.log(results.data)
// this.setState({
// items: results.data
// })
// })
this.retrieveCONFIGParameters()
}
async retrieveCONFIGParameters() {
const serviceCommand = new GetServiceCommand()
const invoker = new ServiceInvoker(serviceCommand, URLS.GET_CONFIG_PARAMETERS)
const returnData = invoker.ExecuteRequest()
await this.setState({
items: returnData
}, function() {
console.log(this.state.items)
});
console.log(this.state.items)
}
render() {
return (
<div>
<h1>Parameter List</h1>
<Container>
<Grid celled>
<Grid.Row>
<Label attached='top left' color='blue'>Current EDI Message Status</Label>
<Grid.Column width={16}>
<Table celled structured color='blue'>
<Table.Header>
<Table.Row>
<Table.HeaderCell><Radio label='Active'></Radio></Table.HeaderCell>
<Table.HeaderCell><Radio label='Inactive'></Radio></Table.HeaderCell>
<Table.HeaderCell><Radio label='All'></Radio></Table.HeaderCell>
</Table.Row>
<Table.Row>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{this.state.items.map((param, i) => {
if (param.active === true) {
return <ParameterListItem key={i} paramData={param} />
} else {
return false // TODO
}
}
)}
</Table.Body>
</Table>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column width={16}>
<Button color='teal'>
<Button.Content>Add</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Modify</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Remove</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Print</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Position</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Re-Instate</Button.Content>
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</Container>
<br></br>
</div>
);
}
}
GetServiceCommand.ts 这是调用 Axios 的 GET 命令。
export class GetServiceCommand implements IServiceCommand {
returnData = [] as any
execute(serviceURL: string): Object {
Axios.get(serviceURL).then(results => {
console.log(results.data)
results.data.forEach((element: any) => {
this.returnData.push(element)
});
})
console.log(this.returnData)
return this.returnData
}
}
如有必要,我可以提供其他类的代码,但它们只是标准的命令模式类,所以我认为不需要。
最后的想法
我知道我所做的可能是矫枉过正,而回到简单地在每个组件中使用 Axios 调用将使这个问题消失。但是这几天一直在我的脑海里,我很想知道我做错了什么。
最后一件事:来自 Axios 调用和状态的数据的 console.log 看起来不同。从 Axios 调用本身来看,数据如下所示:
(6) [{…}, {…}, {…}, {…}, {…}, {…}]
0: {code: "8", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
1: {code: "5", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
2: {code: "2", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
3: {code: "6", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
4: {code: "6", active: false, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
5: {code: "6", active: false, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
length: 6
从状态:
[]
0: {code: "8", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
1: {code: "5", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
2: {code: "2", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
3: {code: "6", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
4: {code: "6", active: false, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
5: {code: "6", active: false, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
length: 6
您会注意到状态以 [] 开头,而 Axios 数据是 (6)。我不确定这是否是导致问题的原因......我认为这不太可能,因为它们只是数组,但我想我会提到它以防万一。
感谢任何花时间查看此内容的人。
编辑
感谢所有提供建议的人。我现在已经设法解决了这个问题,虽然感觉有点脏。
这些建议强调了 Axios 调用和返回的问题 - 在整个节目中花了一些时间处理大量的 console.logs 之后,我开始意识到 GetServiceCommand 中的 Return 实际上是空白的;这很令人困惑,因为其他 console.logs 显示它一旦进入组件就会有数据,但这不可能是正确的,因为 Return 在数据添加到数组之前触发。我认为 console.logs 本质上误导了我(或者更准确地说,我误解了它们)。
解决方案是提供一个setState mutator函数,可以在任何地方传递。
问题在于,因为我使用的是 Typescript,所以一个简单的 <Component functionMutator={this.functionToSetstate} 不起作用,所以我不得不更改我的 REST 层以接受一个单独的参数,即作为 Function 类型的函数。
然后将 GetServiceCommand 更改为不返回任何内容,而是在 Axios 调用完成后使用数据调用此函数,类似于 Qiarash 的建议。
以防万一有人感兴趣,这是我的更新代码:
maincomponent.js 在这里,我创建了一个 mutator 函数并将其绑定到构造函数中的“this”。然后我稍后在 ComponentDidMount() 中调用的函数中调用 REST
setStateItems(items) {
this.setState({
items: items
}, function() {
console.log("setStateItems called")
})
}
constructor(props) {
super(props)
this.state = {
error: null,
isLoaded: false,
items: [],
}
this.setStateItems = this.setStateItems.bind(this)
}
retrieveCONFIGParameters() {
const serviceCommand = new GetServiceCommand()
const invoker = new ServiceInvoker(serviceCommand, EdiUrls.GET_CONFIG_PARAMETERS, this.setStateItems)
invoker.ExecuteRequest()
}
GetServiceCommand.ts 您会注意到,现在只需调用从 maincomponent.js 传入的函数,而不是 Return
export class GetServiceCommand implements IServiceCommand {
execute(serviceURL: string, functionToCall: Function) {
let returnData: Array<string>;
returnData = []
Axios.get(serviceURL).then(results => {
console.log(results.data)
results.data.forEach((element: any) => {
console.log("test4")
returnData.push(element)
});
functionToCall(returnData)
})
}
}
再次感谢您的帮助和建议。我不相信这是解决这个问题的好方法,但我确实学到了很多关于 React、状态和 Typescript 的知识。
【问题讨论】:
-
ServiceInvoker来自哪里?它是包的一部分还是您的自定义方法?通常invoker.ExecuteRequest()应该是一个异步调用,完成后它应该使用resultData 调用setState。您当前的代码无法以这种方式运行。 -
amaj:感谢您的评论。 ServiceInvoker 是我创建的一个自定义类,它构成了命令设计模式的一部分。我现在正在尝试您的建议 - 不满意,但会坚持下去,稍后再报告。
-
@Lovelocke,这个答案可能会对你有所帮助。可以试一次吗? stackoverflow.com/a/51532619/8579855
-
@Lovelocke,也请参考此链接,stackoverflow.com/a/42602520/8579855
标签: javascript reactjs typescript axios