从node+koa后端模板引擎渲染到vue+node+koa+ajax前端请求


熟悉 VueReact 开发的同学对于 ajax 请求数据前端渲染应该是不陌生的,这里先从 node+koa 后端模板引擎渲染 .html 说起,可以更好的理解如何从后端渲染过渡到现在的主流 ajax 请求+前端框架的开发形式的转变,更能理解 ajax 请求的好处到底在哪,毕竟 ajax 是后期发展起来的,它并不是一开始就有的,既然发展起来并得到了如此广泛的应用,一定是因为它解决了重要的难题。

node+koa+koa-swig

初始化项目

npm init -y

服务器

// app.js
const Koa = require('koa') // 包装过的http
const KoaStaticCache = require('koa-static-cache') // 静态资源中间件
const Router = require('koa-router')
const co = require('co')
const Render = require('koa-swig') // 模板引擎
const path = require('path')
const bodyParser = require('koa-bodyparser') // 处理post请求

const app = new Koa() // 创建服务器

/* 处理静态资源:任何请求,首先通过 KoaStaticCache 中间件处理看是否是静态资源请求 */
app.use( KoaStaticCache(__dirname + '/static', {
    
    
  prefix: '/public' // 如果当前请求 url 是以 /public 开始的,则作为静态资源请求,映射到我服务器上的 /static 路径中的文件
}) )

/* 处理请求正文中的数据(处理post请求参数)*/
app.use(bodyParser())

/* 服务器重启会重置数据,所以可以写成一个本地文件,对文件进行增删改查(类似数据库的功能了) */
let datas = {
    
    
  maxId: 3,
  appName: 'TodoList',
  skin: 'index.css',
  tasks: [
    {
    
    id: 1, title: '测试任务1', done: true},
    {
    
    id: 2, title: '学习koa', done: false},
    {
    
    id: 3, title: '学习sql', done: false},
  ]
}

/* 设置模板引擎 */
app.context.render = co.wrap(Render({
    
    
  root: path.join(__dirname, 'views'),
  autoescape: true, // 数据是否编码(html)
  cache: false,
  // cache: 'memory', // 内存中缓存模板,下次访问就直接从内存中读取模板
  ext: 'html'
}))

/* router.routes() 也相当于一个中间件,请求 url 会经过我们定义的路由进行过滤 */
const router = new Router()

/* 首页,模板引擎中设置了文件内容为views文件夹,所以 / 会去找 views/index.html文件 */
router.get('/', async ctx => {
    
    
  ctx.body =  await ctx.render('index.html', {
    
    datas})
})

/* 添加 */
router.get('/add', async ctx => {
    
    
  ctx.body =  await ctx.render('add.html', {
    
    datas})
})
router.post('/posttask', async ctx => {
    
    
  let cur_title = ctx.request.body.title || ''
  if(cur_title) {
    
    
    datas.tasks.unshift({
    
    
      id: ++datas.maxId,
      title: cur_title,
      done: false
    })
    ctx.body = await ctx.render('message', {
    
    
      msg: '添加成功',
      href: '/'
    })
  } else {
    
    
    ctx.body = await ctx.render('message', {
    
    
      msg: '请输入任务标题',
      href: 'javascript:history.back()'
    })
  }
})

/* 改变 */
router.get('/change/:id', ctx => {
    
    
  let cur_id = ctx.params.id
  datas.tasks.forEach(task => {
    
    
    if(task.id * 1 === cur_id * 1) {
    
    
      task.done = !task.done
    }
  })
  ctx.response.redirect('/')
})

/* 删除 */
router.get('/remove/:id', async ctx => {
    
    
  let cur_id = ctx.params.id
  datas.tasks = datas.tasks.filter(task => task.id * 1 !== cur_id * 1)
  ctx.body = await ctx.render('message', {
    
    
    msg: '删除成功',
    href: '/'
  })
})

app.use(router.routes())

app.listen(80, () => {
    
    
  console.log('启动成功...运行在http://localhost:80')
})

后端模板内容

<!-- views/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <link rel="stylesheet" href="/public/{
     
     {datas.skin}}">
