美文网首页
Hello Vue .

Hello Vue .

作者: Super三脚猫 | 来源:发表于2020-12-23 22:43 被阅读0次

    Vue 预习 🎬

    logo.png

    🧩Hello Vue

    • 宿主文件

    • 引入 vue.js

    • 创建vue实例

      ...
      <!-- 宿主文件 -->
      <div id='app'>
          {{title}}
      </div>
      ...
      <script src='./vue.js'></script>
      <script>
        const app = new Vue({
            el: '#app', // 选择器
            data() {
                return {
                    title: 'Hello, vue!'
                }
            },
        })
      </script>
    
    • 理解 vue 的核心理念(设计思想)
    1. 数据驱动应用(不需要直接操作DOM直接数据绑定)
    2. MVVM 模式的践行者 (Model、View、ViewModel)
    eqLqPV
    • MVVM 框架的三个要素:响应式、模板引擎及其渲染
    1. 响应式:数据响应式,改数据界面就会跟随变化
    2. 模板引擎:Vue解析视图里的表达式
    3. 渲染:Vue的渲染函数得到虚拟DOM,虚拟DOM再转化为真正DOM

    🧩Vue 核心知识 模板语法

    • 插值文本
    1. v-once 指令 (当数据改变时,插值处的内容不会更新)
    2. v-html 输出真正的 HTML
    3. v-bind 绑定在 HTML attribute
    4. JavaScript 表达式: {{ ok ? 'YES' : 'NO' }}
    • 列表渲染
    <div v-for="c in courses" :key="c.id">
        {{ c }}
    </div>
    
    • 用户输入(双向绑定)
    <p>
        <input type="text" v-model='course' v-on:keydown.enter='addCourse'>
        <button @click='addCourse'>新增</button>
    </p>
    ...
    ...
    methods: {
        addCourse() {
            // 1. 新增元素
            this.courses.push(this.course)
            // 2. 清空输入框
            this.course = ''
        }
    },
    
    • classstyle绑定
    :class="{active: selectCourse === c}
    
    • 列表渲染
    <div class="course-list" v-else>
        <div v-for="c in courses" :key="c.id" :class="{active: selectCourse === c}"
            @click='selectCourse = c'>
            {{ c }}
        </div>
    </div>
    
    • 条件渲染
    <!-- 条件渲染 -->
    <div v-if='courses.length == 0'>没有课程</div>
    

    注意:v-forv-if 会有优先级问题,不要在同一标签中同时使用

    • 模板语法底层是怎样实现的
    console.log(app.$option.render)
    

    🧩计算属性 & 监听器

    原计算方式Demo:

    <p>
    <!-- 绑定表达式 -->
    <!-- 课程总数:{{courses.length + '门'}} -->
    </p>
    
    • 计算属性 computed
    <!-- 计算属性 -->
    <!-- 课程总数:{{total}} -->
    ...
    const app = new Vue({
          computed: {
            total() {
              return this.courses.length + '门'
            }
          },
      })
    
    • 监听器 watch
    <!-- 监听器 -->
    课程总数:{{totalCount}}
    ...
    data() {
        return {
            ...
            totalCount: 0, // 先定义值
        }
    },
    ...
    // 下面这种不能生效,因为初始化时不会触发
    // watch: {
    //     courses(newValue, oldValue) {
    //         this.totalCount = newValue.length + '门'
    //     }
    // },
    watch: {
        courses: {
            immediate: true, // 即时触发
            // deep: true, // 深数据使用
            handler(newValue, oldValue) {
                this.totalCount = newValue.length + '门'
            }
        }
    },
    

    🧩核心知识 - 生命周期

    Vue实例的生命周期过程中会运行一些叫做生命周期钩子的函数,这给用户在不同阶段添加自己代码 的机会

    • 最常用的 createdmounted
    1. created未加载完DOM
    2. mounted已加载完,可以直接做操作DOM的操作
    ...
    async created() {
        const courses = await getCourses()
        this.courses = courses
    }
    
    • 使用场景分析
    {
      beforeCreate(){} // 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务 
      created(){} // 组件初始化完毕,各种数据可以使用,常用于异步数据获取
      beforeMount(){} // 未执行渲染、更新,dom未创建
      mounted(){} // 初始化结束,dom已创建,可用于获取访问数据和dom元素
      beforeUpdate(){} // 更新前,可用于获取更新前各种状态
      updated(){} // 更新后,所有状态已是最新
      beforeDestroy(){} // 销毁前,可用于一些定时器或订阅的取消
      destroyed(){} // 组件已销毁,作用同上
    }
    

    🧩核心知识 - 组件化

    组件封装注意:

    1. 单项数据流(一个变量由外再传出,中间不做变量再定义)
    2. 无状态组件封装
    • 组件注册
    <!-- 新增组件 -->
    <component-add @add='parentAdd'></component-add>
    

    注意:直接在DOM中使用注册组件时,DOM不区分大小写(例如:<h1>/<H1>),所以建议是中划线起名

    • Prop & 组件自定义事件
    Vue.component('component-add', {
        data() {
            return {
                course: ''
            }
        },
        // 参数父传子
        props: {
            courses: {
                type: Array, // Prop 限制传输类型
                default: []
            }
        },
        template: `
        <div>
            <!-- 用户输入 -->
            <p>
                <input type="text" v-model='course' v-on:keydown.enter='addCourse'>
                <button @click='addCourse'>新增</button>
            </p>
        </div>
        `,
        methods: {
            addCourse() {
                // $emit 参数子传父
                this.$emit('add', this.course) // add 是注册组件绑定的方法名
                this.course = '' // 清空输入值
            }
        },
    })
    ···
    ···
    // 创建vue实例
    const app = new Vue({
        el: '#app', // 选择器
        data() {
            return {
                title: 'Hello, vue!',
                ok: true,
                course: '',
                courses: [],
                totalCount: 0,
            }
        },
        methods: {
            addCourse() {
                // 1. 新增元素
                this.courses.push(this.course)
                // 2. 清空输入框
                this.course = ''
            },
            parentAdd(value) {
                this.courses.push(value)
            }
        },
    })
    
    • 在组件上使用 v-model (变形上面封装的组件例子🌰)

    注意:了解 v-model 的语法糖,实现无状态组件封装

    // v-model
    <component-add v-model='course' @add='parentAdd'></component-add>
    // 语法糖
    <component-add :value='course' @input='course=$event' @add='parentAdd'></component-add>
    

    组边变动的Demo:

    <!-- 新增组件 -->
    <component-add v-model='course' @add='parentAdd'></component-add>
    ···
    ···
    Vue.component('component-add', {
        props: ['value'],
        template: `
        <div>
            <!-- 用户输入 -->
            <p>
                <input type="text" v-on:keydown.enter='addCourse'
                :value='value' @input='onInput'>
                <button @click='addCourse'>新增</button>
            </p>
        </div>
        `,
        methods: {
            addCourse() {
                this.$emit('add') // 这里调用的还是外部事件 @add='parentAdd'
            },
            onInput(e) {
                // 外部的 input,也就是 v-model 语法糖解析后的 input 事件!
                this.$emit('input', e.target.value)
            },
        },
    })
    
    • 插槽/具名插槽/sync 修饰
    <!-- 弹窗组件 -->
    <message :show.sync='isShow'>
    <!-- <message @update:show='$event'> -->
        <!-- 具名插槽 -->
        <template v-slot:title>
            <h2>恭喜</h2>
        </template>
        <template>
        <!-- 不起名字的默认语法糖 -->
        <!-- <template v-slot:default> -->
            {{addValueName}}
        </template>
    </message>
    

    作用域插槽:

    <template v-slot:title='slotProps'>
        <!-- <h2>恭喜</h2> -->
        <!-- 作用域插槽 -->
        {{slotProps.scopeSlot}}
    </template>
    ···
    ···
    <!-- 通过 slot 插槽获取传入的内容-->
    <slot name='title' scopeSlot='作用域插槽'></slot>
    
    • 组件化的理解

    组件化是Vue的精髓,Vue应用就是由一个个组件构成的。Vue的组件化涉及到的内容非常多,当面试时
    被问到:谈一下你对Vue组件化的理解。这时候有可能无从下手,可以从以下几点进行阐述:

    1. 定义:组件是可复用的 Vue 实例,准确讲它们是VueComponent的实例,继承自Vue

    2. 优点:从上面案例可以看出组件化可以增加代码的复用性、可维护性和可测试性

    3. 使用场景:什么时候使用组件?以下分类可作为参考:

    • 通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等
    • 业务组件:它们完成具体业务,具有一定的复用性,例如登录组件、轮播图组件。
    • 页面组件:组织应用各部分独立内容,需要时在不同页面组件间切换,例如列表页、详情页组件
    1. 如何使用组件
    • 定义:Vue.component()components选项,sfc
    • 分类:有状态组件,functionalabstract
    • 通信:props$emit()/$on()provide/inject$children/$parent/$root/$attrs/$listeners
    • 内容分发: <slot>,<template>,v-slot
    • 使用及优化:iskeep-alive,异步组件
    1. 组件的本质
      vue中的组件经历如下过程
      组件配置 => VueComponent实例 => render() => Virtual DOM=> DOM

    所以组件的本质是产生虚拟DOM

    🧩核心知识 - Vue 必回API

    • 数据相关API
    1. Vue.set() as this.$set
    2. Vue.delete() as this.$delete

    向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新

    • 事件相关API
    1. vm.$on
      监听当前实例上的自定义事件。事件由vm.$emit触发,回调函数会接收所有传入事件触发函数的额外参数
    2. vm.$emit
      触发当前实例上的事件。附加参数都会传给监听器回调
    3. vm.$once 监听一个自定义事件,但是只触发一次,一旦触发后,监听器就会被移除
    4. vm.$off 移除自定义事件监听器
    vm.$off() 如果没有提供参数,则移出所有的事件监听器
    vm.$off('test') 如果只提供了事件,则只移出该时间的所有监听器
    vm.$off('test', callback) 如果同时提供了时间和回调,则只移出这个回调的监听器
    
    • 事件总线 (总线设计模式)

    通过在Vue原型上添加一个Vue实例作为事件总线,实现组件间相互通信,而且不受组件间关系的影响

    Vue.prototype.$bus = new Vue()

    这样做可以在任意组件中使用this.$bus访问到该Vue实例

    Demo:

    <!-- 派发事件:toolbar -->
    <div class="toolbar">
        <button @click='$bus.$emit("message-close")'>清除弹框</button>
    </div>
    ···
    ···
    <script src='./vue.js'></script>
    <script>
    // 注册事件总线
    Vue.prototype.$bus = new Vue()
    ···
    </script>
    ···
    ···
    // 监听事件 $on 上面有解释
    mounted() {
        this.$bus.$on('message-close', () => {
            // Do something...
            this.$emit('update:show', false)
        })
    }
    
    • 组件或元素引用 (ref 如哎服润丝)

    ref被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的$refs对象上。如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件示实例

    获取焦点 Demo:

    ···
    <input type="text" ... ref="inp">
    ···
    ···
    mounted() {
      // mounted之后才能访问到inp
      this.$refs.inp.focus()
    }
    

    🧩扩展知识 - 过度&动画

    • CSS 过度动画 (transition组件)
    transition组件添加动画
    <style>
    /**
     * 动画相关样式
     * enter 入场动画 -> 准备离场
     * enter-active 入场后过度名
     * leave 离场动画
     * leave-to 离场前
     * leave-active 离场后
    **/
    .fade-enter, .fade-leave-to {
        opacity: 0
    }
    .fade-enter-active, .fade-leave-active {
        transition: opacity 1.5s
    }
    </style>
    ···
    ···
    <script>
    Vue.component('message', {
        // 使用 transition 组件应用过度动画
        template: `
         <transition name="fade">
           ...
         </transition>
        `, 
    })
    </script>
    
    • 使用 CSS 动画库

    引入animate.css

    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">

    transition 设置

    <transition enter-active-class="animated bounceIn"
                leave-active-class="animated bounceOut">
    
    • JS 动画 (JavaScript 钩子)

    可以在<transition>属性中声明JavaScript钩子,使用JS实现动画

    <transition
      v-on:before-enter="beforeEnter" // 动画开始前,设置初始状态
      v-on:enter="enter" // 执行动画
      v-on:after-enter="afterEnter" // 动画结束,清理工作
      v-on:enter-cancelled="enterCancelled" // 取消动画
      v-on:before-leave="beforeLeave"
      v-on:leave="leave"
      v-on:after-leave="afterLeave" 
      v-on:leave-cancelled="leaveCancelled"
    ></transition>
    
    • 结合 transition 一起使用 Demo:
    ···
    // 动画钩子
    .fade-enter-active, .fade-leave-active {
        transition: opacity 1.5s
    }
    ···
    ···
    <transition name='fade'
        @before-enter='beforeEnter'
        @enter='enter'
        @before-leave='beforeLeave'
        @leave='leave'
        >
        <div class='message-box' v-if='show'>
            <!-- 通过 slot 插槽获取传入的内容-->
            <slot name='title' scopeSlot='作用域插槽'></slot>
            <slot></slot>
            <span class='message-box-close' @click='toggle'>X</span>
        </div>
    </transition>
    ···
    ···
    methods: {
        beforeEnter(el) {
            // el 指的就是 transition 子元素本身
            // 动画的初始状态
            el.style.opacity = 0;
        },
        enter(el, done) {
            // 这里会有 浏览器重排/回流
            // 触发回流才能激活动画
            document.body.offsetHeight
            // 动画结束状态
            el.style.opacity = 1;
            // done 函数,动画结束后的动作
            // 监听动画结束事件,并执行 done 函数 
            el.addEventListener('transitionend', done)
        },
        beforeLeave(el) {
            // 离开之前
            el.style.opacity = 1;
        },
        leave(el, done) {
            // 离开之后
            el.style.opacity = 0;
            // 监听动画结束事件,并执行 done 函数 
            el.addEventListener('transitionend', done)
        },
    },
    
    • JS动画,引入第三方动画Demo:
    // 引入JS
    <script src='https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js'></script>
    ···
    methods: {
      beforeEnter(el) {
          // el 指的就是 transition 子元素本身
          // 动画的初始状态
          el.style.opacity = 0;
      },
      enter(el, done) {
          // 内部帮忙做了回流,这里就不需要了
          Velocity(el, { opacity: 1 }, { duration: 1500, complete: done })
      },
      beforeLeave(el) {
          // 离开之前
          el.style.opacity = 1;
      },
      leave(el, done) {
          Velocity(el, { opacity: 0 }, { duration: 1500, complete: done })
      },
    },
    

    知识点:涉及浏览器回流原理

    • 列表过度

    利用transition-group可以对v-for渲染的每个元素应用过度

    transition-group 使用
    transition-group用于列表的过渡效果:

    1. 默认为span, 可以使用tag 转换为其他元素。
    2. 子元素通常使用v-for进行循环。
    3. 子元素必须要有唯一的key属性,且key不能为索引值。
    4. css过渡是应用在子元素中,而不是这个容器本身。
    5. 对应的js事件钩子:before-enter、enter、after-enter、enter-cancelled、before-leave、leave、after-leave、leave-cancelled
    ···
    .fade-enter, .fade-leave-to {
        opacity: 0
    }
    .fade-enter-active, .fade-leave-active {
        transition: opacity 1.5s
    }
    ···
    ···
    <!--
        如果需要过滤的元素是通过v-for循环渲染出来,不能使用transition包裹,需要使用transition-group
        如果要为v-for循环创建元素设置动画,必须为每一个元素设置:key属性
    -->
    <transition-group name="fade" class="content" tag="ul">
        <div v-for="c in courses" :key="c" :class="{active: selectCourse === c}"
            @click='selectCourse = c'>
            {{ c }}
        </div>
    </transition-group>
    

    🧩扩展知识 - 过滤器

    过滤器分为:全局过滤器、局部过滤器

    • 全局过滤器
    ···
    {{ c | currency('$') }}
    ···
    Vue.filter('currency', (value, symbol = '¥') => {
        return symbol + value
    })
    
    • 局部过滤器
    ···
    {{ c | currency('$') }}
    ···
    ···
    methods: {
      ···
    },
    // 局部过滤器
    filters: {
        currency(value, symbol = '¥') {
            return symbol + value
        }
    }
    
    • 自定义指令

    除了核心功能默认内置的指令 ( v-modelv-show ),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操 作,这时候就会用到自定义指令

    ···
    <!-- toolbar -->
    <div class="toolbar" v-permisstion="getPermisstion()">
        <button @click='$bus.$emit("message-close")'>清除弹框</button>
    </div>
    ···
    // 获取权限的方法
    getPermisstion() {
        return 'admin';
    }
    ···
    ···
    let role = 'admin2';
    // 自定义事件
    Vue.directive('permisstion', {
        inserted(el, binding) {
            if (role != binding.value) {
                el.parentElement.removeChild(el)
            }
        }
    })
    

    知识点:directive下的钩子函数

    相关文章

      网友评论

          本文标题:Hello Vue .

          本文链接:https://www.haomeiwen.com/subject/aiqpnktx.html