前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从MVC到MVVM(为什么要用vue)

从MVC到MVVM(为什么要用vue)

作者头像
代码之风
发布2019-03-14 10:06:56
1.7K0
发布2019-03-14 10:06:56
举报
文章被收录于专栏:马涛涛的专栏

axios

功能类似于jQuery.ajax

代码语言:javascript
复制
axios.post()
axios.get()
axios.put()
axios.patch()
axios.delete()
  1. jQuery.ajax功能更多
  2. 除了ajax功能之外没有其他功能(更庄专注)

ajax操作使用axios,dom操作使用vue,从此可以不使用jquery

Mock

使用axios模拟后台请求与响应就是Mock,也有专门的Moc库例如: http://mockjs.com/

生成随机数据,拦截 Ajax 请求

使用axios和jQuery完成简单的前后台交互(请求与响应)

要求从后台获取数据,初始化书的数量。加减书的时候也发送请求与响应,同时更新后台数据。

演示地址: https://jsbin.com/jipewutagi/...

代码语言:javascript
复制
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>使用axios和jQuery完成简单的前后台交互(请求与响应)</title>
  
  <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<div id='app'>
  <div>
    书的名称:《__name__》 <br>
  书的数量:<span id='number'>__number__</span>
  </div>
  <button id='addOne'>加一</button>
  <button id='minusOne'>减一</button>
  <button id='reset'>归零</button>
</div>
</body>
</html>
代码语言:javascript
复制
let book = {
  name:'JavaScriptBook',
  number:10,
  id:'1'
}
// 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
axios.interceptors.response.use(function(response){
  let {config: {url, method, data}} = response
  //   ES6语法,从response里的config拿出url method data,并声明
  if(url === '/book/1' && method === 'get'){//如果,就把初始book的数据响应过来
    response.data = book
  } else if(url === '/book/1' && method === 'put'){
    let dataObj = JSON.parse(data)//先把拿到的请求转化为对象
    Object.assign(book,dataObj)
    //     Object.assign这个函数的作用是局部更新对象,把接收到的请求(book)里面的number局部更新
    console.log(book)
    response.data = book ;//局部更新后,再次赋值给响应,在这里略过存储数据库,因为这只是假的后台
  }

  return response;
})
// -------------上面是假的后台-----------------
// 先声明好变量(有时候变量不能固定,比如更新了html的代码后,这个变量就是旧的,就得重新取)
let $app = $('#app')
// let $number = $('#number')
// let $addOne = $('#addOne')
// let $minusOne = $('#minusOne')
// let $reset = $("#reset")

// 请求初始值
axios.get('/book/1')
  .then(({data})=>{//获取响应成功后更新html的代码
  //   这里使用的是es6语法,实际上是let data = response.data
  let originalHtml = $app.html()
  let newHtml = originalHtml.replace('__name__',data.name).replace('__number__', data.number)
  $app.html(newHtml)
})

// 进行加一个的请求
$app.on('click', '#addOne', function(){//事件委托,点击app上的addone的时候,将点击事件委托给addone
  let newNumber = $('#number').text()-0+1
  let book = {
    number:newNumber
  }

  axios.put('/book/1', book)
    .then(({data})=>{
    $('#number').text(data.number)//接收到响应之后在更新前端代码
  })
})
//下面减一和重置同理
$app.on('click', '#minusOne', function(){//事件委托,点击app上的addone的时候,将点击事件委托给addone
  let newNumber = $('#number').text()-0-1
  let book = {
    number:newNumber
  }

  axios.put('/book/1', book)
    .then(({data})=>{
    $('#number').text(data.number)//接收到响应之后在更新前端代码
  })
})

$app.on('click', '#reset', function(){//事件委托,点击app上的addone的时候,将点击事件委托给addone
  let newNumber = 0
  let book = {
    number:newNumber
  }

  axios.put('/book/1', book)
    .then(({data})=>{
    $('#number').text(data.number)//接收到响应之后在更新前端代码
  })
})

