React 应用程序重新渲染并使用 axios 无限获取数据

React app re-rendering and infinitely fetching data using axios

我是一名自学成才的开发人员,几乎完全使用原生 iOS 应用程序。对于我的一个项目,我聘请了一名网络开发人员来构建一个 React 网络应用程序(由 Firebase 托管)。他来自乌克兰,突然不得不停下来,所以我接手完成了它。因此,我无法按照传统方式学习 React/HTTPS/axios/Node.js,即在教程、练习、核心概念等方面进展缓慢。虽然几个月前,我能够完成它,并且一切工作正常。然而,在过去的几周里,我不得不重组我的整个架构,包括创建一个新的 GCP 项目、一个新的 Firebase 项目、新的 git 存储库等,其中包括除了一些代码之外的所有重新配置优化和数据模型调整。在这次重组中的某个时刻,我的问题出现了。我提到所有这些是为了指出 A) 我一直严重依赖他的原创作品,尤其是在设置 Firebase 托管方面,B) 我我不确定问题出在哪里。我 90% 确定它与 React 一起使用,但这很奇怪,因为自从我两个月前完成它并且它按预期工作时我并没有真正对异步网络调用进行任何更改。

无论如何,在出现时,Web 应用程序请求将哪个视图呈现给客户端,这是 NotFoundViewReviewUserViewUserProfileView,由给定的后端决定一个userId。问题是当显示 UserProfileView 时,某些东西会导致此视图无限地重新呈现。起初,在不超过两秒钟的时间内,它正确地显示了这个视图,就在非常快速地重置和重新渲染之前。我认为它与 useEffect and/or useState React 挂钩有关,但我不确定在哪个视图中。

非常感谢任何帮助,谢谢。

export enum ViewState { 
    UserProfile = 'UserProfile',
    ReviewForm = 'ReviewForm',
    NotFound = 'NotFound'
}

....................................................

export class ApiHandler { 
    // Returns a ViewState enum case for main view routing
    public static requestViewState(id: string): Promise<AxiosResponse<ViewState>> { 
        console.log('REQUESTING VIEW STATE')
        return axios.get(`${API_URL}/user/${id}/view-state`)
    }

    // Returns the requested user's profile data + other info
    public static requestUserData(id: string): Promise<AxiosResponse<UserData>> { 
        console.log('REQUESTING USER DATA')
        return axios.get(`${API_URL}/user/${id}`)
    }

    // More API calls ...
}

....................................................

export function RoutingView(props: RouteComponentProps<RoutingViewProps>) { 
    const userId = props.match.params.id
    const [viewState, setViewState] = useState<ViewState>()
    const [showError, setShowError] = useState<boolean>()
    const [showLoader, setShowLoader] = useState<boolean>(true)

    useEffect(() => { 
        loadViewState()
    }, [])

    if (showLoader) { 
        return <PageLoader />
    }

    if (showError) { 
        return <FailedToLoad />
    }

    switch (viewState) { 
        case ViewState.UserProfile:
            return <UserProfileView id={userId} />
        case ViewState.ReviewForm:
            return <ReviewUserView id={userId} />
        default:
            return <NotFoundView />
    }

    async function loadViewState(): Promise<void> { 
        setShowLoader(true)
        try { 
            const viewStateData = await ApiHandler.requestViewState(userId)
            setViewState(viewStateData.data)
        } catch (error) { 
            console.log(error)
            setShowError(true)
        }
        setShowLoader(false)
    }
}

....................................................

export default function UserProfileView(props: UserProfileProps) { 
    console.log('INITIALIZED USERPROFILEVIEW')
    const userId = props.id
    const [userData, setUserData] = useState<UserData>()
    const [showLoader, setShowLoader] = useState<boolean>(false)
    const [service, setService] = useState<Service | null>()
    const [loadFailed, setLoadFailed] = useState<boolean>()

    useEffect(() => {
        if (userData?.user) { 
            const user = userData?.user
            document.title = `${user?.firstName} ${user?.lastName}`
        }
    }, [userData])

    useEffect(() => { 
        loadUserData()
    }, [userData])

    if (loadFailed) { 
        return <FailedToLoad />
    }

    return <div>
        {showLoader ? <PageLoader/> : ''}

        { 
            userData?.user && <div className={service ? styles.Hidden : ''}>
                <UserInfo 
                    user={userData?.user}
                    services={userData?.services}
                    selectedService={(service) => setService(service)}
                    submitted={userData?.hasSubmitted}
                />
            </div>
        }

    // More view components ...

    </div>

    async function loadUserData(): Promise<void> { 
        setShowLoader(true)
        try {
            const res = await ApiHandler.requestUserData(userId)
            setUserData(res.data as UserData)
        } catch (error) { 
            console.log(error)
            setLoadFailed(true)
        }
        setShowLoader(false)
    }
}

在本地调试时,这是控制台快速打印的内容。虽然我不知道是什么,但重复模式可能表示某些东西。 注意:为了简单起见,我在post中重命名了一些东西,ReferralRequest.view实际上是UserProfileView

改为这样做:

//After every rendering ONLY IF `prop` or `state` changes in this case `userDate always change`
useEffect(() => { 
 loadUserData()
}, [userData])

这样做:

//Runs ONCE after initial rendering
useEffect(() => { 
 loadUserData()
}, [])

要说为什么,你应该知道 useEffect(callback, dependencies) 的 dependencies 参数。

这里是使用的区别

1-) 未提供依赖项:

side-effect 在每次渲染后运行。

示例:

 useEffect(() => {
    // Runs after EVERY rendering
  });  

2-) 一个空数组[]:这是你应该做的。

side-effect 在初始渲染后运行一次。

示例:

 useEffect(() => {
    // Runs ONCE after initial rendering
  }, []);

3-) 有道具或状态值:这是你的问题。

side-effect 仅在任何依赖值更改时运行。

示例:

useEffect(() => {
    // Runs ONCE after initial rendering
    // and after every rendering ONLY IF `prop` or `state` changes
  }, [prop, state]);

更改代码

 useEffect(() => { 
    loadUserData()
}, [userData])

 useEffect(() => { 
    if (!userData) loadUserData()
}, [userData])

修复了无限获取,useEffect 方法依赖于 userData 的任何变化。然后每次提取都会更改该变量,从而导致无限循环。添加 if null 检查将停止另一次数据获取。

这是因为这段代码

useEffect(() => { 
    loadUserData()
}, [userData])

问题是 useEffect 的第二个参数是效果所依赖的变量。

如果 useEffect 中的 call-back 更改了这些变量中的任何一个,则会再次调用它,通过加载此数据生成无限循环,使用 setUserData 查看它们, 数据改变并且 loadUserData 再次调用等等。