一篇文章开发todolist
【前言】
本文实战开发一个todolist,
基于以下技术栈:
react:开发ui, https://reactjs.org/
webpack,构建前端项目, https://webpack.js.org/
localStorage,存储数据, https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage
lerna,管理代码项目, https://lerna.js.org/
pm2,生产环境部署nodejs服务, https://pm2.keymetrics.io/
【使用lerna创建项目】
本次实战涉及两个项目:
dishi-server,nodejs服务端项目,简单的部署和跳转到首页
dishi-web,todolist,前端ui部分的项目
以上两个项目使用lerna进行管理,
lerna相关的使用可以看这篇文章: 一篇文章学会lerna
lerna安装和初始化
-- 安装
npm i -g lerna
-- 初始化,在项目下
lerna init
lerna创建项目
通过lerna的create命令,创建两个项目dishi-server和dishi-web
lerna create dishi-web
按提示会自动在packages文件夹下创建好对应的项目

lerna使用independent模式
默认生成的lerna.json内容如下
{
"packages": [
"packages/*"
],
"version": "0.0.0"
}
在这个模式下,packages下所有项目的版本统一管理,
这样不太方便,修改为每个项目版本单独管理,如下
{
"packages": [
"packages/*"
],
"version": "independent"
}
package.json修改为private模式
由于dishi-web,dishi-server并不对外发布npm包,
所以修改package.json内的private为true,
否则每次lerna publish时会自动发布npm包,
{
"name": "dishi-web",
"private": true,
"version": "0.0.0",
"scripts": {},
"dependencies": {}
}
lerna publish提交项目
修改完以上内容后,可以git add,git cz提交到本地,
然后使用lerna的publish能力提交项目,
会自动打tag,加push
lerna publish
选择完每个项目对应版本后,会自动提交
lerna添加常用命令
在根项目下的package.json中添加lerna的常用命令
clean:清理packages下每个项目的依赖
init:安装packages下每个项目的依赖,添加—hoist参数代表将所有项目的依赖提取到根项目下
"scripts": {
"clean": "lerna clean -y",
"init": "lerna bootstrap --ignore-prepublish --hoist"
},
以上代码见: https://github.com/uikoo9/dishi-monorepo/tree/dishi-web%400.0.2
【开发后端项目】
本次实战中后端项目比较简单,只需要跳转一个html即可
这里使用一个简单的nodejs下web框架实现, https://qiao-z.vincentqiao.com/#/
qiao-z:安装
npm i qiao-z
qiao-z:controller
qiao-z会自动扫描本地以Controller.js结尾的文件,并注册路由,
注册一个get请求,/*代表所有请求都会命中这个路由
/**
* index controller
*/
module.exports = function (app) {
// index
app.get('/*', function (req, res) {
res.render('./views/index.html');
});
};
qiao-z:启动
其中app.js代码如下
'use strict';
// port
const port = require('./server/config.json').port;
// app
const app = require('qiao-z')();
// listen
app.listen(port);
启动
node app.js
启动后即可本地访问 http://localhost:9011/ ,效果如下

使用pm2在生产环境部署服务
生产环境下不适合用node启动,需要使用pm2, https://pm2.keymetrics.io/
pm2的使用,可以看这篇文章的介绍:一篇文章学会pm2
安装pm2
npm i -g pm2
pm2运行服务
pm2 start app.js --name dishi
效果如下

nginx反向代理及acme申请https证书
在自己服务器上配置一个nginx反向代理,
以及使用acme.sh申请https证书,
acme.sh的使用,可以看这篇文章的介绍:centos随笔04:acme.sh申请https证书
以上代码见: https://github.com/uikoo9/dishi-monorepo/tree/dishi-server%400.0.3
【搭建前端项目】
前端项目基于react搭建,
构建使用qiao-webpack, https://qiao-webpack.vincentqiao.com/#/
webpack的使用,可以看这篇文章:一篇文章学会Webpack5.x
qiao-webpack:安装
npm i -D qiao-webpack
qiao-webpack:推荐配置
webpack的配置比较多,这里推荐使用如下的配置和结构
|--dishi-web
|--webpack
|--common
|--alias.js
|--entry.js
|--dev
|--plugins.js
|--server.js
|--prod
|--output.js
|--plugins.js
|--template
|--mobile.html
|--pc.html
|--qiao-webpack.dev.js
|--qiao-webpack.prod.js
alias.js是webpack中别名设置,方便在代码内引入其他资源和文件
'use strict';
// path
const path = require('path');
// alias
module.exports = {
'@components': path.resolve(__dirname, '../../src/components'),
'@styles': path.resolve(__dirname, '../../src/styles'),
'@utils': path.resolve(__dirname, '../../src/utils'),
'@views': path.resolve(__dirname, '../../src/views'),
};
entry.js是webpack的文件入口,
'use strict';
// entry
module.exports = {
// dishi
'dishi-index-pc': '@views/dishi/dishi-index-pc-view.jsx',
'dishi-index-mobile': '@views/dishi/dishi-index-mobile-view.jsx',
};
plugins.js是webpack设置plugin相关的参数,
本文中涉及的,一个是css压缩相关,一个是html生成相关
css压缩相关的,对应mini-css-extract-plugin,在qiao-webpack中已经内置
// css
{
type: 'css',
filename: '[name].[contenthash:8].css',
chunkFilename: '[id].[contenthash:8].css',
ignoreOrder: true,
},
html生成相关的,对应html-webpack-plugin,在qiao-webpack中已经内置
{
type: 'html',
inject: 'body',
title: 'dishi-index-pc',
chunks: ['dishi-index-pc'],
filename: 'dishi-index-pc.html',
template: pcPath,
},
dev下的server.js是webpack中devServer相关配置
'use strict';
// path
const path = require('path');
// static path
const distPath = path.resolve(__dirname, '../../static');
/**
* dev server
*/
module.exports = {
port: 5277,
static: distPath,
};
prod下的output.js是webpack中output相关配置
这里会将html构建到dishi-server/views文件夹,将静态文件构建到dishi-server/static文件夹
'use strict';
// path
const path = require('path');
// static path
const distPath = path.resolve(__dirname, '../../../dishi-server/static');
// output
module.exports = {
filename: '[name].[contenthash:8].js',
path: distPath,
clean: true,
};
template下是两个html模板
qiao-webpack:构建
添加两个脚本
"dev": "qwebpack dev ./webpack/qiao-webpack.dev.js",
"build": "qwebpack build ./webpack/qiao-webpack.prod.js",
这里执行build命令,效果如下

