完整版的Vue基础学习

Vue 基础学习

MVC、MVP、MVVM

MVC

img

  • View 传送指令到 Controller
  • Controller 完成业务逻辑后,要求 Model 改变状态
  • Model 将新的数据发送到 View,用户得到反馈

不足之处:依赖太多

  • View 依赖Controller和Model
  • Controller依赖View和Model
  • Model 和View的关系虽然很弱, 但是也需要某种方式来通知View进行数据更新。

m层和v层直接打交道,导致这两层还存在耦合的

因为所有逻辑都写在c层,导致c层特别臃肿

MVP

img

  • 各部分之间的通信,都是双向的。
  • 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

img

  • 唯一的区别是,它采用双向绑定(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中 接收一个回调函数 函数参数ewValueoldValue,可以看做computedmethods的结合体

组件

1、组件化的特性:

高内聚性,组建功能必须是完整的,如我要实现下拉菜单功能,那在下拉菜单这个组件中,就把下拉菜单所需要的所有功能全部实现。
低耦合度,通俗点说,代码独立不会和项目中的其他代码发生冲突。在实际工程中,我们经常会涉及到团队协作,传统按照业务线去编写代码的方式,就很容易相互冲突,所以运用组件化方式就可大大避免这种冲突的存在、每一个组件都有子集清晰的职责,完整的功能,较低的耦合便於单元测试和重复利用。

2、组件化的优点:

提高开发效率
方便重复使用
简化调试步骤
提升整个项目的可维护性
便于协同开


Vue生命周期

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 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
  4. v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

Transition Diagram

<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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章