1.七爪源码:如何使用 Git Hooks 为您的 Java Maven 项目赋能,以实现自动代码格式化和语义版本控制
2.为ä½è¦ä½¿ç¨React Hooksï¼
3.webpack5loader和plugin原理解析
4.大家都能看得懂的源码 - ahooks 是怎么处理 DOM 的?
5.husky 源码浅析
6.带你了解vue3的自定义hooks
七爪源码:如何使用 Git Hooks 为您的 Java Maven 项目赋能,以实现自动代码格式化和语义版本控制
在软件开发过程中,保持代码质量和一致性至关重要,而Git Hooks提供了一个强大的工具来自动化这些过程。对于Java Maven项目,腾讯文档扫码登录源码通过集成Git Hooks,可以实现代码格式化和语义版本控制,从而提升团队协作效率和代码质量。
Git Hooks允许我们在特定的Git事件时运行脚本,从而在开发流程的关键点进行自定义操作。在本例中,我们关注于两个关键的Hooks:pre-commit和commit-msg。pre-commit在提交前运行,用于检查代码格式。而commit-msg在提交后运行,用于确保提交消息符合特定标准。
要使用Git Hooks,首先确保在项目的根目录下的pom.xml文件中配置了相应的Maven插件,以便将自定义的钩子复制到Git的默认hooks目录,并将Maven安装作为目标。对于我们的用例,我们需要为commit-msg和pre-commit指定自定义钩子。
对于pre-commit钩子,我们通常会使用一个工具来格式化代码,如Prettier、Google Java Format、Eclipse JDT或Palantir Java Format。确保这些工具在项目中可用,并且为pre-commit配置一个自定义的钩子。这样,每次准备提交代码时,系统都会自动应用代码格式化规则,保证提交的代码保持一致性和可读性。
在项目目录中,我们通常会看到一个.git/hooks目录,easypanel源码其中包含了预定义和自定义的Git Hooks。通过这种方式,我们不仅能够确保代码格式统一,还能够通过commit-msg钩子来标准化提交消息的格式,如使用特定的关键词来描述更改的性质,从而使得历史记录更加清晰和易于理解。
为了确保Git Hooks的正确执行,需要给予这些脚本执行权限。在项目的.git/hooks目录下,您会发现预定义的钩子文件,如pre-commit和commit-msg,这些文件可以按照您的需求进行调整或替换。通过这样的设置,我们可以确保每次提交前后的操作都符合预期,从而提升代码质量和团队协作效率。
此外,对于自动版本控制,还可以利用commit-msg钩子的作者发布的npm包来实现语义版本控制。通过运行特定的命令,可以自动创建标签,连同更改日志和更新的版本号一起推送到存储库中,从而简化版本管理和发布流程。
综上所述,通过集成Git Hooks,可以显著提升Java Maven项目的代码质量和开发流程的效率。这包括代码格式化、提交消息标准化、自动版本控制等关键环节,从而为团队协作提供坚实的基础。
为ä½è¦ä½¿ç¨React Hooksï¼
å¨äºè§£React Hooksä¹åï¼æä»¬å¿ é¡»å ç¥éReactçå½æ°ç»ä»¶æ§è´¨ä»¥åçå½æ°ç»ä»¶ä¸ºä»ä¹è¦ç¨Hooksã
大ä¼å¿é½ç¥éï¼æ ç»ä»¶ä¸Reactï¼èReactçç»ä»¶åå为类å¼ç»ä»¶åå½æ°å¼ç»ä»¶ï¼ä¸ºä»ä¹å½æ°å¼ç»ä»¶å¤§åReactå¼åè çæ¨å¹¿ï¼
é¦å æ们ä»æºç çè§åº¦æ¥ç®åè°ä¸ä¸ç±»å¼ç»ä»¶åå½æ°å¼ç»ä»¶çåºå«ï¼
å®ä¹ç±»ç»ä»¶æ¶ï¼æä»¬å¿ é¡»ç»§æ¿React.Component
åæ¶ï¼å®å ·æä¸ä¸ªrenderå½æ°
å±ä»¬è§å¥½å°±æ¶ï¼æ¯ç«å ·ä½æä½éè¦æ¶åæºç ï¼ä¸æ¯ä¸æ¶åä¼è§£éçæ¸ çãé£ä¹renderå è½½ç»ä»¶æ¶ï¼åçäºä»ä¹ï¼
1.æ ¹æ®ç»ä»¶æ ç¾ï¼æ¾å°ç»ä»¶
2.ç±äºæ¯ç±»å¼ç»ä»¶ï¼å建æ°çå®ä¾ï¼å¹¶éè¿è¯¥å®ä¾è°ç¨å°ååä¸çrenderæ¹æ³
3.å°èæDom转å为çå®DOM
ç±»å¼ç»ä»¶è¢«å®ä¹ä¸ºå¤æçç»ä»¶ï¼è¿ä¸æ¯Reactæå¸æçï¼Reactæ³è¦çç»ä»¶æ¯çº¯å½æ°ç»æç管éï¼é£ä¹ä¾¿å¼åºäºä½ä¸ºç®åç»ä»¶çå½æ°ç»ä»¶ã
èå½æ°ç»ä»¶ä½ä¸ºä¸ä¸ªå½æ°ï¼æ¯æ²¡æ继æ¿React.Componentçï¼ä»åªéè¦ä¸¤æ¥ 1.æ¾å°ç»ä»¶ 2.渲æç»ä»¶ ï¼æ以ä¹å°± ä¸åå¨çå½å¨æï¼ä»¥åç¶æåthisã
è¿å°±æå³çï¼å½æ°ç»ä»¶å®ç°æå ³stateç管çï¼éè¦åå©reduxï¼ç§æ¿reduxè½ä¸ç¨å°±ä¸ç¨çååï¼å ¶å®æçæ¶åè¿è®é¦çï¼å ¨å±ç®¡çæ¹ä¾¿ï¼ï¼å¾å¾ä¼å¦åè½ç®åçReactç»ä»¶åå¾ç¬¨éã
äºæ¯Reactå¢éèª.8çæ¬ä»¥æ¥ï¼æ¨åºäºç¨³å®çReact HOOKSæ¥è§£å³ä¸è¿°é®é¢ã
React约å®é©åçåç¼ä¸ºuseï¼æ以éè¦èªå®ä¹é©åæ¶ï¼ä¸è¬ä½¿ç¨use为åç¼å建é©åãé¤æ¤ä¹å¤ï¼Reacté»è®¤æä¾äºåç§é©åï¼
1ï¼useState
ç¸å½äºä¸ç§éæ声æï¼ç®çæ¯å¼å ¥ç¶æï¼æ¤æ¶çé©åä¿åç¶æãuseState()æ¥æ¶å½æ°ç¶æçåå§å¼ï¼å ·æ两个åæ°ï¼ç¬¬ä¸ä¸ªä¸ºç¶æåéï¼ç¬¬äºä¸ªä¸ºæ¹åç¶æçæ¹æ³ï¼æ¯å¦const [number,setNumber] = useState(0)
2)useEffect
å¯ä½ç¨é©åï¼ç¨æ¥æ¿ä»£çå½å¨æï¼æ常è§æ¯åæå¡å¨è¯·æ±æ°æ®
useEffect()ä½ä¸ºå¸¸ç¨çé©åä¹ä¸ï¼æ¥æ¶ä¸¤ä¸ªåæ°ï¼ç¬¬ä¸ä¸ªåæ°æ¯å½æ°ï¼ç¬¬äºä¸ªåæ°æ¯ä¸ä¸ªæ°ç»ï¼ç»åºä¾èµé¡¹ï¼æ°ç»éçå¼ä»£è¡¨éè¦çæµç对象ã
é®é¢æ¥äºï¼çå½å¨æé£éä½ç°ï¼
å½ç»ä»¶åæ°åçæ¹åæ¶ï¼useEffect()å°±ä¼æ§è¡ãç»ä»¶ç¬¬ä¸æ¬¡æ¸²ææ¶ï¼useEffect()ä¹ä¼æ§è¡ãæ¯ä¸æ¯ç¸å½äºçå½å¨æä¸ç componentDidMount() å¢ï¼èuseEffect()ä¸çreturnï¼ä¸è¬åå¨ç¬¬ä¸ä¸ªåæ°çå¼æ¥æä½åï¼ç¸å½äº componentWillUnmount() ï¼å¨ç»ä»¶å¸è½½åæ§è¡ï¼åæ¶å°¾å·¥ä½ã
3ï¼useReducer
å±äºactioné©åï¼useReducer()å°ç®åºæ°çstateï¼è¿åä¸ä¸ªæ°ç»ãä¾å¦const [state, dispatch] = useReducer(ReducerFunc, initState)
第ä¸ä¸ªå¼æ¯å½åç¶æå¼ï¼ç¬¬äºä¸ªå¼æ¯åéactionçdispatchãä¸ReduxçReducerä¸æ ·ï¼è½å¤å ±äº«ç¶æï¼ä½æ¯ä¸åä¹å¤æ¯æ²¡æ³æä¾ä¸é´ä»¶åæ¶é´æ è¡(time travel)ã
4)useContext
å ±äº«ç¶æçä¸ä¸ªé©åï¼å¨ç»ä»¶å¤é¨å»ºç«ä¸ä¸ªContextï¼å 裹ç»ä»¶æ¶ï¼å¯ä»¥å°è¢«å 裹ç»ä»¶çç¶æå ±äº«ç»ç»ä»¶å é¨è°ç¨çå ¶ä»ç»ä»¶ï¼å³ï¼
1.å¤é¨å»ºç«Context()
2.å 裹å«æç¶æçç»ä»¶1
3.å¨å ¶ä»å½æ°ç»ä»¶å é¨è°ç¨è¯¥Context()æ¶ï¼å¯ä»¥å°ç»ä»¶1çç¶æå ±äº«
ä½æ¯æä¸ç¹éè¦æ³¨æçæ¯ï¼ä½¿ç¨useContextè¿è¡çæ°æ®æµç®¡çï¼æ¯å½contextæ´æ°æ¶ï¼ææ ä½¿ç¨ å°è¯¥contextçç»ä»¶é½ä¼éæ°æ¸²æãæ以éè¦æ¹æ³å¯¹useContext()è¿è¡ä¼åï¼åå°ä¸å¿ è¦çæ´æ°ï¼ä¼åæ¹æ³å¯ä»¥åèï¼ å¦ä½ä¼é å°å¤ç使ç¨React Context 导è´çä¸å¿ è¦æ¸²æé®é¢ï¼
é¤æ¤ä¹å¤è¿æä¸äºèªå¸¦çé©åï¼æ¯å¦ï¼
5)useCallbackåuseMemo
å¯ä»¥ç¨åReactæ§è½ä¼å
reactå¾ç¦ï¼ä¸ä½æ´æ°æ°æ®ï¼renderä¾æ¬¡diffå·æ°èç¹ï¼æ¦é½æ¦ä¸ä½ãuseCallbackåuseMemoå°±æ¯æ¦ä½ä»å·æ°çæ¹æ³ã
å设Reactç»ä»¶ä¸æ个buttonï¼åæ¶å£°æäºclickæ¹æ³ï¼æ¯æ¬¡renderæ¶ï¼buttonåclickæ¹æ³é½ä¼éæ°renderãäºæ¯å¯ä»¥ä½¿ç¨useCallback()ï¼é¿å ç»ä»¶éå¤æ æä¹ç渲æã
æ¯å¦ï¼
åå æ¯ç¼åäºç¸åçå¼ç¨ï¼ä»¥æ¤é¿å äºæ ærenderã
useMemoåæ°ç¨æ³ä¸è´ï¼ä¸è¿useCallbackä¸è¬ç¨äºç¼åå½æ°ï¼useMemoç¨äºç¼å计ç®ç»æä¹ç±»ã
useCallback(fn, deps)ç¸å½äºuseMemo(()=>fn, deps)
*å¯ä»¥æ¨åºï¼ä½¿ç¨useCallbackå®ç°useMemoçæ¹æ³ï¼
useMemo(fun,...deps)
useCallback(fun(...deps), [...deps])
è¿ä¸¤è æ¯çä»·çã
webpack5loader和plugin原理解析
大家好,今天为大家解析下loader和plugin一、区别loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、8729 源码压缩等,最终一起打包到指定的文件中
plugin赋予了Webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决loader无法实现的其他事从整个运行时机上来看,如下图所示:
可以看到,两者在运行时机上的区别:
loader运行在打包文件之前plugins在整个编译周期都起作用在Webpack运行的生命周期中会广播出许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果
对于loader,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scss或A.less转变为B.css,单纯的文件转换过程
下面我们来看看loader和plugin实现的原理
Loader原理loader概念帮助webpack将不同类型的文件转换为webpack可识别的模块。
loader执行顺序分类
pre:前置loader
normal:普通loader
inline:内联loader
post:后置loader
执行顺序
4类loader的执行优级为:pre>normal>inline>post。
相同优先级的loader执行顺序为:从右到左,从下到上。
例如:
//此时loader执行顺序:loader3-loader2-loader1module:{ rules:[{ test:/\.js$/,loader:"loader1",},{ test:/\.js$/,loader:"loader2",},{ test:/\.js$/,loader:"loader3",},],},//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},使用loader的方式
配置方式:在webpack.config.js文件中指定loader。(pre、normal、postloader)
内联方式:在每个import语句中显式指定loader。(inlineloader)
开发一个loader1.最简单的loader//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};它接受要处理的源码作为参数,输出转换后的js代码。
2.loader接受的参数content源文件的内容
mapSourceMap数据
meta数据,可以是任何内容
loader分类1.同步loadermodule.exports=function(content,map,meta){ returncontent;};this.callback方法则更灵活,因为它允许传递多个参数,而不仅仅是content。
module.exports=function(content,map,meta){ //传递map,让source-map不中断//传递meta,让下一个loader接收到其他参数this.callback(null,content,map,meta);return;//当调用callback()函数时,总是返回undefined};2.异步loadermodule.exports=function(content,map,meta){ constcallback=this.async();//进行异步操作setTimeout(()=>{ callback(null,result,map,meta);},);};由于同步计算过于耗时,在Node.js这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的loader异步化。但如果计算量很小,借条 源码同步loader也是可以的。
3.RawLoader默认情况下,资源文件会被转化为UTF-8字符串,然后传给loader。通过设置raw为true,loader可以接收原始的Buffer。
module.exports=function(content){ //content是一个Buffer数据returncontent;};module.exports.raw=true;//开启RawLoader4.PitchingLoadermodule.exports=function(content){ returncontent;};module.exports.pitch=function(remainingRequest,precedingRequest,data){ console.log("dosomethings");};webpack会先从左到右执行loader链中的每个loader上的pitch方法(如果有),然后再从右到左执行loader链中的每个loader上的普通loader方法。
在这个过程中如果任何pitch有返回值,则loader链被阻断。webpack会跳过后面所有的的pitch和loader,直接进入上一个loader。
loaderAPI方法名含义用法this.async异步回调loader。返回this.callbackconstcallback=this.async()this.callback可以同步或者异步调用的并返回多个结果的函数this.callback(err,content,sourceMap?,meta?)this.getOptions(schema)获取loader的optionsthis.getOptions(schema)this.emitFile产生一个文件this.emitFile(name,content,sourceMap)this.utils.contextify返回一个相对路径this.utils.contextify(context,request)this.utils.absolutify返回一个绝对路径this.utils.absolutify(context,request)更多文档,请查阅webpack官方loaderapi文档
手写clean-log-loader作用:用来清理js代码中的console.log
//loaders/clean-log-loader.jsmodule.exports=functioncleanLogLoader(content){ //将console.log替换为空returncontent.replace(/console\.log\(.*\);?/g,"");};手写banner-loader作用:给js代码添加文本注释
loaders/banner-loader/index.js
constschema=require("./schema.json");module.exports=function(content){ //获取loader的options,同时对options内容进行校验//schema是options的校验规则(符合JSONschema规则)constoptions=this.getOptions(schema);constprefix=`/**Author:${ options.author}*/`;return`${ prefix}\n${ content}`;};loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},0手写babel-loader作用:编译js代码,将ES6+语法编译成ES5-语法。
下载依赖
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},1loaders/babel-loader/index.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},2loaders/banner-loader/schema.json
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},3手写file-loader作用:将文件原封不动输出出去
下载包
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},4loaders/file-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},5loader配置
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},6手写style-loader作用:动态创建style标签,插入js中的样式代码,使样式生效。
loaders/style-loader.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},7Plugin原理Plugin的作用通过插件我们可以扩展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务,拥有更强的构建能力。
Plugin工作原理webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack通过Tapable来组织这条复杂的生产线。webpack在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,bootdo 源码去改变生产线的运作。webpack的事件流机制保证了插件的有序性,使得整个系统扩展性很好。——「深入浅出Webpack」
站在代码逻辑的角度就是:webpack在编译代码过程中,会触发一系列Tapable钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当webpack构建的时候,插件注册的事件就会随着钩子的触发而执行了。
Webpack内部的钩子什么是钩子钩子的本质就是:事件。为了方便我们直接介入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做:hooks(钩子)。开发插件,离不开这些钩子。
TapableTapable为webpack提供了统一的插件接口(钩子)类型定义,它是webpack的核心功能库。webpack中目前有十种hooks,在Tapable源码中可以看到,他们是:
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},8Tapable还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:
tap:可以注册同步钩子和异步钩子。
tapAsync:回调方式注册异步钩子。
tapPromise:Promise方式注册异步钩子。
Plugin构建对象Compilercompiler对象中保存着完整的Webpack环境配置,每次启动webpack构建时它都是一个独一无二,仅仅会创建一次的对象。
这个对象会在首次启动Webpack时创建,我们可以通过compiler对象上访问到Webapck的主环境配置,比如loader、plugin等等配置信息。
它有以下主要属性:
compiler.options可以访问本次启动webpack时候所有的配置文件,包括但不限于loaders、entry、output、plugin等等完整配置信息。
compiler.inputFileSystem和compiler.outputFileSystem可以进行文件操作,相当于Nodejs中fs。
compiler.hooks可以注册tapable的不同种类Hook,从而可以在compiler生命周期中植入不同的逻辑。
compilerhooks文档
Compilationcompilation对象代表一次资源的构建,compilation实例能够访问所有的模块和它们的依赖。
一个compilation对象会对构建依赖图中所有模块,进行编译。在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。
它有以下主要属性:
compilation.modules可以访问所有模块,打包的每一个文件都是一个模块。
compilation.chunkschunk即是多个modules组成而来的一个代码块。入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk。
compilation.assets可以访问本次打包生成所有文件的结果。
compilation.hooks可以注册tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改。
compilationhooks文档
生命周期简图开发一个插件最简单的插件plugins/test-plugin.js
//此时loader执行顺序:loader1-loader2-loader3module:{ rules:[{ enforce:"pre",test:/\.js$/,loader:"loader1",},{ //没有enforce就是normaltest:/\.js$/,loader:"loader2",},{ enforce:"post",test:/\.js$/,loader:"loader3",},],},9注册hook//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};0启动调试通过调试查看compiler和compilation对象数据情况。
package.json配置指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};1运行指令
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2此时控制台输出以下内容:
PSC:\Users\\Desktop\source>//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};2>source@1.0.0debug>node--inspect-brk./node_modules/webpack-cli/bin/cli.jsDebuggerlisteningonws://.0.0.1:/ea-7b--a7-fccForhelp,see:/post/开发思路:
我们需要借助html-webpack-plugin来实现
在html-webpack-plugin输出index.html前将内联runtime注入进去
删除多余的runtime文件
如何操作html-webpack-plugin?官方文档
实现:
//loaders/loader1.jsmodule.exports=functionloader1(content){ console.log("hellofirstloader");returncontent;};7大家都能看得懂的源码 - ahooks 是怎么处理 DOM 的?
深入浅出ahooks源码系列文章之十三,完整文档地址如下。
本文主要探讨ahooks在处理DOM类Hooks时的规范及源码实现。
ahooks中的大部分DOM类Hooks会接收一个名为target的参数,用于表示要处理的元素。target可以接受三种类型:React.MutableRefObject(通过`useRef`保存的DOM)、`HTMLElement`、或者函数(用于SSR场景)。
目标元素支持动态变化,这在实际应用中是常见的需求。
ahooks通过`useTargetElement`方法实现目标元素的获取,兼容第一点的参数规范。
`useEffectWithTarget`和`useLayoutEffectWithTarget`是针对第二点,支持target动态变化的实现,分别调用`createEffectWithTarget`函数。
在`packages/hooks/src/utils/useEffectWithTarget.ts`和`packages/hooks/src/utils/useLayoutEffectWithTarget.ts`中,`useEffect`和`useLayoutEffect`被调用,它们在内部封装处理逻辑。
`createEffectWithTarget`是核心函数,用于创建相应的副作用效果。
总结,ahooks通过规范的输入输出,支持丰富的DOM操作场景,内部进行封装处理,使用户能快速上手并灵活运用。
本文已收录至个人博客,欢迎关注。
husky 源码浅析
解析 Husky 源码:揭示 Git 钩子的奥秘
前言
在探索 Husky 的工作原理之前,让我们先回顾一下自定义 Git Hook 的概念。通过 Husky,我们能够实现对 Git 钩子的指定目录控制,灵活地执行预先定义的命令。本篇文章将带领大家深入 Husky 的源码,揭示其工作流程和使用 Node.js 编写 CLI 工具的要点。Husky 工作流程
从 Husky 的安装流程入手,我们能够直观地理解其工作原理。主要步骤如下:执行 `npx husky install`。
通过 Git 命令,将 hooks 目录指向 Husky 提供的目录。
确保新拉取的仓库在执行 `install` 后自动调整 Git hook 目录,以保持一致性。
在这一过程中,Husky 通过巧妙地添加 npm 钩子,确保了新仓库在安装完成后能够自动配置 Git 钩子路径,实现了跨平台的统一性。源码浅析
bin.ts
bin.ts 文件简洁明了,核心在于模块导入语法和 Node.js CLI 工具的实现。它支持了导入模块的两种方式,并解释了在 TypeScript 中如何灵活使用它们。npm 中的可执行文件
通过配置 package.json 的 `bin` 字段,我们可以将任意脚本或工具作为 CLI 工具进行全局安装,以便在命令行中直接调用。Husky 利用这一特性,为用户提供了一个简洁的安装流程和便捷的调用方式。获取命令行参数
在 Node.js 中,`process.argv` 提供了获取命令行参数的便捷方式。通过解析这个数组,我们可以轻松获取用户传递的参数,实现命令与功能的对应。index.ts
核心逻辑在于安装、配置和卸载 Git 钩子的函数。Husky 的代码结构清晰,易于理解。其中,`core.hooksPath` 的配置和权限设置(如 `mode 0o`)是关键步骤,确保了 Git 钩子的执行权限和统一性。husky.sh
作为初始化脚本,husky.sh 执行了一系列环境配置和日志输出操作。其重点在于根据不同 Shell 环境(如 Zsh)进行适配性处理,确保 Husky 在各类环境中都能稳定运行。结语
Husky 的实现通过 `git config core.hooksPath` 和 `npm prepare` 钩子的巧妙结合,不仅简化了 Git 钩子的配置流程,还提升了代码的可移植性和一致性。使用 Husky,开发者能够更灵活地管理 Git 钩子,提升项目的自动化程度。带你了解vue3的自定义hooks
Vue3,借鉴React Hooks,发展出的Composition API,允许我们自定义封装hooks。接下来,我们将使用TypeScript风格封装一个简单的计数器逻辑hooks(useCount)。
使用方式如下:
效果如下:
以下是useCount的源码:
代码中,首先定义了hooks函数的入参类型和返回类型,使用接口来指定Range和Result参数,这样可以避免IDE提示错误,确保业务逻辑正确。接着,在增加inc和减少dec的两个函数中加入了类型守卫检查,确保传入的delta类型值在所有场景下都能被正确识别,防止可能出现的类型检查失效问题。最后,代码简洁明了,实现了计数器的增减逻辑。
如果你对Vue3和Composition API感兴趣,欢迎关注公众号:小何成长,这里分享的都是我曾经踩过的坑和学到的知识。希望我们能在编程的道路上共同进步。
Java ShutDown Hook介绍和实例
Java的ShutDown Hook机制允许开发者在Java虚拟机(JVM)即将关闭之前执行一些清理或终止操作。这种机制提供了一个钩子,让开发者在JVM关闭时捕获关闭事件并执行相应的逻辑。在源码中,主要涉及两个类:ApplicationShutdownHooks和Runtime。
添加Hook实际上是在ApplicationShutdownHooks的静态Map中放入新的线程。这些线程在程序退出时被运行,每个带有Hook任务的线程的start()方法才被执行。由于Hook之间是独立的线程,它们的执行顺序没有关系。主线程调用每个Hook线程的join方法,确保主线程等待所有Hook执行完毕后退出。
有一些情况无法添加Hook:1. ApplicationShutdownHooks已经在调用Hook时,hooks会被置为null,无法添加新Hook。2. Hook的Thread不能是已经在运行状态的线程。3.因为储存的Hook是根据线程是否相同来判断的,所以不能添加相同的Hook。
ShutDown Hook不适用于处理非正常退出的情况,如kill -9命令。同时,使用ShutDown.halt和kill -9一样都是强制退出,不会给Hook执行的机会。
下面是一个简单的ShutDown测试例子。通过GitHub地址可以找到这个例子。
ShutDown的使用相对简单,网上有很多关于Spring和Dubbo等开源框架使用ShutDown Hook的例子,主要应用于资源释放和清理工作。
需要注意的是,Hook的执行顺序是无序的,不能重复添加相同的Hook,且已经执行的Hook不能再创建新的Hook。
在平时的应用中,ShutDown Hook的使用频率较低。一个有用的场景是在JVM挂掉时,Hook中可以给监控程序发送通知,如发邮件等,以便技术人员进行处理。
关于ShutDown Hook的相关资料,可以参考Oracle官网资料、Java Shutdown Hook的场景使用和源码分析、以及Adding Shutdown Hooks for JVM Applications等。