首先Vue里面用到了mvc,那么要理解Vue就要先从mvc讲起
接下来我们通过写一个图书信息展示来理解
html
JS Bin
书名:"JavaScript高级程序设计"
数量:2
js
$("#app").on("click","#add",function(){
var oldNumber=$("#number").text()
var newNumber = oldNumber-0+1
$("#number").text(newNumber)
})
$("#app").on("click","#minus",function(){
var oldNumber=$("#number").text()
var newNumber = oldNumber-0-1
$("#number").text(newNumber)
})
$("#app").on("click","#reset",function(){
var newNumber = 0
$("#number").text(newNumber)
})
上面的代码是直接把数据写进页面里的,我们可以发一个请求来得到这个数据再写到页面里,这里我们引入axios来请求数据,并在真正返回response之前用axios的拦截机拦截一下,然后修改response之后再返回response
axios.interceptors.response.use(function(response){
let {url,method,data}=response.config
if(url === '/books/1' && method === 'get'){
response.data = {
name:'JavaScript高级程序设计',
number:2,
id:1
}
}
return response
})
axios.get('/books/1').then((response)=>{
let{data} = response
let originalHtml = $('#app').html()
let newHtml = originalHtml.replace('__name__',data.name)
.replace('__number__',data.number)
$('#app').html(newHtml)
})
这里先用axios发一个get请求,然后拦截机里的response.config可以拿到请求的url,method,data,如果请求的url是“/books/1”而且方法是get那么就返回数据,同时原始html里的number,name用__name__
和__number__
代替。当请求成功拿到response之后,拿到里面的data,然后把data里面的name和number的值替换之前的__name__
和__number__
。
然后每次点击按钮,能不能也发一个put请求,然后通过响应再改数值,还是用axios来put请求
$("#app").on("click","#add",function(){
var oldNumber=$("#number").text()
var newNumber = oldNumber-0+1
axios.put('/books/1',{number:newNumber}).then(()=>{
$("#number").text(newNumber)
})
})
var book={
name:'JavaScript高级程序设计',
number:2,
id:1
}
axios.interceptors.response.use(function(response){
let {url,method,data}=response.config
if(url === '/books/1' && method === 'get'){
response.data = book
}else if(url === '/books/1' && method === 'put'){
data = JSON.parse(data)
Object.assign(book,data)
console.log(book)
response.data = book
}
return response
})
把newNumber当做data一起请求过去,然后为这个put做一个路由,当url和method都满足后,先把请求的data变成对象,然后用Object.assign这个api把book里面的number替换掉,再返回这个book。
到这里我们已经完成了一个人“意大利面条式代码”,长短交错看起来很乱,而且数据视图还有逻辑都是想到哪里写哪里,这就有了引入mvc的需求。
“m”就是“model”负责数据,
“v”就是view负责页面样式,
“c”就是controller负责逻辑,
let model={
data:{
name:'',
number:0,
id:''
},
fetch(id){
return axios.get(`/books/${id}`).then((response)=>{
this.data = response.data
return response
})
},
updata(data){
let id = this.data.id
return axios.put(`/books/${id}`,data).then((response)=>{
this.data = response.data
return response
})
}
}
model首先有一个data初始值,然后fetch函数发一个get请求,然后取到响应的data再把它赋值给model的data,updata函数也是一样的。
let view = {
el:'#app',
template:`
书名:《__name__》
数量:__number__
`,
render(data){
let html = this.template.replace('__name__',data.name)
.replace("__number__",data.number)
$(this.el).html(html)
}
}
view里面有操作的元素el:'#app'
,然后是HTML模板,render函数接受一个data然后用data里面的name和number来替代模板HTML里的对应元素,再把新的HTML渲染到el里面
let controller = {
init(options){ //init接受一个对象{view:view,model:model}
let view = options.view
let model = options.model
this.view = view //把view和model绑到controller上面
this.model = model
this.view.render(this.model.data) //页面做一个初始化,把model里面data的初始值渲染到页面
this.bindEvents() //绑定事件
this.model.fetch(1).then(()=>{
this.view.render(this.model.data)
}) //拿到拦截机返回的response然后赋值到model的data上,render函数渲染拿到的data到页面
},
addOne(){
var oldNumber=$("#number").text()
var newNumber = oldNumber-0+1
this.model.updata({number:newNumber}) //这里的this是addOne,后面bind(this)之后才是controller
.then(()=>{
this.view.render(this.model.data)
})
},
minus(){
var oldNumber=$("#number").text()
var newNumber = oldNumber-0-1
this.model.updata({number:newNumber})
.then(()=>{
this.view.render(this.model.data)
})
},
reset(){
var newNumber = 0
this.model.updata({number:0})
.then(()=>{
this.view.render(this.model.data)
})
},
bindEvents(){
$(this.view.el).on("click",'#add',this.addOne.bind(this)) //第一个this是controller,bind(this)让addOne里面的this和外面的一样都是controller
$(this.view.el).on("click","#minus",this.minus.bind(this))
$(this.view.el).on("click","#reset",this.reset.bind(this))
}
}
这样mvc三部分就写完了!
那么为了避免每一个页面都要重新写一遍这三个模型,于是想到可以写一个构造函数,然后每次就只用new就可以了
思路是把私有的属性放到构造函数里,调用的时候传进去,把公用属性放到原型里
function Model(options){
this.data = options.data
this.resource = options.resource
}
Model.prototype.fetch = function(id){
return axios.get(`/${this.resource}s/${id}`).then((response) => {
this.data = response.data
console.log(this.data)
return response
})
}
Model.prototype.update = function(data){
let id = this.data.id
return axios.put(`/${this.resource}s/${id}`, data).then((response) => {
this.data = response.data
console.log('response')
console.log(response)
return response
})
}
let model = new Model({
data: {
name: '',
number: 0,
id: ''
},
resource: 'book'
})
function View({el, template}){
this.el = el
this.template = template
}
View.prototype.render = function(data){
let html = this.template
for(let key in data){
html = html.replace(`__${key}__`, data[key])
}
$(this.el).html(html)
}
let view = new View({
el: '#app',
template: `
书名:《__name__》
数量:__number__
`
})
接下来引入Vue,Vue强制把data传给view,因为Vue需要用data来初始化
template,不需要render。Vue会把当前model的data里的所有属性更新到View上面,所以直接修改View的值view.book = model.data
这样直接替换了之前的render函数,只需要更新view里的data,就会自动更新HTML。
let view =new Vue({
el:'#app',
data:{
book:{
name:'未命名',
number:0,
id:""
},
n:1
},
template:`
书名:《{{book.name}}》
数量:{{book.number}}
N 的值是 {{n}}
//绑定事件
`,
created(){
model.fetch(1).then(()=>{
this.book = model.data
})
},
methods:{
addOne(){
model.updata({
number:this.book.number+(this.n-0)
})
.then(()=>{
view.book = model.data
})
},
minus(){
model.updata({
number:this.book.number-(this.n-0)
})
.then(()=>{
view.book = model.data
})
},
reset(){
model.updata({
number:0
})
.then(()=>{
view.book = model.data
})
}
}
})
可以使用v-model指令在元素上进行双向数据绑定。
双向数据绑定:之前我们可以通过book.number=3
来修改内存值,然后Vue根据修改的内存值去更新里的数字,只就是单向绑定。如果我们引入
标签,当我们修改
标签里的数字,这时Vue会根据修改的数据来修改内存值,这就是双向绑定。