前端组件化开发实践总结

前端组件化开发实践总结自从 2010 年第一份工作接触了前后端半分离的开发方式之后 在后面的这些年里 对前端的组件化开发有了更全面一点的认识 组件化在我们的前端开发中 对提高开发效率 代码的可维护性和可复用性有很大帮助 甚至对跟设计师沟通的效率和企业的品牌形象都有着深刻的影响 这篇文章就把我在开发中总结的一些组件化开发经验分享一下 示例中的所有代码都是伪代码 你可以按照实际情况应用到 React 或 Vue 的项目中 前端组件化发展历史在讨论组件化开发之前 我们先看看前端组件化开发的发展历史 网页开发刚起步时 并没有 前端

自从 2010 年第一份工作接触了前后端半分离的开发方式之后,在后面的这些年里,对前端的组件化开发有了更全面一点的认识,组件化在我们的前端开发中,对提高开发效率、代码的可维护性和可复用性有很大帮助,甚至对跟设计师沟通的效率和企业的品牌形象都有着深刻的影响。这篇文章就把我在开发中总结的一些组件化开发经验分享一下。示例中的所有代码都是伪代码,你可以按照实际情况应用到 React 或 Vue 的项目中。

前端组件化发展历史

在讨论组件化开发之前,我们先看看前端组件化开发的发展历史。网页开发刚起步时,并没有『前端』这个概念,更不用提组件化了。当时,网页只是作为可以在浏览器中浏览的富文本文件,开发网页就是使用一些标签让浏览器解析并显示。受制于 HTML 只是描述式的语言,网页中的代码没有办法复用,即使是类似的页面,都需要复制粘贴大量的重复代码:

 
    <nav>  
     
     nav> <main>  
      
      main> <footer>  
       
       footer>  
       <nav>  
        
        nav> <main>  
         
         main> <footer>  
          
          footer> 

后来随着模板引擎的出现,可以把网页的代码分割成片段(Fragments)或模板(Templates),例如导航,内容,页脚等等,之后在需要的地方,使用引入(Include)语法,把这些片段引入进来,从而避免重复代码,这样形成了组件化的雏形,常见的动态脚本语言都有自己的模板引擎,例如 PHP、ASP 和 JSP:

 
    <nav>  
     
     nav>  
     <jsp:include page="nav.jsp" /> 
 
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core" > <h:commandButton id="submit" value="Submit"> <f:ajax event="click" />  
     h:commandButton> <h:outputText id="result" value="#{userNumberBean.response}" />  
      html> 
// UserNumberBean.java @Named @RequestScoped public class UserNumberBean implements Serializable { 
    /* 其它代码省略 */ public String getResponse() { 
    if ((userNumber != null) && (userNumber.compareTo(dukesNumberBean.getRandomInt()) == 0)) { 
    return "Yay! You got it!"; } if (userNumber == null) { 
    return null; } else { 
    return "Sorry, " + userNumber + " is incorrect."; } } } 

在这里插入图片描述

React 和 Vue 目前的流行程度比较高一些,它们都是纯客户端的、组件化的 JS 库,不依赖任何后端编程语言,让程序员都能够快速上手。不过,随着项目规模的扩大,如何利用好这些库进行组件化开发,成了前端工程师必须要掌握的课题。

什么是组件

常见的基础组件有按钮、导航、提示框、表单输入控件、对话框、表格、列表等,我们在它们的基础上又能组合出更复杂的组件,那么对于前端中的组件的定义就非常广泛了,小到一个按钮,大到一个页面都可以形成一个组件,例如两个相似的页面,可以复用一个页面组件,只需要通过修改组件的属性,来形成一个新的页面。

在这里插入图片描述

为什么要组件化开发

你可能也知道,React 和 Vue 其实也可以完全按照传统的网页开发方式进行开发,最多是把网页大体分成几个部分,放到单独的几个文件里就好了,对于简单的网站来说没什么问题,但是如果开发的是大型的应用,例如网页版的 音乐、微信、邮箱等,它们有大量的、细小的组件,并且重复出现,这时如果针对每个页面编写 HTML 和样式,那么就会造成太多冗余代码了。

 
    <div class="card"> <div class="card__title"> 
     div> <div class="card_content"> 
      div>  
       div>  
       <div class="card"> <div class="card__title"> 
        div> <div class="card_content"> 
         div>  
          div>  
          <div class="card"> <div class="card__title"> 
           div> <div class="card_content"> 
            div>  
             div> 

