我正在尝试 useEffect
示例,如下所示:
useEffect(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}, []);
https://i.stack.imgur.com/YFRR5.png
我建议看看Dan Abramov (one of the React core maintainers) answer here:
我认为你让它变得比它需要的更复杂。
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await response.json()
dataSet(response)
}
fetchMyAPI()
}, [])
return <div>{JSON.stringify(data)}</div>
}
从长远来看,我们将不鼓励这种模式,因为它鼓励竞争条件。例如 - 在您的通话开始和结束之间可能发生任何事情,并且您可以获得新的道具。相反,我们会推荐 Suspense 用于数据获取,它看起来更像
const response = MyAPIResource.read();
并且没有效果。但与此同时,您可以将异步内容移至单独的函数并调用它。
您可以阅读有关 experimental suspense here 的更多信息。
如果你想在 eslint 之外使用函数。
function OutsideUsageExample({ userId }) {
const [data, dataSet] = useState<any>(null)
const fetchMyAPI = useCallback(async () => {
let response = await fetch('api/data/' + userId)
response = await response.json()
dataSet(response)
}, [userId]) // if userId changes, useEffect will run again
useEffect(() => {
fetchMyAPI()
}, [fetchMyAPI])
return (
<div>
<div>data: {JSON.stringify(data)}</div>
<div>
<button onClick={fetchMyAPI}>manual fetch</button>
</div>
</div>
)
}
如果您使用 useCallback,请查看其工作原理示例 useCallback。 Sandbox。
import React, { useState, useEffect, useCallback } from "react";
export default function App() {
const [counter, setCounter] = useState(1);
// if counter is changed, than fn will be updated with new counter value
const fn = useCallback(() => {
setCounter(counter + 1);
}, [counter]);
// if counter is changed, than fn will not be updated and counter will be always 1 inside fn
/*const fnBad = useCallback(() => {
setCounter(counter + 1);
}, []);*/
// if fn or counter is changed, than useEffect will rerun
useEffect(() => {
if (!(counter % 2)) return; // this will stop the loop if counter is not even
fn();
}, [fn, counter]);
// this will be infinite loop because fn is always changing with new counter value
/*useEffect(() => {
fn();
}, [fn]);*/
return (
<div>
<div>Counter is {counter}</div>
<button onClick={fn}>add +1 count</button>
</div>
);
}
当您使用异步功能时
async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}
它返回一个 Promise,并且 useEffect
不希望回调函数返回 Promise,而是希望什么都不返回或返回一个函数。
作为警告的解决方法,您可以使用自调用异步函数。
useEffect(() => {
(async function() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
})();
}, []);
或者为了让它更干净,你可以定义一个函数然后调用它
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
第二种解决方案将使其更易于阅读,并将帮助您编写代码以在触发新请求时取消先前的请求或将最新的请求响应保存在状态
useEffect
时,您可以返回一个函数来进行清理,例如取消订阅事件。当您使用异步函数时,您无法返回任何内容,因为useEffect
不会等待结果
在 React 提供更好的方法之前,您可以创建一个帮助器 useEffectAsync.js
:
import { useEffect } from 'react';
export default function useEffectAsync(effect, inputs) {
useEffect(() => {
effect();
}, inputs);
}
现在你可以传递一个异步函数:
useEffectAsync(async () => {
const items = await fetchSomeItems();
console.log(items);
}, []);
更新
如果您选择这种方法,请注意它是错误的形式。当我知道它是安全的时,我会求助于它,但它总是糟糕的形式和随意的。
Suspense for Data Fetching 仍处于试验阶段,将解决部分情况。
在其他情况下,您可以将异步结果建模为事件,以便您可以根据组件生命周期添加或删除侦听器。
或者,您可以将异步结果建模为 Observable,以便您可以根据组件生命周期订阅和取消订阅。
useAsyncEffect
很容易误导人们认为如果他们从异步效果中返回一个清理函数,它将在适当的时间运行。这可能会导致内存泄漏或更严重的错误,因此我们选择鼓励人们重构他们的代码,以使与 React 生命周期交互的异步函数的“接缝”更加明显,从而希望代码的行为更加慎重和正确。
您也可以使用 IIFE 格式来保持简短
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
(async () => {
let response = await fetch('api/data')
response = await response.json()
dataSet(response);
})();
}, [])
return <div>{JSON.stringify(data)}</div>
}
此处可以使用 void operator。
代替:
React.useEffect(() => {
async function fetchData() {
}
fetchData();
}, []);
或者
React.useEffect(() => {
(async function fetchData() {
})()
}, []);
你可以写:
React.useEffect(() => {
void async function fetchData() {
}();
}, []);
它更干净更漂亮。
异步效果可能会导致内存泄漏,因此在卸载组件时执行清理非常重要。在 fetch 的情况下,这可能如下所示:
function App() {
const [ data, setData ] = React.useState([]);
React.useEffect(() => {
const abortController = new AbortController();
void async function fetchData() {
try {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url, { signal: abortController.signal });
setData(await response.json());
} catch (error) {
console.log('error', error);
}
}();
return () => {
abortController.abort(); // cancel pending fetch request on component unmount
};
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
React.useEffect(() => { (async () => () {... })();}, []);
我通读了这个问题,感觉答案中没有提到实现 useEffect 的最佳方法。假设您有一个网络呼叫,并且想要在您得到响应后做某事。为了简单起见,让我们将网络响应存储在状态变量中。有人可能想使用 action/reducer 来使用网络响应更新存储。
const [data, setData] = useState(null);
/* This would be called on initial page load */
useEffect(()=>{
fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(data => {
setData(data);
})
.catch(err => {
/* perform error handling if desired */
});
}, [])
/* This would be called when store/state data is updated */
useEffect(()=>{
if (data) {
setPosts(data.children.map(it => {
/* do what you want */
}));
}
}, [data]);
参考=> https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
then(res => res.json())
?
对于其他读者,错误可能来自没有括号包裹异步函数的事实:
考虑异步函数 initData
async function initData() {
}
此代码将导致您的错误:
useEffect(() => initData(), []);
但是这个,不会:
useEffect(() => { initData(); }, []);
(注意 initData() 周围的括号
要使用 React Hooks
从外部 API
获取,您应该调用从 useEffect
挂钩内部的 API 获取的函数。
像这样:
async function fetchData() {
const res = await fetch("https://swapi.co/api/planets/4/");
res
.json()
.then(res => setPosts(res))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);
我强烈建议您不要在 useEffect
Hook 中定义您的查询,因为它会无限次重新渲染。由于您无法使 useEffect
异步,因此您可以将其中的函数设为异步。
在上面显示的示例中,API 调用在另一个分离的异步函数中,因此它确保调用是异步的并且只发生一次。此外,useEffect's
依赖数组([])是空的,这意味着它的行为就像 React 类组件中的 componentDidMount,它只会在组件挂载时执行一次。
对于加载文本,您可以使用 React 的条件渲染来验证您的帖子是否为空,如果是,则渲染加载文本,否则,显示帖子。当您完成从 API 获取数据并且帖子不为空时,else 将为真。
{posts === null ? <p> Loading... </p>
: posts.map((post) => (
<Link key={post._id} to={`/blog/${post.slug.current}`}>
<img src={post.mainImage.asset.url} alt={post.mainImage.alt} />
<h2>{post.title}</h2>
</Link>
))}
我看到您已经在使用条件渲染,所以我建议您更深入地研究它,尤其是在验证对象是否为空时!
如果您需要有关使用 Hooks 使用 API 的更多信息,我建议您阅读以下文章。
https://betterprogramming.pub/how-to-fetch-data-from-an-api-with-react-hooks-9e7202b8afcd
https://reactjs.org/docs/conditional-rendering.html
.then
和 await
?我认为 await
的全部意义在于替换 .then
。
fetch
需要 500ms,并且组件在 250ms 后卸载,回调将尝试更新卸载组件的状态,抛出错误。
尝试
const MyFunctionnalComponent: React.FC = props => { useEffect(() => { // 使用 IIFE (async function anyNameFunction() { await loadContent(); })(); }, []);返回
; };
其他答案已经很多例子给出并且解释清楚了,所以我将从TypeScript类型定义的角度来解释。
useEffect
挂钩 TypeScript 签名:
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
effect
的类型:
// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);
// Destructors are only allowed to return void.
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
现在我们应该知道为什么 effect
不能是 async
函数。
useEffect(async () => {
//...
}, [])
异步函数将返回一个带有隐式 undefined
值的 JS 承诺。这不是 useEffect
的期望。
我知道已经很晚了,但是我遇到了同样的问题,我想分享一下我用这样的功能解决了它!
useEffect(() => {
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}) ()
}, [])
请试试这个
useEffect(() => {
(async () => {
const products = await api.index()
setFilteredProducts(products)
setProducts(products)
})()
}, [])
使用自定义 library 提供的 useAsyncEffect 钩子,安全地执行异步代码和在效果内发出请求变得微不足道,因为它使您的代码可以自动取消(这只是功能列表中的一件事)。查看Live Demo with JSON fetching
import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpFetch from "cp-fetch";
/*
Notice: the related network request will also be aborted
Checkout your network console
*/
function TestComponent(props) {
const [cancel, done, result, err] = useAsyncEffect(
function* () {
const response = yield cpFetch(props.url).timeout(props.timeout);
return yield response.json();
},
{ states: true, deps: [props.url] }
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>
{done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
</div>
<button className="btn btn-warning" onClick={cancel} disabled={done}>
Cancel async effect
</button>
</div>
);
}
export default TestComponent;
只是关于纯脚本语言如何使用 Aff
monad 处理这个陈旧效果问题的注释
没有纯文字
你必须使用 AbortController
function App() {
const [ data, setData ] = React.useState([]);
React.useEffect(() => {
const abortController = new AbortController();
void async function fetchData() {
try {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url, { signal: abortController.signal });
setData(await response.json());
} catch (error) {
console.log('error', error);
}
}();
return () => {
abortController.abort(); // cancel pending fetch request on component unmount
};
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
或 stale
(from NoahZinsmeister/web3-react example)
function Balance() {
const { account, library, chainId } = useWeb3React()
const [balance, setBalance] = React.useState()
React.useEffect((): any => {
if (!!account && !!library) {
let stale = false
library
.getBalance(account)
.then((balance: any) => {
if (!stale) {
setBalance(balance)
}
})
.catch(() => {
if (!stale) {
setBalance(null)
}
})
return () => { // NOTE: will be called every time deps changes
stale = true
setBalance(undefined)
}
}
}, [account, library, chainId]) // ensures refresh if referential identity of library doesn't change across chainIds
...
带有纯文本
检查如何useAff
kills it's Aff
in the cleanup
function
Aff
实现为 state machine (without promises)
但这里与我们相关的是:
Aff 编码如何停止 Aff - 你可以把你的 AbortController 放在这里
如果错误被抛出到光纤或光纤内部,它将停止运行效果(未测试)和 Affs(它不会从第二个示例运行,因此它不会设置平衡(平衡))
忽略警告,将 useEffect
钩子与 async function
一起使用,如下所示:
import { useEffect, useState } from "react";
function MyComponent({ objId }) {
const [data, setData] = useState();
useEffect(() => {
if (objId === null || objId === undefined) {
return;
}
async function retrieveObjectData() {
const response = await fetch(`path/to/api/objects/${objId}/`);
const jsonData = response.json();
setData(jsonData);
}
retrieveObjectData();
}, [objId]);
if (objId === null || objId === undefined) {
return (<span>Object ID needs to be set</span>);
}
if (data) {
return (<span>Object ID is {objId}, data is {data}</span>);
}
return (<span>Loading...</span>);
}
要正确执行并避免错误:“警告:无法在未安装的设备上执行 React 状态更新......”
useEffect(() => {
let mounted = true;
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
if (mounted) {
setPosts(newPosts);
}
} catch (e) {
console.error(e);
}
})();
return () => {
mounted = false;
};
}, []);
或外部函数并使用对象
useEffect(() => {
let status = { mounted: true };
query(status);
return () => {
status.mounted = false;
};
}, []);
const query = async (status: { mounted: boolean }) => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
if (status.mounted) {
setPosts(newPosts);
}
} catch (e) {
console.error(e);
}
};
或中止控制器
useEffect(() => {
const abortController = new AbortController();
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`, { signal: abortController.signal });
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
setPosts(newPosts);
} catch (e) {
if(!abortController.signal.aborted){
console.error(e);
}
}
})();
return () => {
abortController.abort();
};
}, []);
将其包装成 useCallback
并将其用作 useEffect
挂钩的依赖项。
const getPosts = useCallback(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}, []);
useEffect(async () => {
getPosts();
}, [getPosts]);
不定期副业成功案例分享
useEffect(() => { let unmounted = false promise.then(res => { if (!unmounted) { setState(...) } }) return () => { unmounted = true } }, [])
eslint
要求我将fetchMyAPI()
作为useEffect
的依赖项