</head>
<body>
  <h1>{
   
   {datas.appName}}</h1>
  <a href="/add">添加新任务</a>
  <hr />
  <ul>
    {% for task in datas.tasks %}
      {% if task.done %}
      <li class="done">
        <input type="checkbox" checked onclick="change({ 
        { 
        task.id}})" />
        [{
   
   {task.id}}] - {
   
   {task.title}}
        <a href="/remove/{
     
     {task.id}}">删除</a>
      </li>
      {% else %}
      <li>
        <input type="checkbox" onclick="change({ 
        { 
        task.id}})" />
        [{
   
   {task.id}}] - {
   
   {task.title}}
        <a href="/remove/{
     
     {task.id}}">删除</a>
      </li>
      {% endif %}
    {% endfor %}
  </ul>

  <script>
    function change(id) {
      
      
      window.location.href = '/change/' + id
    }
  </script>
</body>
</html>

添加任务的模板就是原生表单

<!-- views/add.html -->
<form action="/posttask" method="POST">
  <input type="text" name="title" />
  <button>添加</button>
</form>

在这里插入图片描述
模板引擎中引入了样式文件,通过服务端的配置,会去 static 路径下去查找

// app.js
app.use( KoaStaticCache(__dirname + '/static', {
    
    
  prefix: '/public' // 如果当前请求 url 是以 /public 开始的,则作为静态资源请求,映射到我服务器上的 /static 路径中的文件
}) )

启动项目

开发 node 程序时,调试过程中每修改一次代码都需要重新启动服务才生效,这是因为 NodeJS 只有在第一次引用到某部分时才会去解析脚本文件,以后都会直接访问内存,避免重复载入和解析,这种设计有利于提高性能,但是却并不利于开发调试,为了每次修改后都能实时生效,安装一个辅助依赖 supervisor 会监视代码的改动重启NodeJS服务。

npm i supervisor
// package.json
"scripts": {
    
    
  "supervisor": ".\\node_modules\\.bin\\supervisor app"
},
npm run supervisor

启动成功后,访问 http://localhost/

项目预览

请添加图片描述

总结

以上就是通过后端模板引擎来渲染页面的方式,个人认为明显的缺陷有:

  • 繁琐的原生html和js语法,以及复杂的引擎模板语法
  • 改变任务状态这种操作,改变了数据以后需要重定向到首页,重新渲染整个页面(更希望只改变数据改变的部分内容即可)

node+koa+vue+ajax

下面尝试使用 Vue + ajax 的形式改写以上功能

改写app.js

// app.js
const Koa = require('koa')
const KoaStaticCache = require('koa-static-cache')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const fs = require('fs')

const app = new Koa()

// 这里的数据改用本地读取的方式来mock
let datas = JSON.parse(fs.readFileSync('./data/data.json'))

/* 静态资源托管 */
app.use( KoaStaticCache(__dirname + '/static', {
    
    
  prefix: '/public',
  gzip: true
}) )

/* 处理body解析 */
app.use(bodyParser())

/* 路由 */
const router = new Router()
router.get('/', async ctx => {
    
    
  ctx.body = 'hello, this is a test!'
})
router.get('/todos', async ctx => {
    
    
  ctx.body = {
    
    
    code: 0,
    result: datas.todos
  }
})
router.post('/add', async ctx => {
    
    
  let title = ctx.request.body.title || ''
  if(!title) {
    
    
    ctx.body = {
    
    
      code: 1,
      result: '请传入任务标题'
    }
  } else {
    
    
    let newTask = {
    
    
      id: ++datas._id,
      title,
      done: false
    }
    datas.todos.unshift(newTask)
    ctx.body = {
    
    
      code: 0,
      result: newTask
    }
    fs.writeFileSync('./data/data.json', JSON.stringify(datas))
  }
})
router.post('/toggle', async ctx => {
    
    
  let id = ctx.request.body.id * 1 || 0
  if(!id) {
    
    
    ctx.body = {
    
    
      code: 1,
      result: '请传入id'
    }
  } else {
    
    
    let todo = datas.todos.find(todo => todo.id * 1 === id)
    todo.done = !todo.done
    ctx.body = {
    
    
      code: 0,
      result: '修改成功'
    }
    fs.writeFileSync('./data/data.json', JSON.stringify(datas))
  }
})
router.post('/remove', async ctx => {
    
    
  let id = ctx.request.body.id * 1 || 0
  if(!id) {
    
    
    ctx.body = {
    
    
      code: 1,
      result: '请传入id'
    }
  } else {
    
    
    datas.todos = datas.todos.filter(todo => todo.id * 1 !== id)
    ctx.body = {
    
    
      code: 0,
      result: '删除成功'
    }
    fs.writeFileSync('./data/data.json', JSON.stringify(datas))
  }
})

