前言
讲解了用Express框架 搭建了一个简易的rmpv的基本模型,希望这篇博客能够让你对rmvp有更好的了解
一、RMVP是什么?
mvp的全称为Model-View-Presenter,由mvc的controller变成了Presenter(中间人),Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理,V和M绝不能通信,意味着HTML页面上不能操作数据,同理数据里也不能有模板,他们如果想操作,需要通过controller(Presenter),controller先去数据源Model拿到数据,再去渲染模板,同理模板上有onclick事件 想要修改数据,从V中发起操作给controller,controller再修改数据,这就是MVP模型,一般情况下,前面还有个R,R就是request请求,请求先走controller,controller负责联动view和model,这就是rmvp模型
二、搭建基本服务
1.构建服务器server.js基本组成
const express = require('express')
const app = express() //直接调用即可
const router = require('./router/index')
app.use('/',router)//R也可以理解为路由,引入路由做中间件
/*
中间件可以不给函数,用express的路由来替代中间件,路由是express中间件,称为路由中间件
引入的router是个 Function 中间件是个函数,函数内部做了处理,拿到了req,res,先不用管入参
*/
app.listen(8080,()=>{
console.log('localhost:8080');
})
2.router文件
const express = require('express')//加载express 为了从express拿出路由
//路由中间件
const router = express.Router()
//console.log(router);//认为router是函数
//get请求语义 获取数据,从后端拉取数据过来
//怎么定义的怎么匹配,会自动匹配到对应的路由,发送对应的数据
router.get("/",(req,res,next)=>{
res.send('hello');
})
router.get('/index',(req,res,next)=>{
res.send('index pages');
})
module.exports = router
运行结果:证明router是个函数
对两个路由进行测试
2.1、如何实现数据的获取
在router文件中添加以下内容
GET
如果地址栏输入localhost:8080/idnex?id=2,需要从req中取到数据
//get请求语义 获取数据,从后端拉取数据过来
router.get('/index',(req,res,next)=>{ //和之前的use不同,这个是怎么定义的怎么匹配,会自动匹配人到对应的路由,发送对应的数据
//获取get方法再url上传递的数据
const query = req.query//这个req是被express增强的,所以可以在路由中间件中访问query,访问query以后,可以拿到query对象
// console.log(query);//{ id: '2' }
// res.send(query)//可以直接返回,此时返回的就是一个json类型
res.json(query)//也可以这么返回,这也是返回一个json字符串类型
})//get就是get请求,get里传入两个参数,第一个参数 就是 路径 第二个参数就是一个回调函数,在路径匹配后会执行这个回调函数,执行完成后哪怕不进行res.json(query)进行返回,也不会把访问权交给下个路由,因为下个路由没有匹配
验证是否正确
POST
需要在server.js导入第三方中间件才能解析,控制台安装body-parser
在server.js中引入
//第三方中间件
const bodyParser = require('body-parser')
//这个是用来解析 parse application/x-www-form-urlencoded
//这个配置专门用来收取前端给我发过来的表单数据
app.use(bodyParser.urlencoded({extended:false}))//bodyParser代入 然后调用一个方法,这个方法返回一个中间件
//extended:false表示请求时候的数据的格式问题 bodyParser要不要进行编码解码的问题
//这个定义要放在路由的上面,因为app.use是有顺序的,next给路由,路由才能拿到数据
//这个是用来解析 parse application/json
app.use(bodyParser.json())//可以解析JSON字符串
然后在router中进行post请求
//post请求语义 添加数据
router.post('/index',(req,res,next)=>{
const data = req.body
//因为get是通过Query String方式传给后端,后端可以通过query方式来取,但是现在post是通过表单数据方式application/x-www-form-urlencoded方式传送,后端不能通过query,只能通过body方式来取
// console.log(data);//此时data是undefined因为缺少第三方中间件boby-parser(解析node.js body的中间件)
//有了插件后 就能正确输出前端发送过来的数据
res.send(data)
})
结果正确
PUT
// 修改数据 put表示覆盖式修改 大量修改字段 比如 四个字段 修改三个字段 或者全部修改 直接覆盖
router.put('/index',(req,res,next)=>{
const data = req.body
console.log(data);
//{ username: 'adimin', password: '123' }
//PUT方式也和PSOT一样能通过body拿数据
res.send('put response')
})
PATCH
//修改数据 patch表示增量式修改,选择式修改
// 如果 只想修改小部分字段,比如只修改一个,那么用patch 只修改一个 类似于补丁逻辑
router.patch('/index',(req,res,next)=>{
res.send('patch response')
})
DELETE
//删除数据
router.delete('/index',(req,res,next)=>{
res.send('delete response')
})
2.2 如果想要不区分请求方式,一律接收
将上面的各种方式全部注释后,在router文件中书写一下代码,
无论什么请求方式的请求都不会报404
但是这样语义会更加不清晰
router.all('/index',(req,res,next)=>{//无论是get还是post都能请求得到,不报404
//all 判断的话 需要在req,res试图拿到前端给我的是get还是Post
res.send('hello')
})
2.3 做rmvp的抽离,抽离controller
创建一个controller文件
const list = (req,res,next) =>{//get请求的控制器
res.send('hello')
}
exports.list= list;
在router中导入
const { list } = require('../controller')//index.js可以省略
router.get("/",list)
测试通过
三、渲染
知识补充
做一个网页可以采用很多种方法架构系统,前后端分离式的架构,和前后端不分离式的架构
需求,现在做一个网页,追求两点,追求最大的速度,第二点追求最好的管理效率(代码的管理效率),这两点矛盾,如果想让代码管理更方便就要拆代码,前后端分离,如果想让页面下载更快访问更快,就要服务端渲染,服务端直接返还页面给前端,前端什么也不需要做,只需要帮助后端做个摸板
前端请求静态资源目录(快)[模板放到后端]
最快的方式,后端直接把静态页面给前端
第二快的方式,
前端往后端请求数据,但是请求的不是html/css /js请求的式/api/内容
如果向后端请求这样的内容,静态资源里就没有这样的资源,后端需要递交给node,node需要解析路径,执行中间件,最后返回一个页面 res.send(),才能把数据返回给浏览器
这样服务端 直接把信息给浏览器,但这样的话前端工作较少,这样的模式 将来会难以维护,全部交给后端,前端只是写完页面抛给后端,但是这种方式却有比较好的优点,就是快
前后端分离 (慢) [模板放到前端]
前端请求后端数据的时候,不是以html/css/js直接给前端的方式,而是请求一个接口,后端返回的不是一个具体的html,而是一个json,虽然json在浏览器上也能给用户看,但是看不懂,所以要解析json成一个网页,这种工作都要前端来做,这样就做成了前后端分离,好处就是前端和后端各,而且可以同时进行,之前的方式 不能同时进行,需要前端把页面做好后,丢给后端才能做,因为后端返回的是一个页面 而不是接口
如果后端返回html ,但是后端需要把数据拿过来渲染摸板,这个摸板出在后端,同理如果后端返回一个JSON给前端 ,前端也需要把JSON放到一个模板里,这个摸板可能是html,但是如果用html需要模板字符串,把变量算出来塞到html 做html拼接
SSR (Server Side Render)
要把纯网页返给前端,需要一个放置静态资源目录的文件夹,里面都是静态资源
利用expresss 内置的中间件托管静态文件 express.static
express相当于给前端提供了静态资源服务,但是这是静态的
里面只有html/css/js,双击html就能启动服务
第一种方法纯静态资源目录
在server.js文件中书写
//静态资源服务中间件(内置中间件)
app.use(express.static('./public'))//利用expresss 内置的中间件托管静态文件
开启服务
服务可以开启,自动跳转静态目录页面上的Index.html
第二种前端请求后端资源
前端请求后代一个接口,后端就会给前端返回一个资源,
请求的这个接口可以通过a链接
在index.html中添加a标签
<a href="/api/list">获取list</a>
当我点击会跳转到一个新页面,里面什么都没有,因为当前静态资源目录根本没有 /api这个文件夹,也没有/list这个文件或者资源,所以就挂了,但是这个/api/list 一定会被express拦截下来,其实静态资源目录是express帮助搭建的服务,意味着请求的每一个资源都是express帮助递过来,但是当我请求/api/list资源express还要递,express递不了,因为静态资源没有,接口也没有,所以要做一个接口
在router文件中定义,此时页面能跳转到并接受controller返回的内容
router.get("/api/list",list)
如果在页面上显示100个li
const list = (req,res,next) =>{
let data = '<ul>'
for(var i=0;i<100;i++){
data += `<li>line${i}</li>`
}
data += '</ul>'
res.send(data)
}
页面正确显示数据
这样渲染数据 比 静态资源稍慢一点,需要在后端运行下js返回给前端,对于前端来说 还是后端给我的,这种现象就叫做服务端渲染(服务端渲染的页面给前端)
在在渲染页面上查看源代码,能看到服务端给我的代码,爬虫能爬到
如果后端返回给前端的是一个JSONP接口,用ajax请求,然后在前端自己用ul li渲染,这叫前端渲染,客户端渲染
CSR (Client Side Render)
当静态页面满足不了需求,后端用程序渲染一段字符串返给前端,后端还能把HTML渲染给前端,直接把HTML做成一个file读取一下,然后返回给前端
再次调用list 客服端渲染,需要换一个方式返回数据
后端只返回一个line1 line2 的结构化数据
需要重写controller文件 核心内容
let data = '{"ret":true,"data":['//后端只发送数据,需要结构化
for(var i=0;i<100;i++){
data += `"line${i}",`
}
data +=']}'
res.send(data)
//两种方式都可以
------------------------------------------
let dataObj = {
ret:true,
data:[]
}
for(var i=0;i<100;i++){
dataObj.data.push('line'+i)
}
res.send(dataObj)//返给前端JSON字符串
前端拿到数据JSON字符串
前端在静态资源里面的JS中 解析字符串,在Index.html引入jquery标签,然后增加存放数据的div
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script src="./scripts/common.js"></script>
<link rel="stylesheet" href="./styles/reset.css">
<body>
hello world.
<img src="./images/1.jpg" alt="">
<a href="/api/list">获取list</a>
<div id="list"></div>
</body>
然后在静态资源中的JS书写以下代码
$.ajax({//jQuery ajax
url:'/api/list',//传给后端的地址
success(result){
console.log(result);
let html = '<ul>'
$.each(result.data,(index,value)=>{
html += '<li>'+value+'</li>'
})
html += '</ul>'
$('#list').html(html)//html是Jquery方法 把一段Html装载到页面上
}
})
3.1渲染模板
在根目录创建新的文件夹view 作为模板文件夹使用,利用art-remplate模板引擎
安装art-template
载入art-template
后端模板
在server文件中 引入
//后端 view engine setup view模板引擎设置
app.engine('art', require('express-art-template'));
app.set('view options', {//注意这个view options 官网是view 估计官网笔误
debug: process.env.NODE_ENV !== 'production',
escape:false
});
app.set('views', path.join(__dirname, './view'));
app.set('view engine', 'art');
在刚刚创建好的view文件夹中 创建list.art,因为配置引擎的时候 写了art,表示set了 view engine 扩展名是art
在list.art中写以下内容,这是字符串拼接的不是对象
{
"ret":true,
"data":{{data}}
}
因为是后端模板引擎,所以在controller文件中,先注释掉之前的,写入以下内容
let dataArray = []
for(var i=0;i<100;i++){
dataArray.push('line '+i)
}
//需要修改首部格式
res.set('content-type','application/json;charset=utf-8')
//安装渲染引擎后,就可以不用res.send 用res.render
res.render('list',{
data:JSON.stringify(dataArray)
})//art-template 帮助定义了render方法
//第一个参数只用写文件名,不用写目录
//第二个参数 写渲染的数据 是一个对象
前端能看到后端发来的数据
前端模板
需要从art-template官网 把template-web.js下载 下来,然后放到Public中的JS文件夹下,拿到后 导入到index.html中,导入完成后,在JS文件夹中新建一个list.art文件写入以下内容
<div>{{data}}<div>
然后在public的JS文件中 用模板来代替之前的渲染工作
$.ajax({//jQuery ajax
url:'/api/list',//传给后端的地址
success(result){
//前端模板
let templateStr = `
<ul>
{{each data}}
<li>{{$value}}</li>
{{/each}}
</ul>
<div>
<b>{{x}}</b>
</div>
`
let html= template.render(templateStr,{
data:result.data,
x:'hello'
})
$('#list').html(html)//html是Jquery方法 把一段Html装载到页面上
}
})
页面正常渲染
后端直接返回页面给前端
修改controller文件 ,把上面的修改首部格式注释,不然整个页面被浏览器解析程JSON字符串,更换controller文件中res.render方式
res.render('list-html',{
data:dataArray
})
在view文件夹中 创建一个list-html.art文件
这个文件是一个html页面
<!-- 有index.js传过来的数组,需要循环渲染数组 -->
<ul>
{{each data}}
<li>{{$value}}</li>
{{/each}}
</ul>
渲染完成
后端通过node.js已经把页面渲染完后到前端已经传新的页面,不过还不够快,最快的是把这个结果再生成一个HTML页面,因为前端访问这个接口,后端还要运行一段JS,最后返回结果,这个JS可能要读取数据库库,访问一段时间,最后生成一个字符串,然后返给前端,中间有延迟
后端直接返回生成的页面
更改controller文件
//如果后端想渲染一个页面的话,需要把它渲染成静态资源目录,不能用render方法,因为直接返回出去,不能生成页面
//需要拿到模板对象,渲染模板
var fs = require('fs')
var path = require('path')
var template = require('art-template');
var html = template(path.join(__dirname,'../view/list-html.art'),{ //这里不能路径解析不能直接写'../view/list-html.art'
data:dataArray
});
// console.log(html);
fs.writeFileSync(path.join(__dirname,'../public/list.html'),html)
//自动生成了 list.html 也就是作者或者编辑人员,做文章发布,点个按钮 编辑发布,静态资源在public目录下,当用户想访问时,直接地址栏输入list.html 就可以运行
res.send("pages has been compiled.")
页面访问时 就会生成list.html,这就是cms网站的原型
四、模型
根目录创建一个新的文件夹model,创建一个list.js
let dataArray = []
for(var i=0;i<1000;i++){
dataArray.push('line' + i)
}
module.exports={
dataArray
}
controller只管拿到模板,拿到数据进行装填
引入listModel
var fs = require('fs')
var path = require('path')
var template = require('art-template');
const listModel = require('../model/list')
var html = template(path.join(__dirname,'../view/list-html.art'),{
// data:dataArray
//当使用model中的数据时
data:listModel.dataArray
});
// console.log(html);
fs.writeFileSync(path.join(__dirname,'../public/list.html'),html)
res.send("pages has been compiled.")
该处使用的url网络请求的数据。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了express框架rmvp的使用,数据库部分需要大家自己去参考mongodb资料,连接即可,希望能帮助到你