对应的html和静态文件生成到了dishi-server下
这样就可以在dishi-server内访问构建好的html和静态文件,
这样做的好处是,
前端项目dishi-web对应前端开发,调试可以使用webpack dev本地调试,
后端项目dishi-server对应后端开发,直接使用前端构建好后的html和静态文件

qiao-cos:安装
如上所述,目前已经可以直接将dishi-server部署服务器了,
但是这里建议不要直接将静态文件,也就是css,js等部署到应用服务器,避免应用服务器的带宽压力
建议将静态文件上传到文件服务器,例如腾讯云的cos,阿里云的oss等
这里使用腾讯云的cos,开发了一个工具:qiao-cos, https://code.insistime.com/#/qiao-cos-cli
npm i -D qiao-cos
qiao-cos:上传
在dishi-web项目中添加脚本
"upload": "qcos fo ./config.json ../dishi-server/static 21_dishi_ls/static"
执行upload命令,效果如下

将对应的html文件,即dishi-server也部署后,访问效果如下

以上代码见: https://github.com/uikoo9/dishi-monorepo/tree/dishi-web%400.0.3
【开发前端项目】
最终效果
1.在input中输入内容,回车添加todo
2.input下展示目前的todolist
3.点击某一个todo,完成todo项并移除

数据结构
每一个todo的数据结构如下,其中key为时间戳
{
key: 1665400441283,
value: '读一本书'
}
todo列表的数据结构是数组,如下
[
{
key: 1665400441283,
value: '读一本书',
},
];
qiao.ls.js:保存数据
数据使用localStorage保存,
具体使用可以看这篇文章:一篇文章学会localStorage
这里使用qiao.ls.js进行操作, https://code.insistime.com/#/qiao.ls.js
由于直接按key,value保存,在localStorage中会比较分散,
这里聚集一下,保存到命名空间todos,最终效果如下

添加todo,删除todo,获取todos的代码如下
// qiao
import { ls, cache } from 'qiao.ls.js';
/**
* add todo
* @param {*} todo
*/
export const addTodo = (key, todo) => {
cache('todos', key, todo);
};
/**
* del todo
* @param {*} key
*/
export const delTodo = (key) => {
cache('todos', key, null);
};
/**
* get todos
* @returns
*/
export const getTodos = () => {
// check
const todos = ls('todos');
if (!todos) return [];
// res
const res = [];
for (const [key, value] of Object.entries(todos)) {
res.push({
key: key,
value: value,
});
}
return res;
};
实现todo input
1.在回车的时候进行保存
2.首先清空input
3.添加todo到localStorage内
4.刷新todo列表
<Input
type="text"
placeholder={'todo...'}
value={todo}
onChange={(e) => setTodo(e.target.value)}
onKeyPress={(e) => {
if (e.nativeEvent.keyCode === 13) {
setTodo('');
addTodo(Date.now(), todo);
setTodos(getTodos());
}
}}
/>
实现todo list
1.遍历todos,显示每一个todo
2.点击时从localStorage删除todo
3.刷新todo列表
{
todos &&
todos.map((item) => {
return (
<div
className="dishi-item"
key={item.key}
onClick={() => {
delTodo(item.key);
setTodos(getTodos());
}}
>
{item.value}
</div>
);
});
}
以上代码见: https://github.com/uikoo9/dishi-monorepo/tree/dishi-web%400.0.8
【总结】
以上就实现了一个基于localStorage本地存储数据的todo list,
最终效果地址: https://insistime.com/dishi-ls
最终代码见: https://github.com/uikoo9/dishi-monorepo/tree/localstorage
1.使用lerna管理项目, 一篇文章学会lerna
2.使用qiao-z开发nodejs服务端代码, https://qiao-z.vincentqiao.com/#/
3.使用pm2部署nodejs服务端代码, 你所不知道的pm2
4.使用react开发前端项目, https://reactjs.org/
5.使用webpack构建前端项目,一篇文章学会Webpack5.x
6.使用qiao-webpack快速构建前端项目, https://qiao-webpack.vincentqiao.com/#/
7.使用腾讯云cos托管静态文件, https://code.insistime.com/#/qiao-cos-cli
8.使用localStorage保存本地数据,一篇文章学会localStorage
9.使用qiao.ls.js操作localStorage数据, https://code.insistime.com/#/qiao.ls.js