Node.js与前端开发实践

208 阅读2分钟

Node.js的应用场景

  1. 前端工程化:Webpack、Babel、Vue、React、TS
  2. Web服务端应用:作为服务器端语言
  3. Electron跨端桌面应用:Visual Studio Code等

Node.js运行时结构-单线程

实际上,是由JS线程 + uv线程池 + V8任务线程池 + V8 Inspector线程组合而成。

  • 优点:不用考虑多线程状态同步问题,也就不需要锁;同时还能高效利用系统资源
  • 缺点:线程阻塞会产生更多的负面影响。解决方法是使用多进程或多线程。

编写HTTP Server

基本的http server

// 导入http包
const http = require('http');
// 自定义的端口号,可用数值为0-62235,0-1023是系统端口,最好不要使用
const port = 3000
// 创建http服务
const server = http.createServer((req, res) => {
  //告诉服务器响应已完成,并向请求的一方发送数据,内容为字符串'hello world'
  res.end('hello world')
})
// 服务器监听端口
server.listen(port, () => {
  console.log('listen on :', port)
})

这个服务目前只能返回字符串,只是实现了服务区最基本的功能——监听请求和发送响应数据,且数据为字符串。

发送json数据的http server

const http = require('http');
const port = 3000

const server = http.createServer((req, res) => {
  // 创建数据缓冲区
  const bufs = []
  // 监听数据,有数据变换,将其添加到缓冲区中
  req.on('data', (buf) => {
    bufs.push(buf)
  })
  req.on('end', () => {
    // 将所有接受到的数据进行拼接和整合,转为utf8格式的字符串
    let buf = Buffer.concat(bufs).toString('utf8')
    // 默认数据
    let msg = 'Hello'
    try {
      // 解析数据
      const ret = JSON.parse(buf)
      msg = ret.msg 
    } catch (err) {
      // res.end('invalid json')
    }
    // 定义响应数据
    const responseJson = {
      msg: `receive: ${msg}`
    }
    // 设置响应头
    res.setHeader('Content-Type', 'application/json')
    // 发送数据
    res.end(JSON.stringify(responseJson))
    console.log('已经向客户端发送数据')
  })
})
server.listen(port, () => {
  console.log('listen on:', port)
})

在此,需要一个客户端发送响应,才能看到结果,所以,我们接下来创建一个发送数据的客户端。

const http = require('http');
const port = 3000
// 定义发送的json数据
const body = JSON.stringify({
  msg: '我是来自客户端的数据'
})
// 创建请求
const request = http.request('http://127.0.0.1:3000', {
  method: 'POST',
  headers: {
    'Content-Tpye': 'application/json'
  }
// 设置得到响应后的回调函数
}, response => {
  const bufs = []
  response.on('data', buf => {
    bufs.push(buf)
  })
  response.on('end', () => {
    const buf = Buffer.concat(bufs).toString('utf8')
    const json = JSON.parse(buf)
    console.log('json.msg', json)
  })
})
// 发送请求
request.end(body)

结合promise创建一个返回json的http server

const http = require('http');
const port = 3000


const server = http.createServer(async (req, res) => {
  
  const msg = await new Promise((resolve, reject) => {
    const bufs = []
    req.on('data', (buf) => { bufs.push(buf) })
    req.on('error', (err) => { reject(err) // 失败的回调})
    req.on('end', () => {
      let msg = 'hello'
      const buf = Buffer.concat(bufs).toString('utf8')
      try {
        const ret = JSON.parse(buf)
        msg = ret.msg
      } catch (err) {
        reject(err) // 失败的回调
      }
      // 成功的回调
      resolve(msg)
    })
  })
  const responseJson = {
    msg: `receive msg: ${msg}`
  }
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify(responseJson))
})

server.listen(port, () => {
  console.log('listen on: ', port)
})

发送静态文件的htpp server

const http = require('http');
const fs = require('fs');
const url = require('url');
const path = require('path');

const port = 3000
// 找到文件所在目录
const folderPath = path.resolve(__dirname, './static')
const server = http.createServer((req, res) => {
  const info = url.parse(req.url)
  // 设置文件所在的绝对路径
  const filePath = path.resolve(folderPath, './'+info.path)
  const fileStream = fs.createReadStream(filePath)
  fileStream.pipe(res)
})

server.listen(port, () => {
  console.log('listen on: ', port)
})

注意

  • path.resolve([…paths])里的每个参数都类似在当前目录执行一个cd操作,从左到右执行,返回的是最后的当前目录。所以const filePath = path.resolve(folderPath, info.path)并不会正确地找到文件所在的路径。
  • 使用pipe会节省内存。

编写http server-React SSR

SSR(Server Side Rendering)相比于传统的HTML模板引擎,能够比避免重复编写代码,相比于SPA,首屏渲染更快、SEO友好;其缺点是QPS较低,需要考虑服务器的渲染性能。

const React = require('react');
const ReactDOMServer = require('react-dom/server');
const http = require('http');

const port = 3000

function App(props) {
  return React.createElement('div', {}, props.children || 'hello')
}

const server = http.createServer((req, res) => {
  res.end(
    `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
    </head>
    <body>
        ${ReactDOMServer.renderToString(App({}))}
    </body>
    </html>
    `
  )
})

server.listen(port, () => {
  console.log('listen on:', port)
})

注意

  • SSR需要处理打包代码,如使用webpack
  • 需要思考在服务器端运行时的逻辑,如在componentDidMount的时候发送网络请求
  • 移除对服务器无意义的副作用配置和环境