Vue 基礎學習
目錄
MVC、MVP、MVVM
MVC
- View 傳送指令到 Controller
- Controller 完成業務邏輯後,要求 Model 改變狀態
- Model 將新的數據發送到 View,用戶得到反饋
不足之處:依賴太多
- View 依賴Controller和Model
- Controller依賴View和Model
- Model 和View的關係雖然很弱, 但是也需要某種方式來通知View進行數據更新。
m層和v層直接打交道,導致這兩層還存在耦合的
因爲所有邏輯都寫在c層,導致c層特別臃腫
MVP
- 各部分之間的通信,都是雙向的。
- View 與 Model 不發生聯繫,都通過 Presenter 傳遞。即View只知道Presenter, 不知道Model 。
- View 非常薄,不部署任何業務邏輯,稱爲"被動視圖"(Passive View),即沒有任何主動性,而 Presenter非常厚,所有邏輯都部署在那裏。
不足之處:
- Presenter還是需要調用View的方法,也就是說Presenter對View有依賴,這樣Presenter就沒辦法單獨做單元測試,非得等到界面做好以後纔行。
- 可以讓View層提取出接口,Presenter只依賴這個接口
p層代替了了c層,v層和m層的交互被p層隔斷,從理論上去除了v和m層的耦合
但是造成p層比原來的c層更加臃腫,爲了緩解這種臃腫,MVVM出現了
MVVM
- 唯一的區別是,它採用雙向綁定(data-binding):View的變動,自動反映在 ViewModel,反之亦然。Angular 和 Ember 都採用這種模式。
- 在MVP模式中:讓Presenter調用View的方法去設置界面,仍然需要大量的、煩人的代碼。所以ViewModel就誕生了,他是一個數據結構,而view可以根據這個數據結構的變化自動隨之變化
基礎指令
v-cloak
或{{value}}
能夠解決 插值表達式的閃爍問題 不過需要在style裏設置樣式
[v-cloak]{ display: none !important; }
v-text
沒有閃爍問題v-text
會覆蓋標籤裏的內容v-html
會將元素當做HTML解析,上面兩個只會當做文本v-bind
綁定屬性 縮寫:
v-on
綁定事件 縮寫@
綁定的方法可以加括號方便傳參數v-model
實現數據的雙向綁定.stop
阻止事件冒泡.capture
使用捕獲機制.self
只有點擊當前元素時才觸發事件.prevent
去阻止默認行爲.once
只觸發一次事件處理函數v-for
遍歷對象 注意:除了有item key 之外還有索引i 如v-for="(item,key,i) in user
使用:key="item"
指定綁定的key key需要具有唯一性(最好爲後臺數據庫傳過來json數據的id)(只能使用number
或者String
)v-if
的特點是每次都會重新刪除或者創建元素 有較高的切換性能消耗 (頻繁的切換最好不用)v-show
不會每次都操作DOM的創建和刪除,只是切換了元素的display:none;
的樣式 有較高的初始渲染消耗
Vue實例中的屬性
el
表示要控制頁面的區域data
表示data中存放的是el中用到的數據methods
存放所需方法,主要處理業務邏輯computed
計算屬性 主要當做屬性來使用,可在其中進行算數邏輯運算,存在緩存機制性能更高 以減輕模板重量 使程序便於維護watch
監視 監視對象在data中 接收一個回調函數 函數參數ewValue
和oldValue
,可以看做computed
和methods
的結合體
組件
1、組件化的特性:
高內聚性,組建功能必須是完整的,如我要實現下拉菜單功能,那在下拉菜單這個組件中,就把下拉菜單所需要的所有功能全部實現。
低耦合度,通俗點說,代碼獨立不會和項目中的其他代碼發生衝突。在實際工程中,我們經常會涉及到團隊協作,傳統按照業務線去編寫代碼的方式,就很容易相互衝突,所以運用組件化方式就可大大避免這種衝突的存在、每一個組件都有子集清晰的職責,完整的功能,較低的耦合便於單元測試和重複利用。
2、組件化的優點:
提高開發效率
方便重複使用
簡化調試步驟
提升整個項目的可維護性
便於協同開
Vue生命週期
-
beforeCreate
(創建前) 在數據觀測和初始化事件還未開始 -
created
(創建後) 完成數據觀測,屬性和方法的運算,初始化事件,$el屬性還沒有顯示出來 -
beforeMount
(載入前) 在掛載開始之前被調用,相關的render函數首次被調用。實例已完成以下的配置:編譯模板,把data裏面的數據和模板生成html。注意此時還沒有掛載html到頁面上。 -
mounted
(載入後) 在el 被新創建的 vm.$el 替換,並掛載到實例上去之後調用。實例已完成以下的配置:用上面編譯好的html內容替換el屬性指向的DOM對象。完成模板中的html渲染到html頁面中。此過程中進行ajax交互。 -
beforeUpdate
(更新前) 在數據更新之前調用,發生在虛擬DOM重新渲染和打補丁之前。可以在該鉤子中進一步地更改狀態,不會觸發附加的重渲染過程。 -
updated
(更新後) 在由於數據更改導致的虛擬DOM重新渲染和打補丁之後調用。調用時,組件DOM已經更新,所以可以執行依賴於DOM的操作。然而在大多數情況下,應該避免在此期間更改狀態,因爲這可能會導致更新無限循環。該鉤子在服務器端渲染期間不被調用。 -
beforeDestroy
(銷燬前) 在實例銷燬之前調用。實例仍然完全可用。 -
destroyed
(銷燬後) 在實例銷燬之後調用。調用後,所有的事件監聽器會被移除,所有的子實例也會被銷燬。該鉤子在服務器端渲染期間不被調用。
Vue中的樣式綁定
1、class的對象綁定
<style>
.activated{
color: red;
}
</style>
<div id='app'>
<div @click="handleDivClick" :class="{activated:isActivated}">
Hello World
</div>
</div>
<script>
var vm=new Vue({
el:'#app',
data:{
isActivated:false
},
methods:{
handleDivClick:function(){
this.isActivated=!this.isActivated;
}
}
});
</script>
2、class的數組綁定
<style>
.activated {
color: red;
}
.activated-one{
font-size: 25px;
}
</style>
<div id='app'>
<div @click="handleDivClick" :class="[activated,activatedOne]">
Hello World
</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
activated: "",
activatedOne:"activated-one"
},
methods: {
handleDivClick: function () {
this.activated = this.activated === "activated" ? "" : "activated";
}
}
});
</script>
3、style對象綁定
<div id='app'>
<div :style="styleObj" @click="handleDivClick">
Hello World
</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
styleObj:{
color:"black"
}
},
methods: {
handleDivClick:function(){
this.styleObj.color=this.styleObj.color==="black"?"red":"black";
}
}
});
</script>
4、style數組綁定
<div id='app'>
<!-- <div @click="handleDivClick" :class="[activated,activatedOne]">
Hello World
</div> -->
<div :style="[styleObj,{fontSize:'25px'}]" @click="handleDivClick">
Hello World
</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
styleObj:{
color:"black"
}
},
methods: {
handleDivClick:function(){
this.styleObj.color=this.styleObj.color==="black"?"red":"black";
}
}
});
</script>
深入理解Vue組件
組件細節
1、tbody、ul、select下只能存放特定的標籤
<body>
<div id='app'>
<table>
<tbody>
<row></row>
<row></row>
<row></row>
</tbody>
</table>
</div>
<script>
Vue.component('row',{
template:'<tr><td>this is row</td></tr>'
})
var vm=new Vue({
el:'#app',
data:{
},
methods:{
}
});
</script>
</body>
上述代碼出現問題,tr出現在了在<table><tbody></tbody></table>
外,因爲<tbody>
裏只能放tr
改:
<div id='app'>
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
2、子組件data爲function
因爲根組件一樣只會被調用一次,子組件會被多次調用,而子組件每個需要自己的數據,所以不能是一個對象,必須是一個方法返回一個對象
Vue.component('row',{
data:function(){
return {
content:'this is content'
}
},
template:'<tr><td>this is row</td></tr>'
})
3、vue 操作DOM方式
vue不推薦操作DOM但有時候不得不去操作(複雜動畫)
標籤中
使用this.$refs.hello獲得DOM
<body>
<div id='app'>
<div ref='hello' @click="handleClick">
Hello World
</div>
</div>
<script>
var vm=new Vue({
el:'#app',
data:{},
methods:{
handleClick:function(){
console.log(this.$refs.hello.innerHTML);
}
}
});
</script>
</body>
組件中
使用this.$refs.hello獲得組件的引用
<body>
<div id='app'>
<!-- <div ref='hello' @click="handleClick">
Hello World
</div> -->
<counter @change="handleChange" ref="one"></counter>
<counter @change="handleChange" ref="two"></counter>
<div>{{total}}</div>
</div>
<script>
Vue.component('counter',{
data:function(){
return{
number:0
}
},
methods:{
handleClick:function(){
this.number ++;
this.$emit("change")
}
},
template:'<div @click="handleClick">{{number}}</div>'
})
var vm=new Vue({
el:'#app',
data:{
total:0
},
methods:{
// handleClick:function(){
// console.log(this.$refs.hello.innerHTML);
// }
handleChange:function(){
this.total=this.$refs.one.number+this.$refs.two.number
}
}
});
</script>
</body>
父子組件間的傳值
注意單向數據流,不推薦改變父組件傳遞過來的數據
1、父組件向子組件傳值
在父組件中使用屬性定義需要傳的值,子組件用props接收
:count="0"
傳遞的是一個數字(js表達式),若爲count="0"
傳遞的是字符
2、子組件向父組件傳值
子組件通過事件觸發的形式向父組件傳值
示例:
<div id='app'>
<counter :count="2" @inc="handleIncrease"></counter>
<counter :count="3" @inc="handleIncrease"></counter>
<div>{{total}}</div>
</div>
<script>
var counter={
props:[
'count'
],
data:function () {
return{
number:this.count
}
},
template:"<div @click='handleClick'>{{number}}</div>",
methods:{
handleClick:function(){
this.number=this.number+2;
this.$emit("inc",2)
}
}
}
var vm=new Vue({
el:'#app',
data:{
total:5
},
methods:{
handleIncrease:function(step){
this.total+=step;
}
},
components:{
counter:counter
}
});
</script>
組件參數校驗
props: {
// content:[Number,String]
content: {
type: [Number, String],
required: false, //是否必傳
default: "default value",
validator:function(value){
return(value.length>5)
}
}
}
組件綁定原生事件
1、子組件直接綁定
Vue.component('child', {
template: "<div @click='handleChildClick'>Child</div>",
props: {},
methods:{
handleChildClick:function(){
alert('child click')
}
}
})
2、給click
加上.native
<body>
<div id='app'>
<child @click.native="handleClick"></child>
</div>
<script>
Vue.component('child', {
template: "<div>Child</div>",
})
var vm = new Vue({
el: '#app',
data: {},
methods: {
handleClick:function(){
alert('click')
}
}
});
</script>
</body>
非父子組件間的傳值
使用 Bus/總線/發佈訂閱模式 實現
<script>
Vue.prototype.bus=new Vue()//讓以後創建的每個Vue示例都有一個共享的屬性bus,且bus是你一個Vue實例
Vue.component('child', {
template: "<div @click='handleClick'>{{selfContent}}</div>",
data:function(){
return {
selfContent:this.content
}
},
props:{
content:String
},
methods:{
handleClick:function(){
this.bus.$emit('change',this.selfContent)//bus是個Vue實例所以有$emit()方法
}
},
mounted:function () {
var this_=this
this.bus.$on('change',function (msg) {//$on監聽bus觸發的事件
this_.selfContent=msg
})
}
})
var vm = new Vue({
el: '#app',
});
</script>
Vue中的插槽
原來:太low
<div id='app'>
<child content="<p>Gu</p>"></child>
</div>
<script>
Vue.component('child',{
props:['content'],
template:'<div><p>hello</p><div v-html="this.content"></div></div>'
})
var vm=new Vue({
el:'#app',
data:{},
});
</script>
1、普通插槽、具名插槽
<div id='app'>
<child>
<!-- <p>Gu</p> -->
<div class="header" slot="header">header</div>
<div class="footer" slot="footer">footer</div>
</child>
</div>
<script>
Vue.component('child',{
props:['content'],
template:'<div><slot name="header">默認內容</slot><p>hello</p><slot name=footer>默認內容</slot></div>'
})
var vm=new Vue({
el:'#app',
data:{},
});
</script>
2、作用域插槽
原來:
<div id='app'>
<child></child>
</div>
<script>
Vue.component('child',{
data:function(){
return{
list:[1,2,3,4]
}
},
template:'<div><ul><li v-for="item of list">{{item}}</li></ul></div>'
})
var vm=new Vue({
el:'#app',
data:{
},
});
</script>
但我們渲染什麼需要外部告訴我們而不是child
子組件控制
作用域插槽應該由template
包裹 slot-scope
傳過來的接收數據
<div id='app'>
<child>
<template slot-scope="props">
<li>{{props.item}}---hello</li>
</template>
</child>
</div>
<script>
Vue.component('child',{
data:function(){
return{
list:[1,2,3,4]
}
},
template:'<div><ul><slot v-for="item of list" :item=item></slot></ul></div>'//傳入item數據
})
var vm=new Vue({
el:'#app',
data:{
},
});
</script>
動態組件與v-once
在組件里加上v-once
那麼組件第一次加載時會被放到內存裏,下次切換時會這從內存中取出,提高性能
<div id='app'>
<component :is="type"></component>
<!-- <child-one v-if="type==='child-one'"></child-one>
<child-two v-if="type==='child-two'"></child-two> -->
<button @click='handleBtnClick'>change</button>
</div>
<script>
Vue.component('child-one', {
template: '<div v-once>child-one</div>'
})
Vue.component('child-two', {
template: '<div v-once>child-two</div>'
})
var vm = new Vue({
el: '#app',
data: {
type:"child-one"
},
methods: {
handleBtnClick:function () {
this.type=this.type==='child-one' ? "child-two" : "child-one";
}
}
});
</script>
Vue中的動畫
Vue中css動畫的原理(過度動畫)
<transition name="fade">
<div v-if="show">hello world</div>
</transition>
當一個元素被transition
包裹以後,vue會自動分析元素的css樣式然後構建一個動畫流程
在進入/離開的過渡中,會有 6 個 class 切換。
v-enter
:定義進入過渡的開始狀態。在元素被插入之前生效,在元素被插入之後的下一幀移除。v-enter-active
:定義進入過渡生效時的狀態。在整個進入過渡的階段中應用,在元素被插入之前生效,在過渡/動畫完成之後移除。這個類可以被用來定義進入過渡的過程時間,延遲和曲線函數。v-enter-to
: 2.1.8版及以上 定義進入過渡的結束狀態。在元素被插入之後下一幀生效 (與此同時v-enter
被移除),在過渡/動畫完成之後移除。v-leave
: 定義離開過渡的開始狀態。在離開過渡被觸發時立刻生效,下一幀被移除。v-leave-active
:定義離開過渡生效時的狀態。在整個離開過渡的階段中應用,在離開過渡被觸發時立刻生效,在過渡/動畫完成之後移除。這個類可以被用來定義離開過渡的過程時間,延遲和曲線函數。v-leave-to
: 2.1.8版及以上 定義離開過渡的結束狀態。在離開過渡被觸發之後下一幀生效 (與此同時v-leave
被刪除),在過渡/動畫完成之後移除。
<style>
.fade-enter,
.fade-leave-to{
opacity: 0;
}
.fade-leave-active,
.fade-enter-active{
transition: opacity 4s;
}
</style>
<div id='app'>
<transition name="fade">
<div v-if="show">hello world</div>
</transition>
<button @click="handleBtnClick">Button</button>
</div>
<script>
var vm=new Vue({
el:'#app',
data:{
show:true
},
methods:{
handleBtnClick:function(){
this.show=!this.show
}
}
});
</script>
使用animate
1、自己寫:
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
.enter {
transform-origin: left center;
animation: bounce-in 1s;
}
.leave {
transform-origin: left center;
animation: bounce-in 1s reverse;
}
<div id='app'>
<transition enter-active-class="enter" leave-active-class="leave">
<div v-if="show">hello world</div>
</transition>
<button @click="handleBtnClick">Button</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
handleBtnClick: function () {
this.show = !this.show
}
}
});
</script>
2、使用animate.css庫
<link rel="stylesheet" type="text/css" href="../../lib/animate/animate.css">
<div id='app'>
<!-- <transition enter-active-class="enter" leave-active-class="leave"> -->
<transition enter-active-class="animated swing" leave-active-class="animated shake">
<div v-if="show">hello world</div>
</transition>
<button @click="handleBtnClick">Button</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
handleBtnClick: function () {
this.show = !this.show
}
}
});
</script>
同時使用過度和動畫
.fade-enter,
.fade-leave-to{
opacity: 0;
}
.fade-enter-active,
.fade-leave-active{
transition: opacity 1s;
}
<div id='app'>
<!-- type="transition"選擇時長爲過度動畫時長 :duration="{enter:5000,leave:10000}"自定義動畫時長-->
<transition
:duration="{enter:5000,leave:10000}"
name="fade"
appear
enter-active-class="animated swing fade-enter-active"
leave-active-class="animated shake fade-leave-active"
appear-active-class="animated swing">
<div v-if="show">hello world</div>
</transition>
<button @click="handleBtnClick">Button</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
handleBtnClick: function () {
this.show = !this.show
}
}
});
</script>
Vue中js動畫與Velocity結合
1、自己寫:
<div id='app'>
<transition name="fade"
@before-enter="handleBeforeEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter">
<div v-if="show">hello world</div>
</transition>
<button @click="handleBtnClick">Button</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
handleBtnClick: function () {
this.show = !this.show
},
handleBeforeEnter:function (el) {
el.style.color="red";
},
handleEnter:function(el,done){
setTimeout(() => {
el.style.color="green";
}, 2000);
setTimeout(() => {
done();
}, 4000);
},
handleAfterEnter:function(el){
el.style.color="#000"
}
}
});
</script>
2、使用velocity.js庫
<script src="../../lib/velocity/velocity.min.js"></script>
<div id='app'>
<transition name="fade" @before-enter="handleBeforeEnter" @enter="handleEnter" @after-enter="handleAfterEnter">
<div v-if="show">hello world</div>
</transition>
<button @click="handleBtnClick">Button</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
handleBtnClick: function () {
this.show = !this.show
},
handleBeforeEnter: function (el) {
el.style.opacity=0;
},
handleEnter: function (el, done) {
Velocity(el,{opacity:1},{duration:1000,complete:done})
},
handleAfterEnter: function (el) {
console.log("動畫結束")
}
}
});
</script>
多個元素的過渡動畫
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-leave-active,
.fade-enter-active {
transition: opacity 1s;
}
<div id='app'>
<!-- mode="in-out"元素先進入在隱藏 -->
<transition name="fade" mode="out-in">
<!-- 動態組件 -->
<component :is="type"></component>
<!-- <child-one v-if="show" key="one"></child-one>
<child-two v-else key="two"></child-two> -->
<!-- <div v-if="show" key="hello">hello world</div>
<div v-else key="bye">bey world</div> -->
</transition>
<button @click="handleBtnClick">Button</button>
</div>
<script>
Vue.component('child-one', {
template: '<div v-once>child-one</div>'
})
Vue.component('child-two', {
template: '<div v-once>child-two</div>'
})
var vm = new Vue({
el: '#app',
data: {
// show: true
type:"child-one"
},
methods: {
handleBtnClick: function () {
// this.show = !this.show
this.type=this.type==="child-one" ?"child-two":"child-one";
}
}
});
</script>
列表過渡動畫
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-leave-active,
.fade-enter-active {
transition: opacity 1s;
}
</style>
<div id='app'>
<transition-group name="fade">
<div v-for="item of list" :key="item.id">{{item.title}}</div>
</transition-group>
<button @click="handleBtnClick">Add</button>
</div>
<script>
var count = 0;
var vm = new Vue({
el: '#app',
data: {
list: []
},
methods: {
handleBtnClick: function () {
this.list.push({
id: count++,
title: "hello world " + (count - 1)
})
}
}
});
</script>
動畫封裝
1、css封裝
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave-active,
.v-enter-active {
transition: opacity 2s;
}
<div id='app'>
<fade :show="show">
<div>Hello World</div>
</fade>
<button @click="handleBtnClick">Button</button>
</div>
<script>
Vue.component("fade",{
props:['show'],
template:'<transition><slot v-if="show"></slot></transition>'
})
var vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
handleBtnClick: function () {
this.show = !this.show
}
}
});
</script>
2、js封裝
<div id='app'>
<fade :show="show">
<div>Hello World</div>
</fade>
<fade :show="show">
<h1>Hello World</h1>
</fade>
<button @click="handleBtnClick">Button</button>
</div>
<script>
Vue.component("fade",{
props:['show'],
template:'<transition @before-enter="handleBeforeEnter" @enter="handleEnter"><slot v-if="show"></slot></transition>',
methods:{
handleBeforeEnter:function(el){
el.style.color="red";
},
handleEnter:function(el,done){
setTimeout(() => {
el.style.color="green";
done();
}, 2000);
}
}
})
var vm = new Vue({
el: '#app',
data: {
show: true
},
methods: {
handleBtnClick: function () {
this.show = !this.show
}
}
});
</script>
學習到這路你的基礎基本已經掌握,可以看下一張實戰
https://blog.csdn.net/Gueyue/article/details/102453943