小组件:可能出什么问题? 我们使用唯一责任原则

我们提请您注意Scott Domes的文章翻译,该文章已发布在blog.bitsrc.io上。 在kat上找出为什么组件应该尽可能小,以及唯一责任原则如何影响应用程序的质量。


奥斯汀 ·柯克( Unstinsplash)的照片

React组件系统(和类似的库)的优点是您的UI分为易于阅读和可重用的小部分。

这些组件非常紧凑(100-200行),这使其他开发人员可以轻松理解和修改它们。

尽管通常会尽量缩短组件,但没有明确,严格的长度限制。 如果您决定将您的应用程序放入一个由3000行组成的可怕的巨大组件中,React不会介意。

...但这是不值得的。 您的大多数组件很可能已经太庞大了-或说,它们执行了太多的功能。

在本文中,我将证明大多数组件(即使具有通常的200行长度)也应更严格地定位。 他们应该只执行一项功能,并执行良好。 这就是Eddie Osmani 在这里所说的很棒。

提示 :在JS中工作时,请使用Bit来组织,组装和重用作为lego部件的组件。 Bit是用于此业务的极其有效的工具,它将帮助您和您的团队节省时间并加快组装速度。 试一试。

让我们演示一下在创建组件时如何出错

我们的应用


想象一下,我们有一个针对博客的标准应用程序。 这是主屏幕上的内容:

class Main extends React.Component { render() { return ( <div> <header> // Header JSX </header> <aside id="header"> // Sidebar JSX </aside> <div id="post-container"> {this.state.posts.map(post => { return ( <div className="post"> // Post JSX </div> ); })} </div> </div> ); } } 

(此示例与许多后续示例一样,应视为伪代码。)

它显示顶部面板,侧边栏和帖子列表。 一切都很简单。

由于我们还需要下载帖子,因此我们可以在安装组件时执行以下操作:

 class Main extends React.Component { state = { posts: [] }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } render() { // Render code } } 

我们也有一些逻辑来调用侧边栏。 如果用户单击上部面板中的按钮,则该侧将退出。 您可以从顶部和侧面板本身将其关闭。

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } handleOpenSidebar() { // Open sidebar by changing state } handleCloseSidebar() { // Close sidebar by changing state } render() { // Render code } } 

我们的组件变得有些复杂,但仍然易于阅读。

可以说,它的所有部分都有一个目的:显示应用程序的主页。 因此,我们遵循唯一责任原则。

唯一责任原则规定,一个组件应仅履行一项功能。 如果我们重新定义来自wikipedia.org的定义,那么事实证明每个组件仅应负责功能[应用程序]的一部分。

我们的主要组件满足此要求。 怎么了

这是原则的另一种表述: 任何[组件]应该只有一个改变的理由

这个定义来自Robert Martin的书,Rapid Software Development。 原则,示例,实践” ,这一点非常重要。

通过关注更改组件的一个原因 ,我们可以创建更好的应用程序,而且这些应用程序将易于配置。

为了清楚起见,让我们使组件复杂化。

并发症


假设在实现Main组件一个月后,我们团队为开发人员分配了一项新功能。 现在,用户将能够隐藏帖子(例如,如果帖子包含不适当的内容)。

这并不难!

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [] }; // older methods get filteredPosts() { // Return posts in state, without the postsToHide } render() { return ( <div> <header> // Header JSX </header> <aside id="header"> // Sidebar JSX </aside> <div id="post-container"> {this.filteredPosts.map(post => { return ( <div className="post"> // Post JSX </div> ); })} </div> </div> ); } } 

我们的同事很容易处理这个问题。 她只添加了一种新方法和一种新属性。 那些浏览简短变更清单的人都没有异议。

几周后,又宣布了另一个功能-改进了移动版本的侧边栏。 开发人员决定创建几个仅在移动设备上运行的JSX组件,而不是弄乱CSS。

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [], isMobileSidebarOpen: false }; // older methods handleOpenSidebar() { if (this.isMobile()) { this.openMobileSidebar(); } else { this.openSidebar(); } } openSidebar() { // Open regular sidebar } openMobileSidebar() { // Open mobile sidebar } isMobile() { // Check if mobile device } render() { // Render method } } 

另一个小变化。 几个新的命名良好的方法和一个新的属性。

这里我们有一个问题。 Main仍然仅执行一个功能(渲染主屏幕),但是您将看到我们现在正在处理的所有这些方法:

 class Main extends React.Component { state = { posts: [], isSidebarOpen: false, postsToHide: [], isMobileSidebarOpen: false }; componentDidMount() { this.loadPosts(); } loadPosts() { // Load posts and save to state } handleOpenSidebar() { // Check if mobile then open relevant sidebar } handleCloseSidebar() { // Close both sidebars } openSidebar() { // Open regular sidebar } openMobileSidebar() { // Open mobile sidebar } isMobile() { // Check if mobile device } get filteredPosts() { // Return posts in state, without the postsToHide } render() { // Render method } } 

我们的组件变得又大又笨重,很难理解。 随着功能的扩展,情况只会恶化。

怎么了?

唯一原因


让我们回到唯一责任原则的定义: 任何组件都应该只有一个改变的理由

以前,我们更改了帖子的显示方式,因此我们不得不更改主要组件。 接下来,我们更改了侧边栏的打开方式-再次更改了Main组件。

该组件具有许多无关的更改原因。 这意味着它执行了太多功能

换句话说,如果您可以显着更改组件的一部分,而不会导致其另一部分发生更改,则您的组件负有太多责任。

更有效的分离


解决方案很简单:您需要将Main组件分为几个部分。 怎么做?

让我们重新开始。 主屏幕的呈现仍然是Main组件的责任,但我们仅将其缩小以显示相关组件:

 class Main extends React.Component { render() { return ( <Layout> <PostList /> </Layout> ); } } 

太好了

如果我们突然更改了主屏幕的布局(例如,添加了其他部分),那么Main也会更改。 在其他情况下,我们没有理由碰他。 太好了

让我们继续Layout

 class Layout extends React.Component { render() { return ( <SidebarDisplay> {(isSidebarOpen, toggleSidebar) => ( <div> <Header openSidebar={toggleSidebar} /> <Sidebar isOpen={isSidebarOpen} close={toggleSidebar} /> </div> )} </SidebarDisplay> ); } } 

这有点复杂。 Layout负责渲染布局组件(侧面板/顶面板)。 但是,我们不会屈服于诱惑,而让Layout负责确定侧边栏是否打开。

我们将此功能分配给SidebarDisplay组件,该组件将必要的方法或状态传递给HeaderSidebar组件。

(上面的示例是React中“ 通过子代渲染道具”模式的示例。如果您不熟悉它,不用担心。重要的是,有一个单独的组件来控制侧边栏的打开/关闭状态。)

然后,如果侧边栏仅负责在右侧渲染侧边栏,则它本身可以非常简单。

 class Sidebar extends React.Component { isMobile() { // Check if mobile } render() { if (this.isMobile()) { return <MobileSidebar />; } else { return <DesktopSidebar />; } } } 

再次,我们抵制了将用于计算机/移动设备的JSX直接插入此组件的诱惑,因为在这种情况下,它将有两个更改原因。

让我们看一下另一个组件:

 class PostList extends React.Component { state = { postsToHide: [] } filterPosts(posts) { // Show posts, minus hidden ones } hidePost(post) { // Save hidden post to state } render() { return ( <PostLoader> { posts => this.filterPosts(posts).map(post => <Post />) } </PostLoader> ) } } 

仅当我们更改帖子列表的PostList方式时, PostList才会更改。 似乎很明显,对不对? 这正是我们所需要的。

仅当我们更改帖子的加载方式时, PostLoader才会更改。 最后,仅当我们更改帖子的呈现方式时, Post才会更改。

结论


所有这些组件都很微小,仅执行一项小功能。 这些更改的原因很容易识别,并且组件本身已经过测试和纠正。

现在,我们的应用程序更易于修改-重新排列组件,添加新功能并扩展现有功能。 您只需要查看组件文件即可确定其用途。

我们知道我们的组成部分会随着时间的推移而变化和增长,但是应用此通用规则将帮助您避免技术负担并提高团队速度。 如何分配组件取决于您,但是请记住- 更改组件必须只有一个原因

感谢您的关注,并期待您的评论!

Source: https://habr.com/ru/post/zh-CN437504/


All Articles