打字稿使用对象上的状态挂钩不保存



我无法保存我的 axios 返回到我的 useState 钩子的对象。 带有字符串和布尔值的常规钩子完美工作。这个没有。 axios 调用以正确的格式返回正确的数据。

我得到了以下使用状态钩子:

const [user, setUser] = useState<IUser>({} as IUser);

我正在从我的 api 中获取数据,我试图将其保存到我的钩子中。

const getUser = async () => {
const { data } = await axios.get('me'); // this holds the correct data {id:1,...}
setUser(data);
console.log(user); // setting this return nothing
}

TL;DR:react中的状态更新是异步的。


setUser在调用时不会直接更新userreact会更新user但它不会告诉您何时会发生这种情况。更新后的值很可能在下一次呈现中可用。

如果您想await状态更新,大多数时候使用useEffect就足够了:

useEffect(() => console.log(user), [user])

我还写了一篇关于这个主题的博客文章,对它进行了深入研究。

您可能需要考虑一种更通用的方法 -

const identity = x => x
const useAsync = (runAsync = identity, deps = []) => {
const [loading, setLoading] = useState(true)
const [result, setResult] = useState(null)
const [error, setError] = useState(null)
useEffect(_ => { 
Promise.reolve(runAsync(...deps))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, deps)
return { loading, result, error }
}

在组件中使用useAsync如下所示 -

const MyComponent = ({ userId = 0 }) => {
const { loading, error, result } =
useAsync(UserApi.getById, [userId])
if (loading)
return <pre>loading...</pre>
if (error)
return <pre>error: {error.message}</pre>
return <pre>result: {result}</pre>
}

如果您有许多组件需要查询用户,则可以相应地专门化useAsync-

const useUser = (id = 0) =>
userAsync(UserApi.getById, [id])
const MyComponent = ({ userId = 0 }) => {
const { loading, error, result:user } =
useUser(userId)
if (loading)
return <pre>loading...</pre>
if (error)
return <pre>error: {error.message}</pre>
return <pre>user: {user}</pre>
}

下面是一个代码片段,您可以在自己的浏览器中运行以验证结果 -

const { useState, useEffect } =
React
const identity = x => x
const useAsync = (runAsync = identity, deps = []) => {
const [loading, setLoading] = useState(true)
const [result, setResult] = useState(null)
const [error, setError] = useState(null)
useEffect(_ => { 
Promise.resolve(runAsync(...deps))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, deps)
return { loading, result, error }
}
const _fetch = (url = "") =>
fetch(url).then(x => new Promise(r => setTimeout(r, 2000, x)))
const UserApi = {
getById: (id = 0) =>
id > 500
? Promise.reject(Error(`unable to retrieve user: ${id}`))
: _fetch(`https://httpbin.org/get?userId=${id}`).then(res => res.json())
}

const useUser = (userId = 0) =>
useAsync(UserApi.getById, [userId])
const MyComponent = ({ userId = 0 }) => {
const { loading, error, result:user } =
useUser(userId)
if (loading)
return <pre>loading...</pre>
if (error)
return <pre style={{color:"tomato"}}>error: {error.message}</pre>
return <pre>result: {JSON.stringify(user, null, 2)}</pre>
}
const MyApp = () =>
<main>
<MyComponent userId={123} />
<MyComponent userId={999} />
</main>
ReactDOM.render(<MyApp />, document.body)
pre {
background: ghostwhite;
padding: 1rem;
white-space: pre-wrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.js"></script>


真正的定制

当然,这只是您可以设计useAsync的一种方式。您如何设计自定义钩子可能会极大地改变它们的使用方式 -

const MyComponent = ({ userId = 0 }) =>
useUser(userId, {
loading: _ => <pre>loading...</pre>,
error: e => <pre style={{color:"tomato"}}>error: {e.message}</pre>,
result: user => <pre>result: {JSON.stringify(user, null, 2)}</pre>
})

这样的自定义钩子可以像这样实现——

const identity = x => x
const defaultVariants =
{ loading: identity, error: identity, result: identity }
const useAsync = (runAsync = identity, deps = [], vars = defaultVariants) => {
const [{ tag, data }, update] =
useState({ tag: "loading", data: null })
const setResult = data =>
update({ tag: "result", data })
const setError = data =>
update({ tag: "error", data })
useEffect(_ => {
Promise.resolve(runAsync(...deps))
.then(setResult, setError)
}, deps)
return vars[tag](data)
}

并且useUser已更新以传递cata

const useUser = (userId = 0, vars) =>
useAsync(UserApi.getById, [userId], vars)

通过运行下面的代码片段来验证结果 -

const { useState, useEffect } =
React
const identity = x => x
const defaultVariants =
{ loading: identity, error: identity, result: identity }
const useAsync = (runAsync = identity, deps = [], vars = defaultVariants) => {
const [{ tag, data }, update] =
useState({ tag: "loading", data: null })

const setResult = data =>
update({ tag: "result", data })
const setError = data =>
update({ tag: "error", data })
useEffect(_ => {
Promise.resolve(runAsync(...deps))
.then(setResult, setError)
}, deps)
return vars[tag](data)
}
const _fetch = (url = "") =>
fetch(url).then(x => new Promise(r => setTimeout(r, 2000, x)))
const UserApi = {
getById: (id = 0) =>
id > 500
? Promise.reject(Error(`unable to retrieve user: ${id}`))
: _fetch(`https://httpbin.org/get?userId=${id}`).then(res => res.json())
}

const useUser = (userId = 0, vars) =>
useAsync(UserApi.getById, [userId], vars)
const MyComponent = ({ userId = 0 }) =>
useUser(userId, {
loading: _ => <pre>loading...</pre>,
error: e => <pre style={{color:"tomato"}}>error: {e.message}</pre>,
result: user => <pre>result: {JSON.stringify(user, null, 2)}</pre>
})
const MyApp = () =>
<main>
<MyComponent userId={123} />
<MyComponent userId={999} />
</main>
ReactDOM.render(<MyApp />, document.body)
pre {
background: ghostwhite;
padding: 1rem;
white-space: pre-wrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.js"></script>

您的代码运行良好,但您在错误的位置记录了数据。getUSer方法上,awaitsetUser都是异步 api 调用,但控制台是同步的,这就是为什么它在更新之前控制台user的原因。 最初user是{},这就是为什么它什么都不给出。

相关内容

  • 没有找到相关文章

最新更新