用戶名 | |
郵箱 | |
性別 | |
省份 | |
愛好 |
摘要:在前端頁面中,把用純對象表示,負責顯示,兩者做到了最大限度的分離。的顯示與否和的布爾值有關,還是只關注數據的變化。兩個組件的布爾值通過兩個臨近的按鈕控制,初始值和的結果都是。組件的聲明在組件上,則完全沒有進入生命周期。
開始前說一說 吐槽
首先, 文章有謬誤的地方, 請評論, 我會進行驗證修改。謝謝。
vue真是個好東西,但vue的中文文檔還有很大的改進空間,有點大雜燴的意思,對于怎么把html文件(通過
生命周期參考文章=> vue生命周期探究(一)
生命周期參考文章=> Vue2.0 探索之路——生命周期和鉤子函數的一些理解
MVVM
本段內容摘錄自廖雪峰老師的MVVM
什么是MVVM?MVVM是Model-View-ViewModel的縮寫。
要編寫可維護的前端代碼絕非易事。我由于前端開發混合了HTML、CSS和JavaScript,而且頁面眾多,所以,代碼的組織和維護難度其實更加復雜,這就是MVVM出現的原因。
在了解MVVM之前,我們先回顧一下前端發展的歷史。
用JavaScript在瀏覽器中操作HTML,經歷了若干發展階段:
第一階段,直接用JavaScript操作DOM節點,使用瀏覽器提供的原生API:
var dom = document.getElementById("name"); dom.innerHTML = "Homer"; dom.style.color = "red";
第二階段,由于原生API不好用,還要考慮瀏覽器兼容性,jQuery橫空出世,以簡潔的API迅速俘獲了前端開發者的芳心:
$("#name").text("Homer").css("color", "red");
第三階段,MVC模式,需要服務器端配合,JavaScript可以在前端修改服務器渲染后的數據。
現在,隨著前端頁面越來越復雜,用戶對于交互性要求也越來越高,想要寫出Gmail這樣的頁面,僅僅用jQuery是遠遠不夠的。MVVM模型應運而生。
在前端頁面中,把Model用純JavaScript對象表示,View負責顯示,兩者做到了最大限度的分離。
把Model和View關聯起來的就是ViewModel。ViewModel負責把Model的數據同步到View顯示出來,還負責把View的修改同步回Model。
ViewModel如何編寫?需要用JavaScript編寫一個通用的ViewModel,這樣,就可以復用整個MVVM模型了。
一個MVVM框架和jQuery操作DOM相比有什么區別?
我們先看用jQuery實現的修改兩個DOM節點的例子:
Hello, Bart!
You are 12.
Hello, Bart!
You are 12.
用jQuery修改name和age節點的內容:
"use strict"; ---- var name = "Homer"; var age = 51; $("#name").text(name); $("#age").text(age); ---- // 執行代碼并觀察頁面變化, 請點擊本節開頭引用鏈接去該文章原創頁面更改會生效。
如果我們使用MVVM框架來實現同樣的功能,我們首先并不關心DOM的結構,而是關心數據如何存儲。最簡單的數據存儲方式是使用JavaScript對象:
var person = { name: "Bart", age: 12 };
我們把變量person看作Model,把HTML某些DOM節點看作View,并假定它們之間被關聯起來了。
要把顯示的name從Bart改為Homer,把顯示的age從12改為51,我們并不操作DOM,而是直接修改JavaScript對象:
Hello, Bart!
You are 12.
"use strict"; ---- person.name = "Homer"; person.age = 51; ---- // 執行代碼并觀察頁面變化, 同上原創頁面更改
執行上面的代碼,我們驚訝地發現,改變JavaScript對象的狀態,會導致DOM結構作出對應的變化!這讓我們的關注點從如何操作DOM變成了如何更新JavaScript對象的狀態,而操作JavaScript對象比DOM簡單多了!
這就是MVVM的設計思想:關注Model的變化,讓MVVM框架去自動更新DOM的狀態,從而把開發者從操作DOM的繁瑣步驟中解脫出來!
簡易點擊開頭鏈接把之后的兩節單向綁定和雙向綁定看一看,我想你會受益匪淺,廖老師寫的很好。
vue發音同view, vue的api, v-model雙向數據綁定 => view-model就是MVVM里面的VM, 通過前文可以通俗的理解為:
v-model => viewModel: 視圖層(用戶看到的界面view)和數據層(Model模型,vue實例中的data,computed,props等都屬于數據層)之間相互"映射", 即兩者任意一個發生改變都會觸發對方的變為一致, 只關注數據(model)的變化。
v-if => view-if => if和數據相關, 如果某個數據的結果為true則渲染這個view,否則不渲染, 也是只關注數據(model)的變化。
v-show => view-show => view的顯示(diplay)與否和show的布爾值有關, 還是只關注數據(model)的變化。
v-bind => view-bind => 和view相關的數據與另外一個數據進行綁定, 顯示的是綁定的數據對應的view, 還是只關注數據(model)的變化。
v-on => view-on => view監聽一個事件,即vue實例中對應的方法method, 其實還是通過click等事件,出發數據的改變(data,computed,props),通過數據(model)的變化再反饋給view,還是只關注數據(model)的變化。
v-for => view-for => 把一個數組等容器形式存在的數據(model)以for循環的方式來渲染view, 還是只關注數據(model)的變化。
MVVM中的VM => viewModel 實際上實現了生產力的解放, 應用的設計脫離了固有的DOM結構, 而是我有數據(model)我想把數據展示(view)出來,其他人或服務通過看到這個試圖(view)就可以獲得數據(model),編輯數據,而不用再拘泥于形式(DOM等),vue框架封裝好了這些操作,讓編程變的高效簡潔,大道至簡。
在vue實例中的生命周期, 方法method, computed, watch, filter, props等, 都是用來處理數據, 然后"映射"到視圖(view)上, 核心就是數據層(Model), 所以, 用vue這個框架來進行前端的頁面的模塊化編程, 組件實例的作用域是孤立的, 需要解決的就是不同組件(父子組件和非父子組件)之間的通信問題, 來進行數據傳遞, 而這個過程會往往伴隨這組件實例間的切換, 就有老組件實例的銷毀和新組件實例的掛載, 理解組件實例的生命周期對于數據能否精準的傳遞至關重要。
正文 1 v-if 在組件上或組件根元素上生命周期對于數據傳遞的影響不知道你有沒有碰到過這種情況, 有一個表單并帶有增刪改查的功能, 那么vue-cli項目構建的方式就會需要兩個組件實例, 一個組件Table.vue作為表單部分, 另一個組件Crud.vue作為添加create, research, update, delete的模態框。
那么以MVVM的思想, 增加或修改一行數據, 點擊按鈕就會用v-if渲染出Crud.vue組件實例, 期間會把Table.vue組件實例的一行數據(data)以正確的組件通信方式傳遞給Crud.vue組件實例, 該組件實例會把傳遞過來的數據"映射"到模態框對應的輸入框(viuw)中, 然后編輯完成以后再以同樣的方式傳回數據, Table.vue組件實例就是渲染相應的新數據信息到表單上, 這就模擬完成了一個簡單的表單編輯功能。流程圖如下:
這個過程模態框組件實例Crud.vue因為v-if的不同聲明位置而經歷不一樣的生命周期, 這會導致Crud.vue
的需要在不同的狀態下才能接收到數據。驗證demo如下:
首先, 新建一個index.html文件(章節末尾有完整代碼), 在html文件中注冊兩個全局組件
my-component-one
template: "A component!"
my-component-two。
template: "A component!"
且都加上了如下代碼(命名為組件生命周期測試代碼組):
beforeRouteEnter(to, from, next) { console.log(this) //undefined,不能用this來獲取vue實例 console.log("組件路由勾子:beforeRouteEnter") next(vm => { console.log(vm) //vm為vue的實例 console.log("組件路由勾子beforeRouteEnter的next") }) }, beforeCreate() { console.log("組件:beforeCreate") console.log("%c%s", "color:red", "el : " + this.$el); //undefined console.log("%c%s", "color:red", "data : " + this.$data); //undefined console.log("%c%s", "color:red", "message: " + this.message) }, created() { this.$nextTick(() => { console.log("nextTick") }) console.log("組件:created") console.log("%c%s", "color:red", "el : " + this.$el); //undefined console.log("%c%s", "color:red", "data : " + this.$data); //已被初始化 console.log("%c%s", "color:red", "message: " + this.message); //已被初始化 }, beforeMount() { console.log("組件:beforeMount") console.log("%c%s", "color:red", "el : " + (this.$el)); //已被初始化 console.log("%c%s", "color:red", "data : " + this.$data); //已被初始化 console.log("%c%s", "color:red", "message: " + this.message); //已被初始化 }, mounted() { console.log("組件:mounted") }, beforeUpdate() { console.log("beforeUpdate") }, updated() { console.log("updated") }, beforeDestroy: function() { console.log("beforeDestroy 銷毀前狀態===============》"); console.log("%c%s", "color:red", "el : " + this.$el); console.log("%c%s", "color:red", "data : " + this.$data); console.log("%c%s", "color:red", "message: " + this.message); }, destroyed: function() { console.log("destroyed 銷毀完成狀態===============》"); console.log("%c%s", "color:red", "el : " + this.$el); console.log("%c%s", "color:red", "data : " + this.$data); console.log("%c%s", "color:red", "message: " + this.message) }, beforeRouteLeave(to, from, next) { console.log(this) //可以訪問vue實例 console.log("組件路由勾子:beforeRouteLeave") next() }
然后聲明組件
組件my-component-one的v-if聲明在組件上, 對應按鈕IfOnComponent。
組件my-component-two的v-if聲明在組件根元素上。對應按鈕IfOnRootElement。
兩個組件的布爾值通過兩個臨近的按鈕控制(toggle),v-if初始值activeOne和activeTwo的結果都是flase。
打開index.html, F12打開開發者工具。頁面剛加載時控制臺如下圖:
如圖所示, 可以看到在v-if聲明在組件根元素上, 初始值為false, 頁面加載時該組件my-component-two的生命周期會處于mounted掛載狀態, 但是沒有被渲染在DOM節點樹上。 組件my-component-one的v-if聲明在組件上, 則完全沒有進入生命周期。
情況1 v-if聲明在組件根元素上點擊按鈕IfOnRootElement, 效果如下圖紅框部分:
如圖所示, 點擊按鈕后activeTwo值的改變成true, 而觸發beforeUpdate => updated
再點擊按鈕IfOnRootElement, 效果如下圖紅框部分:
如圖所示, 再次點擊按鈕后activeTwo值的變回false, 又觸發beforeUpdate => updated
所以,v-if聲明在組件根元素上。 組件實例數據的改變會觸發beforeUpdate => updated
情況2 v-if聲明在組件上頁面剛加載時, my-component-one沒有進入生命周期, 先清空控制臺, 點擊按鈕IfOnComponent, 效果如下圖紅框部分:
如圖所示, 點擊按鈕后activeOne值的改變成true, 而觸發beforeCreate => create => beforeMount => Mounted, 組件實例掛載并被渲染到DOM節點樹中。
再點擊按鈕IfOnComponent, 效果如下圖紅框部分:
如圖所示, 再次點擊按鈕后activeOne值的變回false, 觸發beforeDestroy => destroyed, 組件不會觸發beforeUpdate => updated而直接進入銷毀。
所以,v-if聲明在組件上。 組件實例與v-if相關數據的改變不會觸發beforeUpdate => updated
本demo完整代碼如下:
vue-lifecycle
這個demo寫下來我們需要下面兩條結論, 來作為接下來實際vue-cli項目的支持.
所以,v-if聲明在組件根元素上。 組件實例數據的改變會觸發beforeUpdate => updated
所以,v-if聲明在組件上。 組件實例與v-if相關數據的改變不會觸發beforeUpdate => updated
我在章節1的開頭說過表單會面臨的問題, 不記得同學可以回去看.
如果有vue init webpck my-project 并(cnpm install)安裝好依賴模塊的項目, 建議在干凈的項目上直接拷貝下文的代碼, 否則建議直接點擊github鏈接clone項目或者下載壓縮包.并按步驟啟動項目, 經過測試, 能順利運行.
本章github源碼鏈接
目錄結構
或vue init webpck my-project 構建完成項目以后,
cd my-project,
npm install或cnpm install
安裝完成后, 先在srcassets文件夾下創建了css文件夾并在里面編寫了需main.css, 代碼如下:
(先不要處理細節, copy代碼先讓項目能運行, 本文的主要關注點是生命周期和MVVM, 對于不知道怎么把html文件中引入vue完成的非腳手架項目移植到vue-cli腳手架上去的同學, 可以參考代碼結構)
main.css:
[v-cloak] { display: none; } table { border: 1px solid #ccc; padding: 0; border-collapse: collapse; table-layout: fixed; margin-top: 10px; width: 100%; } table td, table th { height: 30px; border: 1px solid #ccc; background: #fff; font-size: 15px; padding: 3px 3px 3px 8px; overflow: hidden; } table th:first-child { width: 30px; } .container, .st { width: 100%; margin: 10px auto 0; font-size: 13px; font-family: "Microsoft YaHei"; } .container .search { font-size: 15px; padding: 4px; } .container .add { padding: 5px 15px; } .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 6; background: rgba(0, 0, 0, 0.7); } .overlay td:first-child { width: 66px; } .overlay .con { position: absolute; width: 420px; min-height: 300px; background: #fff; left: 50%; top: 50%; -webkit-transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0); /*margin-top: -150px;*/ padding: 20px; }
然后修改App.vue并在srccomponent目錄下編寫了組件Table.vue和Crud.vue, 代碼如下:
App.vue:
Table.vue:
{{ head }} {{index+1}} {{ value.toString() }}
Crud.vue:
最后后修改了router目錄下的index.js文件, 代碼如下:
index.js:
import Vue from "vue" import Router from "vue-router" import HelloWorld from "@/components/HelloWorld" import Table from "@/components/Table" Vue.use(Router) export default new Router({ routes: [ { path: "/helloWorld", name: "HelloWorld", component: HelloWorld }, { path: "/", name: "Table", component: Table } ] })
這里插一句, 分配路由的時候, 每個name對應字符串, 會出現在F12的vue devtools的組建實例結構中, 如下圖所示.
如上圖所示, 由于Crud.vue定義的name是Crud所以在dev tools中顯示的是 my-project項目目錄的根目錄, 輸入命令npm start即可在本地8080打開項目. 如上組件層級圖所示, 這個項目一共三個組件來模擬表單操作。即: App.vue項目自帶組件, 顯示一張Vue的圖片, 路由的根組件。 Crud.vue是一個編輯模態框如下圖所示: 現在我們應該對模塊化表單組件結構有了一定理解了, 先無視代碼細節, 來關注和第一章一樣的v-if聲明位置對于組件生命周期的影響,以表單的編輯功能為例。 思考一下, 現在需要一個具有增刪改查功能的表單, 有表格主體, 有一個能添加一列信息的表格, 有一個能添加新信息和編輯修改信息模態框, 還有刪除按鈕, 和搜索框。 為了編寫可復用的組件, 把一個簡易的表單分成兩個組件, 一個是展示表單信息的表格Table.vue, 還有一個模態框Crud.vue, 當需要刪除一行數據的時候刪除對應行的數據(model), 搜索框忽略, 增加和編輯信息需要用到模態框, 那么就涉及組件之間的通信問題來傳遞數據(model), 那么問題來了。 這兩個組件的組合方式是同級組件(非父子組件方式通信), 還是父子組件呢? 這你可以自行設計。 本項目采用的父子組件的方式來組合兩個組件, 即在Table.vue中有如下代碼: 打開項目顯示如下: 還記得之前的結論么, v-if聲明在組件根元素上, 組件會在頁面渲染完成后處于mounted狀態! 讓我們來看一看打開項目的初始生命周期: 頁面的渲染順序是, 所有組件先從最高父級組件至最低子組件都先經歷beforeCreate => created => beforeMount => 暫停, 下一個組件! (App.vue => Table.vue => Crud.vue) 當最后一個子組件達到beforeMount,然后全部組件以相反的順序進入掛載狀態mounted。 (Crud.vue => Table.vue => App.vue) 事件循環的順序(nextTick)是父 => 子 了解nextTick 現在我想要編輯第一行學生名字叫做李明的數據, 點擊編輯按鈕拿到李明的數據, 要怎么喚醒組件Crud然后傳遞李明的數據進行編輯呢? 記住, Props向下傳遞, 事件向上傳遞。 注意, HTML 特性是不區分大小寫的。所以,當使用的不是字符串模板時,camelCase (駝峰式命名) 的 prop 需要轉換為相對應的 kebab-case (短橫線分隔式命名)。 例如, html標簽上的modify-list等價于props中的"modifyList"。 點擊編輯, 拿到李明對象數據之后賦值給selectedList, 父組件的selectedList發生了變化, 且isActive變化為true, v-bind 來動態地將 prop(modifyList, isActive) 綁定到父組件的數據。每當父組件的數據變化時,該變化也會傳導給子組件Crud.vue, 子組件有段代碼this.list = this.modifyList, list是和模態框輸入框input進行雙向綁定v-model的數據。 子組件的isAcitve布爾值轉換為true, 模態框顯示出來。 重點來了, 假設我們不了解vue實例的生命周期, 不了解不同v-if聲明位置的生命周期, 那么Crud.vue組件接收到的李明對象selectedList和isActive的數據該在什么哪個生命周期進行賦值呢, created,mounted 還是updated, 一個一個的試? 這對于高效簡潔的編程是有阻礙的, 在這個思維胡亂的過程中, 怎么能寫出模塊化高的程序?怎么保證程序沒有bug? 好了, 現在我們知道了v-if聲明在組件根元素上在打開頁面時就出在生命周期的mounted狀態, 如果在created或mounted之前的生命周期會接收不到Table父組件傳遞的數據。 list為空對象, 所以視圖顯示如下: mounted之后的生命周期beforeUpdate和updated可以接收到數據。視圖顯示如下: 然后點擊保存按鈕, 把Crud組件修改的數據list通過$emit事件發出數據, 父組件Table通過$on監聽并接收收到的數據list, Table組件根據list中的名字更新相應行的數據(model), 然后通過雙向綁定v-model把更新的樹反應在表格視圖中。 然后隨意修改數據,如下圖 注意到, 表格中的用戶名也跟著變化。 再來1張圖: 關鍵的是, 點擊美容會被修改: 這是因為在 JavaScript 中對象和數組是引用類型,指向同一個內存空間,如果 prop 是一個對象或數組,在子組件內部改變它會影響父組件的狀態。 目前的解決方法是克隆一個內存空間中新生成對象李明, 在進行數據傳遞就不會出現問題。 即先把組件Table的李明對象轉成JSON字符串, 在轉回來, 達到"克隆"。 還有其他方法么? 歡飲討論。 問題(待解決): 在vue的編碼規范中有如下聲明: 所以, 我考慮Table.vue傳遞李明的字符串, edit()方法修改如下: 然后再在Crud.vue中解析成對象, 修改如下: 此時點擊一行數據進行編輯, 瀏覽器會進入死循環, 卡死。 解析放到beforeupdate 點擊編輯, 循壞100來此報錯: 哪位大神能詳盡的解釋一下么? updated狀態下進行解析生成新對象, 組件Crud.vue又會進入beforeUpdate => updated狀態又成新解析的對象, 無限循環直到內存溢出, 那么為什么解析放在updated中回掉瀏覽器器會直接卡死, 而beforeUpdated中遞歸會中止并報錯? 還要刪除組件Crud.vue根組件, 那么還記得之前的結論么, v-if聲明在組件上, 組件會在v-if為true后頁面才開始渲染顯示到DOM節點樹, 并顯示相應的視圖, 且組件關閉后后被銷毀 讓我們來看一看打開項目的初始生命周期: 可以看到沒有組件Crud.vue的控制臺數據。 現在, 傳遞的數據給list可以放在created beforeMount mounted任一生命周期中, 項目就能順利運行, 但是不能放在beforeUpdate updated生命周期中, 因為數據動態綁定的關系, 組件Crud.vue渲染到生命周期的created狀態時props中定義的數據就已經被傳遞數據并賦值了, 所以點擊編輯打開模態框并不會觸發beforeUpdate updated的生命周期, 在里面進行list是賦值行為是沒有意義的。 現在可以遵守規范, 讓組件Table.vue可以傳遞李明的字符串, 再在Crud.vue中解析成對象, 因為上述原理, 解析的新對象不會遞歸出發組件beforeUpdate updated的生命周期而進入死循環。 為了理解更深你不妨試試。 vue組件實例的生命周期還有需要探究的地方。 本章github源碼鏈接 文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。 轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93012.html 摘要:在第一版的基礎上進行了優化,新增一些面試題知識點,對一些知識點進行更加深入的描述。可以在該鉤子中進一步地更改狀態,不會觸發附加的重渲染過程。改變中的狀態的唯一途徑就是顯式地提交。這兩個可以在不進行刷新的情況下,操作瀏覽器的歷史紀錄。
在第一版的基礎上進行了優化,新增一些面試題/知識點,對一些知識點進行更加深入的描述。
一、對于MVVM的理解?
MVVM 是 Model-View-Vie... 摘要:這里借鑒了一下的處理方式,我們把單獨模塊的包裝成一個函數,提供一個全局的回調方法,加載完成時候再調用回調函數。
感謝本文引用鏈接的各位大佬們,小菜鳥我只是個搬運工
1.談一談你理解的vue是什么樣子的?
vue是數據、視圖分離的一個框架,讓數據與視圖間不會發生直接聯系。MVVM
組件化:把整體拆分為各個可以復用的個體
數據驅動:通過數據變化直接影響bom展示,避免dom操作。
可以在... 閱讀 3729·2021-09-22 15:49 閱讀 3300·2021-09-08 09:35 閱讀 1422·2019-08-30 15:55 閱讀 2321·2019-08-30 15:44 閱讀 714·2019-08-29 16:59 閱讀 1597·2019-08-29 16:16 閱讀 479·2019-08-28 18:06 閱讀 890·2019-08-27 10:55表單組件是
export default {
name: "Crud",
data() {
...
},
等組件的組件名字都可以自定義, 最好使命名能有自描述性.
Table.vue是表單組件。如下圖所示:引入Crud.vue
聲明該組件到模板中
Crud聲明
Table通過v-bind命令傳遞數據給子組件
Crud.vue:
modify() {
this.$emit("edit", this.list);
}
Table.vue:
...
this.selectedList = JSON.parse(JSON.stringify(this.students[index]))
傳遞過于復雜的對象使得我們不能夠清楚的知道哪些屬性或方法被自定義組件使用,這使得代碼難以重構和維護。
edit(index) {
this.selectedList = JSON.stringify(this.students[index]);
this.toggleEdit();
}
updated() {
this.list = JSON.parse(this.modifyList);
}
beforeupdate() {
this.list = JSON.parse(this.modifyList);
}
相關文章
Vue面試中,經常會被問到的面試題/知識點(2019改進版)
vue常用知識點總結
發表評論
0條評論
silvertheo
男|高級講師
TA的文章
閱讀更多
怎么設置虛擬主機映射-中興,光纖貓F420虛擬主機如何配置?
什么是性能測試?性能測試目的?性能測試的主要分類以及性能測試的常用指標?
leaflet 入門視頻
前端面試每日 3+1 —— 第127天
JavaScript常見的六種繼承方式
grid布局快速入門
DIV+CSS IE6/IE7/IE8/FF兼容問題匯總
Reflect 通過反射獲取自定義注解值給另外一個對象賦值