跳到正文

用组件的方式思考

构建现代界面,关注的不再只是页面,而是系统。一旦开始用组件的方式思考,一切都会不同。

我还记得第一次理解这一点的时刻。当时,我正盯着一个复杂的仪表盘,试图弄清楚为什么一次小修改会破坏三个不同的区域。代码纠缠在一起:样式四处影响,逻辑重复,修改任何东西都得在几十个文件里翻找。

后来,我用组件重新构建了它。混乱一下子有了结构。

组件究竟是什么

组件是一块相对独立的界面,管理自己的逻辑、样式和状态。它就像一块乐高积木:单独看很简单,与其他积木组合起来,却能形成强大的整体。

function Button({ children, onClick, variant = 'primary' }) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {children}
    </button>
  );
}

这个按钮不关心自己出现在哪里。它可以放在弹窗、表单或者导航栏中。这就是封装的力量。

思维方式的转变

组件思维需要一套不同的思考方式:

  • 拆解界面。 每个界面都是由更小部分组成的一棵树。
  • 识别模式。 如果同样的东西正在构建第二次,它很可能应该成为一个组件。
  • 划清边界。 每个组件都应该只承担一种职责。
  • 为复用而设计。 让组件能够在不同场景中工作。

组合优于配置

好的组件可以相互组合。与其把每个选项都塞进属性,不如让组件协同工作:

// 与其创建一个拥有 20 个属性的巨大 Card 组件……
<Card
  title="Hello"
  subtitle="World"
  image="/photo.jpg"
  footer="Read more"
  variant="outlined"
/>

// ……不如组合更小、职责明确的组件
<Card>
  <CardImage src="/photo.jpg" />
  <CardContent>
    <CardTitle>Hello</CardTitle>
    <CardSubtitle>World</CardSubtitle>
  </CardContent>
  <CardFooter>Read more</CardFooter>
</Card>

第二种方式更灵活。需要一张没有图片的卡片?不放入 CardImage 就好。想自定义底部区域?把 CardFooter 换成你需要的内容即可。

状态应该放在需要它的地方

组件思维中最难的一部分,是决定状态应该归谁管理。这里有一个简单的规则:

  • 局部状态: 只有当前组件需要,例如下拉菜单是否展开。
  • 提升的状态: 多个同级组件需要,把它提升到共同的父组件。
  • 全局状态: 许多没有直接关系的组件都需要,使用上下文或状态仓库。
// 局部状态:只有下拉菜单关心它
function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  // ...
}

// 提升的状态:表单需要知道所有输入框的值
function Form() {
  const [values, setValues] = useState({});
  return (
    <>
      <Input value={values.name} onChange={...} />
      <Input value={values.email} onChange={...} />
    </>
  );
}

组件本身也是文档

设计良好的组件能够说明自身。阅读组件树时,你应该能直接理解界面结构:

<Page>
  <Header>
    <Logo />
    <Navigation />
    <UserMenu />
  </Header>
  <Main>
    <Sidebar />
    <Content />
  </Main>
  <Footer />
</Page>

无需注释,结构本身已经说明了一切。

调试的优势

在基于组件的系统中,出了问题,你知道应该去哪里查找。问题通常在以下位置:

  • 组件内部:检查它自己的逻辑
  • 传入的属性:检查父组件
  • 状态管理:检查状态存放的位置

再想想调试一个两千行、所有东西都彼此牵连的文件是什么感受。组件带来隔离,隔离让问题更清晰。

从小处开始

你不需要一夜之间重构整个代码库。先从一个组件开始,把它提取出来,定义清晰的属性,看看使用起来如何。

然后再做一个,再做一个。

很快,你看到的就不再只是页面,而是一套由可复用、可组合的部分构成的系统。构建界面的乐趣,也会在这时显现出来。

模板示例

继续阅读