如果使用组件化的方式,我们可以把重复出现的页面内容定义成组件,这样就不用复制粘贴同样的代码了,减少代码的体积:

 
     
    <div class="card"> <div class="card__title"> 
     div> <div class="card_content"> 
      div>  
       div>  
       <Card />  
       <Card />  
       <Card /> 

当某个页面内容是复合组件时,那么可以直接把基础组件直接拿过来应用:

<Card> <Title></Title> <Button></Button> </Card> 

利用这种方式,甚至可以达到 1 分钟产生一个新页面的恐怖速度,这对于你来说,节省时间去摸鱼,岂不美哉?

<Page title="页面1" data="{...}" /> <Page title="页面2" data="{...}" /> 

对于大型的公司来说,公司的网站、APP、桌面应用、Web 端应用等的设计风格都是一样的,同样的组件会在不同平台中使用,这个时候团队之间可以共享一套组件库,复用到各端平台上,减少重复开发的成本。React 和 Vue 都支持创建跨平台的应用,这时组件化开发就显得更重要了:

在这里插入图片描述

接下来分享一下我在组件化开发中总结的经验:

一、组件的规划

在正式进行前端应用开发之前,需要有充分的时间来分析,看看页面上的哪些内容需要做成组件。这一步只需要大致规划一下:

  • 如果公司设计师提供了详细的设计规范,那么直接按照规范中的组件来开发就可以了。

在这里插入图片描述

  • 如果只有设计稿,那么就看看哪些内容有 2 次或更多次在其它页面中用到了,这些大概率需要设计为组件。
  • 如果连设计稿都没有,那么可以用市面上现成的组件库,例如 ant design。
  • 如果没有设计稿,还要自己从零开发,那么可以根据通用的套路,把基础组件,例如按钮、菜单等,规划出来,并把可能会多次用到的页面内容,也规划出来,例如导航,底部信息、联系方式区域,这样可以只改动需要变化的部分,不影响其它部分。

这一步不用太过详细,浪费太多时间,后续开发的时候如果遇到不确定是否要写成组件的部分,可以直接把这部分代码写到大的组件里,如果其它组件又用到了,再把它抽离成组件。

二、组件代码管理

组件的代码应该遵循就近原则,也就是说:

  • 和组件有关的 HTML、CSS 、JS 代码和图片等静态资源应该放在同一个目录下,方便引用。
  • 组件里的代码应该只包括跟本组件相关的 HTML 模板、CSS 样式和 JS 数据逻辑。
  • 所有的组件应放到一个统一的『组件文件夹中』。

如果组件之间有可以复用的 HTML 和 CSS,那么这个复用的部分可以直接定义成一个新的组件。

如果组件之间有可以复用的 JS 数据逻辑,那么可以把公用的数据逻辑抽离出来,放到公共的业务逻辑目录下,使用到该逻辑的组件,统一从这个目录中导入。

如果项目中使用到了全局状态管理,那么状态管理的数据应放在独立的目录里,这个目录还会存放分割好的状态片段 reducer,之后统一在 store 中合并。

project |-- components # 所有组件 |-- Card # Card 组件 |-- index.js # Card 组件 js 代码 |-- Card.html       # Card 组件 html 模板 |-- Card.css # Card 组件 css 样式 |-- icon.svg # Card 组件用到的 icon |-- logics # 公共业务逻辑 |-- getUserInfo.js # 例如获取用户信息 |-- data # 全局状态 |-- store.js # 全局状态管理 store |-- user.reducer.js # user reducer |-- blogPost.reducer.js # blogPost reducer |-- apis # 远程请求 API 业务逻辑 |-- user.js # user API |-- blogPost.js # blogPost API 

三、组件样式管理

在编写组件样式的时候,应只设置组件 CSS 盒子内部的样式,影响外部布局的样式要尽可能的避免,而应该由使用该组件的父组件去设置。例如,如果有一个组件设置了外边距,但是这个组件经常会用于 grid 或 flex 布局中,那么这个额外的边距会对布局造成影响,只能通过重置外边距的方式取消边距,这就不如组件不设置外边距,由父组件的布局决定它的位置,或者外边距。

在这里插入图片描述

类似的还有定位相关的样式,绝对定位、固定定位等对文档流有影响,应交由父组件决定,除非这个组件只有绝对定位这一种情况,例如对话框。
组件中的 CSS 要局部化,以避免影响全局样式。传统的 CSS 样式是全局的,如果有两个不同的组件,使用了相同的 class 名字,那么后定义的样式会覆盖之前定义的。一般前端库中都有定义局部样式的功能,例如通过 CSS Modules。

 
    <style scoped> 
     style>  
     Button.module.css 

