前言
学习目标:实现一个简单的 node 应用:todo list。
功能主要有:
- 添加新任务
- 清空任务列表
- 展示所有任务
- 操作任务
- 修改任务标题
- 修改任务状态
- 删除单个任务
一、环境安装
- node
- npm
二、项目初始化
- 新建文件夹
mkdir node-todo-1
- 进入文件目录
cd node-todo-1
- 初始化包文件
npm init -y
- 新建文件
touch index.js
三、commander.js
官网链接:github.com/tj/commande…
1. 安装依赖
yarn add commander
2. 选项
// index.js const { program } = require('commander'); program .option('-a --add', 'add an item') .option('-d, --delete', 'delete an item'); program.parse(process.argv);
.option(flags, desc) 用于定义选项
3. 看看效果
-h 是 --help 的缩写,默认用于显示帮助列表。
node index.js -h
4. 命令
看着文档,复制粘贴,摸索一下。
// index.js const { program } = require('commander'); // 选项 program .option('-a --add', 'add an item') .option('-d, --delete', 'delete an item'); // 命令 program .command('add') // 在终端中输入:node index add task1 task2 task3 .argument('<tasks...>', 'taskNameList') // 多参数处理 .description('The cmd is used to add a task or more tasks.') // 命令描述 .action((tasks) => { console.log(tasks); // tasks is arguments list // 将参数列表合并处理成字符串 const words = tasks.join(' '); console.log(words); }) program.parse(process.argv);
.command(nameAndArgs) 用于定义命令,命令名字和输入的参数可以写在一起!
.argument(arg) 用于定义参数,注意多参数的情况要用'<args...>'的形式!
.description(desc) 是命令的描述
.action(fn) 是输入命令后执行的回调
- 执行看看,node index add task1 task2 task3
四、回调的分离
将 index.js 重命名为 cli.js,区别在于,将 .action() 的内容分离到 index.js 中统一管理。
index.js 中存放各种 api。(添加新任务、清除任务列表、展示所有任务等等)
1. Linux 命令
cat ~/.todo 表示:查看根目录下 .todo 文件中的内容
rm ~/.todo 表示:删除跟目录下的 .todo 文件
2. 添加新任务(功能 1)
- 引入index.js,使用其中的方法。
// cli.js const { program } = require('commander'); const api = require('./index') // 选项 program .option('-a --add', 'add an item') .option('-d, --delete', 'delete an item'); // 命令 program .command('add') // 在终端中输入:node index add task1 task2 task3 .argument('<tasks...>', 'taskNameList') // 多参数处理 .description('The cmd is used to add a task or more tasks.') // 命令描述 .action((tasks) => { const words = tasks.join(' '); api.add(words); // 执行add方法,添加新任务到数据库! }) program .command('clear') .description('The cmd is used to clear all tasks.') .action((tasks) => { // 将参数列表合并处理成字符串 const words = tasks.join(' '); console.log(words); }) program.parse(process.argv);
- add() 方法是 index.js 中的方法,它定义了触发 add Commander 命令时的处理逻辑。
- 读取数据库文件 fs.readFile()
- 添加一个新任务
- 将新任务写入文件 fs.writeFile()
// index.js const homedir = require('os').homedir(); // 获取home目录 const home = process.env.HOME || homedir; // 先从系统变量中获取 const path = require('path'); const dbPath = path.join(home, '.todo'); // 数据库路径(拼接而来的) const fs = require('fs'); module.exports.add = (taskContent) => { // 1.读取文件 fs.readFile(dbPath, {flag: 'a+'}, (err, data) => { if (err) { console.log(err); } else { let list; try { // 此处的 data.toString() 应是一个JSON字符串,需要转换为真的数组! list = JSON.parse(data.toString()); } catch (error) { // 如果报错,说明没有这样的数据,就创建一个新的数组! list = []; } // 2.添加一个任务 const task = { title: taskContent, completed: false } list.push(task); // 将新建的任务推进 list 中 // 3.将任务存储到文件 const string = JSON.stringify(list); // 将 list 转换为 JSON 字符串 // 将数据写入文件中 fs.writeFile(dbPath, string, (err) => { if (err) { console.log(err); return; } }) } }) }
Node.js 内置了很多模块,可以获取宿主环境中的某些信息。文档查阅:devdocs.io/
- os 模块是操作系统模块,os.homedir() 可以获取系统的根目录路径。
- process.env.HOME 可以获取进程中设置的 HOME 变量的对应路径。
- path 模块是路径模块,path.join(...path) 可以拼接多个路径。
- fs 模块是文件模块,
- 文件读取:fs.readFile(filePath, options, (err, data) => {}) devdocs.io/node~14_lts…
- 文件写入:fs.writeFile(filePath, data, (err) => {}) devdocs.io/node~14_lts…
最后,尝试一下:
3. 方法封装之面向接口编程
在“添加任务”的三个步骤中,希望一个步骤就是一条执行语句,而非现在这样一堆代码冗在那里!
也就是说先设计好接口,然后封装代码,以后使用某个功能时,只需要调用对应的接口即可。
- 数据库中存放读写操作:
- 注意 fs.readFile() 以及 fs.writeFile() 都是异步操作,因此不可以直接 return 结果。
- 利用 Promise 对象改写异步操作,
- 并且在出错时,直接 return reject(err);(直接返回失败的理由,不执行下面的代码。)
// db.js const homedir = require('os').homedir(); const home = process.env.HOME || homedir; const path = require('path'); const dbPath = path.join(home, '.todo'); const fs = require('fs'); const db = { // 1. 读取文件 read (path = dbPath) { return new Promise ((resolve, reject) => { fs.readFile(path, {flag: 'a+'}, (err, data) => { if (err) return reject(err); let list; try { list = JSON.parse(data.toString()); } catch (error) { list = []; } resolve(list); }) }) }, // 2. 写入文件 write (list, path = dbPath) { return new Promise((resolve, reject) => { const string = JSON.stringify(list); fs.writeFile(path, string, (err) => { if (err) return reject(err); resolve(); }) }) } } module.exports = db;
- 接口调用
// index.js const db = require('./db'); module.exports.add = async (taskContent) => { // 1.读取文件 const list = await db.read(); // 2.添加一个任务 list.push({title: taskContent, completed: false}); // 3.将任务写入文件 await db.write(list); }
最后,尝试一下:
4. 清除任务列表(功能 2)
直接写入一个空的数组即可:
// cli.js const { program } = require('commander'); const api = require('./index') // 选项 program .option('-a --add', 'add an item') .option('-d, --delete', 'delete an item'); // 命令 // 命令1:添加新任务 program .command('add') // 在终端中输入:node index add task1 task2 task3 .argument('<tasks...>', 'taskNameList') // 多参数处理 .description('The cmd is used to add a task or more tasks.') // 命令描述 .action((tasks) => { const words = tasks.join(' '); api.add(words) .then(() => { console.log('添加成功!'); }) .catch(err => { console.log('添加失败!错误原因:' + err); }); }) // 命令2:清空任务列表 program .command('clear') .description('The cmd is used to clear all tasks.') .action(() => { api.clear() .then(() => { console.log('清除成功!'); }) .catch(err => { console.log('清除失败!错误原因:' + err); }); }) program.parse(process.argv);
// index.js const db = require('./db'); // 添加新任务 module.exports.add = async (taskContent) => { // 1.读取文件 const list = await db.read(); // 2.添加一个任务 list.push({title: taskContent, completed: false}); // 3.将任务写入文件 await db.write(list); } // 清空任务列表 module.exports.clear = async (title) => { await db.write([]); }
最后,尝试一下:
5. 展示所有任务(功能 3)
process.argv 表示用户输入在终端的参数个数,官网描述更好:
当用户仅输入 node cli.js 两项参数时,展示所有任务:
// cli.js // 用户直接调用 node cli.js if (process.argv.length === 2) { void api.showAll(); } else { program.parse(process.argv); }
// inidex.js // ... // 展示所有任务 module.exports.showAll = async () => { // 1. 读出之前的任务 const list = await db.read(); // 2. 打印直接的任务 list.forEach((task, index) => { console.log(`${task.completed ? '[x]' : '[_]'} ${index + 1} -> ${task.title}`); }); }