与useEffect一起使用时如何防止触发useCallback(并遵守eslint-plugin-react-hooks)?
How to prevent useCallback from triggering when using with useEffect (and comply with eslint-plugin-react-hooks)?
我有一个用例,其中页面必须在首次呈现和单击按钮时调用相同的提取函数。
代码类似于下面(参考:https://stackblitz.com/edit/Whosebug-question-bink-62951987?file=index.tsx):
import React, { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { fetchBackend } from './fetchBackend';
const App: FunctionComponent = () => {
const [selected, setSelected] = useState<string>('a');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [data, setData] = useState<string | undefined>(undefined);
const query = useCallback(async () => {
setLoading(true)
try {
const res = await fetchBackend(selected);
setData(res);
setError(false);
} catch (e) {
setError(true);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
query();
}, [query])
return (
<div>
<select onChange={e => setSelected(e.target.value)} value={selected}>
<option value="a">a</option>
<option value="b">b</option>
</select>
<div>
<button onClick={query}>Query</button>
</div>
<br />
{loading ? <div>Loading</div> : <div>{data}</div>}
{error && <div>Error</div>}
</div>
)
}
export default App;
我的问题是获取函数总是在任何输入更改时触发,因为 eslint-plugin-react-hooks
强制我在 useCallback
挂钩中声明所有依赖项(例如:选定状态)。我必须使用 useCallback
才能将其与 useEffect
.
一起使用
我知道我可以将函数放在组件外部并传递所有参数(props、setLoading、setError 等)以使其工作,但我想知道是否可以存档同样的效果,同时将 fetch 函数保留在组件内部并遵守 eslint-plugin-react-hooks
?
[更新]
对于任何有兴趣查看工作示例的人。这是从已接受的答案派生的更新代码。
https://stackblitz.com/edit/Whosebug-question-bink-62951987-vxqtwm?file=index.tsx
像往常一样将所有依赖项添加到 useCallback
,但不要在 useEffect 中创建其他函数:
useEffect(query, [])
对于异步回调(比如你的查询),你需要使用旧式的承诺方式.then
,.catch
和 .finally
回调,以便将 void 函数传递给 useCallback
,这是 useEffect
.
所要求的
另一种方法可以在 React's docs 上找到,但根据文档不推荐使用。
毕竟,传递给 useEffect
的内联函数无论如何都会在每次重新渲染时重新声明。使用第一种方法,只有当查询的部门发生变化时,您才会传递新函数。警告也应该消失。 ;)
我认为您可以轻松实现所需的行为,因为
useEffect(() => {
query();
}, [data]) // Only re-run the effect if data changes
有关详细信息,请导航至此 official docs 页面的末尾。
有一些模型可以实现某些功能,当组件 mounts 和 点击 button/other。在这里,我为您带来了另一种模型,您可以通过仅使用 hooks 而无需根据按钮单击直接调用 fetch 函数 来实现这两者。它还将帮助您满足 钩子 deps 数组 的 eslint 规则,并轻松避免无限循环。实际上,这将利用名为 useEffect
和其他 useState
的效果钩子的力量。但是如果你有多个函数来获取不同的数据,那么你可以考虑很多选择,比如 useReducer 方法。好吧,看看这个项目,我试图实现与您想要的类似的东西。
https://codesandbox.io/s/fetch-data-in-react-hooks-23q1k?file=/src/App.js
我们来谈谈模型
export default function App() {
const [data, setDate] = React.useState("");
const [id, setId] = React.useState(1);
const [url, setUrl] = React.useState(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
fetch(url)
.then(response => response.json())
.then(json => {
setDate(json);
setIsLoading(false);
});
}, [url]);
return (
<div className="App">
<h1>Fetch data from API in React Hooks</h1>
<input value={id} type="number" onChange={e => setId(e.target.value)} />
<button
onClick={() => {
setIsLoading(true);
setUrl(`https://jsonplaceholder.typicode.com/todos/${id}`);
}}
>
GO & FETCH
</button>
{isLoading ? (
<p>Loading</p>
) : (
<pre>
<code>{JSON.stringify(data, null, 2)}</code>
</pre>
)}
</div>
);
}
这里我使用初始 link 在第一次渲染中获取数据,并且在每个按钮单击而不是调用任何方法时我更新了效果挂钩的 deps 数组中存在的状态,useEffect
,以便 useEffect
再次运行。
我有一个用例,其中页面必须在首次呈现和单击按钮时调用相同的提取函数。
代码类似于下面(参考:https://stackblitz.com/edit/Whosebug-question-bink-62951987?file=index.tsx):
import React, { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { fetchBackend } from './fetchBackend';
const App: FunctionComponent = () => {
const [selected, setSelected] = useState<string>('a');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [data, setData] = useState<string | undefined>(undefined);
const query = useCallback(async () => {
setLoading(true)
try {
const res = await fetchBackend(selected);
setData(res);
setError(false);
} catch (e) {
setError(true);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
query();
}, [query])
return (
<div>
<select onChange={e => setSelected(e.target.value)} value={selected}>
<option value="a">a</option>
<option value="b">b</option>
</select>
<div>
<button onClick={query}>Query</button>
</div>
<br />
{loading ? <div>Loading</div> : <div>{data}</div>}
{error && <div>Error</div>}
</div>
)
}
export default App;
我的问题是获取函数总是在任何输入更改时触发,因为 eslint-plugin-react-hooks
强制我在 useCallback
挂钩中声明所有依赖项(例如:选定状态)。我必须使用 useCallback
才能将其与 useEffect
.
我知道我可以将函数放在组件外部并传递所有参数(props、setLoading、setError 等)以使其工作,但我想知道是否可以存档同样的效果,同时将 fetch 函数保留在组件内部并遵守 eslint-plugin-react-hooks
?
[更新] 对于任何有兴趣查看工作示例的人。这是从已接受的答案派生的更新代码。 https://stackblitz.com/edit/Whosebug-question-bink-62951987-vxqtwm?file=index.tsx
像往常一样将所有依赖项添加到 useCallback
,但不要在 useEffect 中创建其他函数:
useEffect(query, [])
对于异步回调(比如你的查询),你需要使用旧式的承诺方式.then
,.catch
和 .finally
回调,以便将 void 函数传递给 useCallback
,这是 useEffect
.
另一种方法可以在 React's docs 上找到,但根据文档不推荐使用。
毕竟,传递给 useEffect
的内联函数无论如何都会在每次重新渲染时重新声明。使用第一种方法,只有当查询的部门发生变化时,您才会传递新函数。警告也应该消失。 ;)
我认为您可以轻松实现所需的行为,因为
useEffect(() => {
query();
}, [data]) // Only re-run the effect if data changes
有关详细信息,请导航至此 official docs 页面的末尾。
有一些模型可以实现某些功能,当组件 mounts 和 点击 button/other。在这里,我为您带来了另一种模型,您可以通过仅使用 hooks 而无需根据按钮单击直接调用 fetch 函数 来实现这两者。它还将帮助您满足 钩子 deps 数组 的 eslint 规则,并轻松避免无限循环。实际上,这将利用名为 useEffect
和其他 useState
的效果钩子的力量。但是如果你有多个函数来获取不同的数据,那么你可以考虑很多选择,比如 useReducer 方法。好吧,看看这个项目,我试图实现与您想要的类似的东西。
https://codesandbox.io/s/fetch-data-in-react-hooks-23q1k?file=/src/App.js
我们来谈谈模型
export default function App() {
const [data, setDate] = React.useState("");
const [id, setId] = React.useState(1);
const [url, setUrl] = React.useState(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
fetch(url)
.then(response => response.json())
.then(json => {
setDate(json);
setIsLoading(false);
});
}, [url]);
return (
<div className="App">
<h1>Fetch data from API in React Hooks</h1>
<input value={id} type="number" onChange={e => setId(e.target.value)} />
<button
onClick={() => {
setIsLoading(true);
setUrl(`https://jsonplaceholder.typicode.com/todos/${id}`);
}}
>
GO & FETCH
</button>
{isLoading ? (
<p>Loading</p>
) : (
<pre>
<code>{JSON.stringify(data, null, 2)}</code>
</pre>
)}
</div>
);
}
这里我使用初始 link 在第一次渲染中获取数据,并且在每个按钮单击而不是调用任何方法时我更新了效果挂钩的 deps 数组中存在的状态,useEffect
,以便 useEffect
再次运行。