app.use(router.routes())

app.listen(80, () => {
    
    
  console.log('启动成功...')
})

index.html

<!-- static/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="/public/css/index.css">
  <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
</head>
<body>
  <div id="app">
    <h1>TotoList</h1>
    <div>
      <input type="text" v-model="newTask" />
      <button @click="add">提交</button>
    </div>
    <hr />
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        <input type="checkbox" :checked="todo.done" @click.prevent="toggle(todo.id)" />
        <span>{
   
   {todo.title}}</span>
        <button @click="remove(todo.id)">删除</button>
      </li>
    </ul>
  </div>

  <script>
    new Vue({
      
      
      el: '#app',
      data: {
      
      
        newTask: '',
        todos: []
      },
      created() {
      
      
        fetch('/todos').then(r => {
      
      
          return r.json()
        }).then(res => {
      
      
          let {
      
      code, result} = res
          if(code * 1 === 0) {
      
      
            this.todos = result
          }
        })
      },
      methods: {
      
      
        remove(id) {
      
      
          fetch('/remove', {
      
      
            method: 'post',
            headers: {
      
      
              'Content-Type': 'application/json;charset=utf-8'
            },
            body: JSON.stringify({
      
      id})
          }).then(r => {
      
      
            return r.json()
          }).then(res => {
      
      
            let {
      
      code, result} = res
            if(code * 1 === 0) {
      
      
              this.todos = this.todos.filter(todo => todo.id * 1 !== id * 1)
            } else {
      
      
              alert(result)
            }
          })
        },
        add() {
      
      
          fetch('/add', {
      
      
            method: 'post',
            headers: {
      
      
              'Content-Type': 'application/json;charset=utf-8'
            },
            body: JSON.stringify({
      
      title: this.newTask})
          }).then(r => {
      
      
            return r.json()
          }).then(res => {
      
      
            let {
      
      code, result} = res
            if(code * 1 === 0) {
      
      
              this.todos.unshift(result)
              this.newTask = ''
            } else {
      
      
              alert(result)
            }
          })
        },
        toggle(id) {
      
      
          fetch('/toggle', {
      
      
            method: 'post',
            headers: {
      
      
              'Content-Type': 'application/json;charset=utf-8'
            },
            body: JSON.stringify({
      
      id})
          }).then(r => {
      
      
            return r.json()
          }).then(res => {
      
      
            let {
      
      code, result} = res
            if(code * 1 === 0) {
      
      
              let todo = this.todos.find(todo => todo.id * 1 === id * 1)
              todo.done = !todo.done
              alert(result)
            } else {
      
      
              alert(result)
            }
          })
        }
      }
    })
  </script>
</body>
</html>

在这里插入图片描述是不是很熟悉了?Vue+ajax就这样应用了

启动项目

跟上面的不同,这里的index.html不再是需要后端渲染的模板引擎了,可以直接丢到服务器上运行的文件

node app.js

启动成功后,访问 http://localhost/public/index.html,静态资源托管还是没变,访问 /public 会映射到 /static 目录下。

项目预览

请添加图片描述

总结

路径是没变的,对数据的操作都是通过 ajax 请求局部改变数据部分内容的,不需要对整个页面重刷新。

附录

以上内容代码可以去这里自行clone。

猜你喜欢

转载自blog.csdn.net/weixin_43443341/article/details/126952827
今日推荐