React中useState异步传递问题:首次点击URL未生效
React状态异步更新导致首次点击fetch请求失效问题
问题场景
点击分类复选框时,需先请求子分类数据并拼接出商品查询URL,再用该URL发起商品请求,但首次点击时,后续fetch无法获取刚更新的categoryUrl状态,导致请求异常;使用useEffect监听状态仍存在延迟问题。
现有代码
// 初始化加载一级分类 useEffect(() => { fetch('http://api.tmweb.ru/category?filter[depth]=1&filter[type]=0',{ headers: { 'Accept': 'application/json', 'Authorization': token, } }) .then(res => res.json()) .then((result) => setCategory(result.data)) }, []) useEffect(()=>{ console.log(categoryUrl) }, [categoryUrl]) // 点击分类复选框触发的函数 let getDataCategory = async(event) => { let id = event.currentTarget.id; if (event.target.checked){ await fetch(`http://api.tmweb.ru/category?filter[parent_id][in]=${id}&filter[type]=0`,{ headers: { 'Accept': 'application/json', 'Authorization': token, }, }) .then(res => res.json()) .then((result) => { // 更新子分类状态 setCategorySub([...categorySub,...result.data]); // 拼接请求商品的URL let url2 = `http://api.tmweb.ru/object?filter[category_id][in][]=${id}&` result.data.forEach(item => {url2 += 'filter[category_id][in][]=' + item.id + '&'}) // 更新URL状态 setCategoryUrl(url2) }); // 首次点击时,此处获取的是旧的categoryUrl await fetch(`${categoryUrl}&filter[type]=0`,{ headers: { 'Accept': 'application/json', 'Authorization': token, }, }) .then(res => res.json()) .then((result) => { console.log(result) setCategoryProd([...categoryProd,...result.data]) }) } else { // 取消勾选时移除对应数据 setCategorySub((prevData) => { return prevData.filter((item) => item.parent_id !== +id); }); setCategoryProd((prevData) =>{ return prevData.filter((item) => item.category_id !== +id); }) } }
核心原因
React的setState是异步操作,调用setCategoryUrl(url2)后,categoryUrl不会立即更新为最新值。因此紧接着的fetch请求仍使用更新前的旧状态,导致首次点击失效。
解决方案
方案1:直接使用生成的url2发起请求(推荐)
无需依赖状态存储拼接好的URL,直接在生成url2后发起请求,绕过异步状态更新的问题:
let getDataCategory = async(event) => { let id = event.currentTarget.id; if (event.target.checked){ await fetch(`http://api.tmweb.ru/category?filter[parent_id][in]=${id}&filter[type]=0`,{ headers: { 'Accept': 'application/json', 'Authorization': token, }, }) .then(res => res.json()) .then((result) => { setCategorySub([...categorySub,...result.data]); let url2 = `http://api.tmweb.ru/object?filter[category_id][in][]=${id}&` result.data.forEach(item => {url2 += 'filter[category_id][in][]=' + item.id + '&'}) setCategoryUrl(url2); // 直接使用本地变量url2发起请求 return fetch(`${url2}&filter[type]=0`,{ headers: { 'Accept': 'application/json', 'Authorization': token, }, }) }) .then(res => res.json()) .then((result) => { console.log(result) setCategoryProd([...categoryProd,...result.data]) }) } else { setCategorySub((prevData) => { return prevData.filter((item) => item.parent_id !== +id); }); setCategoryProd((prevData) =>{ return prevData.filter((item) => item.category_id !== +id); }) } }
方案2:用useEffect监听categoryUrl触发请求
若必须依赖categoryUrl状态,将商品请求逻辑放到useEffect中,确保状态更新后再执行:
// 初始化加载一级分类 useEffect(() => { fetch('http://api.tmweb.ru/category?filter[depth]=1&filter[type]=0',{ headers: { 'Accept': 'application/json', 'Authorization': token, } }) .then(res => res.json()) .then((result) => setCategory(result.data)) }, []) // 监听categoryUrl变化,触发商品请求 useEffect(()=>{ // 避免初始空值触发无效请求 if(categoryUrl){ fetch(`${categoryUrl}&filter[type]=0`,{ headers: { 'Accept': 'application/json', 'Authorization': token, }, }) .then(res => res.json()) .then((result) => { console.log(result) setCategoryProd([...categoryProd,...result.data]) }) } }, [categoryUrl, categoryProd, token]) // 点击函数仅负责更新子分类和categoryUrl let getDataCategory = async(event) => { let id = event.currentTarget.id; if (event.target.checked){ await fetch(`http://api.tmweb.ru/category?filter[parent_id][in]=${id}&filter[type]=0`,{ headers: { 'Accept': 'application/json', 'Authorization': token, }, }) .then(res => res.json()) .then((result) => { setCategorySub([...categorySub,...result.data]); let url2 = `http://api.tmweb.ru/object?filter[category_id][in][]=${id}&` result.data.forEach(item => {url2 += 'filter[category_id][in][]=' + item.id + '&'}) setCategoryUrl(url2); }); } else { setCategorySub((prevData) => { return prevData.filter((item) => item.parent_id !== +id); }); setCategoryProd((prevData) =>{ return prevData.filter((item) => item.category_id !== +id); }) } }
补充说明
- 方案1更高效,避免了异步状态更新的等待开销,直接使用本地变量完成请求。
- 方案2需注意useEffect的依赖数组,确保所有用到的状态、变量都加入,避免闭包导致的旧值问题。
内容的提问来源于stack exchange,提问作者Александр
相关产品推荐
相关产品推荐

