useEffect函数必须返回一个清除函数,否则什么都不返回

我正在尝试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);
}
}, []);

我在控制台得到这个警告。但我认为,对于异步调用,清理是可选的。我不知道为什么我得到这个警告。链接沙盒为例。< a href = " https://codesandbox。io / s / 24 rj871r0p noreferrer“rel = > https://codesandbox.io/s/24rj871r0p enter image description here < / p >

617505 次浏览

当你使用一个async函数

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();
}, []);

第二种解决方案将使其更易于阅读,并将帮助您编写代码,以便在触发新请求时取消以前的请求或将最新的请求响应保存在状态中

< a href = " https://codesandbox。io/s/jpknv0kyn9" rel="noreferrer">Working codesandbox

我建议看看Dan Abramov (React核心维护者之一)在这里回答:

我觉得你把事情搞得太复杂了。

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>
}

从长期来看,我们将不鼓励这种模式,因为它助长了竞争条件。例如-在你的呼叫开始和结束之间任何事情都可能发生,你可以得到新的道具。相反,我们将推荐使用悬疑来获取数据,它看起来更像

const response = MyAPIResource.read();

没有影响。但与此同时,你可以将异步的东西移动到一个单独的函数并调用它。

你可以阅读更多关于这里的实验悬念


如果你想使用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沙盒

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>
);
}

在React提供更好的方法之前,你可以创建一个帮助器useEffectAsync.js:

import { useEffect } from 'react';




export default function useEffectAsync(effect, inputs) {
useEffect(() => {
effect();
}, inputs);
}

现在你可以传递一个async函数:

useEffectAsync(async () => {
const items = await fetchSomeItems();
console.log(items);
}, []);

更新

如果您选择这种方法,请注意这是一种糟糕的形式。当我知道这是安全的时候,我就会这样做,但这总是糟糕的方式和随意的。

数据获取的悬念,这仍然是实验性的,将解决一些情况。

在其他情况下,您可以将异步结果建模为事件,以便您可以根据组件生命周期添加或删除侦听器。

或者你可以将异步结果建模为可观测的,这样你就可以根据组件的生命周期订阅和取消订阅。

我通读了这个问题,感觉答案中没有提到实现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]);

Reference => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

试一试

const MyFunctionnalComponent: React.FC = props => {
useEffect(() => {
// Using an IIFE
(async function anyNameFunction() {
await loadContent();
})();
}, []);
return <div></div>;
};

对于其他读者,错误可能来自没有括号包装async函数的事实:

考虑异步函数initData

  async function initData() {
}

这段代码将导致你的错误:

  useEffect(() => initData(), []);

但这一个,不会:

  useEffect(() => { initData(); }, []);

注意initData()周围的括号

空白符可以在这里使用。
而不是:< / p >

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>;
}

请试试这个

 useEffect(() => {
(async () => {
const products = await api.index()
setFilteredProducts(products)
setProducts(products)
})()
}, [])

为了使用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钩子中定义你的查询,因为它会被无限次地重新渲染。由于不能将useEffect设置为异步,所以可以将其内部的函数设置为异步。

在上面所示的例子中,API调用在另一个分离异步函数中,因此它确保调用是异步的,并且只发生一次。另外,useEffect's依赖数组([])是空的,这意味着它的行为就像React类组件中的componentDidMount一样,它只会在组件被挂载时执行一次。

对于加载文本,你可以使用React的有条件的呈现来验证你的文章是否为空,如果是,呈现一个加载文本,否则,显示文章。当你完成从API获取数据并且post不为空时,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的信息,我建议您阅读以下文章。

< a href = " https://betterprogramming。pub / how-to-fetch-data-from-an-api-with-react-hooks-9e7202b8afcd noreferrer“rel = > https://betterprogramming.pub/how-to-fetch-data-from-an-api-with-react-hooks-9e7202b8afcd < / >

https://reactjs.org/docs/conditional-rendering.html

使用自定义图书馆提供的useAsyncEffect钩子,安全地执行异步代码并在效果中发出请求变得微不足道,因为它使您的代码可自动取消(这只是功能列表中的一件事)。检查带有JSON抓取的实时演示

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;

同样的演示使用axios

将其包装成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]);

你也可以使用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>
}

只是注意一下purescript语言如何用Aff单子处理这个陈旧效果的问题

没有PURESCRIPT

你必须使用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 (来自NoahZinsmeister/web3-react的例子)

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


...

与PURESCRIPT

检查__ABC0在__ABC2函数中杀死它的Aff

Aff被实现为状态机(没有承诺)

但与我们相关的是:

  • 你可以把你的AbortController放在这里
  • 如果错误是抛向纤维或纤维内部,它将停止运行Effects(未测试)和Affs(它将不会运行第二个例子中的then,因此它将不会运行setBalance(balance))

忽略警告,并像这样使用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>);
}

其他答案已经有很多例子给出了,并且解释得很清楚,所以我将从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 () => {
//...
}, [])

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);
}
}) ()
}, [])

正确执行并避免错误:“警告:无法对未挂载的…执行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);
}
};


或AbortController


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();
};
}, []);








最简单的方法是从'use-async-effect'中使用useAsyncEffect 你可以在NPM上找到它
const ProtectedRoute = ({ children }) => {


const [isAuth, setIsAuth] = useState(false);


useAsyncEffect(async () => {
try {
const data = await axios("auth");
console.log(data);
setIsAuth(true);
} catch (error) {
console.log(error);
}
}, []);






if (!isAuth)
return <Navigate to="/signin" />


return children;


}