从MVC到MVVC

vue.js网址:https://cn.vuejs.org/v2/guide/
Vue就是用来代替MVC中的View的。
MVVC = MVC + 双向绑定

课程简介


从 MVC 到 MVVM
预习:Vue 官方文档

代码

MVC 版代码:http://jsbin.com/dujutov/1/edit?js,output
Vue 版代码:http://jsbin.com/dujutov/2/edit?html,js,output
Vue 浮层例子:http://jsbin.com/nabugot/1/edit?js,output
Vue 轮播例子:http://jsbin.com/liqicir/2/edit?js,output
Vue tab切换例子:http://jsbin.com/hijawuv/1/edit?html,css,output

睁大眼睛看清楚我用的是 jsbin.com 不是 js.jirengu.com,用 js.jirengu.com 有 bug 我才改成 jsbin.com 的。

用 Vue 代替 View

双向绑定

引入axios

  1. 复习一下MVC

  2. 我们做一个书籍展示的页面,需要引入axios这个库(https://github.com/axios/axios)

  3. axios是一个ajax的库,是一个基于Promise的Http客户端。

    1. axios相对于jQuery的好处是除了get和post,还有put,patch,delete等,其用法几乎是照抄jQuery,但是好处是支持更多的API,
    2. 除了ajax功能之外就没有其余功能了,也就是更加专注。
  4. 以前我们使用jQuery,发现jQuery有一个问题,就是所有功能都混在一起,即ajax相关使用。现在我们想将ajax和dom操作分开,使用ajax的时候就使用axios,谁用dom操作的时候就使用vue

    image.png

  5. 所以,后面基本不适用jQuery了

  6. 我们做一个图书库存管理的系统,现在以简单的样式实现


    image.png
  7. 我们的数据库最好是从数据库里面拿,axios比jQuery好的一个点在于支持自己给自己造数据,即可以模拟服务器返回响应,其中interceptor就是拦截机的意思,其作用就会在真正返回response之前使用一个函数,这个函数会对response进行修改。


    image.png
  8. 上面这个操作就是mock server:为了更好的分工合作,让前端能在不依赖后端环境的情况下进行开发,其中一种手段就是为前端开发者提供一个web容器,这个本地环境就是 mock server。

  9. 我们可以根据不同的url来mock不同的数据,代码如下所示,当axios的请求路径不一样的时候,将得不到返回数据

    axios.interceptors.response.use(function(response){
      // config里面有重要的url,method,data属性,这个data是请求的data
      let {config: {url, method, data}} = response
      data = JSON.parse(data||'{}')
      // 这个row是响应的data
      let row = {
        id: 1, name: 'JavaScript高级程序设计', number: 2
      }
      if(url === '/books/1' && method === 'get'){
        response.data = row
      }else if(url === '/books/1' && method === 'put'){
        response.data = Object.assign(row, data)
      }
      return response
    })
    
    axios.get('/books/1')
      .then((response)=>{
      console.log(response)
    })
    
    image.png
  10. ES6中,支持这种解构语法:let {config: {url, method, data}} = response,下面图片中,第三行等于第一行+第二行


    image.png
  11. 同样,下面第二种比第一种简洁,也是ES6的解构语法

    // 第一种写法
    axios.get('/books/1')
      .then((response)=>{
      let data = response.data
      console.log(data)
    })
    
    // 第二种写法
    axios.get('/books/1')
      .then(({data})=>{
      console.log(data)
    })
    
  12. 我们将拿到的数据重新渲染到页面中,使用替换即可

    axios.interceptors.response.use(function(response){
      // config里面有重要的url,method,data属性,这个data是请求的data
      let {config: {url, method, data}} = response
      data = JSON.parse(data||'{}')
      // 这个row是响应的data
      let row = {
        id: 1, name: 'JavaScript高级程序设计', number: 2
      }
      if(url === '/books/1' && method === 'get'){
        response.data = row
      }else if(url === '/books/1' && method === 'put'){
        response.data = Object.assign(row, data)
      }
      return response
    })
    
    axios.get('/books/1')
      .then(({data})=>{
      let originalHtml = $('#app').html()
      let newHtml = originalHtml.replace('__name__', data.name)
        .replace('__number__', data.number)
      $('#app').html(newHtml)
    })
    
    image.png
  13. 上面最终我们可以看到前面这个拿到的数据会显示在网页中

  14. 委托:但是我们发现一个BUG,发现原先的点击事件起不了作用,这是因为我们在操作$('#app').html(newHtml)这句话的时候,原先里面的button被替换了,我们需要做一个委托,下面这个写法的意思就是在点击app里面的任何一个元素的时候,如果这个元素符合#addOne这个条件,就会执行相关代码,这样就算代码被替换页没有关系,因为app始终是没有动的,只是里面的内容换了罢了

    $('#app').on('click', '#addOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 +1
      $('#number').text(newNumber)
    })
    
  15. 整体的JS代码如下

    axios.interceptors.response.use(function(response){
      // config里面有重要的url,method,data属性,这个data是请求的data
      let {config: {url, method, data}} = response
      data = JSON.parse(data||'{}')
      // 这个row是响应的data
      let row = {
        id: 1, name: 'JavaScript高级程序设计', number: 2
      }
      if(url === '/books/1' && method === 'get'){
        response.data = row
      }else if(url === '/books/1' && method === 'put'){
        response.data = Object.assign(row, data)
      }
      return response
    })
    
    axios.get('/books/1')
      .then(({data})=>{
      let originalHtml = $('#app').html()
      let newHtml = originalHtml.replace('__name__', data.name)
        .replace('__number__', data.number)
      $('#app').html(newHtml)
    })
    
    $('#app').on('click', '#addOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 +1
      $('#number').text(newNumber)
    })
    
    $('#app').on('click', '#minusOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 -1
      $('#number').text(newNumber)
    })
    
    $('#app').on('click', '#reset', function(){
      $('#number').text(0)
    })
    
  16. 到目前为止,我们先使用ajax获取一个很简单的数据,获取到数据之后,将数据替换到html中,同时通过事件委托监听app的点击事件;当我们点击的时候,我们需要发送请求,到后台去改数值,而不是表面上的加一减一。每一次改变number的时候,我们需要将新的number先put到服务器上面去,put成功就在页面中更改值,发送不成功就不要更改值。后端那边也要做一个监听,将put上去的值给服务器。Object.assign(book, data),这个API用于部分更新,只更改对应的部分,可以一次性赋值更改,可以多次覆盖性更改


    image.png

    image.png
  17. 目前为止,我们做了一个模拟后台,三个点击按钮当被点击的时候,就会发送put请求,但是这个代码很麻烦,因为每次都要重复写put请求,这种属于意大利面条式代码,没有太多组织性,总体的代码如下,每次点击的时候会出现延迟更新的状态

    let book = {
      id: 1,
      name: 'JavaScript高级程序设计',
      number: 2
    }
    axios.interceptors.response.use(function(response){
      // config里面有重要的url,method,data属性,这个data是请求的data
      let {config: {url, method, data}} = response
      if(url === '/books/1' && method === 'get'){
        // 这个data是响应的data
        response.data = book
      }else if(url === '/books/1' && method === 'put'){
        Object.assign(book, data)
        response.data = book
      }
      return response
    })
    
    axios.get('/books/1')
      .then(({data})=>{
      let originalHtml = $('#app').html()
      let newHtml = originalHtml.replace('__name__', data.name)
        .replace('__number__', data.number)
      $('#app').html(newHtml)
    })
    
    /* 上面是加了一个假的后台 */
    
    $('#app').on('click', '#addOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 +1
      axios.put('/books/1', {
        number: newNumber
      }).then(()=>{
        $('#number').text(newNumber)
      })
    })
    
    $('#app').on('click', '#minusOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 -1
      axios.put('/books/1', {
        number: newNumber
      }).then(()=>{
        $('#number').text(newNumber)
      })
    })
    
    $('#app').on('click', '#reset', function(){
      axios.put('/books/1', {
        number: 0
      }).then(()=>{
        $('#number').text(0)
      })
    })
    

