查看原文
其他

React 之道:软件设计、架构和最佳实践

王龙 大前端技术之路 2022-06-29

(给大前端技术之路加星标,提升前端技能

我从 2016 年就开始使用 React,不过在应用架构和设计方面还没能总结出一个最佳实践。

虽然在低层面上有一些最佳实践,但在架构方面,大多数团队都会构建自己的“东西”。

当然是不存在所有业务领域都通用的最佳实践的。但确实有一些规则可以帮助你构建一个高效的基础代码。

软件架构的目的就是高效灵活。开发者可以高效开发,并能在不需要重写其核心的情况下进行修改。

这篇文章整合了一些对我和我合作过的团队行之有效的原则和规则。

我概述了有关组件、程序结构、测试、样式、状态管理和数据获取的优秀实践。有些例子可能过于简单化了,所以我们可以把重点放在原则上,而不是实现上。

把这里的一切都当作一种观点,而不是唯一的。构建软件的方法可不止一种。

优先函数组件

优先函数组件-他们语法更加简单。没有生命周期方法、构造函数或样板代码。你可以用更少的代码来表达相同的逻辑,而且不会失去可读性。

除非你要用错误边界,否则这是你最好的选择。这样你脑中需要保持的模型会变得小得多。

// 👎 Class components are verbose
class Counter extends React.Component {
  state = {
    counter0,
  }

  constructor(props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState({ counterthis.state.counter + 1 })
  }

  render() {
    return (
      <div>
        <p>counter: {this.state.counter}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>

    )
  }
}

// 👍 Functional components are easier to read and maintain
function Counter() {
  const [counter, setCounter] = useState(0)

  handleClick = () => setCounter(counter + 1)

  return (
    <div>
      <p>counter: {counter}</p>
      <button onClick={handleClick}>Increment</button>
    </div>

  )
}

编写风格一致的组件

对组件使用相同的风格。将helper函数放在相同的位置,以相同的方式导出,并遵循相同的命名模式。

没有真正一种方法要比另一种方法好。

无论是在文件的底部导出还是直接在组件的定义中导出,请选好一个并坚持它就行。

对组件命名

始终命名组件。这对读取错误堆栈信息以及使用React Dev工具都有所帮助。

而且在开发的时候也能更容易的在文件中找组件的位置。

// 👎 Avoid this
export default () => <form>...</form>

// 👍 Name your functions
export default function Form() {
  return <form>...</form>
}

管理Helper函数

工具类方法不需要依赖于组件闭包信息的应该把其移到外面去。比较好的地方是组件定义之前,这样代码文件在阅读的时候就能上到下的读。

这样就减少了组件中的噪声,只留下那些必要的部分。

// 👎 Avoid nesting functions which don't need to hold a closure.
function Component({ date }) {
  function parseDate(rawDate) {
    ...
  }

  return <div>Date is {parseDate(date)}</div>
}

// 👍 Place the helper functions before the component
function parseDate(date) {
  ...
}

function Component({ date }) {
  return <div>Date is {parseDate(date)}</div>
}

你应该尽可能的减少组件中的工具方法。而尽量多的把这些方法移出去,把方法依赖的组件内部信息作为参数。

这样逻辑仅依赖于一些纯函数的组合,就容易地跟踪bug和扩展。

// 👎 Helper functions shouldn't read from the component's state
export default function Component() {
  const [value, setValue] = useState('')

  function isValid() {
    // ...
  }

  return (
    <>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
        onBlur={validateInput}
      />
      <button
        onClick={() => {
          if (isValid) {
            // ...
          }
        }}
      >
        Submit
      </button>
    </>

  )
}

// 👍 Extract them and pass only the values they need
function isValid(value) {
  // ...
}

export default function Component() {
  const [value, setValue] = useState('')

  return (
    <>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
        onBlur={validateInput}
      />
      <button
        onClick={() => {
          if (isValid(value)) {
            // ...
          }
        }}
      >
        Submit
      </button>
    </>

  )
}

不硬编码标签

不要为导航、过滤器或列表硬编码标签。使用一个配置对象并循环遍历这个配置。

这样你就只需要在第一个地方修改标签和项内容。

// 👎 Hardcoded markup is harder to manage.
function Filters({ onFilterClick }) {
  return (
    <>
      <p>Book Genres</p>
      <ul>
        <li>
          <div onClick={() => onFilterClick('fiction')}>Fiction</div>
        </li>
        <li>
          <div onClick={() => onFilterClick('classics')}>
            Classics
          </div>
        </li>
        <li>
          <div onClick={() => onFilterClick('fantasy')}>Fantasy</div>
        </li>
        <li>
          <div onClick={() => onFilterClick('romance')}>Romance</div>
        </li>
      </ul>
    </>

  )
}

// 👍 Use loops and configuration objects
const GENRES = [
  {
    identifier'fiction',
    name: Fiction,
  },
  {
    identifier'classics',
    name: Classics,
  },
  {
    identifier'fantasy',
    name: Fantasy,
  },
  {
    identifier'romance',
    name: Romance,
  },
]

function Filters({ onFilterClick }) {
  return (
    <>
      <p>Book Genres</p>
      <ul>
        {GENRES.map(genre => (
          <li>
            <div onClick={() => onFilterClick(genre.identifier)}>
              {genre.name}
            </div>
          </li>
        ))}
      </ul>
    </>

  )
}

组件长度

React组件其实就是一个输入props返回html标签的方法。他们遵循和普通发放一样的设计原则。

如果一个方法处理的事情太多了,就提取出一些逻辑到其他方法。组件也是一样,如果一个组件有太多功能项了,就拆分为更多小的组件。

如果一个部分结构很复杂,需要逻辑判断和循环,那就进行提取。

依赖props和回调进行通信和获得数据。代码行数不是一个客观的衡量标准。应该多想想责任和抽象。

在JSX中写注释

当需要更清晰的内容时,就该打开代码块并提供额外的信息(写注释)。html标签也是业务逻辑的一部分,所以你也应该积极的提供注释。

function Component(props) {
  return (
    <>
      {/* If the user is subscribed we don't want to show them any ads */}
      {user.subscribed ? null : <SubscriptionPlans />}
    </>

  )
}

使用Error Boundaries

一个组件中的错误不应该导致整个UI瘫痪。只有在很少的情况下,如果发生严重错误,我们才会删除整个页面或重定向。大多数情况下,如果我们只在屏幕上隐藏一个特定的元素就可以了。

一个处理数据的方法中可能会有多个的try/catch。所以error boundaries也不仅仅是组件最上层。多用error boundaries包装单个组件库,可以避免级联故障。

function Component() {
  return (
    <Layout>
      <ErrorBoundary>
        <CardWidget />
      </ErrorBoundary>

      <ErrorBoundary>
        <FiltersWidget />
      </ErrorBoundary>

      <div>
        <ErrorBoundary>
          <ProductList />
        </ErrorBoundary>
      </div>
    </Layout>

  )
}

解构Props

大多数的React组件就是函数。他们输入props返回html标记。在一个正常的函数中你会直接使用传递进来的参数,所以这个原则也一样适用。不需要到处重复props这个字段。

一个不解构props的理由是这样好区分props和内部state。但是在一个普通方法中参数和变量是没差别的。不要创造不必要的规则。

// 👎 Don't repeat props everywhere in your component
function Input(props) {
  return <input value={props.value} onChange={props.onChange} />
}

// 👍 Destructure and use the values directly
function Component({ value, onChange }) {
  const [state, setState] = useState('')

  return <div>...</div>
}

Props的数量

一个组件应该接收多少props是一个主观的问题。一个组件拥有的props的数量与它所做的事情相关。你给它传递的props越多,它承担的功能就越多。

太多的props是一个组件做了太多事情的信号。

如果一个组件有超过5个的prop,那么就要考虑是否要拆分了。在某些情况下,它可能只是需要大量的数据。比如一个input输入框,就可能有很多个prop。在另外一些情况下,这是需要进行进一步提取的信号。

注意:组件使用的props越多,重新渲染的理由也就越多。

传递对象而不是原始类型

一种减少props数量的方法是传入对象而不是原始类型。一个一个地传递用户名、电子邮件和设置,还不如将它们组合在一起。这样即使用户新增一个字段,也不用做修改。

使用TypeScript 让其更加简单。

// 👎 Don't pass values on by one if they're related
<UserProfile
  bio={user.bio}
  name={user.name}
  email={user.email}
  subscription={user.subscription}
/>

// 👍 Use an object that holds all of them instead
<UserProfile user={user} />

条件渲染

在某些情况下,使用短路运算符进行条件渲染可能会适得其反,界面中可能会出现一个不需要的0。为了避免这种默认情况,请使用三元运算符。唯一要注意的是它们更冗长。

短路运算符减少了代码量,这很好。三元运算符虽然更长,但不会出错。而且,添加判断条件的也更容易。

// 👎 Try to avoid short-circuit operators
function Component() {
  const count = 0

  return <div>{count && <h1>Messages: {count}</h1>}</div>
}

// 👍 Use a ternary instead
function Component() {
  const count = 0

  return <div>{count ? <h1>Messages: {count}</h1> : null}</div>
}

避免嵌套的三元操作符

三元操作符超过一层后就会变的难以理解。虽然他们看上去更加简洁,但保证可读性更加重要。

// 👎 Nested ternaries are hard to read in JSX
isSubscribed ? (
  <ArticleRecommendations />
) : isRegistered ? (
  <SubscribeCallToAction />
) : (
  <RegisterCallToAction />
)

// 👍 Place them inside a component on their own
function CallToActionWidget({ subscribed, registered }) {
  if (subscribed) {
    return <ArticleRecommendations />
  }

  if (registered) {
    return <SubscribeCallToAction />
  }

  return <RegisterCallToAction />
}

function Component() {
  return (
    <CallToActionWidget
      subscribed={subscribed}
      registered={registered}
    />

  )
}

抽取列表

使用map之类的方法遍历一个数组很常见。但在一个有很多html标签的组件里,这种额外的缩进和map语法降低了可读性。当你需要map一些element,把他们放到一个单独的组件中,即使其html标签很少。这样父组件就不需要关心细节,只需要展示列表。只有在这个组件的主要职责就是展现列表的时候才把loop操作保留在组件中。试着为每个组件只保留一个map,但是如果html标记很长或很复杂,则提取列表。

// 👎 Don't write loops together with the rest of the markup
function Component({ topic, page, articles, onNextPage }) {
  return (
    <div>
      <h1>{topic}</h1>
      {articles.map(article => (
        <div>
          <h3>{article.title}</h3>
          <p>{article.teaser}</p>
          <img src={article.image} />
        </div>
      ))}
      <div>You are on page {page}</div>
      <button onClick={onNextPage}>Next</button>
    </div>

  )
}

// 👍 Extract the list in its own component
function Component({ topic, page, articles, onNextPage }) {
  return (
    <div>
      <h1>{topic}</h1>
      <ArticlesList articles={articles} />
      <div>You are on page {page}</div>
      <button onClick={onNextPage}>Next</button>
    </div>

  )
}

解构props时给与默认值

指定默认属性值的一种方法是给组件加一个defaultProps属性。这意味着组件及其参数的值不会放在一起。

最好在解构props时给与默认值。这样定义和值放在一起,从上到下阅读代码更容易,不用跳跃。

// 👎 Don't define the default props outside of the function
function Component({ title, tags, subscribed }) {
  return <div>...</div>
}

Component.defaultProps = {
  title'',
  tags: [],
  subscribedfalse,
}

// 👍 Place them in the arguments list
function Component({ title = '', tags = [], subscribed = false }) {
  return <div>...</div>
}

避免嵌套的render方法

当你需要从组件或逻辑中提取html标记时,不要将其放在同一组件中的函数体中。组件只是一个函数。这样定义它就是嵌套在其父级中。

这意味着它将有权访问其父级的所有状态和数据。它使代码更不可读-这个函数在所有组件之间做了什么?

把它移动到自己的组件中,只依赖于自己的props而不是闭包。

// 👎 Don't write nested render functions
function Component() {
  function renderHeader() {
    return <header>...</header>
  }
  return <div>{renderHeader()}</div>
}

// 👍 Extract it in its own component
import Header from '@modules/common/components/Header'

function Component() {
  return (
    <div>
      <Header />
    </div>

  )
}

状态管理

使用reducer

有时,你需要一种更强大的方式来管理状态。在准备使用外部库之前,先试试useReducer。这是一个用来进行复杂状态管理很好的机制,不需要依赖第三方库。

结合React的context和TypeScript,useReducer可以非常强大。不过,它还没有被广泛使用。人们仍然会去第三方库。

如果需要多个状态,请将它们移到一个reducer中。

// 👎 Don't use too many separate pieces of state
const TYPES = {
  SMALL'small',
  MEDIUM'medium',
  LARGE'large'
}

function Component() {
  const [isOpen, setIsOpen] = useState(false)
  const [type, setType] = useState(TYPES.LARGE)
  const [phone, setPhone] = useState('')
  const [email, setEmail] = useState('')
  const [error, setError] = useSatte(null)

  return (
    ...
  )
}

// 👍 Unify them in a reducer instead
const TYPES = {
  SMALL'small',
  MEDIUM'medium',
  LARGE'large'
}

const initialState = {
  isOpenfalse,
  type: TYPES.LARGE,
  phone'',
  email'',
  errornull
}

const reducer = (state, action) => {
  switch (action.type) {
    ...
    default:
      return state
  }
}

function Component() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    ...
  )
}

