⚡node打怪升级系列 - Koa篇

前言

NodeJS这东西是不是学过了,之后感觉又像没学到什么东西???

我最近翻到了之前学习node的笔记,又结合了一些大佬的经验,这里把node系列相关的东西串联一下,分享给小伙伴们,顺便我自己也加深一下印象。

总共分为六篇

node打怪升级系列 - 基础篇

node打怪升级系列 - Koa篇

node打怪升级系列 - 浅谈require函数

node打怪升级系列 - 手写中间件篇

node打怪升级系列 - 手写发布订阅和观察者篇

node打怪升级系列 - 手写compose(洋葱模型)

node打怪升级系列 - 手写简易脚手架篇

本文重点记录下Koa篇里的装备

正文开始

1,Node.js的框架选择

目前市面上主流的Node.js框架就是ExpressKoa二分天下啦

这里分为三大块简单聊下二者之间的区别

Express的社区更大,Koa支持ES6+的特性,小伙伴们可以自行选择~~~

  • 社区

Express的社区更大,有更多的学习资源和插件,可以快速上手一个项目

  • 写法

    • KoaExpress更简洁,Koa引入了ES6特性通过async/await,消除了回调嵌套,使得异步/同步代码更易于阅读和理解。

    • Express使用回调函数处理异步逻辑,特殊业务场景可能会导致回调嵌套,使得代码的可读性变差。

  • 中间件

    • Koa的中间件更强大一些,Koa中间件封装成Promise对象返回,更适合自己开发中间件

    • Express未引入ES6特性,Express中间件封装成callback返回

本文是Koa框架的打怪篇

3,为啥有了Koa等框架的诞生

想必,小伙伴们在刚看到这句话的时候,心理就有了答案啦~~~

因为人太懒了,哈哈

但是,就是因为懒,也催生出了很多技术和科技的发展。

比如在日常的项目开发中,其实TCPHttp已经可以应付大多数业务场景了,但是Koa还是应运而生了,为啥呢,因为框架的诞生是为了让开发人员少些代码,多些划水时间~~~ 呃~~~

4,HTTP连接

4.1,搭建一个服务端,实现一个getBooks接口

Node Api不熟悉的可以看下⚡node打怪升级系列 - 基础篇

js 复制代码
const http = require('http'); // 引入处理网络的http模块Api

const hostName = '127.0.0.1'; // 本地ip
const port = '9118'; // 端口号

http.createServer(function (req, res) {
  if (req.url === '/getBooks' && req.method === 'GET') {
    const data = [{ 'label': '语文' }, { 'label': '英文' }];
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(data));
  } else {
    res.writeHead(404);
    res.end('Server is lost~~~');
  }
}).listen(port, hostName)
console.log(`Http Server is listening on ${port}`)

http.createServer:创建一个服务器并监听端口

res.writeHead: 设置了HTTP响应的状态码和HTTP响应头。

res.end("Hello Http"): 这一行用于结束HTTP响应,并给客户端发送响应体。

4.2,客户端调用getBooks接口

js 复制代码
const http = require('http');

const options = {
  hostname: '127.0.0.1',
  port: 9118,
  path: '/getBooks',
  method: 'GET'
}

const req = http.request(options, (res) => {
  res.setEncoding('utf-8');
  res.on('data', (data) => {
    console.log(`clint receive data: ${data}`);
  });
});

req.on('error', (error) => {
  console.error(`Request error: ${error}`);
});

req.end();

5,TCP连接

5.1,搭建一个服务端,建立长连接,监听客户端传过来的消息并且发消息给客户端

js 复制代码
const net = require('net'); // 引入处理网络的tcp模块Api

const hostName = '127.0.0.1';
const port = '9002';

