react循环调用useState同步处理方案
目录:
- 场景介绍
- 循环调用useState同步处理方案(添加数据)
1. 回调函数 useState(data) 改成:useState(data => 处理data)
2. 抽离函数外使用变量
3.循环调用usesate内,再调接口添加到原数据方案(添加数据+异步接口)
1. 可改造 setArr为function
2. 封装使用useState的回调函数
3. 借用useEffect监听变化
推荐阅读:react循环遍历useState的数组异步调取接口追加参数后修改原数组处理方案:https://www.cnblogs.com/sexintercourse/p/17710045.html
场景介绍:
map数组处理(过滤等)后调用到hooks异常。如:添加商品套餐,循环调用校验后添加到useState数组上。人员数组处理后调用useState数组等等…
在使用useState() 时,先了解 Hook 的规则
- 仅顶层调用 Hook:不能在循环,条件,嵌套函数等中调用useState()。在多个useState()调用中,渲染之间的调用顺序必须相同。
- 仅从React 函数调用 Hook: 必须函数组件或自定义钩子内部调用useState()。
所以: 不能在循环里面调用hook
.
循环调用useState同步处理方案:
回调函数 useState(data) 改成:useState(data => 处理data)
抽离函数外使用变量
改造前:
import React, { useState } from 'react'; // 随机对象 用于模拟区分 function obj() { return { name: `某某`, age: parseInt(Math.random() * 1e2) } } const Home = () => { const [arr, setArr] = useState([]); return ( <div> <button onClick={ addObj }>点击+5条数据</button> { arr.map(item => <div key={`arr${item.age}`}> { item.name }:{ item.age }</div>) } </div> ) // 点击+5条数据 function addObj(){ for (let i = 0; i < 5; i++) { setArr([...arr, obj()]) } } } export default Home;
结果:每次点击只添加一次,并非5次。注意 useState 异步的,拿不到上步操作后的最新数据
改造后:
1.回调函数 useState(data) 改成:useState(data => 处理data)
import React, { useState } from 'react'; // 随机对象 用于模拟区分 function obj() { return { name: `某某`, age: parseInt(Math.random() * 1e2) } } const Home = () => { const [arr, setArr] = useState([]); return ( <div> <button onClick={ addObj }>点击+5条数据</button> { arr.map(item => <div key={`arr${item.age}`}> { item.name }:{ item.age }</div>) } </div> ) // 点击+5条数据 function addObj(){ for (let i = 0; i < 5; i++) { // setArr([...arr, obj()]) //原来 setArr(i => [...i, obj()]) //现在。 i是形参,当前为arr变量,可为x、y、a等 } } } export default Home;
2.将使用useState()申明的地方替换为全局变量(函数组件外)
import React, { useState } from 'react'; // 随机对象 用于模拟区分 function obj() { return { name: `某某`, age: parseInt(Math.random() * 1e2) } } let arr = []; const Home = () => { const [other, setOther] = useState(0); //触发 render刷新 非必须可用项目其他方式 return ( <div> <button onClick={ addObj }>点击+5条数据</button> { arr.map(item => <div key={`arr${item.age}`}> { item.name }:{ item.age }</div>) } </div> ) // 点击+5条数据 function addObj(){ for (let i = 0; i < 5; i++) { arr = [...arr, obj()]; setOther(Math.random()); //核心 只要能触发 render再次刷新的其他方式都行 否则arr数据已更新,dom也不刷新 } } } export default Home;
结果:
正常了~~
注意: setOther(Math.random()); 只是为了刷新render,只要能触发刷新,可用其他替换!!!render不刷新,useEffect监听不到变化的
对于一些项目需要每个数据,进行查询接口查询后再添加
循环usesate内调接口添加到原数据方案
可改造 setArr为function
封装使用useState的回调函数
借用useEffect监听变化
1.可改造 setArr为function:
import React, { useState } from 'react'; // 随机对象 用于模拟区分 function obj() { return { name: `某某`, id: parseInt(Math.random() * 1e4) } } let arr = []; const Home = () => { // let [arr] = useState([]); 和 外部let arr = []; 二选一 const [other, setOther] = useState(0); //触发 render刷新 非必须可用项目其他方式 return ( <div> <button onClick={ addObj }>点击+5条数据</button> { arr.map(item => <div key={`arr${item.id}`}> { item.name }:{ item.id } { item?.addKey || ''}</div>) } </div> ) // 点击+5条数据 function addObj(){ for (let i = 0; i < 5; i++) { let getObj = obj(); arr = [...arr, getObj]; setArr(getObj); setOther(Math.random()); //核心 只要能触发 render再次刷新的其他方式都行 否则arr数据已更新,dom也不刷新 } } //原始:let [arr, setArr] = useState([]); 中setArr异步请求接口 追加参数 function setArr(ops){ let setTime = parseInt(Math.random() * 1e4); // 模拟异步接口请求,且接口返回时间不稳定 setTimeout(() => { arr.map((ii, idx) => { if(ii.id == ops.id){ ii.addKey = '追加的key用时:' + setTime; arr[idx] = ii; } }) setOther(Math.random()); //核心 只要能触发 render再次刷新的其他方式都行 否则arr数据已更新,dom也不刷新 }, setTime) } } export default Home;
2.封装使用useState的回调函数:
import React, { useState, useEffect, useRef } from 'react'; function useCallbackState (od) { const cbRef = useRef(); const [data, setData] = useState(od); useEffect(() => { cbRef.current && cbRef.current(data); }, [data]); return [data, function (d, callback) { cbRef.current = callback; setData(d); }]; } // 随机对象 用于模拟区分 function obj() { return { name: `某某`, id: parseInt(Math.random() * 1e4) } } const Home = () => { const [arr, setArr] = useCallbackState([]); return ( <div> <button onClick={ addObj }>点击+5条数据</button> { arr.map(item => <div key={`arr${item.id}`}> { item.name }:{ item.id } { item?.addKey || ''}</div>) } </div> ) // 点击+5条数据 function addObj(){ for (let i = 0; i < 5; i++) { setArr(i => [...i, obj()], (data) => { // 5次循环只执行一次,且是最后一次 addKeyFun(data); }) } } // 追加参数 function addKeyFun(data){ data.map((ii, idx) => { let setTime = parseInt(Math.random() * 1e4); // 模拟异步接口请求,且接口返回时间不稳定 setTimeout(() => { setArr(i => { i[idx].addKey = '追加的key用时:' + setTime; return [...i] }); }, setTime) }) } } export default Home;
3.借用useEffect监听变化
3.1:利用es6中的Promise.all()处理(复杂任务也适用)
3.2:利用es5中的回调函数callback处理(逻辑有点绕,因要固定排序)
3.3:useState回调函数写法(数据量大的时候需要额外测试处理)
.
3.1:利用es6中的Promise.all()处理
import React, { useState, useEffect, useRef } from 'react'; // 随机对象 用于模拟区分 function obj() { return { name: `某某`, id: parseInt(Math.random() * 1e4) } } const Home = () => { const [arr, setArr] = useState([]); useEffect(() => { addKeyList() }, [arr.length]) // 注意监听arr.length 而不是arr 否则死循环 return ( <div> <button onClick={ addObj }>点击+5条数据</button> { arr.map(item => <div key={`arr${item.id}`}> { item.name }:{ item.id } { item?.addKey || ''}</div>) } </div> ) // 点击+5条数据 function addObj(){ for (let i = 0; i < 5; i++) { setArr(i => [...i, obj()]); //一定要使用函数方式 } } // 循环添加处理 function addKeyList(){ // taskList是一个promise任务数组 let taskList = arr.map(item => { return addKeyFun(item) }) Promise.all(taskList).then(res => { setArr(res); //所有接口请求返回后才会一次性渲染 }) } // 追加参数 function addKeyFun(item){ return new Promise((resolve, reject) => { let setTime = parseInt(Math.random() * 1e4); // 模拟异步接口请求,且接口返回时间不稳定 setTimeout(() => { item.addKey = '追加的key用时:' + setTime; resolve(item) }, setTime) }) } } export default Home;
3.2:利用es5中的回调函数callback处理
import React, { useState, useEffect, useRef } from 'react'; // 随机对象 用于模拟区分 function obj() { return { name: `某某`, id: parseInt(Math.random() * 1e4) } } const Home = () => { const [arr, setArr] = useState([]); useEffect(() => { addKeyList() }, [arr.length]) // 注意监听arr.length 而不是arr 否则死循环 return ( <div> <button onClick={ addObj }>点击+5条数据</button> { arr.map(item => <div key={`arr${item.id}`}> { item.name }:{ item.id } { item?.addKey || ''}</div>) } </div> ) // 点击+5条数据 function addObj(){ for (let i = 0; i < 5; i++) { setArr(i => [...i, obj()]); //一定要使用函数方式 } } // 循环添加处理 function addKeyList(){ let httpNum = 0; let resArr = [...arr]; arr.map((item, index) => { addKeyFun(item, index, (obj, idx) => { httpNum++; //每次接口请求记录次数+1,注意不管接口成功还是失败都要+1,注意接口error处理 resArr[idx] = obj; //赋值新数据到数组 if(httpNum == arr.length){ //最后一次请求-统一设置到数组 setArr(resArr); } }) }) } // 追加参数 function addKeyFun(item, index, callback){ let setTime = parseInt(Math.random() * 1e4); // 模拟异步接口请求,且接口返回时间不稳定 setTimeout(() => { item.addKey = '追加的key用时:' + setTime; callback && callback(item, index); }, setTime) } } export default Home;
3.3:useState回调函数写法(数据量大的时候需要额外测试处理)
import React, { useState, useEffect, useRef } from 'react'; // 随机对象 用于模拟区分 function obj() { return { name: `某某`, id: parseInt(Math.random() * 1e4) } } const Home = () => { const [arr, setArr] = useState([]); useEffect(() => { addKeyFun(arr) }, [arr.length]) // 注意监听arr.length 而不是arr 否则死循环 return ( <div> <button onClick={ addObj }>点击+5条数据</button> { arr.map(item => <div key={`arr${item.id}`}> { item.name }:{ item.id } { item?.addKey || ''}</div>) } </div> ) // 点击+5条数据 function addObj(){ for (let i = 0; i < 5; i++) { setArr(i => [...i, obj()]); //一定要使用函数方式 } } // 追加参数 function addKeyFun(data){ data.map((ii, idx) => { let setTime = parseInt(Math.random() * 1e4); // 模拟异步接口请求,且接口返回时间不稳定 setTimeout(() => { setArr(i => { i[idx].addKey = '追加的key用时:' + setTime; return [...i] }); }, setTime) }) } } export default Home;
结果:
原自
react循环调用useState同步处理方案_react usestate 同步_搬砖的前端的博客-CSDN博客