React 最佳实践

275 阅读2分钟

总结一下在阅读他人代码,以及看文章时了解到的一些最佳实践写法。

避免条件渲染,尽早 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 的好处:

  1. 逻辑更加清晰,便于维护,一眼就能看出这个组件有三种状态,像原先条件渲染的代码就很难一眼看出该组件有几种展示状态
  2. 易于拓展,比如添加错误处理,可以直接放在前面 return,如果是条件渲染写在一起的话,还要再梳理逻辑

mp.weixin.qq.com/s/wf4pvKdF_…

不要在 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 中保存的状态将被重置,并且一旦组件再次渲染,useEffectuseCallback 和 useMemo 都需要重新运行并重新计算新值。

如果代码会触发一些网络请求或进行一些复杂的计算,那么当组件再次渲染时,这些请求也会运行。同样,如果将一些表单数据存储在组件的内部状态中,则每次组件隐藏时都会重置。