总结一下在阅读他人代码,以及看文章时了解到的一些最佳实践写法。
避免条件渲染,尽早 return
条件渲染的写法
export function ShoppingList() {
const { data, isPending } = useQuery(/* ... */)
return (
<Card>
<CardHeading>Welcome 👋</CardHeading>
<CardContent>
{data?.assignee ? <UserInfo {...data.assignee} /> : null}
{isPending ? <Skeleton /> : null}
{!data && !isPending ? <EmptyScreen /> : null}
{data
? data.content.map((item) => (
<ShoppingItem key={item.id} {...item} />
))
: null}
</CardContent>
</Card>
)
}
尽早 return 的写法
export function ShoppingList() {
const { data, isPending } = useQuery(/* ... */)
if (isPending) {
return (
<Layout>
<Skeleton />
</Layout>
)
}
if (!data) {
return (
<Layout>
<EmptyScreen />
</Layout>
)
}
return (
<Layout>
{data.assignee ? <UserInfo {...data.assignee} /> : null}
{data.content.map((item) => (
<ShoppingItem key={item.id} {...item} />
))}
</Layout>
)
}
尽早 return 的好处:
- 逻辑更加清晰,便于维护,一眼就能看出这个组件有三种状态,像原先条件渲染的代码就很难一眼看出该组件有几种展示状态
- 易于拓展,比如添加错误处理,可以直接放在前面 return,如果是条件渲染写在一起的话,还要再梳理逻辑
不要在 return 后使用 hook && 不要在 if 语句内使用 hook
hook 的调用顺序必须是固定的,如果在 if 语句内部调用就可能会导致,初始化时调用了 hook 重渲染时没有调用 hook,这就会产生无法预料的错误。return 后调用的问题也是同样,这就相当于是条件渲染。
结合上面的尽早 return,声明变量应该放在 return 之前
import { useState } from 'react'
const Component = ({ propValue }) => {
if (!propValue) {
return null
}
// 这个 hook 是有条件的,因为只有当 propValue 存在时才会调用它
const [value, setValue] = useState(propValue)
return <div>{value}</div>
}
上面的代码就是错误的,应该像下面这么写
import { useState } from 'react'
const Component = ({ propValue }) => {
// 在条件渲染之前放 hooks
const [value, setValue] = useState(propValue)
if (!propValue) {
return null
}
return <div>{value}</div>
}
让子组件决定是否应该渲染
这一点我觉得十分重要,很多时候包括看其他人的代码都是这样的
import { useState } from 'react'
const ChildComponent = ({ shouldRender }) => {
return <div>Rendered: {shouldRender}</div>
}
const Component = () => {
const [shouldRender, setShouldRender] = useState(false)
return <>
{ !!shouldRender && <ChildComponent shouldRender={shouldRender} /> }
</>
}
以上代码是在父组件中控制子组件是否渲染,事实上是否渲染子组件的逻辑写在子组件中更好,代码如下
import { useState } from 'react'
const ChildComponent = ({ shouldRender }) => {
if (!shouldRender) {
return null
}
return <div>Rendered: {shouldRender}</div>
}
const Component = () => {
const [shouldRender, setShouldRender] = useState(false)
return <ChildComponent shouldRender={shouldRender} />
}
这样写的好处是 React 可以继续渲染 ChildComponent,即使它不可见。这意味着,ChildComponent 可以在隐藏时保持其状态,然后第二次渲染而不会丢失其状态。它一直都在那里,只是不可见。
如果组件像第一个代码那样停止渲染,则 useState 中保存的状态将被重置,并且一旦组件再次渲染,useEffect、useCallback 和 useMemo 都需要重新运行并重新计算新值。
如果代码会触发一些网络请求或进行一些复杂的计算,那么当组件再次渲染时,这些请求也会运行。同样,如果将一些表单数据存储在组件的内部状态中,则每次组件隐藏时都会重置。