使用 NextJS 路由器时的 useEffect 依赖
useEffect dependencies when using NextJS router
我有一个 NextJS 项目,使用 NextJS 路由器根据某个状态变量将用户路由到页面。
我查找了如何使用 NextJS router documents 做我想做的事情,其中有这个例子:
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
if (!(user || loading)) {
router.push('/login')
}
}, [user, loading])
return <p>Redirecting...</p>
}
当我将该示例粘贴到我的代码中时,ESLint 对我没有将路由器作为依赖项包括在内感到不高兴 - 显示以下消息:
React Hook useEffect has a missing dependency: 'router'. Either include it or remove the dependency array.eslintreact-hooks/exhaustive-deps
该消息是有道理的 - 我们在效果中使用了 useRouter 挂钩,但没有将其添加到效果的依赖项数组中。
但是,将其添加到依赖项数组自然会导致无限重新渲染循环(因为我使用的是动态路由,所以由于路由器正在更改,所以会一遍又一遍地调用相同的效果)。
我应该忽略来自 ESLint 的警告,还是应该一起做一些不同的事情?
编辑:值得注意的是我使用的是NextJS ESlint config
目前,这是一个错误。
useRouter
方法似乎改变了 useRouter
本身。因此,每次您调用这些方法之一时,useRouter
都会发生变化并导致此循环。
还有一个问题是Next.js没有记住useRouter
,所以即使值相同它也会改变。
目前,我发现的最接近的解决方法来自对这个未决问题的评论 https://github.com/vercel/next.js/issues/18127#issuecomment-950907739。
它所做的是将 useRouter
“转换”为 useRef
并导出 push
方法。所以每次你使用这个方法,如果这个值没有改变,这个引用就不会改变。
解决方法:
I quickly came up with this, which seems to have worked:
import { useRouter } from 'next/router'
import type { NextRouter } from 'next/router'
import { useRef, useState } from 'react'
export default function usePush(): NextRouter['push'] {
const router = useRouter()
const routerRef = useRef(router)
routerRef.current = router
const [{ push }] = useState<Pick<NextRouter, 'push'>>({
push: path => routerRef.current.push(path),
})
return push
}
It returns a push function that's semantically memoized and therefore safe to use with useEffect, e.g.
const push = usePush()
useEffect(() => {
checkLoggedIn().then(loggedIn => {
if (!loggedIn) {
push('/login')
}
})
}, [push])
@mledezma 提出的解决方法工作得很好,如果你还不能理解 TS,这是一个 JS 重构:
import { useRef, useState } from "react";
import { useRouter } from "next/router";
export const usePush = () => {
const router = useRouter();
const routerRef = useRef(router);
routerRef.current = router;
const [routerPush] = useState(() => {
const push = (path) => {
routerRef.current.push(path);
};
return push;
});
return routerPush;
};
我有一个 NextJS 项目,使用 NextJS 路由器根据某个状态变量将用户路由到页面。
我查找了如何使用 NextJS router documents 做我想做的事情,其中有这个例子:
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
if (!(user || loading)) {
router.push('/login')
}
}, [user, loading])
return <p>Redirecting...</p>
}
当我将该示例粘贴到我的代码中时,ESLint 对我没有将路由器作为依赖项包括在内感到不高兴 - 显示以下消息:
React Hook useEffect has a missing dependency: 'router'. Either include it or remove the dependency array.eslintreact-hooks/exhaustive-deps
该消息是有道理的 - 我们在效果中使用了 useRouter 挂钩,但没有将其添加到效果的依赖项数组中。
但是,将其添加到依赖项数组自然会导致无限重新渲染循环(因为我使用的是动态路由,所以由于路由器正在更改,所以会一遍又一遍地调用相同的效果)。
我应该忽略来自 ESLint 的警告,还是应该一起做一些不同的事情?
编辑:值得注意的是我使用的是NextJS ESlint config
目前,这是一个错误。
useRouter
方法似乎改变了 useRouter
本身。因此,每次您调用这些方法之一时,useRouter
都会发生变化并导致此循环。
还有一个问题是Next.js没有记住useRouter
,所以即使值相同它也会改变。
目前,我发现的最接近的解决方法来自对这个未决问题的评论 https://github.com/vercel/next.js/issues/18127#issuecomment-950907739。
它所做的是将 useRouter
“转换”为 useRef
并导出 push
方法。所以每次你使用这个方法,如果这个值没有改变,这个引用就不会改变。
解决方法:
I quickly came up with this, which seems to have worked:
import { useRouter } from 'next/router'
import type { NextRouter } from 'next/router'
import { useRef, useState } from 'react'
export default function usePush(): NextRouter['push'] {
const router = useRouter()
const routerRef = useRef(router)
routerRef.current = router
const [{ push }] = useState<Pick<NextRouter, 'push'>>({
push: path => routerRef.current.push(path),
})
return push
}
It returns a push function that's semantically memoized and therefore safe to use with useEffect, e.g.
const push = usePush()
useEffect(() => {
checkLoggedIn().then(loggedIn => {
if (!loggedIn) {
push('/login')
}
})
}, [push])
@mledezma 提出的解决方法工作得很好,如果你还不能理解 TS,这是一个 JS 重构:
import { useRef, useState } from "react";
import { useRouter } from "next/router";
export const usePush = () => {
const router = useRouter();
const routerRef = useRef(router);
routerRef.current = router;
const [routerPush] = useState(() => {
const push = (path) => {
routerRef.current.push(path);
};
return push;
});
return routerPush;
};