-
2022-04-28 16:52:38
一、概述
Trunk账号
1.认证CocoaPods API的服务
2.用来管理公共仓库中的自己的组件索引文件(.podspec文件)
1.记录一个组件的名称/版本/资源储存路径/维护者信息等
2.每个组件都必须有一个索引文件索引库(Spec Repo)
1.存放索引文件的仓库
2.储存在CocoaPods服务器上,我们下载或更新Pod的时候会把这个仓库拷贝一份到本地,本地存放路径:~/.cocoapods/repos
3.CocoaPods提供一个公共索引库,储存在本地的路径为:~/.cocoapods/repos/master
4.我们可以创建私有索引库,储存在本地的路径为:~/.cocoapods/repos/自定义索引库名 (本文也会带领大家去创建自己的索引库)组件模板 (pod lib create [组件名])
1.CocoaPods提供用于快速创建组件的模板
2.里边可以制作我们的代码,可以做单元测试等,包含一个对应的索引文件
3.组件化就是以这个模板为基础,制作自己的组件
二、具体操作
索引库
1.创建私有索引库
- 在Git 上创建一个私有的仓库 (xxSpecs)
- 打开终端cd到桌面目录下:
cd Desktop - 终端输入:
- pod repo add xxSpecs[本地索引库名称] " 所建仓库地址"
- 之后输入远端Git仓库的账号和密码
2.检查是否安装成功
- cd 到新建索引库
cd ~/.cocoapods/repos/xxSpecs - 验证索引文件仓库
pod repo lint .
至此索引库就创建完成
3.注册trunk
- 首先查看是否注册过,命令如下:
- pod trunk me
- 如果没有注册,使用如下命令进行注册:
- pod trunk register '邮箱地址' '用户名'
- 然后您的邮箱会受到一条信息,点击其中的链接进行验证,这样既完成注册了
- 接着您就可以操作如下命令进行查看:
- pod trunk me
4.下载并创建组件模板
- Github创建一个远程组件仓库 (xxKit)
- 打开终端cd到桌面目录下:
cd Desktop - 下载组件模板并设置组件名(这里组件名一定要与 远程仓库名称一致)
- pod lib create xxKit
- 组件基本设置
// 使用哪种系统的模板
What platform do you want to use?? [ iOS / macOS ]
ios
// 使用哪种语言
What language do you want to use?? [ Swift / ObjC ]
objc
// 是否创建测试Demo
Would you like to include a demo application with your library? [ Yes / No ]
yes
// 使用哪种测试框架
Which testing frameworks will you use? [ Quick / None ]
None
// 是否需要测试视图
Would you like to do view based testing? [ Yes / No ]
yes
// 测试Demo的类前缀
What is your class prefix?
XX输入完成电脑桌面将自动创建一个名为 xxKit 的Git组件仓库(仓库里面会包含一个名为:xxKit.podspec 的索引文件)
5. 在组件模版里编写自己的代码
在创建的组件模版下面,有个classes文件,删除replace文件,把自己写好的类,拷贝到这里面来:
修改.specs文件
s.name 私有库的名字
s.version 私有库的版本:管理代码库的版本,这个是和git平台代码对应的tag版本是一一对应的
s.summary 私有库概要
s.description 描述
s.license 许可证
s.author 创建库的用户
s.source 代码在 gitLab上存储的地址,也就是远程仓库
s.ios.deployment_target 这个库最低可以安装的平台
s.source_files 存储代码文件的路径
s.resource_bundles 存储图片的路径
s.frameworks 代码中需要用到的 framework
s.dependency 依赖的第三方库打开终端
-
cd 到桌面xxKit 目录下
- cd /xxKit
- cd Example
- pod install
6. 把做好的组件推送到自己的组件仓库
- cd 到桌面xxKit 目录下
cd /xxKit - 添加代码到git缓存区
git add . - 提交一个缓存区代码
git commit -m "xxKit组件初始化" - 关联Github的远程组件仓库
- git remote add origin "远程仓库地址" (注意:如果需要修改远程仓库的地址使用命令: git remote set-url origin "远程仓库地址")
- 推送版本到master分支(-f强制推送,覆盖掉之前的所有文件)
git push origin master -f (或者:git push origin master) - 添加版本标签(标签号必须与索引文件里的标签号一致)
git tag 0.1.0 - 标签推送到组件仓库
git push --tags - 验证本地索引
pod lib lint --allow-warnings(--allow-warnings 可以忽略警告) - 验证远程索引文件
pod spec lint xxKit.podspec --verbose
获取tag列表 git tag 删除tag git tag -d "tag名称" 提交删除tag git push origin: "tag名称"
三、关联远程cocopods
1. 制作好的组件关联CocoaPods服务器刚才创建的私有索引库
- cd 桌面组件xxKit目录下
cd /xxKit - 推送组件的索引文件到服务器,并告诉服务器存在哪个私有仓库中
- pod repo push "本地索引库名称" xxKit.podspec --allow-warnings
- 查看本地的CocoaPods仓库(可看到公共库和自己的私有库)
pod repo
2.检查组件
- 更新本地CocoaPods仓库(这里也一定要注意,这一步也不可缺少)
pod repo update --verbose - 搜索刚才制作的组件
pod search xxKit
四、项目使用
1.新建一个项目工程,并添加Pod
2.配置Podfile文件
-
文件中导入索引库可不指定组件路径,依照索引库中最新的tag更新组件
source 'https://github.com/zhanghua19860221/xxSpecs.git' pod 'xxxKit'(可不指定分支)
-
文件中不导入索引库 根据配置路径更新组件
pod 'xxKit', :git => 'https://github.com/zhanghua19860221/xxKit.git', :branch => 'master_0803'(可指定分支)
五、cocopods常用命令
1.查看镜像:gem sources -l
2.查看pod版本:pod --version
3.查看repo:pod repo
4.安装pod:sudo gem install cocoapods
5.卸载pod:sudo gem uninstall cocoapods
6.重置代理:git config --global --unset http.proxy
7.git config --global --unset https.proxy
8.查看ruby:ruby --version
9.编辑host:sudo vim /etc/hosts
10.查看本地安装过的cocopods相关东西:gem list --local | grep cocoapods
11.删除cocoapods-core:sudo gem uninstall cocoapods-deintegrate
12.删除cocoapods-downloader:sudo gem uninstall cocoapods-downloader
13.删除cocoapods-plugins:sudo gem uninstall cocoapods-plugins
14.删除cocoapods-search:sudo gem uninstall cocoapods-search
15.删除cocoapods-trunk:sudo gem uninstall cocoapods-trunk
16.删除cocoapods-try:sudo gem uninstall cocoapods-try
17.只想单独更新某个第三方到本地Cocoapods库中最新版本,不更新其他本地第三方:pod update 第三方名字 --verbose --no-repo-update
例如:pod update AFNetworking --verbose --no-repo-update
18.只想给项目添加新的第三方,不更新本地已经存在的第三方:pod install --verbose --no-repo-update借鉴文章:
更多相关内容 -
组件化开发
2022-03-31 15:56:10业务越来越多,代码量也越来越多,耦合严重,层次混乱,页面互相之间的跳转有着极强的关联性,所有代码都写在app module中,编译一次都要5-6分钟,为了方便以后项目的开发/测试以及提高编译性能就需要进行组件化了。...单工程遇到的问题
随着项目逐渐发展,业务越来越多,代码量也越来越多,耦合严重,层次混乱,页面互相之间的跳转有着极强的关联性,所有代码都写在app module中,编译一次都要5-6分钟,为了方便以后项目的开发/测试以及提高编译性能就需要进行组件化了。
组件化的优势
- 降低耦合度:每个业务模块无不关联,可自由拆卸、自由组装,重复利用
- 加快编译速度:每个组件可以单独编译运行,发布时也可以合并成一个app。
- 提高协作效率:团队中每个人负责自己的组件,不会影响其他组件,降低团队成员熟悉项目的成本。
组件如何划分
我们可以分为基础组件、Common组件、业务基础组件、业务组件、注册登录组件、app壳组件。
- 基础组件:最基础的,一般不会怎么变化的功能,比如网络请求、图片加载、日志、工具类、权限等。
- Common组件:服务暴露接口、自定义view、部分共用实体类、部分共用资源文件、各种Base基类(BaseActivity/BaseFragment等),依赖基础组件。
- 业务基础组件:比如分享、推送等功能,依赖基础组件。
- 业务组件:项目的各个业务模块,我们日常主要围绕着业务组件开发,依赖Common组件。
- app壳组件:原则上不包含业务代码,依赖Common组件和业务组件。
- 注册登录组件:依赖Common组件,业务组件、app壳组件按需依赖该组件。
项目结构图
组件单独调试、gradle配置
统一配置依赖
项目划分了组件需要对每个组件进行统一配置,否则版本不一致会出现各种问题。首先需要在项目根目录创建一个config.gradle文件,然后统一定义版本、依赖等,最后在各个组件中的build.gradle中使用config.gradle定义的版本、依赖。
动态配置插件
现在项目都是gradle构建的,gradle提供了两种插件用来配置不同的工程
- com.android.application:app插件
- com.android.library:library插件
app插件配置android app工程,项目构建后会打包成apk包。library插件用来配置android library工程,项目构建后会打包成aar包。我们需要动态配置这两种插件,当需要发布包时需要配置library插件,当开发单独调试的时候需要配置app插件。
我们需要gradle.properties文件中定义一个
isRelease = false
变量,当为true的时候不能单独运行,当为false的时候可以独立运行。
动态配置ApplicationId和AndroidManifest
一个app要想独立运行需要一个ApplicationId的,一个app也只有一个ApplicationId,所以在单独调试和集成调试时组件的 ApplicationId 应该是不同的。一个app需要有一个启动页,集成调试的时候AndroidManifest会合并成一个,因此需要配置两个AndroidManifest,一个有启动页一个没有启动页,根据isRelease来动态切换。
//单独调试 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jk.order"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.CustomARouter"> <activity android:name=".OrderActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> //集成调试 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jk.order"> <application> <activity android:name=".OrderActivity" /> </application> </manifest>
组件之间页面跳转
组件化的核心解耦,组件之间无不关联,相互之间不能有引用,那么怎么实现组件之间的跳转呢?比较著名的有两款框架,分别是阿里的ARouter和美团的WMRouter,他们的原理类似,都是基于apt技术动态生成代码,实现可以根据配置的路由信息进行跳转、数据共享、拦截器等。ARouter功能更加强大,使用人数也更多,因此我们选择ARouter作为路由框架,具体的使用方法参考ARouter文档。
组件之间数据共享
由于组件之间不能相互引用,那如果B组件要用到A组件中的数据该怎么办呢?那么我们可以通过Arouter中的暴露服务。在module_common组件中定义接口,然后在需要暴露数据的组件中实现该接口并定义路由,这样就可以在其他组件调用到该组件的数据了。
//common组件IBuildConfigProvider interface IBuildConfigProvider : IProvider { fun getApplicationId(): String fun getApiHost(): String fun getBaseUrl(): String fun getGhzsUrl(): String fun getVersionName(): String fun getFlavor(): String } //A组件BuildConfigImpl @Route(path = RouteConsts.provider.buildConfig, name = "BuildConfig暴露服务") class BuildConfigImpl : IBuildConfigProvider { override fun getApplicationId(): String = BuildConfig.APPLICATION_ID override fun getApiHost(): String = BuildConfig.API_HOST override fun getBaseUrl(): String = BuildConfig.BASE_URL override fun getGhzsUrl(): String = BuildConfig.GHZS_URI override fun getVersionName(): String = BuildConfig.VERSION_NAME override fun getFlavor(): String = BuildConfig.FLAVOR override fun init(context: Context?) { } } //在B组件中使用依赖查找的方式发现服务 val buildConfig = ARouter.getInstance().build(RouteConsts.provider.buildConfig).navigation() as? IBuildConfigProvider
Application生命周期分发
我们通常会在Application的onCreate中做一些初始化任务,而业务组件有时也需要获取应用的Application,也要在应用启动时进行一些初始化任务。直接在壳工程Application的onCreate操作就可以啊,但是这样做会带来问题:因为我们希望壳工程和业务组件代码隔离,并且我们希望组件内部的任务要在业务组件内部完成。那么如何做到各业务组件无侵入地获取 Application生命周期呢?答案是使用spi技术,它可用于在Android组件化开发中Application生命周期主动分发到组件。具体使用如下:
引入依赖:
//common组件 build.gradle api 'com.google.auto.service:auto-service:1.0-rc7' kapt 'com.google.auto.service:auto-service:1.0-rc7'
然后在Common组件中创建一个接口,这个接口和Application中的接口方法一致:
interface IApplication { fun attachBaseContext(base: Context) fun onCreate() fun onLowMemory() fun onTerminate() fun onTrimMemory(level: Int) fun onConfigurationChanged(newConfig: Configuration) }
接下来就在各个组件中实现IApplication接口:
@AutoService(IApplication::class) class ApplicationImpl : IApplication { override fun attachBaseContext(base: Context) { app = base as Application } override fun onCreate() { } override fun onLowMemory() { } override fun onTerminate() { } override fun onTrimMemory(level: Int) { } override fun onConfigurationChanged(newConfig: Configuration) { } companion object { lateinit var app: Application } }
最后就在app模块中使用ServiceLoader加载出每个模块实现的ApplicationImpl,Application执行每个生命周期的时候分发给每个组件:
class App : Application() { private var mApplicationList: List<IApplication> = ServiceLoader.load(IApplication::class.java, javaClass.classLoader).toList() override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) mApplicationList.forEach { it.attachBaseContext(this) } } override fun onCreate() { super.onCreate() mApplicationList.forEach { it.onCreate() } } override fun onLowMemory() { super.onLowMemory() mApplicationList.forEach { it.onLowMemory() } } override fun onTerminate() { super.onTerminate() mApplicationList.forEach { it.onTerminate() } } override fun onTrimMemory(level: Int) { super.onTrimMemory(level) mApplicationList.forEach { it.onTrimMemory(level) } } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) mApplicationList.forEach { it.onConfigurationChanged(newConfig) } } }
这样就实现了Application生命周期分发,构建app包,查看META-INF/services目录会生成一个文件,这个文件包含了我们在各个模块实现的IApplication类的全类名:
资源冲突
整个项目中尽量不要出现重复的资源,否则会出现资源冲突问题,如果一个资源很多模块都要用到,需要放到common组件中。网上都是推荐在gradle文件中配置
resourcePrefix "moudle_prefix"
,但是感觉不太好,这样会导致重复资源打包进apk,导致包体积增大。总结
本文介绍了单工程带来的问题、组件化的优势、组件的配置/调试、组件之间的数据共享、资源冲突、以及通过SPI技术实现Application生命周期分发等,本文提到的问题基本上就是组件化大部分所面对的问题。
参考
-
前端组件化开发实践总结
2021-10-13 12:46:52自从 2010 年第一份工作接触了前后端半分离的开发方式之后,在后面的这些年里,对前端的组件化开发有了更全面一点的认识,组件化在我们的前端开发中,对提高开发效率、代码的可维护性和可复用性有很大帮助,甚至对跟...自从 2010 年第一份工作接触了前后端半分离的开发方式之后,在后面的这些年里,对前端的组件化开发有了更全面一点的认识,组件化在我们的前端开发中,对提高开发效率、代码的可维护性和可复用性有很大帮助,甚至对跟设计师沟通的效率和企业的品牌形象都有着深刻的影响。这篇文章就把我在开发中总结的一些组件化开发经验分享一下。示例中的所有代码都是伪代码,你可以按照实际情况应用到 React 或 Vue 的项目中。
前端组件化发展历史
在讨论组件化开发之前,我们先看看前端组件化开发的发展历史。网页开发刚起步时,并没有『前端』这个概念,更不用提组件化了。当时,网页只是作为可以在浏览器中浏览的富文本文件,开发网页就是使用一些标签让浏览器解析并显示。受制于 HTML 只是描述式的语言,网页中的代码没有办法复用,即使是类似的页面,都需要复制粘贴大量的重复代码:
<!-- index.html --> <nav> <!-- 导航 --> </nav> <main> <!-- 内容 --> </main> <footer> <!-- 页脚 --> </footer> <!-- blogPost.html --> <nav> <!-- 相同的导航 --> </nav> <main> <!-- 不同的内容 --> </main> <footer> <!-- 相同的页脚 --> </footer>
后来随着模板引擎的出现,可以把网页的代码分割成片段(Fragments)或模板(Templates),例如导航,内容,页脚等等,之后在需要的地方,使用引入(Include)语法,把这些片段引入进来,从而避免重复代码,这样形成了组件化的雏形,常见的动态脚本语言都有自己的模板引擎,例如 PHP、ASP 和 JSP:
<!-- nav.jsp --> <nav> <!-- 导航 --> </nav> <!-- index.jsp --> <jsp:include page="nav.jsp" />
只是这些片段在数据管理方面仍然会比较麻烦,还是需要使用客户端 JavaScript 脚本,手动控制数据和 HTML 视图的同步。而对于样式,也还是存在全局污染的问题。
再后来,有些高级的开发语言,例如 Java, 推出了基于服务器的组件化开发技术,例如 JSF (JSP 的后继),基于 MVC 设计模式,通过 Java Class (POJO) 定义数据模型(Model),为 JSF 页面模板提供数据。JSF 的数据模型中有事件和生命周期相关的方法,而模板和数据模型通信的方式是 Ajax,通过使用 JSF facelets 组件的方式,可以直接发送 Ajax 请求,调用模型中的方法:<!-- index.xhtml --> <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."; } } }
代码来源:Jarkarta EE 官方示例
不过这种方式严格限定了编程语言,例如要用 JSF 技术就必须要使用 Java,并且样式污染的问题和客户端数据管理的问题仍然没有解决。
随着 Node.js 的普及,JavaScript 可以脱离浏览器运行了,并且带来了 npm 包管理器,基于 npm 庞大的工具链,可以 Node.js 的代码打包成浏览器中能够运行的代码。而这期间,产生了像 React、Vue 和 Angular 这样的、适合大型前端项目开发的库和框架。React 和 Vue 目前的流行程度比较高一些,它们都是纯客户端的、组件化的 JS 库,不依赖任何后端编程语言,让程序员都能够快速上手。不过,随着项目规模的扩大,如何利用好这些库进行组件化开发,成了前端工程师必须要掌握的课题。
什么是组件
那么到底什么是组件呢?你想一下日常生活中,电脑上的硬件,例如显卡、内存、CPU,或者汽车的零部件:轮胎、发动机、方向盘等,这些都属于组件,它们组合起来能形成一个完整可用的产品,对于遵循了设计规范的组件,只要型号匹配,无论品牌、样式,都可以互相兼容。
我们前端工程师其实就当于是一个工厂,我们需要按照一定的规范,产生出合格的网页组件,利用它们组装成完整的页面。组件一般包含 HTML 模板、CSS 样式和 JavaScript 数据逻辑,自成一体,可以直接在其它组件中使用,组件本身的模板、样式和数据不会影响到其它组件。组件还包含一系列可配置的属性,动态的产生内容。常见的基础组件有按钮、导航、提示框、表单输入控件、对话框、表格、列表等,我们在它们的基础上又能组合出更复杂的组件,那么对于前端中的组件的定义就非常广泛了,小到一个按钮,大到一个页面都可以形成一个组件,例如两个相似的页面,可以复用一个页面组件,只需要通过修改组件的属性,来形成一个新的页面。
为什么要组件化开发
你可能也知道,React 和 Vue 其实也可以完全按照传统的网页开发方式进行开发,最多是把网页大体分成几个部分,放到单独的几个文件里就好了,对于简单的网站来说没什么问题,但是如果开发的是大型的应用,例如网页版的 QQ 音乐、微信、邮箱等,它们有大量的、细小的组件,并且重复出现,这时如果针对每个页面编写 HTML 和样式,那么就会造成太多冗余代码了。
<!-- playlist.html --> <div class="card"> <div class="card__title"></div> <div class="card_content"></div> </div> <!-- homepage.html --> <div class="card"> <div class="card__title"></div> <div class="card_content"></div> </div> <!-- user.html --> <div class="card"> <div class="card__title"></div> <div class="card_content"></div> </div>
如果使用组件化的方式,我们可以把重复出现的页面内容定义成组件,这样就不用复制粘贴同样的代码了,减少代码的体积:
<!-- 伪组件 --> <!-- Card.comp --> <div class="card"> <div class="card__title"></div> <div class="card_content"></div> </div> <!-- playlist.html --> <Card /> <!-- homepage.html --> <Card /> <!-- user.html --> <Card />
当某个页面内容是复合组件时,那么可以直接把基础组件直接拿过来应用:
<Card> <Title></Title> <Button></Button> </Card>
利用这种方式,甚至可以达到 1 分钟产生一个新页面的恐怖速度,这对于你来说,节省时间去摸鱼,岂不美哉?
<Page title="页面1" data="{...}" /> <Page title="页面2" data="{...}" />
对于大型的公司来说,公司的网站、APP、桌面应用、Web 端应用等的设计风格都是一样的,同样的组件会在不同平台中使用,这个时候团队之间可以共享一套组件库,复用到各端平台上,减少重复开发的成本。React 和 Vue 都支持创建跨平台的应用,这时组件化开发就显得更重要了:
使用组件化开发之后,代码也容易维护了,如果页面某个部分出了问题,那么我们可以针对出现问题的组件进行修复,多次用到这个组件的地方也都会一起修复了。
最后,设计师在设计产品界面的时候,会把重复出现的 UI 也做成『组件』,这个组件的概念几乎和前端中的组件一模一样,这时可以跟设计师交流想法,看看他/她是怎么规划组件的,这样也减少了在开发时,设计规划组件的时间。
接下来分享一下我在组件化开发中总结的经验:
一、组件的规划
在正式进行前端应用开发之前,需要有充分的时间来分析,看看页面上的哪些内容需要做成组件。这一步只需要大致规划一下:
- 如果公司设计师提供了详细的设计规范,那么直接按照规范中的组件来开发就可以了。
- 如果只有设计稿,那么就看看哪些内容有 2 次或更多次在其它页面中用到了,这些大概率需要设计为组件。
- 如果连设计稿都没有,那么可以用市面上现成的组件库,例如 ant design。
- 如果没有设计稿,还要自己从零开发,那么可以根据通用的套路,把基础组件,例如按钮、菜单等,规划出来,并把可能会多次用到的页面内容,也规划出来,例如导航,底部信息、联系方式区域,这样可以只改动需要变化的部分,不影响其它部分。
这一步不用太过详细,浪费太多时间,后续开发的时候如果遇到不确定是否要写成组件的部分,可以直接把这部分代码写到大的组件里,如果其它组件又用到了,再把它抽离成组件。
二、组件代码管理
组件的代码应该遵循就近原则,也就是说:
- 和组件有关的 HTML、CSS 、JS 代码和图片等静态资源应该放在同一个目录下,方便引用。
- 组件里的代码应该只包括跟本组件相关的 HTML 模板、CSS 样式和 JS 数据逻辑。
- 所有的组件应放到一个统一的『组件文件夹中』。
如果组件之间有可以复用的 HTML 和 CSS,那么这个复用的部分可以直接定义成一个新的组件。
如果组件之间有可以复用的 JS 数据逻辑,那么可以把公用的数据逻辑抽离出来,放到公共的业务逻辑目录下,使用到该逻辑的组件,统一从这个目录中导入。
如果项目中使用到了全局状态管理,那么状态管理的数据应放在独立的目录里,这个目录还会存放分割好的状态片段 reducer,之后统一在 store 中合并。
对于项目中的** API 处理**,可以把它们单独放到一个文件夹里,对于同一个数据类型的操作,可以放到同一个 js 文件里,例如对 user 用户数据的增删改查,这样能尽最大可能进行复用,之后在组件里可以直接引入相关 api 进行调用。如果直接写在组件里,那么使用相同 API 的组件就会造成代码重复。
例如下面是一个组件化开发的目录结构示例(框架无关):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。<!-- Vue --> <style scoped></style> <!-- React,通过文件名 --> Button.module.css
修改子组件的样式时,优先使用选择器特异性(CSS Specificity)策略选定特定元素,而行内样式(inline-style)在设置简单样式的时候使用,尽一切可能避免
!important
,因为如果再有上一层组件(爷爷组件)需要修改这个组件的样式时,就会很困难了。四、组件属性管理
组件属性的命名要与它实际展示的内容相符。例如一个博客文章组件,title 属性表示标题,content 代表内容,showFullArticle 表示是否显示全文。
<!-- 不推荐,full 表示什么? --> <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": ... /* ... */ }
六、组件的组合
前端开发就是组合不同的组件。
在组合组件的时候,尽量使用『插槽』的形式(例如 React 的 children/render props,Vue 的 slot),这样可以减少组件的嵌套,避免多层传递属性或事件监听。
使用 slot:// Layout 组件 <div> <!-- nav slot --> <!-- content slot --> <!-- footer slot --> </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="<ContentComp />" />
如果组件结构类似,但是有些内部的组件不一样,可以考虑通过『插槽』来复用。
<!-- Comp --> <div> <h1></h1> <!-- slot --> </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://zxuqian.cn/7-ways-to-organize-frontend-components,欢迎访问查看更多前端开发教程!
Bilibili:峰华前端工程师
公众号:峰华前端工程师 -
iOS组件化开发流程
2021-11-18 15:12:39组件化开发之前,我们先了解一下,什么是组件化,为什么要组件化开发 举个很简单的例子,我们平时在开发的时候用的第三方库,我们直接pod下来就可以使用,跟项目是分开的独立模块,就可以理解为一个组件,为什么要...iOS组件化开发流程
前期准备工作
组件化开发之前,我们先了解一下,什么是组件化,为什么要组件化开发
举个很简单的例子,我们平时在开发的时候用的第三方库,我们直接pod下来就可以使用,跟项目是分开的独立模块,就可以理解为一个组件,为什么要组件化开发呢,当我们的项目越来越庞大的时候,各模块之间耦合度比较高,尤其存在多个项目时,有的模块是一样的,那么我们就可以把这个模块分离处理,单独维护,供多个项目使用,节约开发和维护成本注册仓库账号(gitHub/gitLabel/码云)
开头说到,我们要把公用或者项目不相关模块分离出来,那么这个分离出来的组件,这个分离出来的模块我们放在远端仓库,通过cocoapod管理,那么我们需要有远端仓库的账号,如果是公司项目,一般这个组件存放在自己公司gitLabel,如果公用组件可以放在gitHub上
1.这里以gitHub为例 ,在gitHub上创建一个仓库,名字跟你的组件名叫一样;
2. 拷贝仓库地址,备用,这里我以LQAlertViewKit为例子;
注册trunk
首先查看是否注册过,命令如下:
pod trunk me
如果没有注册,使用如下命令进行注册:
pod trunk register '邮箱地址' 's-ITBoy'
然后您的邮箱会受到一条信息,点击其中的链接进行验证,这样既完成注册了
接着您就可以操作如下命令进行查看:
pod trunk me
创建组件模版
我们可以在桌面新建一个文件夹,用来存放自己的组件,使用终端cd到这个文件夹下,使用 pod lib create [组件名],
例如pod lib create LQAlertViewKit
在组件模版里编写自己的代码
在创建的组件模版下面,有个classes文件,把自己写好的类,拷贝到这里面来,
删除replace文件,pod install,你会发现项目会出现这两个文件,如果你的组件有依赖库,还需如下图
修改.specs文件
push到仓库
cd到目录下,commit代码,然后push到仓库
git push origin master -f
给组件打一个tag
git tag 0.1.0
版本号需要和specs版本号一致,如上图
git push --tags
校验specs文件
1.如果我们需要把组件索引存放在自己私有仓库里,那么我们还需要在github上建一个私有仓库,例如
然后把liqiuSpecs文件 添加到本地repo
pod repo add [仓库名] [仓库URL地址]
,仓库url地址https://github.com/qingqiusuomeng/liqiuSpecs.git然后验证specs文件
pod lib lint LQAlertViewKit.podspec —sources='https://github.com/qingqiusuomeng/liqiuSpecs.git,https://github.com/CocoaPods/Specs.git' --verbose --allow-warnings --use-libraries --skip-import-validation
关联私有仓库或上传cocoapods
验证通过后推送组件索引到git,
pod repo push liqiuSpecs LQAlertViewKit.podspec --allow-warnings
1.如果443,取消代理
git config --global http.sslVerify false
如果要上传到cocoapos验证方式pod lib lint —verbose --allow-warnings
pod trunk push 组件名.podspec --verbose --allow-warnings
-
Vue基础知识总结 4:vue组件化开发
2021-07-21 23:37:50和过程化编程相比,函数式编程里函数的计算可随时调用。 filter函数自动过滤对象的所有元素,返回true才会存入指定对象; Reduce函数对数组内部的所有元素进行汇总; 2、代码实例 <!DOCTYPE -
Android进阶——组件化开发实践(一)
2022-03-11 16:39:37一、组件化的意义 随着Android 项目代码和结构逐渐复杂,维护成本会指数型上升,通常我们会利用Android Studio自带的Module去拆分项目代码。但这种拆分显然需要基于一定逻辑和结构,目前主流的拆分思路有两种:分别... -
前端为什么要组件化开发?
2021-11-05 11:02:39文章目录什么是前端模块化,组件化,工程化?为什么要组件化?组件化和模块化的区别插槽组件传值组件库 什么是前端模块化,组件化,工程化? 前端模块化: 可以理解为一组自定义业务的抽象封装,是根据项目的情况... -
组件化开发和模块化开发概念辨析
2018-01-29 00:57:06组件化开发和模块化开发概念辨析 网上有许多讲组件化开发、模块化开发的文章,但大家一般都是将这两个概念混为一谈的,并没有加以区分。而且实际上许多人对于组件、模块的区别也不甚明了,甚至于许多博客文章专门... -
Vue全家桶之组件化开发
2020-01-06 07:56:26学习组件化开发,首先掌握组件化的开发思想,组件的注册方式,组件间的数据交互方式,组件插槽的用法,vue调式工具的用法,组件的方式来实现业务逻辑功能。 组件化开发思想,组件注册,组件调式,组件间的数据交互,... -
Vue 组件化开发
2018-12-20 15:50:21Vue 组件化开发 提示: 本次分享知识点基于 vue.js,需要对 vue.js 有一定的了解。 什么叫做组件化 vue.js 有两大法宝,一个是数据驱动,另一个就是组件化,那么问题来了,什么叫做组件化,为什么要组件化?接下来我... -
iOS组件化开发从开始到完整总结
2019-09-19 17:23:16组件化介绍 需求来源 随着项目规模不断扩大,业务模块增多,开发过程中会有多条产品线(多人或多小组开发不同的功能);如果用传统的开发模式,会导致代码臃肿,编译速度越来越慢,开发效率低下,代码维护成本越来越高. ... -
Android模块化和组件化开发
2019-07-24 08:59:301.2:模块化和组件化的区别 1.3:模块化的优点 1.4:模块化的层级介绍 二.如何实现组件化 2.1:实现模块化需要解决的问题 2.2:各个问题的解决方法 一:模块化介绍 (1)对于简单的小项目,大多都采用的是... -
教你打造一个Android组件化开发框架
2017-12-03 23:21:51无需注解,支持任意功能调用&回调的android组件化开发框架。兼容同步&异步调用及同步&异步实现,并做到调用方式和实现方式解耦。 -
Java通用后台组件化开发框架
2020-07-18 20:52:22一款 Java 语言基于 SpringBoot2.x、Layui、Thymeleaf、MybatisPlus、Shiro、...自研了一套个性化的组件,实现了可插拔的组件式开发方式:单图上传、多图上传、下拉选择、开关按钮、单选按钮、多选按钮、图片裁剪等 -
组件化开发和模块化开发
2018-11-27 15:32:36组件化开发和模块化开发实际上是两种编程思想,也可以被认为是两种解决方案。组件化开发注重重用,可以用作实现基础架构的技术方案。举个例子:加入现在我需要实现一个几何图形库,包括图形的生成、修改、删除等基本... -
Android组件化开发简单示例
2020-11-21 15:01:52一、组件化初始模型 1、通过一个简单的android项目初始架构图来了解组件化,如下图: 打个生动的比喻,把我们的APP当成一个电脑主机,那么app外壳就是主机外壳,main组件就是主板,其他各个组件就类似于硬盘、... -
组件化开发之如何封装组件
2021-04-19 15:59:22自从React,Vue等前端框架在市面上大量使用之后,组件化开发逐渐成为了前端主流开发方式,今天我就在这里给大家分享一下在我们平时的开发中我们自己应该如何去封装组件。主要从以下三个方面给大家讲解: 什么是组件... -
Android如何组件化开发,组件可随时作为插件更新升级
2022-03-15 09:09:49开发之前先弄明白,何为组件化开发。 简单的说组件化开发就是分模块开发, 1, 可作为单独的应用来开发测试验证, 2, 可以作为模块来集成, 3, 设计的好,也可作为插件来动态更新或修复Bug. 这就是组件开发的... -
Javascript组件化开发设计思想
2018-09-20 15:52:39一、引言 项目中经常用web弹层组件-layer,其常见的代码如下: ...那么,什么是组件开发、为什么要采用这种开发形式、怎么进行组件化开发呢?下面就来一探究竟吧。 二、什么是组件化开发 当多组功能相... -
二、vue组件化开发(轻松入门vue)
2020-04-15 17:28:59二、vue组件化开发(轻松入门vue) Vue组件化开发五、组件化开发1. 组件注册组件命名规范组件注册注意事项全局组件注册局部组件注册2. Vue调试工具下载3. 组件间数据交互父组件向子组件传值props传递数据原则:单向... -
你曾遇到的某大厂奇葩问题:Android组件化开发,组件间的Activity页面跳转
2022-02-04 17:29:102、常规业务模块,该层的组件就是我们真正的业务组件了。我们通常按照功能模块来划分业务组件,例如注册登录、用户个人中心、APP的首页模块等。这里的每个业务组件都是一个小的APP,只需要修改一下对应的module的... -
《Vue入门到精通系列之二》--- 组件化开发与前端模块化
2022-03-04 11:42:06一、组件化开发 1.内容概述 2.认识组件化 2.1.什么是组件化? 2.2.Vue组件化思想 3.注册组件 3.1.注册组件的基本步骤 3.2.注册组件步骤解析 4.组件其它补充 4.1.全局组件和局部组件 4.2.父组件和子组件 4.3.注册组件... -
Android组件化开发之一:为什么要进行组件化开发
2018-01-16 15:11:38组件化开发系列文章 1. Android组件化开发之一:为什么要进行组件化开发 2. Android组件化开发之二:组件化架构 3. Android组件化开发之三:组件化开发手册 4. Android组件化开发之四:组件化填坑之旅 5. Android... -
组件化开发的优点和缺点(简述)
2019-11-14 14:53:06组件化开发的优点和缺点(简述)(每个人都会有自己的理解和看法,以下仅代表个人看法) 优点 组件更加清晰直观 组件关系更加清晰 结果可以预测 优越 最小化了重绘(diff算法) 避免了不必要的dom操作 ... -
Vue.js教程-组件化开发
2020-08-04 14:15:55Vue.js教程-组件化开发前言Vue组件化什么是组件化Vue单页面开发的解释组件化思想组件的使用原理实际开发中的使用-父子组件父子组件传递数据父传子-props用法子传父-this.$emit()自定义事件父组件直接获取子组件的... -
什么叫组件化开发
2018-02-24 21:38:46转载:什么叫组件化开发? - aloo的回答 - 知乎 https://www.zhihu.com/question/29735633/answer/90873592 从第一代码农写下第一行代码开始到上个世纪的80年代的软件危机,码农一直在考虑一个问题,怎么让写代码... -
Android组件化开发
2020-02-26 14:23:21什么是组件化开发 所谓组件化,就是将整个庞大的项目以业务逻辑进行拆分成多个模块,并且各个模块之间相互独立,相互解耦,每一个模块可以单独进行开发调试,各个模块调试完,以library的形式依赖在壳App中组合成一... -
前端组件化开发
2016-11-28 16:40:54它的核心意义是分离职责,属于代码级模块化的产出。它本身是提供服务的功能逻辑,是一组具有一定内聚性代码的组合,职责明确。 组件(Component)和模块(Module)又是一对容易混淆的名词,也常常被用来相互替换。个人... -
Android组件化开发的实现(二)Android组件之间页面如何跳转和传递数据
2018-12-28 23:54:56上一篇文章Android组件化开发的实现(一),讲了组件化架构首先要解决的几个问题: 一.如何统一管理编译环境 二.如何实现各个组件既能单独调试运行,又能集成到整体里 三.如何避免组件之间资源引用 本篇文章,...