Hello Vue .

2020-12-23  本文已影响0人  Super三脚猫

Vue 预习 🎬

logo.png

🧩Hello Vue

  ...
  <!-- 宿主文件 -->
  <div id='app'>
      {{title}}
  </div>
  ...
  <script src='./vue.js'></script>
  <script>
    const app = new Vue({
        el: '#app', // 选择器
        data() {
            return {
                title: 'Hello, vue!'
            }
        },
    })
  </script>
  1. 数据驱动应用(不需要直接操作DOM直接数据绑定)
  2. MVVM 模式的践行者 (Model、View、ViewModel)
eqLqPV
  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 = ''
    }
},
: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>
<!-- 计算属性 -->
<!-- 课程总数:{{total}} -->
...
const app = new Vue({
      computed: {
        total() {
          return this.courses.length + '门'
        }
      },
  })
<!-- 监听器 -->
课程总数:{{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实例的生命周期过程中会运行一些叫做生命周期钩子的函数,这给用户在不同阶段添加自己代码 的机会

  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>),所以建议是中划线起名

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
<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)
        },
    },
})
<!-- 弹窗组件 -->
<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. 如何使用组件
  1. 组件的本质
    vue中的组件经历如下过程
    组件配置 => VueComponent实例 => render() => Virtual DOM=> DOM

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

🧩核心知识 - Vue 必回API

  1. Vue.set() as this.$set
  2. Vue.delete() as this.$delete

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

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

获取焦点 Demo:

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

🧩扩展知识 - 过度&动画

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>

引入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">

可以在<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>
···
// 动画钩子
.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
<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下的钩子函数

上一篇下一篇

猜你喜欢

热点阅读