- 原文地址:How do you separate components?
- 原文做者:James K Nelson
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:undead25
- 校对者:薛定谔的猫、Germxu
React 组件会随着时间的推移而逐步增加。幸亏我意识到了这一点,否则个人一些应用程序的组件将变得很是可怕。html
但这其实是一个问题吗?虽然建立许多只使用一次的小组件彷佛有点奇怪……前端
在一个大型的 React 应用程序中,拥有大量的组件自己没有什么错。实际上,对于状态组件,咱们固然是但愿它们越小越好。node
关于状态它一般不会很好地分解。若是有多个动做做用于同一状态,那么它们都须要放在同一个组件中。状态能够被改变的方式越多,组件就越大。另外,若是一个组件有影响多个状态类型的动做,那么它将变得很是庞大,这是不可避免的。react
但即便大型组件不可避免,它们使用起来仍然是很是糟糕的。这就是为何你会尽量地拆分出更小的组件,遵循关注点分离的原则。android
固然,提及来容易作起来难。ios
寻找关注点分离的方法是一门技术,更是一门艺术。但你能够遵循如下几种常见模式……git
根据个人经验,有四种类型的组件能够从较大的组件中拆分出来。github
有关视图组件(有些人称为展现组件)的更多信息,请参阅 Dan Abramov 的名著 —— 展现组件和容器组件。redux
视图组件是最简单的组件类型。它们所作的就是显示信息,并经过回调发送用户输入。它们:后端
你能够从较大的组件中拆分出展现组件的一些迹象:
能够从较大的组件中拆分出展现组件的一些示例:
onChange
回调中)。控制组件指的是存储与部分输入相关的状态的组件,即跟踪用户已发起动做的状态,而这些状态还未经过 onChange
回调产生有效值。它们与展现组件类似,可是:
你能够从较大的组件中拆分出控制组件的一些迹象:
控制组件的一些示例:
你常常会发现你的不少控件具备相同的行为,但有不一样的展示形式。在这种状况下,经过将展示形式拆分红视图组件,并做为 theme
或 view
属性传入是有意义的。
你能够在 react-dnd 库中查看链接器函数的实际示例。
当从控件中拆分出展现组件时,你可能会发现经过 props
将单独的 ref
函数和回调传递给展现组件感受有点不对。在这种状况下,它可能有助于传递链接器函数,这个函数将 refs 和回调克隆到传入的元素中。例如:
class MyControl extends React.Component {
// 链接器函数使用 React.cloneElement 将事件处理程序
// 和 refs 添加到由展现组件建立的元素中。
connectControl = (element) => {
return React.cloneElement(element, {
ref: this.receiveRef,
onClick: this.handleClick,
})
}
render() {
// 你能够经过属性将展现组件传递给控件,
// 从而容许控件以任意标记和样式来做为主题。
return React.createElement(this.props.view, {
connectControl: this.connectControl,
})
}
handleClick = (e) => { /* ... */ }
receiveRef = (node) => { /* ... */ }
// ...
}
// 展现组件能够在 `connectControl` 中包裹一个元素,
// 以添加适当的回调和 `ref` 函数。
function ControlView({ connectControl }) {
return connectControl(
<div className='some-class'> control content goes here </div>
)
}复制代码
你会发现控制组件一般会很是大。它们必须处理和状态密不可分的 DOM,这就使得控制组件的拆分特别有用;经过将 DOM 交互限制为控制组件,你能够将任何与 DOM 相关的杂项放在一个地方。
一旦你将展现和控制代码拆分到独立的组件中后,大部分剩余的代码将是业务逻辑。若是有一件事我想你在阅读本文以后记住,那就是业务逻辑不须要放在 React 组件中。将业务逻辑用普通 JavaScript 函数和类来实现一般是有意义的。因为没有一个更好的名字,我将它称之为控制器。
因此只有三种类型的 React 组件。但仍然有四种类型的组件,由于不是每一个组件都是一个 React 组件。
并非每辆车都是丰田(但至少在东京大部分都是)。
控制器一般遵循相似的模式。它们:
你能够从你的组件中拆分出控制器的一些迹象:
一些控制器的示例:
一些控制器是全局的;它们彻底独立于你的 React 应用程序。Redux 的 stores 就是一个是全局控制器很好的例子。但并非全部的控制器都须要是全局的,也并非全部的状态都须要放在单独的控制器或者 store 中。
经过将表单和列表的控制器代码拆分为单独的类,你能够根据须要在容器组件中实例化这些类。
容器组件是将控制器链接到展现组件和控制组件的粘合剂。它们比其余类型的组件更具备灵活性。但仍然倾向于遵循一些模式,它们:
connect
这样的高阶函数生成。 虽然有时候你能够从其余容器中拆分出容器组件,但这不多见。相反,最好将精力集中在拆分控制器、展现组件和控制组件上,并将剩下的全部都变成你的容器组件。
一些容器组件的示例:
App
组件connect
返回的组件。observer
返回的组件。<Link>
组件(由于它使用上下文并影响环境)。你怎么称呼一个不是视图、控制、控制器或容器的组件?你只是把它叫作组件!很简单,不是吗?
一旦你拆分出一个组件,问题就变成了我把它放在哪里?老实说,答案很大程度上取决于我的喜爱,但有一条规则我认为很重要:
若是拆分出的组件只在一个父级中使用,那么它将与父级在同一个文件中。
这是为了尽量容易地拆分组件。建立文件比较麻烦,而且会打断你的思路。若是你试着将每一个组件放在不一样的文件中,你很快就会问本身“我真的须要一个新组件吗?”所以,请将相关的组件放在同一个文件中。
固然,一旦你找到了重用该组件的地方,你可能但愿将它移动到单独的文件中。这就使得把它放到哪一个文件中去成为一个甜蜜的烦恼了。
将一个庞大的组件拆分红多个控制器、展现组件和控制组件,增长了须要运行的代码总量。这可能会减慢一点点,但不会减慢不少。
我遇到过惟一一次因为使用太多组件而引发性能问题 —— 我在每一帧上渲染 5000 个网格单元格,每一个单元格都有多个嵌套组件。
关于 React 性能的是,即便你的应用程序有明显的延迟,问题确定不是出于组件太多。
因此你想使用多少组件均可以。
我在本文中提到了不少规则,因此你可能会惊讶地听到我其实并不喜欢严格的规则。它们一般是错的,至少在某些状况下是这样。因此必需要明确的是:
『能够』拆分并不意味着『必须』拆分。
假设你的目标是让你的代码更易于理解和维护,这仍然留下了一个问题:怎样才是易于理解?怎样才是易于维护?而答案每每取决于谁在问,这就是为何重构是技术,更是艺术。
有一个具体的例子,考虑下这个组件的设计:
<!DOCTYPE html>
<html>
<head>
<title>I'm in a React app!</title>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/react@15.6.1/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.6.1/dist/react-dom.js"></script>
<script> // 这里写 JavaScript </script>
</body>
</html>复制代码
class List extends React.Component {
renderItem(item, i) {
return (
<li key={item.id}> {item.name} </li>
)
}
render() {
return (
<ul> {this.props.items.map(this.renderItem)} </ul>
)
}
}
ReactDOM.render(
<List items={[ { id: 'a', name: 'Item 1' }, { id: 'b', name: 'Item 2' } ]} />, document.getElementById('app') )复制代码
尽管将 renderItem
拆分红一个单独的组件是彻底可能的,但这样作实际上会有什么好处呢?可能没有。实际上,在具备多个不一样组件的文件中,使用 renderItem
方法可能会更容易理解。
请记住:四种类型的组件是当你以为它们有意义的时候,你可使用的一种模式。它们并非硬性规定。若是你不肯定某些内容是否须要拆分,那就不要拆分,由于即便某些组件比其余组件更臃肿,世界末日也不会到来。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。