Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/429.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 组件';s状态是从其prop派生的,该prop来自异步API调用_Javascript_Reactjs - Fatal编程技术网

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
重新计算或不重新计算在术语上有任何差异是不好的