All Articles

TS + React + Antd + Koa2 + MongoDB 打造 TodoList 全栈应用

前言

JS 作为一门弱类型语言,时常受到 Java,C# 等老牌编程语言的“歧视”,并且诞生之初,只能运行在浏览器端上,被戏称为“玩具语言”。早前 Node.JS 的出现,让 JS 向后端领域进军。如今 TypeScript 的出现则让它焕发了新的生命,从本质上向这个语言添加了可选的静态类型和基于类的面向对象编程,作为JavaScript的一个超集,与当下最流行的前端框架 React 有着天生的契合度。目前可谓大红大紫。

2019年,我们有必要全面掌握 TypeScript,并应用到实践中。

本项目以功能清晰的 TodoList 为切入点,结合 React全家桶、Antd 打造代码健壮性强,用户界面简洁的前端应用,以 Koa2 + MongoDB 为核心构建可维护性高的后端服务。

是一个全栈应用 TodoList 的最佳实践。大家可以自己动手试一试。

项目预览

话不多上,我们先看实际效果。 线上访问地址,大家可以自行注册体验。(自己搭建的阿里云服务器,已设置 HTTPS 安全,不过由于是学生版,首屏加载速度较慢,请耐心等待,后续会进行优化)

技术栈

  • 前端

    • TypeScript(使 JS 成为强类型语言)
    • React(当下最流行的前端框架)
    • Axios(处理 HTTP 请求)
    • Ant-Design(UI 框架)
    • React-Router(处理页面路由)
    • Redux(数据状态管理)
    • Redux-Saga(处理异步 Action)
  • 后端

    • Koa2(基于 Node.js 平台的下一代 web 开发框架)
    • MongoDB(非关系型数据库)

功能点

  • RESTful 风格接口设计
  • HTTP请求封装,错误处理
  • 组件化,代码分层
  • 用户登录注册
  • Todo 的关键词查询
  • Todo 内容修改
  • Todo 状态更改
  • Todo 记录删除

实践分析

TypeScript

TS最基础的赋予我们给JS变量设置类型的能力,还带来了接口,泛型,枚举,类,装饰器,命名空间等全新内容。

let a: number = 1; // int a = 1;
let b: string = 'Hello'; // string b = 'Hello'
let arr: number[] = [1, 2, 3]; //  int arr[] = {1,2,3};

TS可以约束我们的传参,变量类型,接口类型,从而避免在开发时产生不必要的错误。小伙伴可以观看官方文档

/interface/UserState.ts为例,导出了一个接口

 export interface UserState {
    user_id?: string; // ?代表可选
    username?: string;
    err_msg?: string;
}

user继承UserState接口,所以 会有属性推导,而在JS中,我们需要自己输入user.err_msg,繁琐且易出错。 在React,我们主要通过无状态组件function,和有状态组件class来构建应用,包括props的传递,函数的传参,类的继承等都非常需要类型约定,可以说TS和React“天生一对”,使用他们,我们的代码健壮性提高了一个档次。

redux状态管理

状态管理是目前构建单页应用中不可或缺的一环,简单应用使用组件内 State 方便快捷,但随着应用复杂度上升,会发现数据散落在不同的组件,组件通信会变得异常复杂,这时候就需要redux来管理全局状态。它遵循三个原则:

  • 组件数据来源于 Store,单向流动
  • 只能通过触发 action 来改变 State,通过定义actionTypes,做到全局惟一
  • Reducer 是纯函数

由于Reducer只能是纯函数(简单来说,一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。),而当处于请求(Fetch)场景时,Action需要发起异步请求,包含了副作用,所以使用借助Redux-Saga来处理异步Action,处理后返回成功的同步Action并触发,此时是一个纯函数,最终改变store数据。

以FETCH_TODO(获取Todo资源)为例,数据流向如下图所示:

接口设计

由于采用的是前后端分离开发,我们通过约定接口来进行数据交换,而当下最流行的便是 RESTful 风格接口,它有以下几个要点:

  • 根据请求目的,设置对应 HTTP Method,如 GET 对应读取资源(Read),PUT 对应更新资源(Update),POST 对应创建资源(Created),DELETE 代表删除资源(Delete),对应数据库 CRUD 操作
  • 动词表示请求方式,名词表示数据源,一般采用复数形式 如 GET/users/2 获取 id 为2的用户
  • 返回相应的HTTP状态码,常见的有200 OK请求成功,201 CREATED创建成功,202 ACCEPTED更新成功,204 NO CONTENT删除成功,401 UNAUTHORIZED未授权,403 FORBIDDEN禁止访问,404 NOT FOUND资源不存在,500 INTERNAL SERVER ERROR服务器端内部错误

以Todo的路由为例,我们可以设计出以下接口

代码分层

首先看后端文件目录: 我们关注于db service routes这三个文件夹。

  • db建立数据模型(Model),相当于MySQL的建表环节
  • service调用数据模型处理数据库的业务逻辑,对数据库进行CURD,返回加工后的数据
  • routes调用service中的方法处理路由请求,设置请求响应

学过Java的小伙伴都知道,一个接口要通过 Domain层,DAO层,Service层,才会进入 Controller层调用,我们的项目类似于这种思想,更好的逻辑分层不仅能提高项目的维护性,还能降低耦合度,这在大型项目中尤为重要。

错误处理

service/user为例,我们定义了userService类,用于处理user的业务逻辑,其中的addUser为注册用户时调用的方法。 由于我们设置了usr字段唯一,所以当用户注册时,输入已经注册过的用户名,就会抛出异常。这时候,我们要catch,并向调用此方法的路由抛出我们自定义的异常'save failed'随后路由层会捕获错误,返回用户名已存在的HTTP响应。这就是一个较为典型的的错误处理过程。

统一响应

关于API调用的返回结果,为了格式化响应体,我们在/utils/response.js编写处理响应的通用函数。

返回一组消息,指明调用是否成功。这类消息通常具有共同的消息体样式。 通用返回格式是由msgerror_codedatarequest四个参数组成的JSON响应体: 当我们请求GET https://b2d1.top:5000/api/todos/5c6a5b2f6622ab4bf6fed958/all时,获取指定用户的所有Todo,返回以下响应体 这不仅仅加强了规范性,而且利于前端接收请求,做出更好的判断或排错

写在结尾

本项目还有许多技术要点、开发技巧,由于篇幅有限,作者就不一一提及了,想继续深入了解的小伙伴,请在评论区留言,我会考虑再写第二篇继续剖析。

希望小伙伴们参照源码,亲自动手做一个属于自己的TodoList,共勉!

之后一段时间,可能会推出如何搭建自己的服务器,并设置HTTPS安全教程,还有小程序的全栈开发流程,请期待吧!

TodoList:GitHub地址