组件系统、数据驱动
v-model,数据发生变化,同步视图,视图发生变化,同步数据
在父向子传值的时候,如果改变父组件的值,子组件会跟着同步更新,反之不允许
为什么自定义指令?
vue 提供的系统指令满足不了我们的需求,那么我们就需要自定义指令
通过 Vue.directive 进行自定义指令的定义
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。定义的时候是一个方法,使用的时候当作属性使用
只要 return 后面的数据发生变化,该计算属性就会重新计算
计算属性具有缓存特性
可以监听数据发生变化,可以监听的数据有(props、data、computed、$route) watch 侦听器如果监听的是一个对象,需要开启深度监听
watch:{
num:{
// 监听数据发生变化的处理函数
handler(newNum) {
console.log(newNum)
},
// 是否开启深度监听
deep: true
}
}
如果想实现首次监听配置 immediate 为 true
可以对数据格式进行处理,例如常用的例如格式化时间 使用方式通过 数据名 |(管道符)后面跟过滤器的名字
生命周期:是指一个对象从创建到运行到销毁的整个过程,被称为生命周期
生命周函数:在不同的生命周期阶段会自动执行对应的函数,而这些函数则被成为生命周期函数
// 创建阶段
beforeCreate() {
// 这个生命周函数,代表开始创建实例了
console.log('beforeCreate',this.num)
},
created () {
// 代表数据和方法已经初始化成功了,此处dom还没有挂载到页面上
console.log('created',this.num,this.$el)
},
beforeMount () {
// 挂在之前
console.log('beforeMount',this.$el)
},
mounted () {
// dom已经挂载了
console.log('mounted',this.$el)
},
// 运行更新阶段
beforeUpdate () {
// 数据更新,页面还没有同步
console.log('beforeUpdated',this.num,document.getElementById('app').innerHTML)
},
updated () {
// 数据更新,页面已经同步
console.log('updated',this.num,document.getElementById('app').innerHTML)
},
// 销毁阶段
beforeDestroy () {
// 销毁之前
console.log('beforeDestroy')
},
destroyed () {
// 已经销毁了
console.log('destroy')
}
在 vue 中对 对象新添加属性,页面是否更新?
不更新,如果想解决这个问题,vm.$set(vm.list, 1, ‘or’)或者 Vue.set
但是 vm.list[3].a = 456,通过索引修改某一项的对象内部的属性是没问题的
如果不是一个函数返回一个新的对象,组件如果多次使用,实际公用的是同一个数据
但是如果是通过函数 返回一个新的对象,这样的话,每个组件的使用数据是独立的
通过 Vue.component 来创建一个全局组件,第一个参数是组件名字,第二个参数是组件的配置对象,可以通过 template 配置组件的结构,data 定义数据等等
在组件内部通过 components 来创建一个局部组件
全局组件和局部组件的区别
局部组件:只能在当前的父组件中使用
全局组件: 在任意地方使用
在组件内部通过 directives 来创建一个局部指令
全局指令和局部指令的区别
局部指令:只能在当前的组件中使用
全局指令: 在任意地方使用
在组件内部通过 filters 来创建一个局部过滤器
全局过滤器和局部过滤器的区别
局部过滤器:只能在当前的组件中使用
全局过滤器: 在任意地方使用
定义一个事件中心,或者是第三方
接收值的组件:通过该事件中心的$on 来定义自定义事件的事件函数来接收值
eventBus.$on('getTab1', data => {
console.log('接收tab1传递的值', data)
})
另一个兄弟组件怎么传:通过事件中心的$emit 触发对应的 $on 的事件,并且把值传递过去
eventBus.$emit('getTab1', this.num)
Vue.component('my-sub1', {
template: '#my-sub1',
data() {
return {
money: 10000000
}
},
provide: {
money: 1000
},
components: {
'sub-a': {
template: '子组件 ',
components: {
'sub-b': {
template: '子组件{{money}}',
inject: ['money']
}
}
}
}
})
new Vue({
el: '#app'
})
默认插槽:
具名插槽
在组件标签中间通过定义 slot 的名字传递子节点
<my-banner>
<div slot="header">
头部
div>
<div slot="footer">
底部
div>
my-banner>
组件内部利用 slot 的 name 进行对应接收
<template id="banner">
<div>
<slot name="header">slot>
<slot name="footer">slot>
div>
template>
作用域插槽
在组件内部定义数据,将数据传递给插槽的结构
通过给 slot 动态绑定属性
<template id="my-li">
<ul>
<li v-for="item in arr">
<slot :row="item"></slot>
</li>
</ul>
</template>
插槽内部:通过 slot-scope=“scope”来接收
<my-li>
<template slot-scope="scope">
<p>{{scope.row}}p>
template>
my-li>
<my-li>
<template slot-scope="scope">
<a href="04-侦听器.html">{{scope.row}}a>
template>
my-li>
利用 Promise 处理异步解决回调地狱的问题
Promise 的 all 的方法
Promise 的 race 的方法
面试题:
现在有三个接口地址,需要三个接口地址请求完事之后进行下一步的逻辑处理(不一定按顺序请求完成)
// .then回调
axios.get('http://xxx').then(res => {
console.log(res)
axios.get('http://xxx').then(res => {
console.log(res)
axios.get('http://xxx').then(res => {
console.log(res)
})
})
})
// .then返回新的Promise继续调用.then
axios
.get('http://xxx')
.then(res => {
return axios.get('http://xxx')
})
.then(res => {
return axios.get('http://xxx')
})
.then(res => {
console.log('三个请求完事')
})
// async await
const asyncHandle = async function() {
const res1 = await axios.get('http://xxx1')
const res2 = await axios.get('http://xxx')
const res3 = await axios.get('http://xxx')
console.log(res1, res2, res3)
}
asyncHandle()
// Promise all方法
const getComments = new Promise(() => {
axios.get('http://xxx')
})
Promise.all([
axios.get('http://xxx'),
axios.get('http://xxx'),
axios.get('http://xxx')
]).then(res => {
console.log(res)
})
请求拦截
axios.interceptors.request.use
响应拦截
axios.interceptors.response.use
什么是路由?
路由就是对应关系,组件和 url 地址,根据不同的地址显示不同的组件,路由也是实现 spa(单页面应用程序)的主要核心,因为单页面应用程序,就是只有一个 html,在这个 html 里面切换组件,根据 url,例如地址为/home,在这个页面中就显示 home 组件
前端路由:url 和组件
后端路由:根据不同的地址请求不同的接口
params 传参
在跳转的时候可以通过/home/10
路由规则:
new VueRouter({
routes: [
{
path: '/home/:id',
component: Home
}
]
})
组件内部怎么来接收参数
this.$route.params.id
query 传参
在跳转的时候可以通过/home?id=10
组件内部怎么来接收参数
this.$route.query.id
路由 history 模式注意的问题
全局钩子:都会对所有的路由进行拦截
beforeEach:进入之前
afterEach:已经进入了
路由独享钩子:可以针对某一个路由进行拦截,这个需要写在路由规则里
{
path: '/',
name: 'home',
beforeEnter: (to,from,next)=>{
console.log('即将进入home')
},
component: Home
}
组件内的守卫:
针对组件进行拦截
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
next()
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
console.log('即将离开about')
if(confirm('当前表单没有提交?确定要离开首页?')){
next()
}
}
babel: 将高级语法转换成浏览器可以识别的语法
loader: 加载器, 结合 webpack 来处理非 js 资源文件 .css .less .sass .png
plugin: webpack 的各种各样的插件,能够增强 webpack 的功能
利用 vue.config.js 关闭 esLint
注册组件
import Vue from 'vue'
import { Button, Select } from 'element-ui'
Vue.use(Button)
Vue.use(Select)
使用组件
<el-select v-model="value" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>el-option>
el-select>
复制数据
options: [{
value: '选项1',
label: '黄金糕'
}, {
value: '选项2',
label: '双皮奶'
}, {
value: '选项3',
label: '蚵仔煎'
}, {
value: '选项4',
label: '龙须面'
}, {
value: '选项5',
label: '北京烤鸭'
}],
value: ''
在进入/离开的过渡中,会有 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
被删除),在过渡/动画完成之后移除。首先给需要添加动画的元素用 transition 包裹起来
利用类名实现动画
.v-enter,
.v-leave-to {
transform: translate(200px, 0);
}
.v-enter-active,
.v-leave-active {
transition: transform 1s;
}
.v-enter-to,
.v-leave {
transform: translate(0, 0);
}
自定义动画类名
<transition name="box2">
<div class="box" v-show="flag">div>
transition>
.box2-enter,
.box2-leave-to {
opacity: 0;
}
.box2-enter-active,
.box2-leave-active {
transition: opacity 1s;
}
.box2-enter-to,
.box2-leave {
opacity: 1;
}
引入 css 动画库
通过 enter-active-class、leave-active-class 设置动画
<transition
enter-active-class="animated bounceIn"
leave-active-class="animated bounceOut"
>
<div class="box" v-show="flag">div>
transition>
vuex 的数据属于应用级别的数据,应用刷新,数据会重置 (具有响应式, 不能持久存储)
结合本地存储来使用
单纯的使用本地: 本地的数据(持久存储,不具有响应式)
缺点:
优点
// 配置自定义入口文件
chainWebpack: config => {
// when: 相当于if 第一个参数:(条件) 第二个参数(满足条件执行的回调函数) if(条件) { 满足条件执行的逻辑 }
// 开发期间 --- main-dev.js
config.when(process.env.NODE_ENV === 'development', config => {
config
.entry('app')
.clear()
.add('./src/main-dev.js')
})
// 发布阶段 --- main-prod.js
config.when(process.env.NODE_ENV === 'production', config => {
config
.entry('app')
.clear()
.add('./src/main-prod.js')
})
}
本质上就是一个 JS 对象,用来描述你希望在屏幕上看到的内容
虚拟 dom 高效更新执行过程
面试语术:
你知道虚拟 dom 吗?简单又谈一下?
本质上就是一个 JS 对象,用来描述你希望在屏幕上看到的内容,虚拟 dom 可以实现高效更新,(后面如果自己能说一气说出来)
如何实现高效更新的?
利用新旧虚拟 dom 树进行对比,从而进行局部进行更新
如何进行新旧 dom 树进行对比?
利用 diff 算法,主要是 tree diff 树对比,component diff 组件对比,element diff 元素对比
加上一些其他话术
所以虚拟 dom 在前端中不管是 vue、react 等等都采用
什么是跨域?
在浏览器里面域名、端口、ip 地址、协议,有任何一项不同,则跨域
A 网站:http://localhost:8080/#/
B 网站:http://localhost:3000/#/
处理跨域的方式?
JsonP(只能处理 get 请求)、cors(后端开启)、代理服务器
module.exports = {
devServer: {
host: 'localhost',
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000', // 要跨域的域名
changeOrigin: true // 是否开启跨域
},
'/get': {
target: 'http://localhost:3000', // 要跨域的域名
changeOrigin: true // 是否开启跨域
}
}
}
}
某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。
在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。
目前小程序分包大小有以下限制:
对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。
代码演示,分包加载之前
pages: [
'pages/tabs/home',
'pages/tabs/cates',
'pages/tabs/search',
'pages/tabs/cart',
'pages/tabs/my',
'pages/goods_list',
'pages/goods_detail/main',
'pages/order',
'pages/orderList'
]
抽离之后
pages: [
'pages/tabs/home',
'pages/tabs/cates',
'pages/tabs/search',
'pages/tabs/cart',
'pages/tabs/my',
'pages/order',
'pages/orderList'
],
subpackages: [
{
name: 'goods',
root: 'goods',
pages: [
'goods_list',
'goods_detail/main'
]
}
]
图片或者图标等资源文件使用线上资源
因为 v-for 的优先级大于 v-if,所以会先根据所有数据生成结构,然后在根据,v-if 进行按需渲染
<div id="app">
<ul>
<li v-for="item in arr" v-if="item%2===0">{{ item }}li>
ul>
div>
<script src="./vue.js">script>
<script>
new Vue({
el: '#app',
data: {
arr: [1, 2, 3, 4]
}
})
script>
我们可以采用计算属性来避免此问题,这样的话,先通过计算属性,计算出要渲染的数据,然后直接循环计算属性即可
<div id="app">
<ul>
<li v-for="item in newArr">{{ item }}li>
ul>
div>
<script src="./vue.js">script>
<script>
new Vue({
el: '#app',
data: {
arr: [1, 2, 3, 4]
},
computed: {
newArr() {
return this.arr.filter(item => {
return item % 2 === 0
})
}
}
})
script>
定义一个 button 组件,在 index.js 中引入并注册
在 components 中创建一个 index.js
import sgButton from './button.vue'
function install (Vue) {
Vue.component(sgButton.name, sgButton)
}
export default {
install
}
在 main.js 中导入该 js 文件,当调用 Vue.use 方法,会默认调用内部的 install 方法
面试题
你有封装过插件吗? Vue.use 的原理是什么?
当调用 Vue.use 方法,会默认调用内部的 install 方法,install 这个方法默认的第一个形参就是 Vue,这样的话我们就可以通过 Vue 注册一些全局组件,给 Vue 扩展一下方法。
vue 中实现语言切换的方式如何实现的
1.NPM 项目安装
cnpm i vue-i18n
2.使用方法
/* 国际化使用规则 */
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
<!-- 需要国际化的数据定义在此处 -->
const messages = {
en: {
message: {
hello: 'world hello'
}
},
zh: {
message: {
hello: '世界'
}
}
}
<!-- 使用i18n -->
const i18n = new VueI18n({
locale: 'zh',
messages
})
export default {
data () {
return {
hello: this.$t('message.hello')
}
},
i18n
}
3.页面数据使用
<div id="#app">
<p>{{ $t("message.hello") }}</p>
</div>
4.案例练习
<div id="app">
<button>切换语言</button>
<ul>
<li>首页</li>
<li>新闻</li>
<li>关于</li>
<li>概况</li>
</ul>
</div>
ul{
list-style: none;
li{
width: 20%;
height: 70px;
line-height: 70px;
background: green;
color: #fff;
float: left;
margin-left: 2%;
text-align: center;
line-height: 70px;
}
}
zh.js
export default {
nav: ['首页', '新闻', '概况', '关于']
}
en.js
export default {
nav: ['home', 'news', 'gk', 'about']
}
import zh from './i18n/zh.js'
import en from './i18n/en.js'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'en',
messages: {
zh,
en
}
})
<template>
<div id="app">
<button @click="changeLang">切换语言button>
<ul>
<li v-for="(item, index) in $t('nav')" :key="index">
{{ item }}
li>
ul>
div>
template>
<script>
export default {
methods: {
changeLang() {
this.$i18n.locale = this.$i18n.locale === 'en' ? 'zh' : 'en'
}
}
}
script>
keep-alive
进行缓存组件,防止同样的数据重复请求 <keep-alive>
<router-view />
</keep-alive>
keep-alive
是
vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染
DOM
keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
keep-alive
可以设置以下props
属性:
include
- 字符串或正则表达式。只有名称匹配的组件会被缓存exclude
- 字符串或正则表达式。任何名称匹配的组件都不会被缓存max
- 数字。最多可以缓存多少组件实例关于keep-alive
的基本用法:
<keep-alive>
<component :is="view"></component>
</keep-alive>
使用includes
和exclude
:
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
匹配首先检查组件自身的 name
选项,如果 name
选项不可用,则匹配它的局部注册名称 (父组件 components
选项的键值),匿名组件不能被匹配
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated
与deactivated
):
beforeRouteEnter
> beforeCreate
> created
> mounted
> activated
> … … > beforeRouteLeave
> deactivated
beforeRouteEnter
>activated
> … … > beforeRouteLeave
> deactivated
使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
举个栗子:
当我们从首页
–>列表页
–>商详页
–>再返回
,这时候列表页应该是需要keep-alive
从首页
–>列表页
–>商详页
–>返回到列表页(需要缓存)
–>返回到首页(需要缓存)
–>再次进入列表页(不需要缓存)
,这时候可以按需来控制页面的keep-alive
history.pushState
API 来完成 URL 跳转而无须重新加载页面这个函数是可以等 dom 重新更新完成会调用
数据渲染完成,页面完成更新即调用 this.$nextTik
当修改了数据,dom 是异步同步的,所以,如果更改了数据,在修改数据下面重新操作 dom 会出问题,需要保证 dom 也更新完成才能操作。