假设我有一段逻辑(例如,搜索栏),并且我想在此期间显示一个微调框。所以我有以下代码:
this.state = {
searching: false,
}
invokeSearch() {
this.setState({ searching: true })
const fuse = new Fuse(arrayOfData, { keys: [name: 'title'] })
const searchResults = fuse.search(searchTerm)
this.setState({ searching: false })
}
我正在以状态存储searching
布尔值,因此可以将其传递给组件以显示或隐藏微调器。
我知道这是行不通的,因为setState
是异步的,因此根据文档,我可以改用回调。
setState()的第二个参数是可选的回调函数,将在setState完成并重新呈现组件后执行。通常,我们建议将componentDidUpdate()用于此类逻辑。
所以我的代码现在看起来像这样:
this.state = {
searching: false,
}
invokeSearch() {
this.setState({ searching: true }, () => {
const fuse = new Fuse(arrayOfData, { keys: [name: 'title'] })
const searchResults = fuse.search(searchTerm)
this.setState({ searching: false })
})
}
但是,这无法正常工作。将this.state.searching
既作为<Text>
组件,又以某种三元逻辑呈现/隐藏微调框,都不会从false明显改变。
我想这与setState的异步,批处理性质有关,这导致它不起作用?
为了解决这个问题,我一直在使用以下解决方案,在我看来,这似乎是一种hack:
this.state = {
searching: false,
}
invokeSearch() {
this.setState({ searching: true }
setTimeout(() => {
const fuse = new Fuse(arrayOfData, { keys: [name: 'title'] })
const searchResults = fuse.search(searchTerm)
this.setState({ searching: false })
}, 0)
}
任何人都可以向我解释为什么回调无法按我预期的方式工作以及如何以更优雅的方式解决该问题吗?
编辑:修订的问题
感谢您所有有见地的答案和评论。经过进一步调查后,我觉得Fuse并不是解决此问题的好例子。我还有另一个异步Redux的例子,我将在下面介绍
当我单击一个按钮时,我的目标是显示一个微调器,更新Redux存储(这将重新渲染项目列表),然后隐藏该微调器。 Redux正在使用Thunk,因此它是异步的,并且在操作上有一个回调设置。 reducer没什么花哨的事情,只是用新的数据值更新Redux存储。
// action
export function updateFilters(filters, successCallback = () => {}) {
return (dispatch) => {
dispatch({ type: ACTIONS.UPDATE_FILTERS, data: filters })
successCallback()
}
}
// component
changeFilter = (filters) => {
this.setState(() => ({
loading: true,
}), () => {
updateFilters(filters, () => {
this.setState({
loading: false,
})
})
}
}
微调器仍然无法渲染!
答案 0 :(得分:1)
如果您想确保通过setState
更新状态,可以传递一个函数而不是文档中所述的对象:
要解决此问题,请使用第二种形式的setState(),该形式接受函数而不是对象。该函数将先前的状态作为第一个参数,而将更新时的props作为第二个参数
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
例如:
this.setState(prevState => ({ searching: !prevState.searching }));
或
this.setState(() => ({ searching: true }));
您可以使用async/await
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
它的好处之一是它暂停了异步函数直到的执行,返回的承诺已被已解决/拒绝,然后您确定可以更新{ {1}}再次非常方便
答案 1 :(得分:0)
使函数state
异步并使用await。这是修改后的代码。
invokeSearch
应该修复它。
答案 2 :(得分:0)
您是否尝试过将更新放入setState回调中?像这样:
invokeSearch() {
this.setState({ searching: true }, () => {
// do async search
this.setState({ searching: false })
}
}
答案 3 :(得分:0)
编辑:您似乎正在定义操作内部的回叫:
export function updateFilters(filters, successCallback = () => {}) {
不仅仅是定义引用回调的变量:
export function updateFilters(filters, successCallback) {
第一个问题的答案: 仍然感觉像您正在设置比赛条件
第一个动作:
this.setState({ searching: true }
已添加到异步堆栈
(在您的“ hackey”解决方案中) 然后,将下一个项目添加到异步堆栈中,即超时。恰好是初始setState在您的超时之前解析。在JS中,无法保证首先会从异步堆栈中消失。
我通常的设计模式是: setState加载->在回调中执行异步调用->在其中,然后解析异步内容,然后解析setState。
在异步等待语法中,该语法更易于阅读:
const invokeSearch = async () =>{
try{
//set loading state
this.setState({ searching: true }
// if you wanted to make certain you weren't setting up a race condition
// you could wrap the above in a promise and await the result of that.
// that seems a bit overkill and harder to read.
// await the results of your search. This will make sure that you don't
// proceed until you have the results
const response = await async stuff
if(response.success){
// do some stuff with response
// then set the new state to searching false as your last step
this.setState({ searching: false })
}else{
//handle errors
}
} catch(err){
console.error(err)
}
}
提取时:
handleSearch(){
fetch()
.then(r => r.json)
.then(data =>{
//do something with data
this.setState({searching: false})
}
}
invokeSearch() {
this.setState({ searching: true }, handleSearch)
}
另一个可能的陷阱是,上一次我在setState上阅读文档时,它会强制重新渲染页面,所以我不确定这是否也可能是您的问题。
这里有一个完整的React组件已经过测试,如果有帮助,它可以正常工作:
import React from "react";
class StarWarsLuke extends React.Component {
state = {
searching: false,
name: ""
};
handleSearch = () => {
fetch("https://swapi.co/api/people/1")
.then(r => r.json())
.then(data => {
console.log(data.name);
this.setState({ searching: false, name: data.name });
});
};
startSearch = () => {
this.setState({ searching: true }, this.handleSearch);
};
render() {
return (
<div>
<p>
Am I searching?
{this.state.searching ? " searching" : " not searching"}
</p>
{this.state.name ? (
<p>{this.state.name}</p>
) : (
<p>I don't have a name yet</p>
)}
<button onClick={this.startSearch}>get my name</button>
</div>
);
}
}
export default StarWarsLuke;