修改子组件的样式时,优先使用选择器特异性(CSS Specificity)策略选定特定元素,而行内样式(inline-style)在设置简单样式的时候使用,尽一切可能避免 !important,因为如果再有上一层组件(爷爷组件)需要修改这个组件的样式时,就会很困难了。

四、组件属性管理

组件属性的命名要与它实际展示的内容相符。例如一个博客文章组件,title 属性表示标题,content 代表内容,showFullArticle 表示是否显示全文。

 
    <BlogPost title="" content="" full="" />  
    <BlogPost title="" content="" showFullArticle="" /> 

组件的属性应有必要的类型检查,避免在使用属性时出现异常,例如在需要数组的地方传递了布尔类型,那么如果代码有遍历数组的逻辑,就会出错。

props: { 
    title: String, content: String, showFullArticle: Boolean } 

代表事件的属性,应该和现有的 HTML 事件名称规范保持一致,以 on 开头,后面用英文表示会触发的事件,例如 onEdit 表示会触发编辑事件。

<BlogPost onEdit="" /> 

组件的属性不能直接和状态进行捆绑,而是应该只作为状态的初始值。如果把属性值作为状态值,那么就破坏了单一数据流向机制,此时该组件可以通过自身修改状态,也能通过父组件的属性变化修改状态,很容易造成数据的不一致。推荐的做法是,设置一个初始值属性,可以通过父组件传递进来,当作状态的初始值,然后丢弃,后续只通过该组件修改状态值。

const { 
    initialTitle } = props; const title = state(initialTitle); // 修改 updateTitle() { 
    title = "..."; } 

五、组件状态管理

// 父组件 const someState = state(""); <ChildComponent prop1="someState"></ChildComponent>; 

当子组件需要给父组件传递状态的值时,要通过事件的方式传递给父组件,尽最大可能避免使用 ref

// 父组件 function getStateVal(val) { 
    console.log(val); } <ChildComponent onChange="getStateVal" /> // 子组件 <input onChange="e => getStateVal(e.target.value)" /> 

状态的变化还应只通过事件或生命周期来进行,不能在其它同步执行的代码中进行,这样不会引起组件的刷新。

const someState = state(); // 错误 const state = "newState"; // 正确 handleButtonClick() { 
    someState = "newState"; } 

全局状态是多个组件共享的,如果有多个组件共享某个状态,那么应该把状态定义在这些组件统一的、最接近的父组件中,或者使用全局状态管理库。

在这里插入图片描述

全局状态的修改,应该由类似于 actions 的行为触发,然后使用 reducer 修改状态,这样能追溯状态的变化路径,方便调试和打印日志。

// 组件 function updateState() { 
    emitAction("changeState"); } // reducer function someState(state, action) { 
    switch(action) { 
    "changeState": someState => { 
    log(someState); return newState } } } 

全局状态推荐按领域(Domain)来拆分 reducer,而不是按页面。例如按 user 用户、文章 posts、评论 comments 来拆分,而不是以 HomePage 主页、BlogPostListPage 博客列表页,BlogPostPage 博客详情页这样来拆分。这样做的好处是,能够最大限度的复用 reducer 中的逻辑。

// userReducer { 
    "updateUser": ... "deleteUser": ... /* ... */ } 

六、组件的组合

// Layout 组件 <div>  
     
     
     
     div> // 首页 function handleNavChange() {} <Layout> <nav onChange="handleNavChange"> 
      nav> <main> 
       main> <footer> 
        footer>  
         Layout> 

不使用 slot:

// Layout props: { onNavChange: function } <Layout>  
    <nav onChange="onNavChange"> 
     nav> <main> 
      main> <footer> 
       footer>  
        Layout> // 首页 function handleNavChange() {} <Layout onNavChange="handleNavChange" /> 

如果有循环展示列表的地方,需要对循环中最外层的组件设置 key,这样在列表发生变化时,能帮助前端库判断是否可以通过排序的方式,重复利用现有的组件,来更新视图,而不是销毁重建。

let todos = [{id: 1, content: "todo1"}, {id:2, content: "todo2"}, {id:3, content: "todo3"}]; <List> for todo in todos: <item content="todo.content" key="todo.id" />  
     List> // todos 顺序变化,列表也只是根据 id 调整顺序,不会销毁重建 todos = [{id: 3, content: "todo3"}, {id:2, content: "todo2"}, {id:1, content: "todo1"}]; 

