推荐阅读:react循环遍历useState的数组异步调取接口追加参数后修改原数组处理方案:https://blog.csdn.net/weixin_44461275/article/details/121428336
map数组处理(过滤等)后调用到hooks异常。如:添加商品套餐,循环调用校验后添加到useState数组上。人员数组处理后调用useState数组等等…
在使用useState() 时,先了解 Hook 的规则
仅顶层调用 Hook:不能在循环,条件,嵌套函数等中调用useState()。在多个useState()调用中,渲染之间的调用顺序必须相同。
仅从React 函数调用 Hook: 必须函数组件或自定义钩子内部调用useState()。
所以: 不能在循环里面调用hook
.
改造前:
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监听不到变化的
对于一些项目需要每个数据,进行查询接口查询后再添加
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;