Logo Vincent
返回文章列表

一篇文章开发todolist

Web
一篇文章开发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

© 2026 vincentqiao.com . 保留所有权利。