我具有以下组件,该组件获取文章数组,显示加载微调器,最后显示文章列表。有输入错误。
const ArticleListContainer: React.FC = () => {
const [articles, setArticles] = useState<Article[] | null>(null)
const [isFetched, setIsFetched] = useState<boolean>(false)
useEffect(() => {
fetchArticles()
.then(data => setArticles(data))
.then(() => setIsFetched(true))
}, [])
return isFetched ? <LoadingSpinner /> : <ArticleList {articles} /> // Error!
}
这里的问题是articles
是可空的。 articles
接受的ArticleList
道具不能为空。
但是,我知道如果已获取数据,articles
不能为null。如果我使用自定义钩子来获取文章,这种关系将变得更加清晰。
const ArticleListContainer: React.FC = () => {
const { data: articles, error, isFetching } = useAsync(fetchArticles)
if (isFetching) {
return <LoadingSpinner />
} else if (error !== null) {
return <ErrorPage />
}
return <ArticleList articles={articles} /> // Error!
}
根据useAsync
的设计,如果isFetching
是false
并且error
是null
,则articles
不能是null
。
这是我想出的一些解决方法。
1。检查是否为空
if (articles === null) {
throw new Error('Articles is null!')
}
我不必这样做,因为除非从理论上讲,除非fetchArticles
的返回值为空,否则永远不会使用此代码。
2。 !
运算符
return <ArticleList articles={articles!} />
在各处放置一堆!
运算符可能不是一个好主意。
是否有更好的方式编写/键入此组件?
答案 0 :(得分:0)
您正在尝试使用单独的useState
钩子对状态进行建模,但是articles
和fetched
状态变量之间是密切相关的-在未加载之前就不会获取文章。
在一个与state machine类似的单一状态复合体/挂钩中处理所有可能的状态及其转换可能是一个好主意。 TypeScript可以帮助我们discriminated union types做出有关最近状态(StackBlitz)的决定:
// State types
type State = Loading | Fetched | Error
type Loading = { type: "loading" }
type Fetched = { type: "fetched"; articles: Article[] }
type Error = {type: "error", message?: string}
const App = () => {
// initially set state to fetched with no articles
const [state, setState] = useState<State>({type: "fetched", articles: []})
useEffect(() => {
setState({type: "loading"}) // change to loading
fetchArticles().then(setState) // change to fetched, when done
}, [])
return state.type === "loading" ?
<div>loading... </div>:
state.type === "error" ?
<div>upps... </div>:
<div>{`fetched: ${getArticleNames(state.articles)}`}</div>
}
// just some helper methods/types
type Article = {name: string}
const getArticleNames = (articles: readonly Article[]) => articles.map(a => a.name)
const defer = (ms: number) => new Promise<void>((resolve, reject) => setTimeout(()=> resolve(), ms))
const fetchArticles = () => defer(2000).then<Fetched>(
()=> ({type: "fetched", articles: [{name: "fooArticle"}]}))
如果您需要更大的示例,那么here是Redux的不错选择。