当前位置: 首页 > Web前端 > HTML

【自学总结】写一个基于node的todo命令行工具

时间:2023-04-02 19:57:06 HTML

点赞再看,微信搜索【大千世界】,关注B站【前端小智】【前端小智】此人没有大厂出身,但有向上积极的心态。.本文已收录到GitHubhttps://github.com/qq44924588...,文章已分类,也整理了很多我的文档和教程资料。最近开源了一个Vue组件,但是还不够完善。欢迎大家一起完善,也希望大家能给个star支持一下。谢谢。github地址:https://github.com/qq44924588...前言最后公司官网需要用node重构,所以这段时间开始学习node系列知识。一般新事物入门,就是做一个todo函数。所以这里也是写一个基于node的todo命令行工具。最终成品:https://github.com/qq44924588...命令行相关库commander.jsnode.js命令行接口的完整解决方案是在node.js中用Ruby实现Commander。为命令行程序提供强大的参数解析能力。它是由TJ编写的工具包。它的作用是让node命令行程序的制作更加简单。具体使用可以参考github:https://github.com/tj/command...inquirer.jsinquirer.js一个用户与命令行交互的工具当开始通过npminit创建package.json时,有会和用户进行大量的交互(当然你也可以忽略通过参数的输入);现在大多数项目都是通过脚手架创建的。使用脚手架时,最明显的是与命令行进行交互。和用户交互,这时候就不得不提inquirer.js了。具体使用方法可以参考github:https://github.com/SBoudrias/...初始化1.运行yarninit-y,添加package.json文件2.运行yarnaddcommander,导入commander库3.创建cli.js使用Commander创建子命令,这里指的是子命令的写法,具体参考项目constprogram=require('commander');program.command('add').description('addatask')在github上。action(()=>{console.log('添加任务');});4、运行nodecliadd,node+文件名+子命令,输出add任务5、数据存储,这里没有使用数据库,直接使用文件,为此,我们需要封装文件相关的读写操作,创建db.js,内容如下consthomedir=require('os').homedir()//先获取用户设置的homeconsthome=process.env.HOME||homedirconstp=require('path')constfs=require('fs')constdbPath=p.join(home,'.todo')constdb={read(path=dbPath){//读取上一个任务returnnewPromise((resolve,reject)=>{fs.readFile(path,{flag:'a+'},(error,data)=>{if(error)returnreject(error)letlisttry{list=JSON.parse(data.toString())}catch(error2){list=[]}resolve(list)})})},write(list,path=dbPath){returnnewPromise((resolve,reject)=>{conststring=JSON.stringify(list)fs.writeFile(path,string,(error)=>{如果(error)returnreject(error)resolve()})})}}module.exports=db这里我们在home环境变量路径下创建了一个.todo文件用来保存我们输入的数据关于nodeApi的查询,这里推荐一个网站https://devdocs.io/,可以很方便的找到对应方法的使用方法,如下图:6.任务的增删改查可以看作是一个接口,所以创建index.js来关闭这些操作,也叫面向接口编程。这里我们举例说明如何添加一个方法:constdb=require('./db.js')module.exports.add=async(title)=>{//读取前面的任务constlist=awaitdb.read()//为其添加标题tasklist.push({title,done:false})//将任务存储到文件中awaitdb.write(list)}testinputnodecli.jsaddbuywaterview:cat~/.todooutput[{"title":"buywater","done":false}]7.显示所有任务在cli.js中添加一个subCommandlist:program.command('list').description('显示所有待办事项列表').action((...args)=>{api.showAll().then(res=>{//console.log('显示任务完成!')},()=>{console.log('显示任务失败!')})});在index.js中实现showAll()方法constinquirer=require('inquirer')module.exports.showAll=async()=>{//读取之前的任务constlist=awaitdb.read()//对任务进行操作inquirer.prompt({type:'list',name:'index',message:'请选择您要操作的任务?',choices:[{name:'Exit',value:'-1'},...list.map((任务,索引)=>{r返回{name:`${task.done?'[x]':'[_]'}${index+1}-${task.title}`,value:index.toString()}}),{name:'+创建任务',value:'-2'}]}).then(answer=>{constindex=parseInt(answer.index)if(index>=0){//选择了一个任务inquirer.prompt({type:'list',name:'行动',选择:[{name:'quit',value:'quit'},{name:'completed',value:'markAsDone'},{name:'Unfinished',value:'markAsUndone'},{name:'ChangeTitle',vaule:'updateTitel'},{name:'Delete',value:'remove'}]}).then(answer2=>{switch(answer2.action){case'markAsDone':list[index].done=truedb.write(list)breakcase'markAsUndone':list[index].done=falsedb.write(list)breakcase'updateTitle':inquirer.prompt({类型:'input',name:'title',message:'Newtitle',default:list[index].title}).then(answer=>{list[index].title=answer.titledb.write(列表)})breakcase'remove':list.splice(index,1)db.write(list)break}})}elseif(index===-2){//createtaskinquirer.prompt({type:'input',name:'title',message:'Inputtasktitle',}).then(answer=>{list.push({title:answer.title,done:false})db.write(list)})}});}然后运行??nodecli.jslist来显示所有任务。这里列出的方法主要是分享如何优化这段冗长的代码优化代码封装功能,重命名,提高代码可读性我们先把选择任务的逻辑,也就是index>=0全部抽取出来,放到函数askForAction中,然后把每个switchcase的逻辑封装成一个Function函数askForAction(list,index){inquirer.prompt({type:'list',name:'action',message:'Pleasechooseanaction',choices:[{name:'Exit',value:'quit'},{name:'Completed',value:'markAsDone'},{name:'Unfinished',value:'markAsUnDone'},{name:'ChangeTitle',value:'updateTitle'},{name:'delete',值:'remove'},]}).then(answer=>{switch(answer.action){case'markAsDone':markAsDone(list,index)break;case'markAsUnDone':markAsUnDone()break;case'updateTitle':updateTitle()break;case'remove':remove()break;}})}上面的 case条件和我们的函数是一致的,所以可以进一步优化,如下:functionaskForAction(list,index){//选择一个任务constactions={markAsDone,markAsUnDone,updateTitle,updateTitle,remove}inquirer.prompt({type:'list',name:'action',message:'请选择一个动作',choices:[{name:'Quit',value:'quit'},{name:'Completed',value:'markAsDone'},{name:'未完成',value:'markAsUnDone'},{name:'ChangeTitle',value:'updateTitle'},{name:'Delete',value:'remove'},]}).then(answer=>{constaction=actions[actions.action]action&&action(list,index)})}在重构过程中,大部分开关可以优化为更简单的模式并发布到npm配置package.json{"name":"hi-node-todo","bin":{"t":"cli.js"},"files":["*.js"],"version":"0.0.1","main":"index.js","license":"MIT","dependencies":{"commander":"^6.2.1","inquirer":"^7.3.3"}}bin有很多包一个或多个可执行文件希望放在PATH中。(实际上,这就是使npm可执行的原因)。要使用此功能,请为`package.json`中的bin字段提供命令名称到文件位置的`map`。在初始化的时候,npm会把他链接到`prefix/bin`(全局初始化)或者`./node_modules/.bin/`(局部初始化)。{"bin":{"npm":"./cli.js"}}当你初始化npm时,它会创建一个指向/usr/local/bin/npm的cli.js脚本的符号链接。如果你只有一个可执行文件,并且名称与包名相同。那么你可以只使用一个字符串,例如{"name":"my-program","version":"1.2.5","bin":"./path/to/program"}//相当于{"name":"my-program","version":"1.2.5","bin":{"my-program":"./path/to/program"}}files是一个包含An项目中的文件数组。如果命名了一个文件夹,则该文件夹中的文件也将被包括在内。(除非被其他条件省略)您还可以提供一个.npmignore文件以保留文件,即使它们包含在文件字段中也是如此。它实际上就像.gitignore。{"files":["bin/","templates/","test/"]}mainmain字段是一个模块ID,指向你程序的主工程。也就是说,如果你的包的名称是foo,并且用户安装了它,然后require("foo"),那么你的主模块的exports对象将被返回。这应该是相对于根目录的模块ID。对于大多数模块来说,它非常有意义,没有别的。{"main":"bin/index.js"}发布:npmaddusernpmpublish用户使用:yarnglobaladdnode-todostlist版本升级:增加查看版本的功能cli.jsconstpkg=require('./package.json')program.version(pkg.version)测试,输入nodecli.js--version,输出0.0.1republish,当直接npmpublishuseruse时,updatepackageyarnglobaladdnode-todos@0.0.2,runt--version,输出0.0.2End~,我是小智,下期见!代码部署后可能存在的bug,无法实时获知。事后为了解决这些bug,花费了大量的时间在日志调试上。顺便推荐一个好用的bug监控工具Fundebug。交流文章每周更新。可以微信搜索“大千世界”阅读即时更新(比博文早一两篇)。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi。我的文档写了很多,欢迎Star和完善,大家可以参考考试中心面试复习,关注公众号,后台会回复福利,福利可以看到,你知道。