优先使用Hooks而不是HOC和Render Props

在某些情况下,我们需要增强组件或使其能够访问外部状态。通常有三种方法可以做到这一点---高阶组件(HOC),Render Props和Hooks。

Hooks已经被证明是实现这种效果的最有效的方法。形而上的看,组件是一个使用其他函数的函数。Hooks允许你访问多个外部源,而不会相互冲突。不管Hooks有多少,你都知道每个值来自哪里。

通过HOC,你可以通过props获得数据。这就不清楚它是来自父组件还是被包装的组件。此外,将多个props链接在一起会导致错误。

Render Props会导致高缩进和不好的可读性。在同一棵树中嵌套多个带有Render Props的组件会看起来更糟。而且它只在html标签中暴露值,因此你必须在其中写逻辑或将其传递下去。

使用Hooks,你可以使用简单的值,容易跟踪,并且不会干扰JSX。

// 👎 Avoid using render props
function Component() {
  return (
    <>
      <Header />
      <Form>
        {({ values, setValue }) => (
          <input
            value={values.name}
            onChange={e => setValue('name', e.target.value)}
          />
          <input
            value={values.password}
            onChange={e => setValue('password', e.target.value)}
          />
        )}
      </Form>
      <Footer />
    </>

  )
}

// 👍 Favor hooks for their simplicity and readability
function Component() {
  const [values, setValue] = useForm()

  return (
    <>
      <Header />
      <input
        value={values.name}
        onChange={e => setValue('name', e.target.value)}
      />
      <input
        value={values.password}
        onChange={e => setValue('password', e.target.value)}
      />
    )}
      <Footer />
    </>

  )
}

使用数据获取库

通常我们要在state中管理的数据是从API中获得的。我们需要将这些数据保存在内存中,去更新它并能在多个地方访问它。

现代的数据获取库像 React Query提供了足够的机制来管理外部数据。我们可以对其进行缓存,使其过期和重新获取。也可以用来发送数据,触发其进行刷新另一段数据。

如果你用到了GraphQL client 库比如Apollo,那就更容易了。它内置了 client state 的概念。

状态管理库

在大多数情况下,您不需要状态管理库。它们应该用于需要管理复杂状态的大型应用程序中。有很多关于这个主题的指南,所以我只想提下我会选择的2中库——Recoil和Redux。

组件构思模型

展示型和容器型

主要的思路是将组件分为两组-展示组件和容器组件。也被称为聪明和愚蠢。

其思想是有些组件没有任何功能和状态。它们只是由父组件通过一些props调用的。容器组件包含业务逻辑、数据获取和状态管理。

这个模型就是后端程序的MVC模。它的通用性足以在任何地方工作,用它也没错。

但是,在现代UI应用中,这种模式是不够的。把所有的逻辑放在一些组件中会导致膨胀。他们会最终承担了太多的责任,而变得难以管理。随着应用的发展,将复杂性集中在几个地方对可维护性来说是不好的。

