NextJS如何随状态变更自动重渲染组件以移除已发送图片
图片发送应用重渲染问题解决
问题场景
我开发了一款图片发送应用:首次渲染时前端展示所有图片,随后批量向指定对象发送图片。需求是每发送一张图片,就将其从前端移除,让用户看到图片发送的倒计时效果。我把useState的setter传递到处理发送逻辑的函数中,但每次调用它时并未触发组件重渲染。测试按钮触发的情况时,每次点击都能正确重渲染并显示剩余图片,但发送图片时无法自动重渲染。
原前端代码
export default function Home() { const [imageLinks, setImageLinks] = useState([""]) // 获取图片链接并设置到state,在浏览器展示 const getImageLinks = async (cids) => { cids.forEach((link) => { let CIDImages = axios.get('https://ipfs.io/' + link) .then(linkMetadata => { let rawLinks = linkMetadata.data.image let correctFormatLink = rawLinks.slice(7); setImageLinks(oldArray => [...oldArray, correctFormatLink]); }) }) } // 图片渲染组件 const images = () => { return ( <> { imageLinks.map((link, index) => { return <img src={`https://ipfs.io/ipfs/${link}`} alt="" key={index}/>; } )} </> ) } useEffect(() => { images() }, [imageLinks, setImageLinks]) return ( { images() } ) }
原发送逻辑代码
export async function mint(setImageLinks, imageLinks, cids) { // 发送图片 let imageTower = []; for (let i = 1; i < cids.length; i++) { imageTower[i] = await tokenMinterFcn(cids[i]) let imageLink = await axios.get('https://ipfs.infura.io' + cids[i]) .then(linkMetadata => { // 修正链接格式 let rawLinks = linkMetadata.data.image let correctFormatLink = rawLinks.slice(7); // 过滤并移除imageLinks数组中的对应链接 imageLinks.filter(imageLink => { if (imageLink === correctFormatLink) { imageLinks.splice(imageLinks.indexOf(imageLink), 1) } }) setImageLinks(imageLinks); }).catch(err => { console.log(err) }) } }
问题根源
- 直接修改原state数组:在
mint函数中用splice直接修改传入的imageLinks数组,再把同一个数组引用传给setImageLinks。React依赖state引用变化判断是否重渲染,相同引用会被判定为state未变更,因此不会触发重渲染。 - 误用filter方法:
filter的作用是返回符合条件的新数组,不是用来遍历修改原数组的,此处用filter查找并删除元素属于用法错误。 - 异步请求处理混乱:
getImageLinks中用forEach循环发起axios请求,会导致多个异步请求同时触发,多次无序更新state;且初始state包含空字符串,会渲染无效图片。 - 无效的useEffect调用:
useEffect中调用images()完全多余,images是渲染函数,直接在return中调用即可,该useEffect没有实际作用。
修复方案
1. 遵循state不可变原则
永远不要直接修改React state中的数组/对象,必须通过创建新数组来更新state。在mint函数中,用filter生成排除当前图片链接的新数组,再传给setImageLinks。
2. 优化异步请求逻辑
用Promise.all批量处理异步请求,确保所有请求完成后一次性更新state,避免多次无序更新;同时移除初始state中的空字符串。
3. 移除无用代码
删掉调用images()的useEffect,直接在组件return中渲染images()的结果。
4. 统一异步代码风格
在mint函数中用async/await替代.then,避免混合写法导致的逻辑混乱;同时移除imageLinks参数,通过setImageLinks的回调获取最新state,避免闭包导致的state过期问题。
修改后代码
前端组件代码
export default function Home() { // 初始化空数组,移除无用的空字符串 const [imageLinks, setImageLinks] = useState([]); // 获取图片链接,用Promise.all批量处理异步请求 const getImageLinks = async (cids) => { const linkPromises = cids.map(link => axios.get(`https://ipfs.io/${link}`) .then(linkMetadata => { const rawLinks = linkMetadata.data.image; return rawLinks.slice(7); // 返回处理后的链接 }) ); // 等待所有请求完成,一次性更新state const newImageLinks = await Promise.all(linkPromises); setImageLinks(prev => [...prev, ...newImageLinks]); }; // 图片渲染组件 const Images = () => { return ( <> {imageLinks.map((link, index) => ( <img src={`https://ipfs.io/ipfs/${link}`} alt={`图片 ${index}`} key={index} /> ))} </> ); }; // 触发发送逻辑的示例方法 const handleStartSend = async (cids) => { await mint(setImageLinks, cids); }; return ( <> <Images /> {/* 示例按钮,触发图片发送 */} <button onClick={() => handleStartSend(/* 传入你的cids数组 */)}> 开始发送图片 </button> </> ); }
修改后的发送逻辑代码
export async function mint(setImageLinks, cids) { for (let i = 1; i < cids.length; i++) { await tokenMinterFcn(cids[i]); try { const linkMetadata = await axios.get(`https://ipfs.infura.io/${cids[i]}`); const rawLinks = linkMetadata.data.image; const correctFormatLink = rawLinks.slice(7); // 通过回调获取最新state,生成新数组更新 setImageLinks(prevLinks => prevLinks.filter(link => link !== correctFormatLink) ); } catch (err) { console.log(err); } } }
内容的提问来源于stack exchange,提问作者Ilyes Makria
相关产品推荐
相关产品推荐

