1.spinnerԴ??
2.autojs修改下拉框高度
3.androidä¸spinner.setOnItemClickListener为ä»ä¹ä¸å¯ä»¥ç¨åï¼
4.Go并åç¼ç¨ï¼goroutineï¼channelåsync详解
5.monorepo多包管理架构实践
spinnerԴ??
progress库安装和介绍
progress是Python第三方库,用于在控制台显示进度条,安装方法为在终端执行pip命令。
progress实现进度条
使用progress库实现进度条非常简便,仅需从库中的bar.py模块导入Bar类,实例化后进行业务处理并在循环中调用next()方法,软件源码打包处理完成后调用finish()方法结束进度条。
Bar类的主要参数包括:message、width、max、suffix、fill、empty_fill、bar_prefix、bar_suffix和color,用于配置进度条显示信息、样式和颜色。
PyCharm进度条显示问题解决
在PyCharm中运行进度条代码时,若未显示进度条效果,可通过以下步骤解决:打开Run配置页面,确保Emulate terminal in output console选项被勾选,重新运行代码。
在PyCharm中运行进度条时,可能会出现光标输出的异常现象。解决方法为:在Bar类所在的导航地图源码源文件bar.py中,找到继承自Progress类的Bar类,进入progress库的__init__.py文件,修改SHOW_CURSOR变量为空字符串,以阻止光标显示。
进度条代码的另外两种写法
1. 上下文管理器:使用with...as...上下文管理器编写进度条,进度条完成后自动结束。
2. 使用iter()方法:简化迭代器操作,自动调用next()方法。
实现更多种类的进度条
1. Bar系列:Bar类及其子类如ChargingBar、FillingSquaresBar等,通过修改suffix、fill、empty_fill等参数,实现不同样式进度条。
2. Spinner系列:实现的Spinner、PieSpinner等类提供不同动画效果的进度条。
3.Counter系列:Counter、Countdown等类用于显示计数器或倒计时进度。
汇总:将上述所有进度条类型整合至单个代码中,可实现全面的进度条功能。
autojs修改下拉框高度
在自动脚本开发中,有时我们可能需要对UI组件进行微调以适应特定需求。例如,当使用AutoJS处理下拉框时,源码安装xpdf我们可能会遇到下拉框过长的问题。本文将探讨如何解决AutoJS中的下拉框高度修改问题。
在AutoJS中,我们首先需要了解下拉框的两种模式:弹框模式(dialog)和下拉框模式(dropdown)。弹框模式下,下拉框的高度不能通过直接设置高度来改变,因为这会引发错误。相比之下,下拉框模式提供了更多的自定义选项,包括设置高度。
要修改下拉框的高度,我们通常会尝试反射访问下拉框的实例,然后修改相关属性。然而,这一方法在AutoJS中可能无法正常工作。这是因为AutoJS是基于Android SDK构建的,但并非完全等同于原生的Android环境。在AutoJS中,`spinner`组件是对其原生Android组件的封装,这导致了某些行为上的差异。
深入AutoJS的源码,我们发现`spinner`的下拉框实例`mPopup`实际上属于`androidx.appcompat.widget.AppCompatSpinner`类。这解释了为什么我们不能直接在AutoJS中修改`mPopup`的高度。`AppCompatSpinner`是c httpclient 源码`Spinner`的子类,它提供了更丰富的样式和行为。在AutoJS中,由于封装了原生的组件,我们实际上不能直接访问或修改`mPopup`实例。
通过创建一个简单的测试环境来验证这一点,我们定义了一个父类和一个子类,并尝试访问`name`字段。在子类中,当`name`字段被定义为私有时,我们无法访问它。然而,如果我们将它改为公有,子类可以访问到父类的`name`值。这表明,当父子类具有同名字段时,子类会优先访问自己的字段,除非被覆盖。
基于上述发现,我们可以通过直接修改`AppCompatSpinner`类的`mPopup`属性来改变下拉框的高度。在AutoJS环境中,这需要通过反射来实现。一旦成功修改了高度,下拉框的显示效果将得到优化,不再显得过长。访美篇源码
这个解决方案不仅解决了下拉框高度调整的问题,还为我们提供了一个重要的学习点:在将Android代码转换为AutoJS时,需要充分考虑AutoJS与Android环境之间的差异。这有助于我们在未来避免类似的陷阱,更好地利用脚本语言进行UI操作的定制。
通过实践与理解,我们可以更灵活地应用AutoJS,解决各种复杂的UI调整需求。记住,每次遇到问题时,首先尝试从基础知识出发,理解背后的原因,而不是盲目依赖外部资源。这样,我们不仅能够解决当前的问题,还能构建更坚实的编程技能基础。
androidä¸spinner.setOnItemClickListener为ä»ä¹ä¸å¯ä»¥ç¨åï¼
æºä»£ç ä¸ææ¡£æ示æç¡®åçA spinner does not support item click events. Calling this method will raise an exception. Overrides: setOnItemClickListener(...) in AdapterViewï¼ææå°±æ¯è¯´spinnerä¸æ¯æOnItemClickListenerï¼å 为å®çå®ç°æ¯å¨AdapterViewä¸æ¥éåçãä½ å¯ä»¥ç¨setOnItemSelectedListener æ¥ä»£æ¿ã
Go并åç¼ç¨ï¼goroutineï¼channelåsync详解
ä¼é ç并åç¼ç¨èå¼ï¼å®åç并åæ¯æï¼åºè²ç并åæ§è½æ¯Goè¯è¨åºå«äºå ¶ä»è¯è¨çä¸å¤§ç¹è²ãå¨å½ä»è¿ä¸ªå¤æ ¸æ¶ä»£ï¼å¹¶åç¼ç¨çæä¹ä¸è¨èå»ã使ç¨Goå¼å并åç¨åºï¼æä½èµ·æ¥é常ç®åï¼è¯è¨çº§å«æä¾å ³é®ågoç¨äºå¯å¨åç¨ï¼å¹¶ä¸å¨åä¸å°æºå¨ä¸å¯ä»¥å¯å¨æåä¸ä¸ä¸ªåç¨ã
ä¸é¢å°±æ¥è¯¦ç»ä»ç»ã
goroutineGoè¯è¨ç并åæ§è¡ä½ç§°ä¸ºgoroutineï¼ä½¿ç¨å ³é®è¯goæ¥å¯å¨ä¸ä¸ªgoroutineã
goå ³é®è¯åé¢å¿ é¡»è·ä¸ä¸ªå½æ°ï¼å¯ä»¥æ¯æåå½æ°ï¼ä¹å¯ä»¥æ¯æ åå½æ°ï¼å½æ°çè¿åå¼ä¼è¢«å¿½ç¥ã
goçæ§è¡æ¯éé»å¡çã
å æ¥çä¸ä¸ªä¾åï¼
packagemainimport("fmt""time")funcmain(){ gospinner(*time.Millisecond)constn=fibN:=fib(n)fmt.Printf("\rFibonacci(%d)=%d\n",n,fibN)//Fibonacci()=}funcspinner(delaytime.Duration){ for{ for_,r:=range`-\|/`{ fmt.Printf("\r%c",r)time.Sleep(delay)}}}funcfib(xint)int{ ifx<2{ returnx}returnfib(x-1)+fib(x-2)}ä»æ§è¡ç»ææ¥çï¼æå计ç®åºäºææ³¢é£å¥æ°åçå¼ï¼è¯´æç¨åºå¨spinnerå¤å¹¶æ²¡æé»å¡ï¼èä¸spinnerå½æ°è¿ä¸ç´å¨å±å¹ä¸æå°æ示å符ï¼è¯´æç¨åºæ£å¨æ§è¡ã
å½è®¡ç®å®ææ³¢é£å¥æ°åçå¼ï¼mainå½æ°æå°ç»æ并éåºï¼spinnerä¹è·çéåºã
åæ¥çä¸ä¸ªä¾åï¼å¾ªç¯æ§è¡æ¬¡ï¼æå°ä¸¤ä¸ªæ°çåï¼
packagemainimport"fmt"funcAdd(x,yint){ z:=x+yfmt.Println(z)}funcmain(){ fori:=0;i<;i++{ goAdd(i,i)}}æé®é¢äºï¼å±å¹ä¸ä»ä¹é½æ²¡æï¼ä¸ºä»ä¹å¢ï¼
è¿å°±è¦çGoç¨åºçæ§è¡æºå¶äºãå½ä¸ä¸ªç¨åºå¯å¨æ¶ï¼åªæä¸ä¸ªgoroutineæ¥è°ç¨mainå½æ°ï¼ç§°ä¸ºä¸»goroutineãæ°çgoroutineéè¿goå ³é®è¯å建ï¼ç¶å并åæ§è¡ãå½mainå½æ°è¿åæ¶ï¼ä¸ä¼çå¾ å ¶ä»goroutineæ§è¡å®ï¼èæ¯ç´æ¥æ´åç»ææægoroutineã
é£æ没æåæ³è§£å³å¢ï¼å½ç¶æ¯æçï¼è¯·å¾ä¸çã
channelä¸è¬åå¤è¿ç¨ç¨åºæ¶ï¼é½ä¼éå°ä¸ä¸ªé®é¢ï¼è¿ç¨é´éä¿¡ã常è§çéä¿¡æ¹å¼æä¿¡å·ï¼å ±äº«å åçãgoroutineä¹é´çéä¿¡æºå¶æ¯ééchannelã
使ç¨makeå建ééï¼
ch:=make(chanint)//chçç±»åæ¯chanintééæ¯æä¸ä¸ªä¸»è¦æä½ï¼sendï¼receiveåcloseã
ch<-x//åéx=<-ch//æ¥æ¶<-ch//æ¥æ¶ï¼ä¸¢å¼ç»æclose(ch)//å ³éæ ç¼å²channelmakeå½æ°æ¥å两个åæ°ï¼ç¬¬äºä¸ªåæ°æ¯å¯éåæ°ï¼è¡¨ç¤ºéé容éãä¸ä¼ æè ä¼ 0表示å建äºä¸ä¸ªæ ç¼å²ééã
æ ç¼å²ééä¸çåéæä½å°ä¼é»å¡ï¼ç´å°å¦ä¸ä¸ªgoroutineå¨å¯¹åºçééä¸æ§è¡æ¥æ¶æä½ãç¸åï¼å¦ææ¥æ¶å æ§è¡ï¼é£ä¹æ¥æ¶goroutineå°ä¼é»å¡ï¼ç´å°å¦ä¸ä¸ªgoroutineå¨å¯¹åºééä¸æ§è¡åéã
æ以ï¼æ ç¼å²ééæ¯ä¸ç§åæ¥ééã
ä¸é¢æ们使ç¨æ ç¼å²ééæä¸é¢ä¾åä¸åºç°çé®é¢è§£å³ä¸ä¸ã
packagemainimport"fmt"funcAdd(x,yint,chchanint){ z:=x+ych<-z}funcmain(){ ch:=make(chanint)fori:=0;i<;i++{ goAdd(i,i,ch)}fori:=0;i<;i++{ fmt.Println(<-ch)}}å¯ä»¥æ£å¸¸è¾åºç»æã
主goroutineä¼é»å¡ï¼ç´å°è¯»åå°ééä¸çå¼ï¼ç¨åºç»§ç»æ§è¡ï¼æåéåºã
ç¼å²channelå建ä¸ä¸ªå®¹éæ¯5çç¼å²ééï¼
ch:=make(chanint,5)ç¼å²ééçåéæä½å¨ééå°¾é¨æå ¥ä¸ä¸ªå ç´ ï¼æ¥æ¶æä½ä»ééç头é¨ç§»é¤ä¸ä¸ªå ç´ ãå¦æéé满äºï¼åéä¼é»å¡ï¼ç´å°å¦ä¸ä¸ªgoroutineæ§è¡æ¥æ¶ãç¸åï¼å¦æééæ¯ç©ºçï¼æ¥æ¶ä¼é»å¡ï¼ç´å°å¦ä¸ä¸ªgoroutineæ§è¡åéã
æ没ææè§ï¼å ¶å®ç¼å²ééåéåä¸æ ·ï¼ææä½é½è§£è¦äºã
ååchannelç±»åchan<-intæ¯ä¸ä¸ªåªè½åéçééï¼ç±»å<-chanintæ¯ä¸ä¸ªåªè½æ¥æ¶çééã
ä»»ä½ååééé½å¯ä»¥ç¨ä½ååééï¼ä½åè¿æ¥ä¸è¡ã
è¿æä¸ç¹éè¦æ³¨æï¼closeåªè½ç¨å¨åéééä¸ï¼å¦æç¨å¨æ¥æ¶ééä¼æ¥éã
çä¸ä¸ªååééçä¾åï¼
packagemainimport"fmt"funccounter(outchan<-int){ forx:=0;x<;x++{ out<-x}close(out)}funcsquarer(outchan<-int,in<-chanint){ forv:=rangein{ out<-v*v}close(out)}funcprinter(in<-chanint){ forv:=rangein{ fmt.Println(v)}}funcmain(){ n:=make(chanint)s:=make(chanint)gocounter(n)gosquarer(s,n)printer(s)}syncsyncå æä¾äºä¸¤ç§éç±»åï¼sync.Mutexåsync.RWMutexï¼åè æ¯äºæ¥éï¼åè æ¯è¯»åéã
å½ä¸ä¸ªgoroutineè·åäºMutexåï¼å ¶ä»goroutineä¸ç®¡è¯»åï¼åªè½çå¾ ï¼ç´å°é被éæ¾ã
packagemainimport("fmt""sync""time")funcmain(){ varmutexsync.Mutexwg:=sync.WaitGroup{ }//主goroutineå è·åéfmt.Println("Locking(G0)")mutex.Lock()fmt.Println("locked(G0)")wg.Add(3)fori:=1;i<4;i++{ gofunc(iint){ //ç±äºä¸»goroutineå è·åéï¼ç¨åºå¼å§5ç§ä¼é»å¡å¨è¿éfmt.Printf("Locking(G%d)\n",i)mutex.Lock()fmt.Printf("locked(G%d)\n",i)time.Sleep(time.Second*2)mutex.Unlock()fmt.Printf("unlocked(G%d)\n",i)wg.Done()}(i)}//主goroutine5ç§åéæ¾étime.Sleep(time.Second*5)fmt.Println("readyunlock(G0)")mutex.Unlock()fmt.Println("unlocked(G0)")wg.Wait()}RWMutexå±äºç»å ¸çååå¤è¯»æ¨¡åï¼å½è¯»é被å ç¨æ¶ï¼ä¼é»æ¢åï¼ä½ä¸é»æ¢è¯»ãèåéä¼é»æ¢åå读ã
packagemainimport("fmt""sync""time")funcmain(){ varrwMutexsync.RWMutexwg:=sync.WaitGroup{ }Data:=0wg.Add()fori:=0;i<;i++{ gofunc(tint){ //第ä¸æ¬¡è¿è¡åï¼å解éã//循ç¯å°ç¬¬äºæ¬¡æ¶ï¼è¯»éå®åï¼goroutine没æé»å¡ï¼åæ¶è¯»æåãfmt.Println("Locking")rwMutex.RLock()deferrwMutex.RUnlock()fmt.Printf("Readdata:%v\n",Data)wg.Done()time.Sleep(2*time.Second)}(i)gofunc(tint){ //åéå®ä¸æ¯éè¦è§£éåæè½åçrwMutex.Lock()deferrwMutex.Unlock()Data+=tfmt.Printf("WriteData:%v%d\n",Data,t)wg.Done()time.Sleep(2*time.Second)}(i)}wg.Wait()}æ»ç»å¹¶åç¼ç¨ç®æ¯Goçç¹è²ï¼ä¹æ¯æ ¸å¿åè½ä¹ä¸äºï¼æ¶åçç¥è¯ç¹å ¶å®æ¯é常å¤çï¼æ¬æä¹åªæ¯èµ·å°ä¸ä¸ªæç å¼ççä½ç¨èå·²ã
æ¬æå¼å§ä»ç»äºgoroutineçç®åç¨æ³ï¼ç¶åå¼åºäºééçæ¦å¿µã
ééæä¸ç§ï¼
æ ç¼å²éé
ç¼å²éé
ååéé
æåä»ç»äºGoä¸çéæºå¶ï¼åå«æ¯syncå æä¾çsync.Mutexï¼äºæ¥éï¼åsync.RWMutexï¼è¯»åéï¼ã
goroutineå大精深ï¼åé¢çåè¿æ¯è¦æ ¢æ ¢è¸©çã
æç« ä¸çèå¾åæºç é½ä¸ä¼ å°äºGitHubï¼æéè¦çåå¦å¯èªè¡ä¸è½½ã
å°åï¼github.com/yongxinz/gopher/tree/main/sc
ä½è ï¼yongxinz
monorepo多包管理架构实践
平时开发项目时,我们会积累和沉淀许多通用的组件、方法等,它们通常适用于多个项目。但由于不同的项目具有不同的代码库,这些通用组件或方法不能简单地复制粘贴,这也不利于统一维护和更新。
如果我们要在一个代码库中维护一套通用的组件、方法、模板等可能服务于其他项目的 package,这些 package 可以独立发布且互不干扰,我们应该如何操作呢?
最初,我并不知道该如何进行调研,于是翻看了一些优秀库(如 vant、element-plus)的底层架构,大致了解了一些情况。结合自己的理解和认知,我使用 pnpm + vite 搭建了一个可供参考使用的 monorepo 多包管理框架。
为何选择 pnpm:
pnpm 相较于 npm、yarn 可以有效节省磁盘空间并提升安装速度。
pnpm 内置了对单个代码仓库包含多个软件包的支持,是 monorepo 架构模式的不二之选。
原理概括:pnpm 将所有的依赖包的层次提升到同一层次并安装存放在一个统一的地方(.pnpm),创建非扁平的 node_modules 目录,包之间的依赖关系可以很清楚地看到,一个包的依赖包以符号链接的形式存在,符号链接会指向当前包的原始位置,即所在的 .pnpm 的顶层位置。
为何选择 vite:
vite 随着 vue 3.x 而来,极大改善了开发体验,冷启动让人惊艳,再也不用担心项目越来越大启动速度越来越慢了。
平时开发项目基本是 vue 3.x + vite + typescript 的组合,vite 有一个库模式的构建方式,这意味着我们在开发项目和通用包时,都可以使用 vite,而无需引入第三方构建工具,使用 vite 无疑具有天然优势。
这里存在一个问题,现在普遍使用 typescript 来开发项目,开发的通用库在发布构建时也需要生成相应的 .d.ts 类型声明文件,vite 的库模式构建是不会自动生成 .d.ts 文件的,为了能自动生成类型声明文件我需要用到 tsc 和 vue-tsc,这里先简单提下,后面会用到。
基本骨架:
包的开发阶段可能需要在一个包中引用另一个包以方便调试,单包开发的话可能需要用 npm link 辅助调试,比较麻烦,多包模式下可以简单地使用 workspace。
组件库:
1. 我们需要在项目的根目录下执行 scripts 命令,以执行对应包中的 scripts 命令,需要用到 pnpm --dir 定位到需要执行命令的目录。
注意:为了更加细粒度地控制构建的过程,这里封装了构建的脚手架命令 vswift-cli build,简单直接点的话可以直接用 "build": "rm -r ./dist && vite build && vue-tsc"。
2. 组件库中有独立的 package.json、tsconfig.json、vite.config.ts 配置,package.json 中有几个关键配置:files、main、module、types、exports、publishConfig。
files - 发布的包所包含的内容,默认会带上 package.json
main - require 的入口文件,如 dist/index.umd.cjs
module - import 的入口文件,如 dist/index.js
types - 类型声明的入口文件,如 dist/types/index.d.ts
exports - 定义导出模块的路径别名
publishConfig - 发布配置
3. tsconfig.json 主要是配置 typescript 的作用范围 include,以及类型声明文件的编译输出。因为是组件库,所以会有 .vue 文件,vue-tsc 可以生成对应的 .vue.d.ts 类型声明文件。
4. vite.config.ts 是库模式 build 的配置,基本配置如下:
5. 最终输出
原始的文件目录
打包输出的文件目录
组件库打包可能存在的问题
解决方法:引入 vite-plugin-css-injected-by-js 插件解决。
以 element-plus 开发场景为例,如果开发的组件库是在 element-plus 组件的基础上开发的,打包后用到的样式会被提取到一个通用的 .css 文件中,这其中包括用到的 element-plus 根(:root)样式变量,这可能会覆盖掉项目中的 :root 样式变量。
解决方法:一个不错的解决方法是根据组件库的名称自定义 element-plus 命名空间,如何自定义命名空间,element-plus 官方文档有详细说明,这里不做赘述。这里有一点需要注意下,我们是打包自己的组件库,命名空间的设置不能是在 App.vue 根组件中了,而在你需要打包的组件中。
方法库:
不同于组件库是以 .vue 文件为主的视觉交互,通用方法中主要是以 .ts 文件为主的通用逻辑方法等,所以它和组件库最大的区别在于生成类型声明文件的工具,组件库是用 vue-tsc,通用方法库则是用 tsc,可参见上面的 vswift-cli build 脚本构建命令,简单直接点的话可以直接用 "build": "rm -r ./dist && vite build && tsc"。
脚手架:
封装自己的脚手架,有 3 个关键要点:bin 配置、files 配置、cli 顶部声明。
脚手架 package 基本结构预览 ------------>
这里解释下 "build": "pnpm clean && tsc && scp -r ../vswift-templates/src/templates ./dist" 的含义-----------> “pnpm clean && tsc” 无需多说,“scp -r ../vswift-templates/src/templates ./dist” 是将 vswift-templates/src/templates 中维护的通用模板拷贝到打包输出目录 dist 中,并随着 @vswift/cli 包一起发布,使用脚手架命令快速创建基础页面的模板将从 “dist/templates” 中获取。
name - 发布后的包名,全局安装:npm install @vswift/cli -g
bin.vswift-cli - 全局安装 @vswift/cli 后,即可在命令行使用 vswift-cli
files - 发布的文件别漏掉 bin.js
bin.js - 顶部需加 #!/usr/bin/env node 声明
注意:脚手架不需要 vite.config.ts 配置,只需在 tsconfig.json 中稍加区别修改,执行 tsc 使用。
outDir - 编译后的输出目录
declaration - 执行 tsc 时输出 .d.ts 声明文件
declarationDir - .d.ts 声明文件的输出目录
最终输出目录 ----------------->
这里推荐几个开发脚手架时常用的 package -------------------->
commander - Node.js 命令行插件,可以格式化输出友好的命令符提示
nanospinner - Node.js 终端微调器,进度、成功、失败等状态友好展示,也可用 ora 代替
consola - 用于 Node.js 和浏览器的优雅控制台记录器
chalk - 终端样式粉笔,着色高亮
vswift-cli build 构建命令 ------------------>
vswift-cli create 根据模板创建基础页面命令 --------------------->
vswift-cli dev 启动组件库预览调试命令 -------------------->
cli 命令行逻辑 --------------->
自动生成的 vswift-cli 命令行 help ----------------------->
发布包:
每个 package 的 package.json 中都有 release 命令,用于发布当前 package,package 会在发布前先 build 操作生成 dist 打包目录,最终发布的内容为 package.json 中 files 配置目录文件,当前 package 的 package.json 会被自动添加进去。
这里使用了 release-it 插件,以方便快速地进行发布。
还需注意,package.json 中要有相应的发布配置 --------------->
架构源码参考: