从node+koa后端模板引擎渲染到vue+node+koa+ajax请求
熟悉
Vue 和
React 开发的同学对于
ajax 请求数据前端渲染应该是不陌生的,这里先从
node+koa 后端模板引擎渲染
.html 说起,可以更好的理解如何从后端渲染过渡到现在的主流
ajax 请求+前端框架的开发形式的转变,更能理解
ajax 请求的好处到底在哪,毕竟
ajax 是后期发展起来的,它并不是一开始就有的,既然发展起来并得到了如此广泛的应用,一定是因为它解决了重要的难题。
node+koa+koa-swig
初始化项目
npm init -y
服务器
const Koa = require('koa')
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')
const app = new Koa()
app.use( KoaStaticCache(__dirname + '/static', {
prefix: '/public'
}) )
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,
cache: false,
ext: 'html'
}))
const router = new Router()
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')
})
后端模板内容
<!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>
添加任务的模板就是原生表单
<form action="/posttask" method="POST">
<input type="text" name="title" />
<button>添加</button>
</form>
模板引擎中引入了样式文件,通过服务端的配置,会去 static 路径下去查找
app.use( KoaStaticCache(__dirname + '/static', {
prefix: '/public'
}) )
启动项目
开发 node 程序时,调试过程中每修改一次代码都需要重新启动服务才生效,这是因为 NodeJS 只有在第一次引用到某部分时才会去解析脚本文件,以后都会直接访问内存,避免重复载入和解析,这种设计有利于提高性能,但是却并不利于开发调试,为了每次修改后都能实时生效,安装一个辅助依赖 supervisor 会监视代码的改动重启NodeJS服务。
npm i supervisor
"scripts": {
"supervisor": ".\\node_modules\\.bin\\supervisor app"
},
npm run supervisor
启动成功后,访问 http://localhost/
项目预览
总结
以上就是通过后端模板引擎来渲染页面的方式,个人认为明显的缺陷有:
- 繁琐的原生html和js语法,以及复杂的引擎模板语法
- 改变任务状态这种操作,改变了数据以后需要重定向到首页,重新渲染整个页面(更希望只改变数据改变的部分内容即可)
node+koa+vue+ajax
下面尝试使用 Vue + ajax 的形式改写以上功能
改写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()
let datas = JSON.parse(fs.readFileSync('./data/data.json'))
app.use( KoaStaticCache(__dirname + '/static', {
prefix: '/public',
gzip: true
}) )
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
<!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/vue@2.6.14"></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。
|