Vue .

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下的钩子函数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容