// 当 TCP 建立成功时,会走这个callback函数,params 是 socket 对象
net.createServer((socket) => {
  const url = [hostName, port].join(':');
  console.log(`${url} 已经连接成功!`);

  socket.on('data', (data) => {
    console.log(`${url} 收到数据: ${data}`); // 监听客户端发来的消息
    socket.write(`服务端已经收到了数据:${data}`);// 向客户端发送消息
  })

  socket.on('close', (data) => {
    console.log(`${url} 正在关闭`);
  })
}).listen(port, hostName);

console.log(`Server is listening on ${port}`)

5.2,客户端和服务端建立长连接,向服务端发送消息并且监听服务端发回来的消息

js 复制代码
const net = require('net');

const hostName = '127.0.0.1';
const port = '9002';

const client = new net.Socket();
const url = [hostName, port].join(':');

let count = 1;

client.connect(port, hostName, () => {
  console.log(`客户端已经成功链接到 ${url}`);

  const timer = setInterval(() => {
    if (count > 10) {
      client.write('我发完了'); // 向服务端发消息
      clearInterval(timer);
      return;
    }
    client.write(`这是我第${count++}次发数据`) // 向服务端发消息
  }, 1000)
})

client.on('data', (data) => {
  console.log(`收到数据:${data}`) // 监听服务端发来的消息
});

client.on('close', () => {
  console.log('客户端关闭链接');
})
// client.destory()

6,Koa连接

安装Koa框架

js 复制代码
npm i -S  koa koa-router koa-static koa2-cors koa-bodyparser

或者

js 复制代码
yarn add koa koa-router koa-static koa2-cors koa-bodyparser

搭建一个服务端

js 复制代码
const Koa = require('koa');  
const Router = require('koa-router');  
  
const app = new Koa();  
const router = new Router();  
  
// 定义getBooks接口  
router.get('/getBooks', (ctx, next) => {  
  const books = [  
    { label: '语文' },  
    { label: '英文' },  
  ];  
  ctx.type = "application/json"
  ctx.body = { data: books, msg: 'success' };
});  
  
// 将路由中间件添加到Koa应用中  
app.use(router.routes());  
app.use(router.allowedMethods());  
  
// 启动服务器并监听端口  
app.listen(3000, () => {  
  console.log('Server is listening on port 3000');  
});

router.allowedMethods()是一个中间件,路由定义的HTTP方法(如GET、POST、PUT等)可以处理,未定义的返回错误。

7,koa装饰器

装饰器是一个函数,写法是@ + 函数名,一般用来对类进行处理,处理共享不同类之间存在相同业务逻辑的优雅写法

本文主要聊下装饰器在Koa实战中的常用写法,想了解更多的可以去看看阮一峰大佬的ECMAScript 6 入门

js 复制代码
function decorator(target, name, descriptor){}

@decorator
class A {}
  • target: 要装饰的类本身 || 要装饰的类的原型对象

  • name: 要装饰的属性名

  • descriptor: 该属性的描述对象

  • 属性存取器(accessor): 我没用过

target在装饰类的时候是类本身,装饰类的方法的时候是类的原型对象

7.1,装饰类

js 复制代码
@testable
class MyTestableClass {
    // ...
}

function testable(target) {
    target.isTestable = true;
}

MyTestableClass.isTestable // true

上文有说target在装饰类的时候是类本身,所以:

js 复制代码
@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

7.2,装饰类的方法

js 复制代码
class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}


function readonly(target, name, descriptor){
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}
// 上文有说target在装饰类的方法时是类的原型对象
readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);

7.3,"装饰函数"

装饰器只能装饰类的方法,是不能装饰函数的,因为函数存在函数提升,但是可以通过高阶函数直接执行

js 复制代码
function doSomething(name) {
  console.log('Hello, ' + name);
}

function loggingDecorator(wrapped) {
  return function() {
    console.log('Starting');
    const result = wrapped.apply(this, arguments);
    console.log('Finished');
    return result;
  }
}

const wrapped = loggingDecorator(doSomething);

loggingDecorator里的arguments属于简写,不是很清楚的,可以看下下面这个

js 复制代码
function loggingDecorator(wrapped) {
  return function(arg) {
    console.log('Starting');
    const result = wrapped.apply(this,[arg]);
    console.log('Finished');
    return result;
  }
}