引入MVC


  1. 我们引入MVC,所谓MVC是将其变成三部分。我们写一个model,所有跟数据相关的操作,都用model来实现
  2. 所有跟html相关的操作都用view,来操作
  3. 所有model和View之外的都交给controller来操作。
  4. 最终的代码如下所示:
    fakeData()
    
    let model = {
      data: {
        name: '',
        number: 0,
        id: 1
      },
      fetch: function(){
        return axios.get('/books/1').then((response)=>{
          this.data = response.data
          return response
        })
      },
      update: function(data){
        let id= this.data.id
        return axios.put('/books/1', data).then((response)=>{
          this.data = response.data
          return response
        })
      }
    }
    
    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) } } let controller = { init: function(options){ let {view, model} = options this.view = view this.model = model this.view.render(this.model.data) this.bindEvents() this.model.fetch(1).then(() => {this.view.render(this.model.data)}) }, addOne: function(){ var oldNumber = $('#number').text() var newNumber = oldNumber -0 +1 this.model.update({number: newNumber}).then(({data})=>{ this.view.render(this.model.data) }) }, minusOne: function(){ var oldNumber = $('#number').text() var newNumber = oldNumber -0 -1 this.model.update({number: newNumber}).then(()=>{ this.view.render(this.model.data) }) }, reset: function(){ this.model.update({number: 0}).then(()=>{ this.view.render(this.model.data) }) }, bindEvents: function(){ $(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)) } } controller.init({view: view, model: model}) /* 上面是加了一个假的后台 */ function fakeData(){ let book = { id: 1, name: 'JavaScript高级程序设计', number: 2 } axios.interceptors.response.use(function(response){ // config里面有重要的url,method,data属性,这个data是请求的data let {config: {url, method, data}} = response if(url === '/books/1' && method === 'get'){ // 这个data是响应的data response.data = book }else if(url === '/books/1' && method === 'put'){ // 传入的data被拆分成一个一个字符串,需要将其放到一起 data = JSON.parse(data) Object.assign(book, data) response.data = book } return response }) }
  5. 上面这个代码中,有三个对象,一个对象是Model,一个View,一个是Controller,假设我们有多个页面,如果每个页面都要写这三个对象,那么就会比较麻烦。我们应该创建类,将共同的属性放入class中。或者写构造函数,将公有的东西写到原型中。
  6. 我们将代码进行更好,变成下面这样:
    fakeData()
    
    // 下面是MVC的类
    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)=>{
        console.log(response)
        this.data = response.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
        return response
      })
    }
    
    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)
    }
    
    // 下面是MVC对象
    let model = new Model({
      data: {
        name: '',
        number: 0,
        id: ''
      },
      resource: 'book'
    })
    
    let view = new View({
      el: '#app',
      template: `
        
    书名:《__name__》 数量:__number__
    ` }) let controller = { init: function(options){ let {view, model} = options this.view = view this.model = model this.view.render(this.model.data) this.bindEvents() this.model.fetch(1).then(() => {this.view.render(this.model.data)}) }, addOne: function(){ var oldNumber = $('#number').text() var newNumber = oldNumber -0 +1 this.model.update({number: newNumber}).then(({data})=>{ this.view.render(this.model.data) }) }, minusOne: function(){ var oldNumber = $('#number').text() var newNumber = oldNumber -0 -1 this.model.update({number: newNumber}).then(()=>{ this.view.render(this.model.data) }) }, reset: function(){ this.model.update({number: 0}).then(()=>{ this.view.render(this.model.data) }) }, bindEvents: function(){ $(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)) } } controller.init({view: view, model: model}) /* 上面是加了一个假的后台 */ function fakeData(){ let book = { id: 1, name: 'JavaScript高级程序设计', number: 2 } axios.interceptors.response.use(function(response){ // config里面有重要的url,method,data属性,这个data是请求的data let {config: {url, method, data}} = response if(url === '/books/1' && method === 'get'){ // 这个data是响应的data response.data = book }else if(url === '/books/1' && method === 'put'){ // 传入的data被拆分成一个一个字符串,需要将其放到一起 data = JSON.parse(data) Object.assign(book, data) response.data = book } return response }) }

引入Vue


  1. 引入Vue,我们先删除自己的view,将里面的let view = new View()改成let view = new Vue()。里面的标记__改成两个大括号,如name改成{{name}}。并且我们需要将model里面的data传到Vue里面来,因为vue需要根据这个data初始化这个template
    image.png
  2. template里面只能有一个根元素,如果有两个根元素,那么特点1:Vue只看第一个,这样原先没有显示的button就出现了


    image.png
  3. Vue就是MVC做了升级,特点2:Vue里面不需要render事件,因为Vue里面自动有一个render机制
  4. 当vue的data里面的数据发生变化的时候,会自动更新,记住,直接更改vue.data = ...是没有用的,需要更改里面的值,这是因为Vue的第三个特点是会将data里面的所有属性升级到当前的view上面,不能改data去改view,应该改data里面的属性,也就是我们找data属性没用了
  5. 以前的更新,最重要的一句话是view.render(midel.data),现在只需要更新view.data,html更新会自动进行
  6. 我们可以将三个属性同时放到一个属性里面,只要这个属性变了就会更新,这个book是view的属性,而不是放到data里面的,Vue会自动将book属性提升到view层面


    image.png
  7. Vue只更改该改的地方,不会整个去刷新页面,而之前的MVC写法,一旦app里面的任意内容发生变化,这个app都会被重新替换。
  8. Vue支持不适用Controller,我们看到我们的Controller最重要的语法是绑定监听,对应到Vue上面就是method,将controller里面所有的函数写到这个method里面,而且bindEvents不需要了,因为vue内置了bindEvents
  9. Vue是不管model的,但是我们省略了所有dom的获取更新操作,这个形成了自动化
  10. 我们还需要进行初始化,Vue里面有一个created属性函数,用于在元素创建成功之后调用,还有一个mount函数,用于挂载成功之后调用。
  11. Vue的好处就是让以前写的代码更智能,让MVC的C合并到Vue里面去
  12. 现在的代码如下所示:
    fakeData()
    
    // 下面是MVC的类
    // 使用Vue之后,M保留了。C被合并到V中了,View使用了Vue
    // 初始化以及事件绑定等操作都放入View中了
    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
        return response
      })
    }
    Model.prototype.update = function(data){
      console.log(data)
      let id= this.data.id
      return axios.put(`/${this.resource}s/${id}`, data).then((response)=>{
        this.data = response.data
        return response
      })
    }
    
    
    
    // 下面是MVC对象
    let model = new Model({
      data: {
        name: '',
        number: 0,
        id: ''
      },
      resource: 'book'
    })
    
    // 使用Vue创建view
    let view = new Vue({
      el: '#app',
      // 放在model里面的数据可以放一份到data中,data里面的属性会自动提升给view
      // 访问的时候使用this.book,而不是this.view.book
      data: {
        book: {
          name: '未命名',
          number: 0,
          id: ''
        },
        n: 1
      },
      // 变量的标识使用两个大括号包围
      template: `
        
    书名:《{{book.name}}》 数量:{{book.number}}
    `, // 这个函数是在el被创建的时候自动调用,可以认为是初始化 created(){ model.fetch(1).then(()=>{ this.book = model.data }) }, // 里面所有的绑定事件可以放到这个里面。使用v-on:click="方法名"进行调用 // Vue有一个好处是,只要放在data标识里面的数据被更改了,就会自动渲染更改的部分,而不会更改其他部分 methods: { addOne(){ model.update({ number: this.book.number + this.n*1 }).then(()=>{ this.view.book = model.data }) }, minusOne(){ model.update({ number: this.book.number - this.n*1 }).then(()=>{ this.view.book = model.data }) }, reset(){ model.update({ number: 0 }).then(()=>{ this.view.book = model.data }) } } }) /* 上面是加了一个假的后台 */ function fakeData(){ let book = { id: 1, name: 'JavaScript高级程序设计', number: 2 } axios.interceptors.response.use(function(response){ // config里面有重要的url,method,data属性,这个data是请求的data let {config: {url, method, data}} = response if(url === '/books/1' && method === 'get'){ // 这个data是响应的data response.data = book }else if(url === '/books/1' && method === 'put'){ // 传入的data被拆分成一个一个字符串,需要将其放到一起 data = JSON.parse(data) Object.assign(book, data) response.data = book } return response console.log(response) }) }
  13. 使用v-model="n"是将值绑定到data里面的n去。这是一种双向绑定,我们将input的值绑定到data的n值上面去,同时又拿到这个值渲染到这个页面中来,现在我们只需要更改input里面的值,后面的渲染也会随即更改


    image.png
  14. 只有input能实现双向绑定,span是不可以的
    • 我们之前使用span的时候,span能拿到data里面的n进行渲染,当更改内存中n的时候,再次渲染将n的值给span。所有的过程都是单向的,拿到内存中的n,渲染给span
    • 但是当有input的时候,我们第一次拿到n的是给input,这是一个默认值,但是当我们使用v-model="n"将input的value值与n链接起来,使得input的value变化的时候,n也变化,页面中其余引用n的部分也跟着变化,这就是双向绑定。
    • image.png
    • 只从内存到页面,这是单向的,但是如果页面能映射到内存,这就是双向的。
  15. Vue是一种自动化的MVC,又叫MVVM

使用Vue做三个小东西


  1. 我们使用了Vue之后,jQuery就可以不使用了,因为我们已经摆脱了操作DOM。jQuery中的ajax已经用axios代替了,我们现在只需要想数据之间的逻辑就好了,不用花大量精力去处理DOM等。

  2. 我们使用Vue可以很容易就实现一个点击的浮动图层,其中v-if="open"的意思就是当open为true的时候,显示,为false的时候就不显示,toggle函数会更改open的值

    let view = new Vue({
      el: '#app',
      data: {
        open: false
      },
      template: `
        
    你好
    `, methods:{ toggle(){ this.open = !this.open } } })
    image.png
  3. 做个轮播,使用了VUE就不用碰DOM,绑定的函数既支持加参数,也支持只写函数名;可以使用v-bind:style绑定一个style属性,

    let view = new Vue({
      el: '#app',
      data: {
        transformValue: ''
      },
      template: `
        
    `, methods:{ go(index){ this.transformValue = `translateX(${-100*(index-1)}px)` } } })
    image.png

    如果要做图片切换,需要使用v-for进行循环

  4. 做一个Tab切换,v-show="值",如果值是真的,那么就展示,如果值是false,那么就不展示;v-on:click="selected = 0"这后面的字符串会被解析成公式进行执行,v-bind:class="{active:selected === 0}的意思就是使用bind绑定一个active属性,但是只有在selected === 0的时候,绑定才生效


  5. 最终的代码如下所示

    let view = new Vue({
      el: '#app',
      data: {
        selected: 'a',
        tabs: [
          {name: 'a', content: 'aaa'},
          {name: 'b', content: 'bbb'},
          {name: 'c', content: 'ccc'}
        ]
      },
      template: `
        
    1. {{tab.name}}
    1. {{tab.content}}
    `, methods:{ } })
  6. 点击的时候会出现内容,以及样式会进行更改


    image.png
  7. VUE学起来真的很简单,但是一开始学VUE会不知道怎么实现

  8. 一个很好的Vue模拟网页:https://jsfiddle.net/chrisvfritz/50wL7mdz/

你可能感兴趣的:(从MVC到MVVC)