事件委托:点击父元素,如果同时点到了子元素就把事件委托给他。 通过事件委托,监听app的点击事件,如果点的是委托的子元素,就执行监听的函数

上面的代码很乱

使用 MVC模式 改写上面的代码

上面的代码很乱。使用MVC模式重构代码,把代码分成视图,数据,控制数据和视图三块,分别用一个对象表示,下面是过程

添加model,分离控制数据的方法

演示代码 https://jsbin.com/ceyukirube/...

代码语言:javascript
复制
fakeData()
// -------------上面是假的后台-----------------
let model = {
  data:{
    name:'',
    number:0,
    id:''
  },
  fetch:function (id){
    return axios.get(`/book/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
  },
  update:function(id,data){
    return axios.put(`/book/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
  }
}

let $app = $('#app')

model.fetch(1).then(({data})=>{//这里把操作数据的方法写在了model里

  let originalHtml = $app.html()
  let newHtml = originalHtml.replace('__name__',data.name).replace('__number__', data.number)
  $app.html(newHtml)
})


$app.on('click', '#addOne', function(){
  let newNumber = $('#number').text()-0+1
  let book = {
    number:newNumber
  }

  model.update(1,book).then(({data})=>{//这里把操作数据的方法写在了model里
    $('#number').text(data.number)
  })
})

$app.on('click', '#minusOne', function(){
  let newNumber = $('#number').text()-0-1
  let book = {
    number:newNumber
  }

  model.update(1,book)
    .then(({data})=>{
    $('#number').text(data.number)
  })
})

$app.on('click', '#reset', function(){
  let newNumber = 0
  let book = {
    number:newNumber
  }

  model.update(1,book)
    .then(({data})=>{
    $('#number').text(data.number)
  })
})




function fakeData(){//假的后台
  let book = {
    name:'JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

添加View,所有跟html相关的操作都给view来做

演示地址 https://jsbin.com/fakegurono/...

代码语言:javascript
复制
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>使用axios和jQuery完成简单的前后台交互(请求与响应)</title>
  
  <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<div id='app'>
  
</div>
</body>
</html>
代码语言:javascript
复制
fakeData()
// -------------上面是假的后台-----------------
let model = {
  data:{
    name:'',
    number:0,
    id:''
  },
  fetch:function (id){
    return axios.get(`/book/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
  },
  update:function(id,data){
    return axios.put(`/book/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
  }
}

let view = {
  el:'#app',
  template:`<div>
书的名称:《__name__》 <br>
书的数量:<span id='number'>__number__</span>
</div>
<button id='addOne'>加一</button>
<button id='minusOne'>减一</button>
<button id='reset'>归零</button>
`,
  render(data){//渲染
    let newhtml = this.template.replace('__name__',data.name).replace('__number__', data.number)
    $(this.el).html(newhtml)
  }

}

let $app = $('#app')

model.fetch(1).then(({data})=>{//es6语法,response里的data
  //这里把操作数据的方法写在了model里
  view.render(data)
  //    或者view.render(model.data)因为上面model在操纵数据的时候,获取响应的时候,把data传给了model.data,所以response.data 和model.data一样,两个都可以用
})


$app.on('click', '#addOne', function(){
  let newNumber = $('#number').text()-0+1
  let book = {
    number:newNumber
  }
  model.update(1,book).
  then(({data})=>{//这里把操作数据的方法写在了model里
    view.render(data)
  })
})

$app.on('click', '#minusOne', function(){
  let newNumber = $('#number').text()-0-1
  let book = {
    number:newNumber
  }
  model.update(1,book)
    .then(({data})=>{
    view.render(data)
  })
})

$app.on('click', '#reset', function(){
  let newNumber = 0
  let book = {
    number:newNumber
  }
  model.update(1,book)
    .then(({data})=>{
    view.render(data)
  })
})



//假的后台,不要看
function fakeData(){//假的后台
  let book = {
    name:'JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

代码开始变得条理清晰

添加Controller (操纵Model和View)

把操纵model和view的操作封装成controller 对象 演示地址: https://jsbin.com/sezuquxuko/...

代码语言:javascript
复制
fakeData()
// -------------上面是假的后台-----------------
let model = {
  data:{
    name:'',
    number:0,
    id:''
  },
  fetch:function (id){
    return axios.get(`/book/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
  },
  update:function(id,data){
    return axios.put(`/book/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
  }
}

let view = {
  el:'#app',
  template:`<div>
书的名称:《__name__》 <br>
书的数量:<span id='number'>__number__</span>
</div>
<button id='addOne'>加一</button>
<button id='minusOne'>减一</button>
<button id='reset'>归零</button>
`,
  render(data){//渲染
    let newhtml = this.template.replace('__name__',data.name).replace('__number__', data.number)
    $(this.el).html(newhtml)
  }

}
let controller = {
  init:function(options){
    this.view = options.view
    this.model = options.model
    this.view.render(this.model.data)
    this.bindEvents()
    
    this.model.fetch(1).then(({data})=>{
      this.view.render(data)
    })
  },
  bindEvents:function(){
    //到这一层,this还指的是controller这个对象,但是到点击事件那一层,addOne函数里,this代表点击的那个元素(jQuery规定的)。
    //     所以要使用addOne.bind(this)把controller这一层的this绑定到addOne那更深入的一层去,使this同一为controller这个对象
    $(this.view.el).on('click', '#addOne', this.addOne.bind(this))
    $(this.view.el).on('click', '#minusOne',this.minusOne.bind(this) )
    $(this.view.el).on('click', '#reset', this.reset.bind(this))
  },
  addOne:function(){
    let newNumber = $('#number').text()-0+1
    let book = {number:newNumber}
    this.model.update(1,book).//这个this已经被bind为controller
    then(({data})=>{
      this.view.render(data)//这个this已经被bind为controller
    })
  },
  minusOne:function(){
    let newNumber = $('#number').text()-0-1
    let book = {
      number:newNumber
    }
    this.model.update(1,book)//这个this已经被bind为controller
      .then(({data})=>{
      this.view.render(data)
    })
  },
  reset:function(){
      let newNumber = 0
      let book = {
        number:newNumber
      }
      this.model.update(1,book)
        .then(({data})=>{
        this.view.render(data)
      })
    }
}

controller.init({model:model,view:view})//初始化,并把Model和View传进去


//假的后台,不要看
function fakeData(){//假的后台
  let book = {
    name:'JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

把Model和View抽象成类

因为这个页面的Model和View只是这个页面特有的,假如下个页面不是这个View和Model,那么还需要重新重写一遍代码,所以要把把Model,View和controller抽象成类。这样每有新的页面中的一块html需要操作,就new一个对象即可。一般来说MVC做成一个库,然后去引用他就好了

先写构造函数,然后把公有属性写在prototype里,最后new就可以了。 演示地址: https://jsbin.com/mifameqona/...

controller类暂时不写

代码语言:javascript
复制
fakeData()
// -------------上面是假的后台-----------------

//从这开始写MVC类
function Model(options){//这里面写特有的属性
  this.data = options.data
  this.resource = options.resource//把请求的地址也写成特有的属性
}
Model.prototype.fetch = function(id){//共有属性
   return axios.get(`/${this.resource}/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
}
Model.prototype.update = function(id,data){
  return axios.put(`/${this.resource}/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
}

function View(options){
  this.el = options.el;
  this.template = options.template;
}
View.prototype.render = function(data){
  var newhtml = this.template
  for(let key in data){
    newhtml = newhtml.replace(`__${key}__`,data[key])//用循环替换data里面的字符串
  }
  $(this.el).html(newhtml)
}
// --------上面是MVC类-----------
//使用MVC类新生成的对象
let bookModel = new Model({data:{name:'',number:0,id:''},resource:'book'})
let bookView = new View({
  el:'#app',
  template:`<div>
书的名称:《__name__》 <br>
书的数量:<span id='number'>__number__</span>
</div>
<button id='addOne'>加一</button>
<button id='minusOne'>减一</button>
<button id='reset'>归零</button>
`
})

let controller = {
  init:function(options){
    this.view = options.view
    this.model = options.model
    this.view.render(this.model.data)
    this.bindEvents()
    
    this.model.fetch(1).then(({data})=>{
      this.view.render(data)
    })
  },
  bindEvents:function(){
    //到这一层,this还指的是controller这个对象,但是到点击事件那一层,addOne函数里,this代表点击的那个元素(jQuery规定的)。
    //     所以要使用addOne.bind(this)把controller这一层的this绑定到addOne那更深入的一层去,使this同一为controller这个对象
    $(this.view.el).on('click', '#addOne', this.addOne.bind(this))
    $(this.view.el).on('click', '#minusOne',this.minusOne.bind(this) )
    $(this.view.el).on('click', '#reset', this.reset.bind(this))
  },
  addOne:function(){
    let newNumber = $('#number').text()-0+1
    let book = {number:newNumber}
    this.model.update(1,book).//这个this已经被bind为controller
    then(({data})=>{
      this.view.render(data)//这个this已经被bind为controller
    })
  },
  minusOne:function(){
    let newNumber = $('#number').text()-0-1
    let book = {
      number:newNumber
    }
    this.model.update(1,book)//这个this已经被bind为controller
      .then(({data})=>{
      this.view.render(data)
    })
  },
  reset:function(){
      let newNumber = 0
      let book = {
        number:newNumber
      }
      this.model.update(1,book)
        .then(({data})=>{
        this.view.render(data)
      })
    }
}
  
controller.init({model:bookModel,view:bookView})//初始化,并把Model和View传进去


//假的后台,不要看
function fakeData(){//假的后台
  let book = {
    name:'JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

在前端开始慢慢发展的时候,前端程序员就是这样进行技术迭代的,上面就是MVC迭代的过程。这就是MVVM出现之前的MVC。

使用vue改写上面的代码

从上面的代码来看,view类的作用是:

  1. 有一个没有填充数据的HTML模板template
  2. model发送请求获取数据后,view把数据填充到模板里,然后渲染填充后的html到页面

VUE框架的作用是可以把MVC里的view类使用VUE代替。 注意:

  1. 需要直接传入data,传入data后vue对象就有了这个data的属性
  2. VUE会有自动的render机制,VUE会帮我们做渲染html的过程,那我们怎么更新(渲染)HTML呢?直接改data数据就好了双向绑定()
  3. template只能有一个根元素

从传统MVC转到VUE的MVC就是忘掉render,把data放到vue上面,要更新数据,就直接更新vue里面的data即可。

把render变成了简单的赋值操作。而且这种渲染只更新你改变的那个值所在的节点,不会渲染全部模板。 vue第一个特点是data归他管,第二就是会精细得更新该渲染的地方。 但vue的野心不仅于此,vue可以让你做到不需要controller。因为controller最重要的功能绑定事件,vue有一种语法可以绑定事件。具体用法是在html属性里添加v-on:click="f",然后在methods 里写f函数即可。

代码 演示地址: https://jsbin.com/bocecuxaya/...

主要代码:

代码语言:javascript
复制
fakeData()
// -------------上面是假的后台-----------------

//从这开始写MVC类
function Model(options){
  this.data = options.data
  this.resource = options.resource
}
Model.prototype.fetch = function(id){
   return axios.get(`/${this.resource}/${id}`).then((response)=>{
      this.data = response.data
      return response
    })
}
Model.prototype.update = function(id,data){
  return axios.put(`/${this.resource}/${id}`,data).then((response)=>{
      this.data = response.data
      return response
    })
}


// --------上面是Model类-----------

let bookModel = new Model({data:{name:'',number:0,id:''},resource:'book'})
let bookView = new Vue({//不用写view类了,直接用vue充当view类,需要传入data,
  el:'#app',
  data:{//data里的属性会转变为vue对象的属性
    book:{
      name:'',
      number:0,
      id:'1'
    },
    n:1//两个数据,一个book对象,一个n的值
  },
  //template只能有一个根元素
  template:`
<div>
  <div>
    书的名称:《{{book.name}}》 <br>
    书的数量:<span id='number'>{{book.number}}</span>
  </div><br><br>

  双向绑定:<br>
  输入需要加或者减的数:<input type='text' v-model='n'><br>
  n的值为<span>{{n}}</span><br><br><br>

  <button v-on:click='addOne'>加n</button>
  <button v-on:click='minusOne'>减n</button>
  <button v-on:click='reset'>归零</button>
</div>
`,
  created:function(){//在创造vue时执行的函数,进行首次渲染
    bookModel.fetch(1).then(({data})=>{
      //vue会直接同步渲染html,所以直接赋值给view.name和number就好了

      this.book = bookModel.data;//或者this.book = data;因为data是传回来的response,在model里,也把传回来的数据放到了model里
//       this.view.render(this.model.data)这句不需要了,因为修改vue数据后会自动渲染
    })
  },
  methods:{//绑定的事件的函数
    addOne:function(){
      let newNumber = this.book.number + (this.n-0)//+n 
      //直接获取内存里的number,因为内存和页面是统一的,不需要获取dom了
      let book = {number:newNumber}
      bookModel.update(1,book).//直接用声明的bookModel对象里面的update方法,因为没有controller了
      then(({data})=>{
        //       this.view.render(data)
        this.book = data//返回的数据直接赋值给book,即可渲染
      })
    },
    minusOne:function(){
      let newNumber = this.book.number - (this.n-0)//-n
      let book = {
        number:newNumber
      }
       bookModel.update(1,book)
        .then(({data})=>{
        //       this.view.render(data)
        this.book = data
      })
    },
    reset:function(){
      let newNumber = 0
      let book = {
        number:newNumber
      }
      bookModel.update(1,book)
        .then(({data})=>{
        //         this.view.render(data)
        this.book = data
      })
    }
  }
})




//假的后台,不要看
function fakeData(){//假的后台
  let book = {
    name:'我是初始名称JavaScriptBook',
    number:10,
    id:'1'
  }
  // 在response真正返回之前拦截,修改他的数据,使用这个api来模拟后台响应数据
  axios.interceptors.response.use(function(response){
    let {config: {url, method, data}} = response
    //   ES6语法,从response里的config拿出url method data,并声明
    if(url === '/book/1' && method === 'get'){
      response.data = book
    } else if(url === '/book/1' && method === 'put'){
      let dataObj = JSON.parse(data)
      Object.assign(book,dataObj)

      console.log(book)
      response.data = book ;
    }
    return response;
  })
}

但是vue不管model层的事 vue做的事就是让mvc里的v更智能,且能合并mvc的c

双向绑定

  1. 单向绑定:从内存里数据的更新到渲染页面的更新
  2. 双向绑定:不单单是从内存到页面,页面上的input修改了,还会反过来映射内存,内存会同时修改(只能input实现,因为只有input可以更改内存)

渲染是一种单向绑定,只单向得改变html的值。

vue就是自动化的mvc,既MVVM

MVVM

通过以上的分析,我们发现,我们不需要去绑定事件,也不需要去render了,我需要做的就是取值和赋值

什么是MVVM: https://juejin.im/entry/59996...

用vue做三个小东西

Vue 浮层例子:http://jsbin.com/nabugot/1/ed... Vue 轮播例子:https://jsbin.com/kerabibico/... Vue tab切换例子:http://jsbin.com/hijawuv/1/ed...

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • axios
    • Mock
    • 使用axios和jQuery完成简单的前后台交互(请求与响应)
    • 使用 MVC模式 改写上面的代码
      • 添加model,分离控制数据的方法
        • 添加View,所有跟html相关的操作都给view来做
          • 添加Controller (操纵Model和View)
            • 把Model和View抽象成类
            • 使用vue改写上面的代码
              • 双向绑定
              • MVVM
              • 用vue做三个小东西
              相关产品与服务
              数据库
              云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档