无状态和有状态

将组件视为有状态的和无状态的。上面提到的构思模型意味着一部分组件应该管理着大多数复杂性。相反,它应该分散在整个应用程序中。

数据应该紧靠着它被使用到的地方。当使用 GraphQL 客户端时,应该在显示它的组件中获取数据。即使不是顶级的。不要考虑容器,要考虑责任。考虑保存一个状态的最合乎逻辑的组件是什么。

例如,一个<Form/>组件应该拥有表单的数据。<Input/>应该接收值并在发生更改时调用回调。<Button/>应该通知form它被按下,并让form处理发生的事件。

谁在表单中进行验证?是input的职责吗?这意味着该组件将了解应用的业务逻辑。它将如何通知form有错误?这个错误将如何被刷新?form会知道吗?如果有一个错误存在,但你还是试图提交,会发生什么?

当你面对这样的问题时,你就应该意识到责任被混淆了。在这种情况下,最好让input保持无状态,并从表单接收错误消息。

应用结构

按Route/Module分组

按容器和组件分组会使应用程序难以查找。要了解什么组件属于哪,你需要对项目有非常高的熟悉度。

并不是所有的组件都是平等的——有些是全球通用的,有些是为应用程序的特定部分设计的。这种结构(容器和组件分组)只适用于很少的项目。组件稍微多点就会变得难以管理。

