vue.js網(wǎng)址:https://cn.vuejs.org/v2/guide/
Vue就是用來代替MVC中的View的税迷。
MVVC = MVC + 雙向綁定
課程簡介
從 MVC 到 MVVM
預(yù)習(xí):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
復(fù)習(xí)一下MVC
我們做一個書籍展示的頁面甥温,需要引入axios這個庫(https://github.com/axios/axios)
-
axios是一個ajax的庫,是一個基于Promise的Http客戶端。
- axios相對于jQuery的好處是除了get和post,還有put,patch痰催,delete等兜辞,其用法幾乎是照抄jQuery迎瞧,但是好處是支持更多的API,
- 除了ajax功能之外就沒有其余功能了逸吵,也就是更加專注凶硅。
-
以前我們使用jQuery,發(fā)現(xiàn)jQuery有一個問題扫皱,就是所有功能都混在一起足绅,即ajax相關(guān)使用『裕現(xiàn)在我們想將ajax和dom操作分開氢妈,使用ajax的時候就使用axios,誰用dom操作的時候就使用vue
所以段多,后面基本不適用jQuery了
-
我們做一個圖書庫存管理的系統(tǒng)首量,現(xiàn)在以簡單的樣式實現(xiàn)
-
我們的數(shù)據(jù)庫最好是從數(shù)據(jù)庫里面拿,axios比jQuery好的一個點在于支持自己給自己造數(shù)據(jù)进苍,即可以模擬服務(wù)器返回響應(yīng)加缘,其中interceptor就是攔截機的意思,其作用就會在真正返回response之前使用一個函數(shù)觉啊,這個函數(shù)會對response進行修改拣宏。
上面這個操作就是mock server:為了更好的分工合作,讓前端能在不依賴后端環(huán)境的情況下進行開發(fā)杠人,其中一種手段就是為前端開發(fā)者提供一個web容器勋乾,這個本地環(huán)境就是 mock server。
-
我們可以根據(jù)不同的url來mock不同的數(shù)據(jù)搜吧,代碼如下所示市俊,當(dāng)axios的請求路徑不一樣的時候,將得不到返回數(shù)據(jù)
axios.interceptors.response.use(function(response){ // config里面有重要的url滤奈,method摆昧,data屬性,這個data是請求的data let {config: {url, method, data}} = response data = JSON.parse(data||'{}') // 這個row是響應(yīng)的data let row = { id: 1, name: 'JavaScript高級程序設(shè)計', 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) })
-
ES6中蜒程,支持這種解構(gòu)語法:let {config: {url, method, data}} = response绅你,下面圖片中,第三行等于第一行+第二行
-
同樣昭躺,下面第二種比第一種簡潔忌锯,也是ES6的解構(gòu)語法
// 第一種寫法 axios.get('/books/1') .then((response)=>{ let data = response.data console.log(data) }) // 第二種寫法 axios.get('/books/1') .then(({data})=>{ console.log(data) })
-
我們將拿到的數(shù)據(jù)重新渲染到頁面中,使用替換即可
axios.interceptors.response.use(function(response){ // config里面有重要的url领炫,method偶垮,data屬性,這個data是請求的data let {config: {url, method, data}} = response data = JSON.parse(data||'{}') // 這個row是響應(yīng)的data let row = { id: 1, name: 'JavaScript高級程序設(shè)計', 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) })
上面最終我們可以看到前面這個拿到的數(shù)據(jù)會顯示在網(wǎng)頁中
-
委托:但是我們發(fā)現(xiàn)一個BUG,發(fā)現(xiàn)原先的點擊事件起不了作用似舵,這是因為我們在操作$('#app').html(newHtml)這句話的時候脚猾,原先里面的button被替換了,我們需要做一個委托砚哗,下面這個寫法的意思就是在點擊app里面的任何一個元素的時候龙助,如果這個元素符合#addOne這個條件,就會執(zhí)行相關(guān)代碼蛛芥,這樣就算代碼被替換頁沒有關(guān)系提鸟,因為app始終是沒有動的,只是里面的內(nèi)容換了罷了
$('#app').on('click', '#addOne', function(){ var oldNumber = $('#number').text() var newNumber = oldNumber -0 +1 $('#number').text(newNumber) })
-
整體的JS代碼如下
axios.interceptors.response.use(function(response){ // config里面有重要的url仅淑,method称勋,data屬性,這個data是請求的data let {config: {url, method, data}} = response data = JSON.parse(data||'{}') // 這個row是響應(yīng)的data let row = { id: 1, name: 'JavaScript高級程序設(shè)計', 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) })
-
到目前為止漓糙,我們先使用ajax獲取一個很簡單的數(shù)據(jù)铣缠,獲取到數(shù)據(jù)之后,將數(shù)據(jù)替換到html中昆禽,同時通過事件委托監(jiān)聽app的點擊事件蝗蛙;當(dāng)我們點擊的時候,我們需要發(fā)送請求醉鳖,到后臺去改數(shù)值捡硅,而不是表面上的加一減一。每一次改變number的時候盗棵,我們需要將新的number先put到服務(wù)器上面去壮韭,put成功就在頁面中更改值,發(fā)送不成功就不要更改值纹因。后端那邊也要做一個監(jiān)聽喷屋,將put上去的值給服務(wù)器。Object.assign(book, data)瞭恰,這個API用于部分更新屯曹,只更改對應(yīng)的部分,可以一次性賦值更改惊畏,可以多次覆蓋性更改
-
目前為止恶耽,我們做了一個模擬后臺,三個點擊按鈕當(dāng)被點擊的時候颜启,就會發(fā)送put請求偷俭,但是這個代碼很麻煩,因為每次都要重復(fù)寫put請求缰盏,這種屬于意大利面條式代碼涌萤,沒有太多組織性淹遵,總體的代碼如下,每次點擊的時候會出現(xiàn)延遲更新的狀態(tài)
let book = { id: 1, name: 'JavaScript高級程序設(shè)計', 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是響應(yīng)的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
- 我們引入MVC笙以,所謂MVC是將其變成三部分。我們寫一個model冻辩,所有跟數(shù)據(jù)相關(guān)的操作猖腕,都用model來實現(xiàn)
- 所有跟html相關(guān)的操作都用view,來操作
- 所有model和View之外的都交給controller來操作恨闪。
- 最終的代碼如下所示:
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: ` <div> 書名:《__name__》 數(shù)量:<span id=number>__number__</span> </div> <div> <button id="addOne">加1</button> <button id="minusOne">減1</button> <button id="reset">歸零</button> </div> `, 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高級程序設(shè)計', 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是響應(yīng)的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 }) }
- 上面這個代碼中,有三個對象钧敞,一個對象是Model蜡豹,一個View,一個是Controller溉苛,假設(shè)我們有多個頁面镜廉,如果每個頁面都要寫這三個對象,那么就會比較麻煩愚战。我們應(yīng)該創(chuàng)建類娇唯,將共同的屬性放入class中〖帕幔或者寫構(gòu)造函數(shù)塔插,將公有的東西寫到原型中。
- 我們將代碼進行更好拓哟,變成下面這樣:
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: ` <div> 書名:《__name__》 數(shù)量:<span id=number>__number__</span> </div> <div> <button id="addOne">加1</button> <button id="minusOne">減1</button> <button id="reset">歸零</button> </div> ` }) 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高級程序設(shè)計', 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是響應(yīng)的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
- 引入Vue,我們先刪除自己的view逢倍,將里面的let view = new View()改成let view = new Vue()捧颅。里面的標(biāo)記__改成兩個大括號,如name改成{{name}}较雕。并且我們需要將model里面的data傳到Vue里面來碉哑,因為vue需要根據(jù)這個data初始化這個template
-
template里面只能有一個根元素挚币,如果有兩個根元素,那么特點1:Vue只看第一個扣典,這樣原先沒有顯示的button就出現(xiàn)了
- Vue就是MVC做了升級妆毕,特點2:Vue里面不需要render事件,因為Vue里面自動有一個render機制
- 當(dāng)vue的data里面的數(shù)據(jù)發(fā)生變化的時候贮尖,會自動更新笛粘,記住,直接更改vue.data = ...是沒有用的湿硝,需要更改里面的值薪前,這是因為Vue的第三個特點是會將data里面的所有屬性升級到當(dāng)前的view上面,不能改data去改view关斜,應(yīng)該改data里面的屬性示括,也就是我們找data屬性沒用了
- 以前的更新,最重要的一句話是view.render(midel.data)痢畜,現(xiàn)在只需要更新view.data垛膝,html更新會自動進行
-
我們可以將三個屬性同時放到一個屬性里面,只要這個屬性變了就會更新丁稀,這個book是view的屬性吼拥,而不是放到data里面的,Vue會自動將book屬性提升到view層面
- Vue只更改該改的地方线衫,不會整個去刷新頁面扔罪,而之前的MVC寫法,一旦app里面的任意內(nèi)容發(fā)生變化桶雀,這個app都會被重新替換矿酵。
- Vue支持不適用Controller,我們看到我們的Controller最重要的語法是綁定監(jiān)聽矗积,對應(yīng)到Vue上面就是method全肮,將controller里面所有的函數(shù)寫到這個method里面,而且bindEvents不需要了棘捣,因為vue內(nèi)置了bindEvents
- Vue是不管model的辜腺,但是我們省略了所有dom的獲取更新操作,這個形成了自動化
- 我們還需要進行初始化乍恐,Vue里面有一個created屬性函數(shù)评疗,用于在元素創(chuàng)建成功之后調(diào)用,還有一個mount函數(shù)茵烈,用于掛載成功之后調(diào)用百匆。
- Vue的好處就是讓以前寫的代碼更智能,讓MVC的C合并到Vue里面去
- 現(xiàn)在的代碼如下所示:
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創(chuàng)建view let view = new Vue({ el: '#app', // 放在model里面的數(shù)據(jù)可以放一份到data中,data里面的屬性會自動提升給view // 訪問的時候使用this.book雕拼,而不是this.view.book data: { book: { name: '未命名', number: 0, id: '' }, n: 1 }, // 變量的標(biāo)識使用兩個大括號包圍 template: ` <div> <div> 書名:《{{book.name}}》 數(shù)量:<span id=number>{{book.number}}</span> </div> <div> <input v-model="n" /> </div> <div> <button v-on:click="addOne">加{{n}}</button> <button v-on:click="minusOne">減{{n}}</button> <button v-on:click="reset">歸零</button> </div> </div> `, // 這個函數(shù)是在el被創(chuàng)建的時候自動調(diào)用纵东,可以認(rèn)為是初始化 created(){ model.fetch(1).then(()=>{ this.book = model.data }) }, // 里面所有的綁定事件可以放到這個里面。使用v-on:click="方法名"進行調(diào)用 // Vue有一個好處是啥寇,只要放在data標(biāo)識里面的數(shù)據(jù)被更改了偎球,就會自動渲染更改的部分,而不會更改其他部分 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高級程序設(shè)計', 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是響應(yīng)的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) }) }
-
使用v-model="n"是將值綁定到data里面的n去。這是一種雙向綁定难裆,我們將input的值綁定到data的n值上面去子檀,同時又拿到這個值渲染到這個頁面中來,現(xiàn)在我們只需要更改input里面的值乃戈,后面的渲染也會隨即更改
- 只有input能實現(xiàn)雙向綁定褂痰,span是不可以的
- 我們之前使用span的時候,span能拿到data里面的n進行渲染症虑,當(dāng)更改內(nèi)存中n的時候缩歪,再次渲染將n的值給span。所有的過程都是單向的谍憔,拿到內(nèi)存中的n匪蝙,渲染給span
- 但是當(dāng)有input的時候,我們第一次拿到n的是給input习贫,這是一個默認(rèn)值逛球,但是當(dāng)我們使用v-model="n"將input的value值與n鏈接起來,使得input的value變化的時候苫昌,n也變化颤绕,頁面中其余引用n的部分也跟著變化,這就是雙向綁定祟身。
- 只從內(nèi)存到頁面奥务,這是單向的,但是如果頁面能映射到內(nèi)存袜硫,這就是雙向的氯葬。
- Vue是一種自動化的MVC,又叫MVVM
使用Vue做三個小東西
我們使用了Vue之后婉陷,jQuery就可以不使用了溢谤,因為我們已經(jīng)擺脫了操作DOM瞻凤。jQuery中的ajax已經(jīng)用axios代替了,我們現(xiàn)在只需要想數(shù)據(jù)之間的邏輯就好了世杀,不用花大量精力去處理DOM等阀参。
-
我們使用Vue可以很容易就實現(xiàn)一個點擊的浮動圖層,其中v-if="open"的意思就是當(dāng)open為true的時候瞻坝,顯示蛛壳,為false的時候就不顯示,toggle函數(shù)會更改open的值
let view = new Vue({ el: '#app', data: { open: false }, template: ` <div> <button v-on:click="toggle">點我</button> <div v-if="open">你好</div> </div> `, methods:{ toggle(){ this.open = !this.open } } })
-
做個輪播所刀,使用了VUE就不用碰DOM衙荐,綁定的函數(shù)既支持加參數(shù),也支持只寫函數(shù)名浮创;可以使用v-bind:style綁定一個style屬性忧吟,
let view = new Vue({ el: '#app', data: { transformValue: '' }, template: ` <div> <div class="window"> <div class="slides" v-bind:style="{transform:transformValue}"></div> </div> <button v-on:click="go(1)">1</button> <button v-on:click="go(2)">2</button> <button v-on:click="go(3)">3</button> </div> `, methods:{ go(index){ this.transformValue = `translateX(${-100*(index-1)}px)` } } })
如果要做圖片切換,需要使用v-for進行循環(huán)
-
做一個Tab切換斩披,v-show="值"溜族,如果值是真的,那么就展示垦沉,如果值是false煌抒,那么就不展示;v-on:click="selected = 0"這后面的字符串會被解析成公式進行執(zhí)行厕倍,v-bind:class="{active:selected === 0}的意思就是使用bind綁定一個active屬性寡壮,但是只有在selected === 0的時候,綁定才生效
-
最終的代碼如下所示
let view = new Vue({ el: '#app', data: { selected: 'a', tabs: [ {name: 'a', content: 'aaa'}, {name: 'b', content: 'bbb'}, {name: 'c', content: 'ccc'} ] }, template: ` <div> <ol> <li v-for="tab in tabs" v-on:click="selected = tab.name" v-bind:class="{active: tab.name === selected}" >{{tab.name}}</li> </ol> <ol> <li v-for="tab in tabs" v-show="selected === tab.name" >{{tab.content}}</li> </ol> </div> `, methods:{ } })
-
點擊的時候會出現(xiàn)內(nèi)容讹弯,以及樣式會進行更改
VUE學(xué)起來真的很簡單况既,但是一開始學(xué)VUE會不知道怎么實現(xiàn)
一個很好的Vue模擬網(wǎng)頁:https://jsfiddle.net/chrisvfritz/50wL7mdz/