如果有按条件展示组件的地方,且切换频率高,或有动画需求,要使用设置 css display 的方式,例如 vue 中的 v-show,如果切换频率低,可以使用加载、销毁的方式,例如 vue 中的 v-if,React 中使用 && 逻辑判断。

七、组件的复用

在复用组件的时候,可以通过改变组件的属性来修改一些简单的组件内容。

<Card title="" content=" 
    " /> 

如果组件结构类似,但是有些内部的组件不一样,可以考虑通过『插槽』来复用。

 
    <div> <h1> 
     h1>  
      
      div>  
      <Comp> <p>替换 slot 
       p>  
        Comp> 

如果有业务逻辑需要复用,尤其是涉及到状态变化的,那么可以把它抽离为公共的业务逻辑,利用 Hooks(React)或 Composables (Vue)来复用。

// 公共逻辑 (/logic/useSomeLogic.js) function useSomeLogic() { 
    const someState = state(); const updateState = (v) => (someState = v); return { 
    someState, updateState, }; } // 组件1复用 import userSomeLoginc from "/logic/useSomeLogic.js"; const { 
    someState, updateState } = useSomeLogic(); // 组件2复用 import userSomeLoginc from "/logic/useSomeLogic.js"; const { 
    someState, updateState } = useSomeLogic(); 

如果有视图样式需要复用,那么可以直接把这部分再抽离成一个新的组件。

小结

这篇文章从组件的代码、样式、属性、状态、组合和复用的这几个场景上,总结了一些我在前端开发中的一些经验和个人看法,可能并不适用所有项目,如果你有更好的最佳实践和经验,欢迎分享!如果觉得本文有帮助,请分享给其它人,感谢!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/226468.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午10:50
下一篇 2026年3月16日 下午10:50


相关推荐

  • ECharts 饼图颜色设置教程 – 4 种方式设置饼图颜色

    ECharts 饼图颜色设置教程 – 4 种方式设置饼图颜色ECharts 饼状图中的每个扇形颜色其实都可以自定义或者随机显示颜色 比如 X 轴是各销售渠道名 那么你可以需要使用全局统一的识别色彩 那么就需要指定每个扇面的颜色 本文讲解 4 种配置修改 ECharts 饼图颜色的方法

    2026年3月17日
    2
  • CFileDialog的使用方法简单介绍

    CFileDialog的使用方法简单介绍

    2021年12月10日
    38
  • vector subscript out of range数组下标越界错误「建议收藏」

    vector subscript out of range数组下标越界错误「建议收藏」在使用vector二维数组时,产生vectorsubscriptoutofrange错误,检查之后并没有发现数组下标越界问题,百度了一下,发现原来是数组并没有初始化赋值,没有分配空间,所以不能采用下标的方式进行访问。解决方法有两个,一个是初始化数组的时候为其分配空间,其值全部赋值为0。vector<vector<int>>myvec(n,vector<int>(n,0));另一个就是使用vector.push_back添加元素,不使用下

    2026年4月15日
    4
  • pycharm运行pyspider

    pycharm运行pyspider系统环境 win7 10python3 7pycharm2019 2 本文不含内容 python 安装 pycharm 安装以及破 jie 设置 pythonpip 国内镜像使用 PycharmClone 访问 Pyspider 项目地址 https github com binux pyspider 拿到项目 giturl https github com binux

    2026年3月27日
    2
  • js 根据内容 生成二维码_html怎么生成二维码

    js 根据内容 生成二维码_html怎么生成二维码js生成二维码以及插入图片先根据qrcode官网demo,不同属性值的变化,二维码的变化效果:https://larsjung.de/jquery-qrcode/latest/demo/进入demo中,审查元素查看里面引用的js文件,你会发现jquery-qrcode-0.14.0.js,这个版本支持二维码中插入图片。下面是我写的一个列子:引用js:&lt;scripttype="text/ja…

    2022年10月17日
    5
  • 2k21本世代和次世代生涯一样吗_nba2k21次世代价格

    2k21本世代和次世代生涯一样吗_nba2k21次世代价格原题链接对于在中国大学MOOC(http://www.icourse163.org/ )学习“数据结构”课程的学生,想要获得一张合格证书,总评成绩必须达到 60 分及以上,并且有另加福利:总评分在 [G, 100] 区间内者,可以得到 50 元 PAT 代金券;在 [60, G) 区间内者,可以得到 20 元PAT代金券。全国考点通用,一年有效。同时任课老师还会把总评成绩前 K 名的学生列入课程“名人堂”。本题就请你编写程序,帮助老师列出名人堂的学生,并统计一共发出了面值多少元的 PAT 代金券。输入格

    2022年8月8日
    8

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号