// 👎 Don't group by technical details
├── containers
|   ├── Dashboard.jsx
|   ├── Details.jsx
├── components
|   ├── Table.jsx
|   ├── Form.jsx
|   ├── Button.jsx
|   ├── Input.jsx
|   ├── Sidebar.jsx
|   ├── ItemCard.jsx

// 👍 Group by module/domain
├── modules
|   ├── common
|   |   ├── components
|   |   |   ├── Button.jsx
|   |   |   ├── Input.jsx
|   ├── dashboard
|   |   ├── components
|   |   |   ├── Table.jsx
|   |   |   ├── Sidebar.jsx
|   ├── details
|   |   ├── components
|   |   |   ├── Form.jsx
|   |   |   ├── ItemCard.jsx

从一开始就用 route/module 的方式进行分组。这种结构支持变化和增长。关键是不要让应用的发展很快的让其架构失效了。如果它是基于组件和容器的分组,就会是这样。

基于模块的结构就易于扩展。你只需在上面添加模块,而不会增加复杂性。

容器/组件结构没有错,但太通用了。它不能告诉读者关于这个项目的任何信息,除了他用了react。

创建通用模块

像Button、输入框和选项卡这样的组件到处都在使用。即使你不打算使用基于模块的结构,也应该提取这些通用内容。

即使你没有用到Storybook,你也可以看到你有哪些组件。它有助于避免重复。你可不希望团队中的每个人都制作自己版本的Button。不幸的是,因为项目结构的糟糕,这种情况经常发生。

使用绝对路径

让事情可以更容易的修改是项目结构的根本目的。绝对路径意味着,如果需要移动组件,则必须进行较少的更改。此外,它还可以更容易地找出一切都是从哪里获得的。

// 👎 Don't use relative paths
import Input from '../../../modules/common/components/Input'

// 👍 Absolute ones don't change
import Input from '@modules/common/components/Input'

我使用@前缀来表示它是一个内部模块,但我也看过用~的。

包装外部组件

尽量不要直接导入太多第三方组件。通过创建适配器,这样我们可以在必要时修改API。而且,我们可以在一个地方更改第三方库。

这也适用于组件库,比如 Semantic UI和工具(utility)组件。最简单的就是从公共模块中重新导出它们,这样它们就可以从同一个地方被拉出来。

组件不需要知道我们使用了什么库作为日期选择器。

// 👎 Don't import directly
import { Button } from 'semantic-ui-react'
import DatePicker from 'react-datepicker'

// 👍 Export the component and use it referencing your internal module
import { Button, DatePicker } from '@modules/common/components'

把组件放到文件夹下

我为每个模块都创建了一个components目录。当我需要创建组件,我会先在那里创建。如果它需要样式或测试之类的额外文件,我会创建它自己的文件夹并将它们放在那里。

一个通用的实践:用一个index.js文件导出React组件比较好。这样离就不需要像import Form from 'components/UserForm/UserForm'这样需要重复的导入路径。尽管如此,还是要保留组件文件的名称,这样当打开多个组件文件时就不会混淆了。

// 👎 Don't keep all component files together
├── components
    ├── Header.jsx
    ├── Header.scss
    ├── Header.test.jsx
    ├── Footer.jsx
    ├── Footer.scss
    ├── Footer.test.jsx

// 👍 Move them in their own folder
├── components
    ├── Header
        ├── index.js
        ├── Header.jsx
        ├── Header.scss
        ├── Header.test.jsx
    ├── Footer
        ├── index.js
        ├── Footer.jsx
        ├── Footer.scss
        ├── Footer.test.jsx

性能

不要过早进行优化

在进行任何类型的优化之前,请确保它们是有原因的。盲目遵循最佳实践是浪费精力,除非它真的在某种程度上影响了应用。

是的,有优化意识是很重要的,但是在实现性能之前,优先构建可读和可维护的组件。写得好的代码更容易改进。

当发现性能问题时,先测定并确定问题的原因。如果bundle包很大,那么尝试减少rerender次数是没有意义的。

一旦你知道性能问题来自何处,请按影响的大小修复它们。

注意bundle大小

