Javascript 组件';s状态是从其prop派生的,该prop来自异步API调用
我最近在开发一些业务逻辑时遇到了这种情况。下面是一个简化的、人为的例子Javascript 组件';s状态是从其prop派生的,该prop来自异步API调用,javascript,reactjs,Javascript,Reactjs,我最近在开发一些业务逻辑时遇到了这种情况。下面是一个简化的、人为的例子 函数应用程序(){ const[myState,setMyState]=useState([]) useffect(()=>{ 设置超时(()=>{ setMyState((prevState)=> prevState.concat([ { 键入:“foo”, 启用:对, }, { 类型:'bar', 启用:对, }, { 键入:“baz”, 启用:对, }, ]) ) }, 500) }, []) 返回( ) } 我使用
函数应用程序(){
const[myState,setMyState]=useState([])
useffect(()=>{
设置超时(()=>{
setMyState((prevState)=>
prevState.concat([
{
键入:“foo”,
启用:对,
},
{
类型:'bar',
启用:对,
},
{
键入:“baz”,
启用:对,
},
])
)
}, 500)
}, [])
返回(
)
}
我使用setTimeout
模拟API调用,它将状态传递给ChildComponent
函数子组件({propFromApp}){
const allTypes=propFromApp.map((项)=>item.type)
常量[checkedItems,setCheckedItems]=useState(所有类型)
返回(
{allTypes.map((类型,索引)=>(
{
if(checkedItems.includes(type)){
setCheckedItems((prevState)=>
prevState.filter((prevType)=>type!==prevType)
)
}否则{
setCheckedItems((prevState)=>prevState.concat(类型))
}
}}
/>
{type}
))}
)
}
而ChildComponent
则负责呈现一组基于道具的复选框。它首先从prop派生一个type
数组,称为allTypes
,然后使用该allTypes
作为初始值设置自己的状态checkedItems
。因此,首先检查所有项目。我之所以让ChildComponent
将它们作为自己的状态保存,是因为组件可以通过复选框控制它们
但是,由于propFromApp
最初是一个空数组,因此ChildComponent
的状态checkedItems
也被初始化为一个空数组,并且在
真正的数据从API调用返回,500毫秒后,setTimeout
中的回调被触发
因此,当道具更新时,我使用useffect
更新checkedItems
所以现在ChildComponent
看起来像这样
函数ChildComponent({propFromApp}){
const allTypes=propFromApp.map((项)=>item.type)
常量[checkedItems,setCheckedItems]=useState(所有类型)
useffect(()=>{
setCheckedItems(所有类型)
},[所有类型])
返回(…)
然而,这给出了一个错误
超过最大更新深度。组件调用时可能会发生这种情况
设置useEffect内部的状态,但是useEffect没有
依赖项数组,或每个渲染上的一个依赖项更改
我想这是因为allTypes
不是一种基本类型,所以每次组件重新呈现时,相等性检查都会确定allTypes
是不同的。因此,最后我必须将其更改为使用usemo
以避免问题。现在ChildComponent
如下所示
函数子组件({propFromApp}){
const allTypes=useMemo(()=>propFromApp.map((项)=>item.type)[
propFromApp,
])
常量[checkedItems,setCheckedItems]=useState(所有类型)
useffect(()=>{
setCheckedItems(所有类型)
},[所有类型])
返回(…)
现在它解决了问题。但我不确定这是正确的方法还是一个好的实践。有人能提出更好的替代方案或指出我的代码不够好的地方吗
这里是live dome只是一个建议,但我会跳过useMemo,简单地围绕useEffects设置一个If语句。这确保useEffect仅在您的条件有效时更新 应用程序中的示例:
useEffect(() => {
if(!myState) {
setTimeout(() => {...
儿童的例子:
useEffect(() => {
if(!checkedItems && allTypes) {
setCheckedItems(allTypes)
}
}, [allTypes, checkedItems])
usemo
在这种情况下通常可以解决问题,但可能无法解决问题。usemo
可能会或可能不会根据比我聪明的人编写的某些内存管理逻辑重新计算其值。因此,您需要编写代码,同时牢记已记录值的引用标识可能会更改。引用标识y更改意味着deps阵列将更改
可以使用usemo
修复无限循环。在大多数情况下,重新计算只会导致额外的重新加载,而不会导致其他任何情况
让usemo
影响应用程序逻辑是不好的。在您的示例中,重新计算将重置checkedItems
,而不重新计算则不会。若要解决此问题,请将派生值推迟到需要派生值时再进行
const [checkedItems, setCheckedItems] = useState(() => propFromApp.map((item) => item.type))
useEffect(() => {
const allTypes = propFromApp.map((item) => item.type)
setCheckedItems(allTypes)
}, [propFromApp])
或者,如果您有很多DEP,那么对DEP进行如此紧密的管理可能会变得不合理的繁重。然后,您可以显式地中断效果回调,除非您预期的更改已经发生
const prevPropFromApp = useRef(propFromApp)
const propFromAppChanged = prevPropFromApp.current !== (prevPropFromApp.current = propFromApp)
useEffect(() => {
if (!propFromAppChanged) return;
setCheckedItems(allTypes)
}, [allTypes, propFromAppChanged])
感谢您的回复,但我认为您的解决方案不正确。
checkedItems
是一个数组,并且!checkedItems
将始终返回false
。您好,感谢您的回复。我确实考虑过您的第一个解决方案,但它需要编写propfromap.map((项目)=>item.type
逻辑两次。我不确定我是否理解您所说的“使用useMemo更改应用程序逻辑不好”的意思。您的意思是,在我的例子中,我用usemo
更改了应用程序逻辑吗?您能否提供一个用usemo
更改应用程序逻辑的示例?只要您需要的转换是纯的,就像您示例中的映射一样,您可以为它编写一个帮助函数以避免重复。我确实是叔叔在我的措辞中,我将编辑答案。我的意思是,usemo
重新计算或不重新计算在术语上有任何差异是不好的