运行下wrapped(123), 可得到下面的结果

可以看到,装饰器是对类的处理,高阶函数可以做到对函数的处理。

7.4,装饰器传参的写法

上面聊的都是@ + 函数名,这里聊下@ + 函数名(参数)是怎么写的

js 复制代码
@Controller('/book')
class BookController {
}

function Controller(prefix = "") {
  return function (target) {
    // 给 controller 类添加路由的前缀
    target.prefix = prefix;
  }
}

7.5,装饰器来优化路由逻辑(写接口)

index.js

js 复制代码
import Koa from 'koa';
import Router from 'koa-router';

import { controllers } from './utils/decorator';
import Book from './bookController';
const app = new Koa();
const router = new Router();

console.log("[[[controllers: ]]]", controllers);

controllers.forEach(item => {
    // 每个路由的前缀
    let { url, constructor, method, handler } = item;
    const { prefix } = constructor;
    if(prefix) url = `${prefix}${url}`;
    console.log(url, method, handler)
    router[method](url, handler);
})

app.use(router.routes())
app.use(router.allowedMethods())

app.listen(3008, () => {
    console.log("server is running in 3008")
})

import Book from './bookController';这句引用但是没用到的代码, 是因为避免无引用时候代码被Tree Shaking删掉

controllers的值收集时机和装饰器有点关系,在编译时,也就是在运行之前就开始收集好的路由信息

bookController.js

js 复制代码
import { RequestMapping, Controller, RequestMethod } from "./decorator"


@Controller('/book')
export default class BookController {

  @RequestMapping(RequestMethod.GET, '/all')
  async getAllBooks(ctx) {
    ctx.body = {
      data: ['JS 一天精通', "css 从入门到放弃"]
    }
  }

  @RequestMapping(RequestMethod.GET)
  async getAll(ctx) {
    ctx.body = {
      data: ['JS XXXX', "css XXXX"]
    }
  }
}

上面的装饰器写法配合index.js中的controllers.forEach运行之后和下面的写法是一个意思

小伙伴们如果觉得装饰器写法繁琐了,在实际开发过程中,可以选择自己喜欢的写法~~~

js 复制代码
router.get('/book/all', async (ctx) => {
  ctx.body = {
    data: ['JS 一天精通', "css 从入门到放弃"]
  }
})

router.get('/book/getAll', async (ctx) => {
  ctx.body = {
    data: ['JS XXXX', "css XXXX"]
  }
})

decorator.js

js 复制代码
export const RequestMethod = {
  "GET": "get",
  "POST": "post",
  "PUT": "put",
  "DELETE": 'delete',
  "OPTION": 'option',
  "PATCH": 'patch'
}

export const controllers = [];


export function Controller(prefix = "") {
  return function (target) {
    // 给 controller 类添加路由的前缀
    target.prefix = prefix;
  }
}
/**
 * 给类的方法添加装饰
 * 
 */
export function RequestMapping(method = "", url = "") {
  return function (target, name, descriptor) {
    let path = "";
    // 判断有没有定义 url
    if (!url) {
      path = `/${name}`;
    } else {
      path = url;
    }
    const item = {
      url: path,
      method: method,
      handler: target[name],
      constructor: target.constructor,
    };
    controllers.push(item);
  }
}

完结

这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。

欢迎转载,但请注明来源。

最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。

相关推荐
理想不理想v5 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
暮毅9 小时前
10.Node.js连接MongoDb
数据库·mongodb·node.js
~甲壳虫15 小时前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
~甲壳虫16 小时前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫16 小时前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
熊的猫16 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
前端青山1 天前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
GDAL1 天前
npm入门教程1:npm简介
前端·npm·node.js
郑小憨2 天前
Node.js简介以及安装部署 (基础介绍 一)
java·javascript·node.js
lin-lins2 天前
模块化开发 & webpack
前端·webpack·node.js