首屏运行所必需JavaScript代码数量是影响用程序性能的最重要因素。你的应用程序可能非常快,但如果需要加载4MB的JS来运行,那就可能没有人会发现这一点。

不要输出一整个bundle。在路由级别甚至更进一步地拆分应用。确保发送尽可能少的JS。

在后台加载或当用户需要的时候加载。如果按下按钮触发PDF下载,您等到这个按钮被hovered了,再开始PDF库的下载。

重新渲染-Callbacks, 数组和对象

尝试减少应用的中不必要的rerender是很好的。但也要注意,对你的应用程序产生最大的影响很少会是rerender次数。

最常见的建议是避免将callback作为props传递。这意味着每次都会创建一个新函数,从而触发一个rerender。我从来没有遇到过因为回调导致性能问题,事实上,我就是一直将callback作为props传递。

如果你真的遇到了闭包导致的性能问题,那就删掉他们。但是不要让你的代码变得不那么可读或者不必要的冗长。

直接传递数组或对象属于同一类问题。它们不能通过引用相等检查,因此将触发rerender。如果需要传递数组,请在组件定义之前将其提取为常量,以确保每次都传递相同的实例。

测试

不要依赖于快照测试

从我在2016年开始使用React以来,我只遇到过一种快照测试能帮我发现的问题。没有参数的new Date()调用,它总是默认为当前日期。

除此之外,快照测试只会在组件更改时发生失败。通常的工作流程便是更改组件,查看快照是否失败,更新快照并继续。

别误会,他们是一个很好的健全检查,但他们不是一个好的组件级测试的替代品。我甚至不再创造它们快照测试用例。

测试渲染正确性

测试的主要功能内容应该是检查组件是否按预期工作。确保它使用默认的props和传递的props都能正确渲染。

验证对于给定的输入(props),函数是否返回正确的结果(JSX)。检测你所需要的一切都在屏幕上。

验证状态和事件

一个有状态的组件很可能对事件做出响应而更改。模拟事件并且确保组件做出了正确的响应。

验证事件处理函数被调用了而且传入的参数是正确的。验证内部状态也被正确的设置了。

边界条件测试

当测试用例已涵盖基本情况,确保你添加了一些处理边界条件的用例。

这意味着传递一个空数组,以确保没有在未检查的情况下访问索引。在API调用中抛出一个错误,以确保组件能够处理它。

编写集成测试

集成测试的意思是验证这个界面或者更大的组件。测试它们作为一部分的提取是否工作得很好。这最能给与我们对应用程序能按预期工作的信心。

因为组件本身可以很好地工作,它们的单元测试也可以通过。不过,它们之间的整合可能会有问题。

样式

使用CSS-in-JS

这是一个很有争议的观点,很多人会不同意。我更愿意使用像Styled Components或Emotion样的库,因为我能在JavaScript中表达关于组件的一切。又少了一个要维护的文件。也不需要考虑CSS的约定。

React中的逻辑单元是组件,因此从关注点分离的角度考虑,组件应该拥有与之相关的所有内容。

注意:在样式方面选择SCSS、CSS模块、库(比如Tailwind)并没有错。不过CSS-in-JS是我推荐的方法。

把styled组件放到一块

在同一个文件中有多个CSS-in-JS 组件是很正常的。理想情况下,我们希望将它们保存在与使用它们的普通组件相同的文件中。

但是,如果它们变得太长(样式一般都会这样),则将它们提取到单独文件中,放在其使用者的旁边。我在如Spectrum的开源项目中见过这种模式。

数据获取

使用数据获取库

React没有明确的一种从API获取或更新数据的方法。每个团队都会创建自己的实现,通常包括一个服务调用一些异步函数,这些函数与API通信。

这条路意味着我们要自己管理加载状态和http错误。这会导致冗长和样板化的代码。

相反的我们可以使用类似React Query 或 SWR这类的库。他们使用hooks和服务器进行通信,并且与组件生命周期自然的结合。

它们内置了缓存功能,并能管理加载和错误状态。我们只需要操作这些库。而且,它们消除了使用状态管理库来处理这些数据的需要。



- EOF -

推荐阅读  点击标题可跳转

1、如何创建一个自动改变的 favicon

2、使用顶层 await 简化 JS 代码

3、10 个 React 安全最佳实践


觉得本文对你有帮助?请分享给更多人

关注「大前端技术之路」加星标,提升前端技能


点赞和在看就是最大的支持❤️

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存