用React和TypeScript处理可为空的状态变量的最佳方法是什么?

时间:2019-12-20 18:06:48

标签: reactjs typescript

我具有以下组件,该组件获取文章数组,显示加载微调器,最后显示文章列表。有输入错误。

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的设计,如果isFetchingfalse并且errornull,则articles不能是null

这是我想出的一些解决方法。

1。检查是否为空

if (articles === null) {
  throw new Error('Articles is null!')
}

我不必这样做,因为除非从理论上讲,除非fetchArticles的返回值为空,否则永远不会使用此代码。

2。 !运算符

return <ArticleList articles={articles!} />

在各处放置一堆!运算符可能不是一个好主意。

是否有更好的方式编写/键入此组件?

1 个答案:

答案 0 :(得分:0)

您正在尝试使用单独的useState钩子对状态进行建模,但是articlesfetched状态变量之间是密切相关的-在未加载之前就不会获取文章。

在一个与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的不错选择。