Vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、销毁等一系列过程,我们称这是Vue的生命周期。
通俗说就是Vue实例从创建到销毁的过程,就是生命周期。
每一个组件或者实例都会经历一个完整的生命周期,总共分为三个阶段:初始化、运行中、销毁。
实例、组件通过new Vue() 创建出来之后会初始化事件和生命周期,然后就会执行beforeCreate
钩子函数,这个时候,数据还没有挂载呢,只是一个空壳,无法访问到数据和真实的dom,一般不做操作
挂载数据,绑定事件等等,然后执行created
函数,这个时候已经可以使用到数据,也可以更改数据,在这里更改数据不会触发updated函数,在这里可以在渲染前倒数第二次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取
接下来开始找实例或者组件对应的模板,编译模板为虚拟dom放入到render函数中准备渲染,然后执行beforeMount
钩子函数,在这个函数中虚拟dom已经创建完成,马上就要渲染,在这里也可以更改数据,不会触发updated,在这里可以在渲染前最后一次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取
接下来开始render
,渲染出真实dom,然后执行mounted
钩子函数,此时,组件已经出现在页面中,数据、真实dom都已经处理好了,事件都已经挂载好了,可以在这里操作真实dom等事情…
当组件或实例的数据更改之后,会立即执行beforeUpdate
,然后vue的虚拟dom机制会重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染,一般不做什么事儿
当更新完成后,执行updated
,数据已经更改完成,dom也重新render完成,可以操作更新后的虚拟dom
当经过某种途径调用$destroy
方法后,立即执行beforeDestroy
,一般在这里做一些善后工作,例如清除计时器、清除非指令绑定的事件等等
组件的数据绑定、监听…去掉后只剩下dom空壳,这个时候,执行destroyed
,在这里做善后工作也可以
生命周期中有多个事件钩子,让我们在控制整个 vue 实例的过程时更容易形成好的逻辑
第一次加载会触发 beforeCreate
、created
、beforeMount
、mounted
生命周期钩子的一些使用方法:
beforecreate : 可以在这加个loading事件,在加载实例时触发
created : 初始化完成时的事件写在这里,比如在这结束loading事件,异步请求也适宜在这里调用
mounted : 挂载元素,获取到DOM节点
updated : 如果对数据统一处理,在这里写上相应函数
beforeDestroy : 可以做一个确认停止事件的确认框
nextTick : 更新数据后立即操作dom
created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
看实际情况,一般在 created
(或beforeRouter
) 里面就可以,如果涉及到需要页面加载完成之后的话就用 mounted
。
在created的时候,视图中的html并没有渲染出来,所以此时如果直接去操作html的dom节点,一定找不到相关的元素
而在mounted中,由于此时html已经渲染出来了,所以可以直接操作dom节点,(此时document.getelementById 即可生效了)。
答:总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
创建前/后: 在beforeCreated阶段,vue实例的挂载元素$el
和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,$el
还没有。
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后:当data变化时,会触发beforeUpdate和updated方法。
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
MVVM是把MVC的Controller和MVP的Presenter改成了ViewModel。
View的变化会自动更新到ViewModel,ViewModel的变化也会自动同步到View上显示。这种自动同步是因为ViewModel中的属性实现了Observer,当属性变更时都能触发对应的操作。
MVC是应用最广泛的软件架构之一,一般MVC分为:
Model( 模型 )、Controller( 控制器 )、View( 视图 )。
这主要是基于分层的目的,让彼此的职责分开。View一般通过Controller来和Model进行联系。Controller是Model和View的协调者,View和Model不直接联系。基本联系都是单向的。
1、View 传送指令到 Controller
2、Controller 完成业务逻辑后,要求 Model 改变状态
3、Model 将新的数据发送到 View,用户得到反馈
MVP 模式将 Controller 改名为Presenter,同时改变了通信方向。
1、各部分之间的通信,都是双向的。
2、View 与 Model 不发生联系,都通过 Presenter 传递。
3、View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
MVVM模式的优点以及与MVC模式的区别
1、低耦合:视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2、可重用性:你可以把一些视图逻辑放在一个ViewModel里面,让很多 view 重用这段视图逻辑。
3、独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
4、可测试:界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
mvc和mvvm其实区别并不大。都是一种设计思想。
主要区别
mvc 中 Controller演变成 mvvm 中的 viewModel,
mvvm 通过数据来显示视图层而不是节点操作。
mvvm主要解决了:
mvc中大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
vue-router 是 vue 的路由插件,
组件:router-link router-view
active-class是 vue-router模块中 router-link 组件中的属性,主要作用是用来实现选中
样式的切换。
active-class使用方法
1、直接在路由js文件中配置linkActiveClass
export default new Router({
linkExactActiveClass: 'active',
})
2、在router-link中写入exact
<router-link to="/" class="menu-home" active-class="active" exact>首页</router-link>
在 router 目录下的 index.js 文件中,对 path 属性加上 /:id。
获取传过来的值
使用 router 对象的 params.id 获取
示例
第一个页面
<div>
<a href="javascript:void(0)" @click="getDetail(1)">
去第二个页面
a>
div>
<script>
export default {
name: 'app',
methods:{
getDetail(id) {
this.$router.push({
name: 'login',
params: {
id: id
}
})
}
}
}
script>
第二个页面
export default {
name: 'login',
data () {
return {
uid:this.$route.params.id,
msg: '这是第二个页面'
}
},
mounted: function() {
console.log(this.uid);
},
}
路由定义(解决如果刷新login页面,将失去传值)
{
path: ':id',
component: login
}
导航钩子的作用
vue-router 的导航钩子,主要用来作用是拦截导航,让他完成跳转或取消。
植入路由导航的方式
全局的
单个路由独享守卫
组件级路由守卫
全局导航钩子主要有两种钩子:前置守卫、后置钩子,
注册一个全局前置守卫:
const router = new VueRouter({ ... });
router.beforeEach((to, from, next) => {
// do someting
});
这三个参数 to 、from 、next 分别的作用:
to: Route,代表要进入的目标,它是一个路由对象
from: Route,代表当前正要离开的路由,同样也是一个路由对象
next: Function,这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数
a. next():进入管道中的下一个钩子,如果全部的钩子执行完了,则导航的状态就是 confirmed(确认的)
b. next(false):这代表中断掉当前的导航,即 to 代表的路由对象不会进入,被中断,此时该表 URL 地址会被重置到 from 路由对应的地址
c. next(‘/’) 和 next({path: ‘/’}):在中断掉当前导航的同时,跳转到一个不同的地址
d. next(error):如果传入参数是一个 Error 实例,那么导航被终止的同时会将错误传递给 router.onError() 注册过的回调
注意:next 方法必须要调用,否则钩子函数无法 resolved
对于全局后置钩子:
router.afterEach((to, from) => {
// do someting
});
不同于前置守卫,后置钩子并没有 next 函数,也不会改变导航本身
顾名思义,即单个路由独享的导航钩子,它是在路由配置上直接进行定义的:
cont router = new VueRouter({
routes: [
{
path: '/file',
component: File,
beforeEnter: (to, from ,next) => {
// do someting
}
}
]
});
至于他的参数的使用,和全局前置守卫是一样的
组件内的导航钩子主要有这三种:beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
。他们是直接在路由组件内部直接进行定义的
我们看一下他的具体用法:
const File = {
template: `This is file`,
beforeRouteEnter(to, from, next) {
// do someting
// 在渲染该组件的对应路由被 confirm 前调用
},
beforeRouteUpdate(to, from, next) {
// do someting
// 在当前路由改变,但是依然渲染该组件是调用
},
beforeRouteLeave(to, from ,next) {
// do someting
// 导航离开该组件的对应路由时被调用
}
}
需要注意是:
beforeRouteEnter 不能获取组件实例 this,因为当守卫执行前,组件实例被没有被创建出来,剩下两个钩子则可以正常获取组件实例 this
但是并不意味着在 beforeRouteEnter 中无法访问组件实例,我们可以通过给 next 传入一个回调来访问组件实例。在导航被确认是,会执行这个回调,这时就可以访问组件实例了,如:
beforeRouteEnter(to, from, next) {
next (vm => {
// 这里通过 vm 来访问组件实例解决了没有 this 的问题
})
}
注意,仅仅是 beforRouteEnter 支持给 next 传递回调,其他两个并不支持。因为归根结底,支持回调是为了解决 this 问题,而其他两个钩子的 this 可以正确访问到组件实例,所有没有必要使用回调
最后是完整的导航解析流程:
router
为VueRouter的实例,相当于一个全局的路由器对象,里面含有很多属性和子对象,例如history对象。。。经常用的跳转链接就可以用this.$router.push,和router-link跳转一样。。。
route
相当于当前正在跳转的路由对象。。可以从里面获取name,path,params,query等。。
提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。
复用组件时,想对路由参数的变化作出响应的话,你可以简单地watch (监测变化) $route 对象:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
或者:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
注意是:
(1)从同一个组件跳转到同一个组件。
(2)生命周期钩子created和mounted都不会调用。
1.query方式传参和接收参数
传参:
this.$router.push({
path:'/xxx',
query:{
id:id
}
})
接收参数:
this.$route.query.id
注意:传参是this.$router
,接收参数是this.$route
,这里千万要看清了!!!
2.params方式传参和接收参数
传参:
this.$router.push({
name:'xxx',
params:{
id:id
}
})
接收参数:
this.$route.params.id
注意:params传参,push里面只能是 name:‘xxxx’,不能是path:‘/xxx’,因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined!!!
另外,二者还有点区别,直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示
vue-router有两种模式,hash模式和history模式
hash模式背后的原理是onhashchange
事件。
window.onhashchange=function(){
let hash=location.hash.slice(1);
document.body.style.color=hash;
}
(localtion是js里管理地址栏的内置对象,是window对象的一部分,可通过window.localtion访问,在w3cshool里的详细介绍)
由于hash发生变化的url都会被浏览器记录下来,使得浏览器的前进后退都可以使用了,尽管浏览器没有亲求服务器,但是页面状态和url关联起来。后来人们称其为前端路由,成为单页应用标配。
比如http://www.abc.com/#/index,hash值为#/index。hash模式的特点在于hash出现在url中,但是不会被包括在HTTP请求中,对后端没有影响,不会重新加载页面。
history模式利用了HTML5 History Interface中新增的pushState()和replaceState()方法。MDN相关介绍(需要特定浏览器支持)。这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。
当使用history模式时,url就像正常的url,例如http://abc.com/user/id相比hash模式更加好看。特别注意,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。
通过history api,我们丢弃了丑陋的#,但是有一个缺点,当刷新时,如果服务器中没有相应的相应或者资源,会分分钟刷出一个404来(刷新需要请求服务器)。所以history模式不怕前进,不怕后退,就怕刷新。
pushState()设置新的url可以是和当前url同源的任意url;hash只可修改#后面的部分,只能设置当前url同文档的url。
pushState()设置的新url可与当前url一致,这样也会把记录添加到栈中;hash必须设置与当前url不同的url的,才会触发动作将记录添加到栈中。
pushState()通过stateObject参数可以添加任意类型的数据到记录中;hash只可添加短字符串。
pushState()可额外设置title属性供后续使用。
不过,hash模式也有比history模式优势的地方。
hash模式下,仅hash符号之前的url会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回404错误。
history模式下,前端的url必须和实际向后端发起请求的url一致,如http://abc.com/user/id,后端如果没有对user/id的路由处理,将返回404错误。
结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载
在项目router/index.js文件中将
import Recommend from 'components/recommend/recommend'
更改为
//方法1:
const Recommend = () => import('components/recommend/recommend')
//方法2:
const Recommend = (resolve) => {
import('components/recommend/recommend').then((module) => {
resolve(module)
})
}
即可实现路由懒加载的效果
路由中配置redirect属性
// router.js
export default new Router({
mode: 'history',
routes: [
// ...
{
name: '404',
path: '/404',
component: () => import('@/views/notFound.vue')
},
{
path: '*', // 此处需特别注意至于最底部
redirect: '/404'
}
],
})
export default new Router({
// mode: 'history', //可以去掉url中的#。但是打包后需要后台配置,否则会404
routes: routerMap,
scrollBehavior(to, from, savedPosition) { //设置回到顶部
if (savedPosition) {
return savedPosition
}
return {x: 0, y: 0}
}
})
路由代码中添加mode:‘history’
const router = new Router({
mode: 'history', //去掉url中的#
})
低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel
可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
1.父组件将数据绑定在子组件上
<template>
<transition name="slide">
<music-list :songs="songs" :title="title" :bg-image="bgImage">music-list>
transition>
template>
2.子组件接受数据props
export default {
props: {
bgImage: {
type: String,
default: ''
},
songs: {
type: Array,
default: () => []
},
title: {
type: String,
default: ''
}
},
}
在子组件里用$emit
向父组件触发一个事件,父组件监听这个事件就行了
父组件
<template>
<div>
<child @fatherMethod="fatherMethod">child>
div>
template>
<script>
import child from '~/components/dam/child';
export default {
components: {
child
},
methods: {
fatherMethod() {
console.log('测试');
}
}
};
script>
子组件
<template>
<div>
<button @click="childMethod()">点击button>
div>
template>
<script>
export default {
methods: {
childMethod() {
this.$emit('fatherMethod');
}
}
};
script>
一般来说,v-show有更高的初始渲染开销,而v-if有更高的切换开销。
v-if更适合条件不经常改变的场景(运行条件不太改变),而v-show适用于频繁切换条件。
相同:v-show 和 v-if 都能控制元素的显示和隐藏。
不同:
将当前组件的修改为
1、作用:当style标签里面有scoped属性时,它的css只作用于当前组建的元素。在单页面项目中可以使组件之间互不污染,实现模块化(实现组件的私有化,不对全局造成样式污染,表示当前style属性只属于当前模块)。
2、实现原理: style 标签中添加 scoped 属性后,vue 就会为当前组件中的 DOM 元素添加唯一的一个自定义属性(唯一性的标记)【data-v-xxx】,即CSS带属性选择器,以此完成类似作用域的选择方式,从而达到样式私有化,不污染全局的作用。
的作用是什么?包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。
keep-alive有两个属性,include和exclude,用于指定是否缓存某些组件,include包含的意思,值为字符串或正则表达式或数组,只有组件的名称与include的值相同时才会被缓存,exclude不包含的意思,指定哪些组件不被缓存,用法和include类似。
通过refs方法
HTML
<div id="App">
<div class="aa" ref="menuItem" @click="aa($event)">aadiv>
<div @click="bb"\>bbdiv>
div>
JS
<script>
new Vue({
el: '#App',
methods: {
bb: function() {
var getMenuText = this.$refs.menuItem.innerText;
console.log(getMenuText);//aa
},
aa: function(e) {
var el = e.target;
console.log(el);//aa
}
}
})
script>
vue文件的一个加载器。
用途:js可以写es6、style样式可以scss或less、template可以加jade等
根据官网的定义,vue-loader 是 webpack 的一个 loader,用于处理 .vue 文件。
通过 vue-loader, webpack 可以将 .vue 文件 转化为 浏览器可识别的javascript。
vue-loader 的工作流程, 简单来说,分为以下几个步骤:
将一个 .vue 文件 切割成 template、script、styles 三个部分。
template 部分 通过 compile 生成 render、 staticRenderFns。
获取 script 部分 返回的配置项对象 scriptExports。
styles 部分,会通过 css-loader、vue-style-loader, 添加到 head 中, 或者通过 css-loader、MiniCssExtractPlugin 提取到一个 公共的css文件 中。
使用 vue-loader 提供的 normalizeComponent 方法, 合并 scriptExports、render、staticRenderFns, 返回 构建vue组件需要的配置项对象 - options, 即 {data, props, methods, render, staticRenderFns…}。
当有相同标签名的元素切换时,需要通过key 特性设置唯一的值来标记以让Vue 区分它们,否则Vue 为了效率只会替换相同标签内部的内容。
在 Vue 中,key 是用于帮助 Vue 识别和跟踪虚拟 DOM 的变化的特殊属性。当 Vue 更新渲染真实 DOM 时,它使用 key 属性来比较新旧节点,并尽可能地复用已存在的真实 DOM 节点,以提高性能。
Vue 在进行虚拟 DOM 的 diff 算法时,会使用 key 来匹配新旧节点,以确定节点的更新、移动或删除。它通过 key 属性来判断两个节点是否代表相同的实体,而不仅仅是根据它们的内容是否相同。这样可以保留节点的状态和避免不必要的 DOM 操作。
key 的工作原理如下:
key 必须是唯一且稳定的,最好使用具有唯一标识的值,例如使用数据的唯一 ID。同时,不推荐使用随机数作为 key,因为在每次更新时都会生成新的 key,导致所有节点都重新渲染,无法复用已有的节点,降低性能。
axios是什么?
请求后台资源的模块
axios安装?
根目录运行
npm install axios -S
在使用的地方
import axios from 'axios'
首先在build/webpack.dev.conf.js设置代理
var express \= require('express')
var axios \= require('axios')
var app \= express()
var apiRoutes \= express.Router()
app.use('/api', apiRoutes)
before(apiRoutes) {
apiRoutes.get('/api/getDiscList', function (req, res) {
var url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg' // 原api
axios.get(url, {
headers: {
referer: 'https://c.y.qq.com/',
host: 'c.y.qq.com'
},
params: req.query
}).then((response) => {
res.json(response.data)
}).catch((e) => {
console.log(e)
})
})
}
在页面中使用
export function getDiscList() {
const url = '/api/getDiscList'
const data = Object.assign({}, commonParams, {
platform: 'yqq',
hostUin: 0,
sin: 0,
ein: 29,
sortId: 5,
needNewCode: 0,
categoryId: 10000000,
rnd: Math.random(),
format: 'json'
})
return axios.get(url, {
params: data
}).then((res) => {
return Promise.resolve(res.data)
})
}
用v-model指令在表单、
及
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但v-model本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model在内部使用不同的属性为不同的输入元素并抛出不同的事件:
1、开始在vue项目中使用sass,在命令行输入一下命令进行安装(使用git命令行要用shift+insert 进行粘贴否则粘贴不上)
cnpm install node-sass --save-dev //安装node-sass
cnpm install sass-loader --save-dev //安装sass-loader
cnpm install style-loader --save-dev //安装style-loader 有些人安装的是 vue-style-loader 其实是一样的!
2、这个时候你打开build文件夹下面的webpack.base.config.js
把里面的module改成这样的
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'),
resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},
{ //从这一段上面是默认的!不用改!下面是没有的需要你手动添加,相当于是编译识别sass!
test: /\.scss$/,
loaders: ["style", "css", "sass"]
}
]
}
3、在需要用到sass的地方添加lang=scss
<style lang="scss" scoped="" type="text/css">
//你的sass语言 $primary-color: #333;
body {
color: $primary-color; //编译后就成了 color:#333;类似于js的变量!
}
style>
assets文件夹是放静态资源;
components是放组件;
router是定义路由相关的配置;
view视图;
app.vue是一个应用主组件;
main.js是入口文件
一. methods和(watch、computed)的 区别
1.watch和computed都是以Vue的依赖追踪机制为基础的,它们都试图处理这样一件事情:当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,也就是自动调用相关的函数去实现数据的变动。
2.对methods:methods里面是用来定义函数的,很显然,它需要手动调用才能执行。而不像watch和computed那样,“自动执行”预先定义的函数
二. methods和computed的 相比, computed的好处
import Vue from 'vue'
new Vue({
el: '#root',
template: `
Name: {{name}}
Name: {{getName()}}
Number: {{number}}
FirsName:
LaseName:
`,
data: {
firstName: 'Jokcy',
lastName: 'Lou',
number: 0
},
computed: {
name () {
console.log('new name')
return `${this.firstName} ${this.lastName}`
}
},
methods: {
getName () {
console.log('getName invoked')
return `${this.firstName} ${this.lastName}`
}
}
})
当我们改变 number 时,整个应用会重新渲染,vue 会被数据重新渲染到 dom 中。这时,如果我们使用 getName 方法,随着渲染,方法也会被调用,而 computed 不会重新进行计算,从而性能开销比较小。当新的值需要大量计算才能得到,缓存的意义就非常大。
如果 computed 所依赖的数据发生改变时,计算属性才会重新计算,并进行缓存;当改变其他数据时,computed 属性 并不会重新计算,从而提升性能。
当我们拿到的值需要进行一定处理使用时,就可以使用 computed。
三. watch和computed的 区别
watch: {
firstName(val) { this.fullName = val + this.lastName }
}
computed: {
fullName() { this.firstName + this.lastName; }
}
v-on可以监听多个方法,例如:
<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />
但是同一种事件类型的方法,vue-cli工程会报错,例如:
<a href="javascript:;" @click="methodsOne" @click="methodsTwo">a>
会报错。
一、为什么使用$nextTick?
二 、在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中?
Vue.nextTick()
的回调函数中。mounted()
钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。三 、$nextTick的其他应用
在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()
的回调函数中。
四 、$nextTick的原理
作用
Vue.nextTick用于延迟执行一段代码,它接受2个参数(回调函数和执行回调函数的上下文环境),如果没有提供回调函数,那么将返回promise对象。
源码
/**
* Defer a task to execute it asynchronously. 延迟任务以异步执行该任务。
*/
export const nextTick = (function () {
const callbacks = []
let pending = false
let timerFunc
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// the nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/*
/nextTick行为利用微任务队列,该队列可以通过本机Promise.then或Mutationobserver访问/。
/MutationObserver有更广泛的支持,但是它在/UIWebView中被严重窃听在i0S>=9.3.3中,当在触摸事件处理程序中触发时。它/触发几次后就完全停止工作了...所以,如果原生
/Promise是可用的,我们将使用它:
*/
/* istanbul ignore if */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
/*/在有问题的UIWeb View中,Promise.then不会完全崩溃,但是//它可能会陷入一种奇怪的状态,回调被推入//微任务队列,但是队列没有被刷新,直到浏览器/需要做一些其他的工作,例如处理一个计时器。因此,我们可以//“强制”通过添加一个空计时器刷新微任务队列。*/
if (isIOS) setTimeout(noop)
}
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// 在本机Promise不可用的情况下使用MutationObserver,//例如PhantomJSi0S7,Android 4.4
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
} else {
// fallback to setTimeout
/* istanbul ignore next */
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}
return function queueNextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}
})()
首先,先了解nextTick中定义的三个重要变量。
callbacks:用来存储所有需要执行的回调函数
pending:用来标志是否正在执行回调函数
timerFunc:用来触发执行回调函数
接下来,了解nextTickHandler()函数。
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
这个函数用来执行callbacks里存储的所有回调函数。
接下来是将触发方式赋值给timerFunc。
最后是queueNextTick函数。
因为nextTick是一个即时函数,所以queueNextTick函数是返回的函数,接受用户传入的参数,用来往callbacks里存入回调函数。
关键在于timeFunc(),该函数起到延迟执行的作用。
从上面的介绍,可以得知timeFunc()一共有三种实现方式。
其中Promise和setTimeout很好理解,是一个异步任务,会在同步任务以及更新DOM的异步任务之后回调具体函数。
四、使用场景
<template>
<div>
<p ref="messageParagraph">{{ message }}p>
<button @click="updateMessage">更新消息button>
div>
template>
<script>
export default {
data() {
return {
message: "原始消息",
};
},
methods: {
updateMessage() {
this.message = "新消息";
this.$nextTick(() => {
// 在DOM更新后执行一些操作
this.$refs.messageParagraph.style.color = "red";
});
},
},
};
script>
<style>
p {
font-size: 20px;
}
style>
在上述示例中,当点击"更新消息"按钮时,调用updateMessage
方法。该方法会将message
数据更新为"新消息",然后使用$nextTick
在DOM更新后执行一些操作。
在$nextTick
的回调函数中,我们通过this.$refs.messageParagraph
访问到元素,并将其文字颜色设置为红色。这个操作需要在DOM更新完成后执行,因此使用
$nextTick
确保操作发生在DOM更新后。
需要注意的是,示例中的$refs.messageParagraph
是通过ref
属性引用到元素的。可以给需要引用的DOM元素或组件添加
ref
属性,然后通过$refs
访问它们。但要注意,在DOM更新之前,$refs
的值是undefined,因此需要在$nextTick
回调中使用$refs
。
在Vue组件中,将data
定义为一个函数是为了确保组件每次实例化时都返回一个新的数据对象。
当data
选项是一个对象时,它会在所有组件实例之间共享同一个数据对象。这样,如果多个组件实例修改了data
中的某个属性,那么这些实例会共享修改结果,导致数据混乱。
通过将data
定义为一个函数,每次创建组件实例时都会调用该函数,并返回一个全新的数据对象。这样,每个组件实例都拥有自己独立的数据对象,互不影响。
此外,将data
定义为函数还有其他一些优点:
函数内部可以进行一些数据的计算和处理,而不仅仅是返回一个简单的对象,从而增加data
的灵活性。
在使用Vue的单文件组件时,可以将data
定义为组件选项中的一个具名函数,提高代码的可读性和维护性。
下面是一个示例,演示了为什么将data
定义为函数是必要的:
<template>
<div>
<p>{{ count }}p>
<button @click="increment">增加button>
div>
template>
<script>
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
};
script>
在这个示例中,每个组件实例都有自己独立的count
属性,点击按钮时,只会在当前组件实例的count
属性增加,不会影响其他组件实例的count
属性。如果将data
定义为对象,则无法实现这种独立性。
<button data-aid="123" @click="getInfo($event)">通过事件对象获取自定义属性</button>
getInfo(event){
//获取自定义data-id
console.log(event.target.dataset.id)
//阻止事件冒泡
event.stopPropagation();
//阻止默认
event.preventDefault()
}
1、 父组件传递数据给子组件,使用props属性来实现
详情请看第二道vue面试题
2、子组件与父组件通信
详情请看第三道面试题
3、非子组件与父组件通信
可以通过 vuex ,还有 event bus 来解决
以下是使用Event Bus实现非子组件与父组件通信的代码示例:
// Event Bus
import Vue from 'vue';
export const eventBus = new Vue();
// Parent Component
<template>
<div>
<h1>父组件</h1>
<button @click="sendMessage">发送消息</button>
</div>
</template>
<script>
import { eventBus } from './eventBus';
export default {
methods: {
sendMessage() {
//eventBus.$emit来发送一个名为message的自定义事件,并传递消息内容。
eventBus.$emit('message', 'Hello from parent component!');
}
}
};
</script>
// Non-child Component
<template>
<div>
<h2>非子组件</h2>
<p>{{ message }}</p>
</div>
</template>
<script>
import { eventBus } from './eventBus';
export default {
data() {
return {
message: ''
};
},
created() {
//eventBus.$on监听事件
eventBus.$on('message', (msg) => {
this.message = msg;
});
}
};
</script>
在上述示例中,我们创建了一个Event Bus实例eventBus
,通过Vue实例来充当事件总线。在父组件中,通过点击按钮,调用sendMessage
方法,并使用eventBus.$emit
来发送一个名为message
的自定义事件,并传递消息内容。
在非子组件中,通过eventBus.$on
监听message
事件,并在事件触发时执行回调函数,将传递的消息内容赋值给message
属性。
这样,当父组件点击按钮发送消息时,非子组件就会接收到该消息并更新显示。通过Event Bus的机制,非子组件实现了与父组件的通信,而它们之间并没有直接的父子关系。
渐进式代表的含义是:主张最少。
每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。
它两个版本都是强主张的,如果你用它,必须接受以下东西:
- 必须使用它的模块机制
- 必须使用它的依赖注入
- 必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免)
所以Angular是带有比较强的排它性的,如果你的应用不是从头开始,而是要不断考虑是否跟其他东西集成,这些主张会带来一些困扰。
它也有一定程度的主张,它的主张主要是函数式编程的理念,
你需要知道:
- 什么是副作用,
- 什么是纯函数,
- 如何隔离副作用。
它的侵入性看似没有Angular那么强,主要因为它是软性侵入。
可能有些方面是不如React,不如Angular,但它是渐进的,没有强主张
Vue的核心的功能,是一个视图模板引擎,但这不是说Vue就不能成为一个框架。如下图所示,这里包含了Vue的所有部件,在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念
Vue.js 最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统。
什么是数据双向绑定?
vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。
为什么要实现数据的双向绑定?
在vue中,如果使用vuex,实际上数据还是单向的,之所以说是数据双向绑定,这是用的UI控件来说,对于我们处理表单,vue的双向数据绑定用起来就特别舒服了。
即两者并不互斥, 在全局性数据流使用单项,方便跟踪; 局部性数据流使用双向,简单易操作。
一、访问器属性
Object.defineProperty()函数可以定义对象的属性相关描述符, 其中的set和get函数对于完成数据双向绑定起到了至关重要的作用,下面,我们看看这个函数的基本使用方式。
过滤器 filter
全局过滤器
局部过滤器
1.在vue2中,v-for的优先级高于v-if,把它们放在同一个标签上时,页面每次渲染的时候都会重复的进行判断是十分消耗性能的
2.在vue3中,又恰好相反v-if的优先级是高于v-for的,同样vue3也不能把它们两者写在一起,正因为v-if优先于v-for,并且v-if又依赖v-for的数据源,在这个情况下将会出现报错的情况。
static/ 中的文件是完全不被Webpack处理的,它们被以相同的文件名直接被复制进最终目标。务必要使用绝对路径去引用它们
src/assets 中放置的文件希望被Webpack处理的,它们可能被重新命名复制进最终目标。使用相对路径引用它们
(1).lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:
<input v-model.lazy="msg" >
(2).number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符
<input v-model.number="age" type="number">
(3).trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<div id='other'>
<input v-model.trim='trim'>
<p ref='tr'>{{trim}}p>
<button @click='getStr'>获取button>
div>
var other = new Vue({
el:'#other',
data:{
trim:''
},
methods:{
getStr(){
console.log(this.$refs.tr.innerHTML)
}
}
})
(1).stop
阻止点击事件冒泡。等同于JavaScript中的event.stopPropagation()
例如:
<a v-on:click.stop="doThis">a>
<a @click.stop="doThis">a>
实例1,防止冒泡:
<div id="app">
<div class="outeer" @click.stop="outer">
<div class="middle" @click.stop="middle">
<button @click.stop="inner">点击我(^_^)button>
div>
div>
div>
//使用了.stop后,点击子节点不会捕获到父节点的事件
(2) .prevent
防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播),等同于JavaScript中的event.preventDefault(),prevent等同于JavaScript的event.preventDefault(),用于取消默认事件。
例如:
<a v-on:submit.prevent="doThis">a>
(3).capture
与事件冒泡的方向相反,事件捕获由外到内,捕获事件:嵌套两三层父子关系,然后所有都有点击事件,点击子节点,就会触发从外至内 父节点-》子节点的点击事件
<a v-on:click.capture="doThis">a>
(4).self
只会触发自己范围内的事件,不包含子元素
<div id="app">
<div class="outeer" @click.self="outer">
<div class="middle" @click.self="middle">
<button @click.stop="inner">点击我(^_^)button>
div>
div>
div>
(5) .once
只执行一次,如果我们在@click事件上添加.once修饰符,只要点击按钮只会执行一次。
<a @click.once="doThis">a>
(6).passive
Vue 还对应 addEventListener 中的 passive 选项提供了 .passive 修饰符
<div v-on:scroll.passive="onScroll">...div>
这个 .passive 修饰符尤其能够提升移动端的性能。不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。
(7).事件修饰符还可以串联
如:
<a v-on:click.stop.prevent="doThis">a>
注:使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
键盘修饰符
在Vue中允许v-on在监听键盘事件时添加关键修饰符。记住所有的keyCode比较困难,所以Vue为最常用的键盘事件提供了别名:
.enter:回车键
.tab:制表键
.delete:含delete和backspace键
.esc:返回键
.space: 空格键
.up:向上键
.down:向下键
.left:向左键
.right:向右键
例如:
<input v-on:keyup.13="submit">
记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:
<input v-on:keyup.enter="submit">
<input @keyup.enter="submit">
可以通过全局 config.keyCodes 对象自定义按键修饰符别名:
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl
.alt
.shift
.meta
<input @keyup.alt.67="clear">
<div @click.ctrl="doSomething">Do somethingdiv>
注意:
请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCode:keyup.17。
.exact修饰符
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。
<button @click.ctrl="onClick">Abutton>
<button @click.ctrl.exact="onCtrlClick">Abutton>
<button @click.exact="onClick">Abutton>
鼠标按钮修饰符
鼠标修饰符用来限制处理程序监听特定的滑鼠按键。常见的有:
.left
.right
.middle
这些修饰符会限制处理函数仅响应特定的鼠标按钮。
Vue 包含两种观察数组的方法分别如下
1.变异方法
顾名思义,变异方法会改变被这些方法调用的原始数组,它们也将会触发视图更新,这些方法如下
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
使用举例:example1.items.push({ message: ‘Baz’ })
2.非变异方法
非变异方法与变异方法的区别就是,非变异方法不会改变原始数组,总是返回一个新数组,
当使用非变异方法时,可以用新数组替换旧数组,非变异方法大致有:filter(), concat() 和 slice()
由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
1.当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
2.当你修改数组的长度时,例如:vm.items.length = newLength
vue针对这两个问题给出了相应的解决办法,使用这两种方法,也会触发状态更新
1.使用vue全局方法Vue.set() 或者使用vm.$set() 实例方法
2.使用 splice,caoncat等修改数组
当vue的data里边声明或者已经赋值过的对象或者数组(数组里边的值是对象)时,向对象中添加新的属性,如果更新此属性的值,是不会更新视图的
Vue.set() 响应式新增与修改数据
调用方法:Vue.set( target, key, value )
target:要更改的数据源(可以是对象或者数组)
key:要更改的具体数据
value :重新赋的值
实例
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
<script src="./js/vue.min.js">script>
head>
<body>
<div id="app">
<ul>
<li v-for="item in listData">{{item}}li>
ul>
<a href="javascript:void(0)" v-text="he" @click="changeData()">a>
div>
body>
<script>
new Vue({
el:"#app",
data:{
he:"点我",
listData:["a","b","c"]
},
methods:{
changeData () {
this.listData[0]="d";
}
}
})
script>
html>
当我点击按钮时候,发现没有任何变化,页面上还是a,b,c
vue.set解决方法
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
<script src="./js/vue.min.js">script>
head>
<body>
<div id="app">
<ul>
<li v-for="item in listData">{{item}}li>
ul>
<a href="javascript:void(0)" v-text="he" @click="changeData()">a>
div>
body>
<script>
new Vue({
el:"#app",
data:{
he:"点我",
listData:["a","b","c"]
},
methods:{
changeData () {
Vue.set(this.listData,0,'X')
}
}
})
script>
html>
Vue.js 最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统。
jquery和vue的区别
1.jquery:这个曾经也是现在依然最流行的web前端js库,可是现在无论是国内还是国外他的使用率正在渐渐被其他的js库所代替,随着浏览器厂商对HTML5规范统一遵循以及ECMA6在浏览器端的实现,jquery的使用率将会越来越低
2.vue:是一个兴起的前端js库,是一个精简的MVVM。从技术角度讲,Vue.js 专注于 MVVM 模型的 ViewModel 层。它通过双向数据绑定把 View 层和 Model 层连接了起来,通过对数据的操作就可以完成对页面视图的渲染。当然还有很多其他的mvmm框架如Angular,React都是大同小异,本质上都是基于MVVM的理念。 然而vue以他独特的优势简单,快速,组合,紧凑,强大而迅速崛起
jquery和vue的对比:
jQuery是使用选择器($
)选取DOM对象,对其进行赋值、取值、事件绑定等操作,其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。比如需要获取label标签的内容:$(“lable”).val();,它还是依赖DOM元素的值。
Vue则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的DOM对象,可以说数据和View是分离的,他们通过Vue对象这个vm实现相互的绑定。这就是传说中的MVVM。
总结就是:
vue数据驱动,通过数据来显示视图层而不是节点操作。\
1.采用ES6的import … from …语法或CommonJS的require()方法引入组件
2.对组件进行注册,代码如下
// 注册Vue.component('my-component', { template: '<div>A custom component!div>'})
3.使用组件
<my-component>my-component>
命令行输入:npm run build
打包出来后项目中就会多了一个文件夹dist,这就是我们打包过后的项目。
解决:到config文件夹中打开index.js文件。
文件里面有两个assetsPublicPath属性,更改第一个,也就是更改build里面的assetsPublicPath属性:
1.与AngularJS的区别
相同点:
都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。
不同点:
AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
2.与React的区别
相同点:
React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。
不同点:
React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。
官方文档解释:
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
虽然废弃了props的双向绑定对于整个项目整体而言是有利且正确的,但是在某些时候我们确实需要从组件内部修改props的需求
在Vue2.0中,实现组件属性的双向绑定方式
子组件修改:
<template>
<el-dialog
title="弹框组件"
:visible.sync="openStatus"
@open="doOpen"
@close="doClose"
:show="openStatus"
size="tiny">
<div class="content-wrapper">
具体业务代码...
div>
el-dialog>
div>
template>
<script type="text/babel">
import videoApi from '../../api/videoApi/videoApi'
export default {
name: 'dialogCompenent',
props: {
result: Boolean
},
/*创建一个openStatus变量缓存result数据
*在子组件需要调用result的地方调用data对象openStatus
*/
data () {
return {
openStatus: this.result
}
},
//新增result的watch,监听变更同步到openStatus
//监听父组件对props属性result的修改,并同步到组件内的data属性
watch: {
result (val) {
this.openStatus = val
}
},
methods: {
doOpen () {
...
},
doClose () {
this.$emit('dialogData', false)//子组件对openStatus修改后向父组件发送事件通知
...
}
}
}
script>
父组件修改:
<template>
<button @click="openDialog">打开弹窗button>
<dialogCompenent :show="result" :result="result" @dialogData="closeDialog">dialogCompenent>
template>
<script type="text/babel">
import dialogCompenent from '/dialogCompenent'
export default {
data () {
return {
result: false
}
},
components: {
dialogCompenent
},
methods: {
openDialog () {
this.result = true
},
closeDialog (data) {
this.result = data//子组件触发父组件事件,进行数据变更,同步result数据
}
}
}
script>
至此,实现了子组件内数据与父组件的数据的双向绑定,组件内外数据的同步。最后归结为一句话就是:组件内部自己变了告诉外部,外部决定要不要变。
结语
那么为什么vue1.0还有的数据双向绑定在vue2.0版本中反而抛弃了呢,通过上述案例我们也可以发现双向绑定的props代码多,不利于组件间的数据状态管理,尤其是在复杂的业务中更是如此,所以尽量不使用这种方式的双向绑定,过于复杂的数据处理使用vuex来进行数据管理。
delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
Vue.delete 直接删除了数组 改变了数组的键值。
var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[1]
console.log(a)
this.$delete(b,1)
console.log(b)
/* 首页--------------首页 */
// 首页底部导航
const index = r => require.ensure([], () => r(require('@/pages/home/index/index')), 'group-index')
// 首页
const home = r => require.ensure([], () => r(require('@/pages/home/home/home')), 'group-index')
接下来就可以正常使用路由了。
使用CDN加速
在做项目时,我们会用到很多库,采用cdn加载可以加快加载速度。详情可以查看Vue项目使用CDN优化首屏加载。
gzip压缩
方法一:使用Nginx反向代理,配置nginx.conf文件,在http节点下加如下代码:
gzip on;
gzip_static on;
gzip_buffers 4 16k;
gzip_comp_level 5;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
方法二:使用node压缩,需要使用compression库,代码如下:
const compression = require('compression');
app.use(compression());
异步加载组件
这里已经有前人栽好树,我们直接右转就可以了。vue异步组件(高级异步组件)使用场景及实践。
服务端渲染
使用pug/jade、ejs、vue通用应用框架Nuxt等等都可以实现后端渲染,并且后端渲染还能对seo优化起到作用。这里配上Nuxt.js中文官网。
只更新变化的部分从而减少DOM性能消耗
Vue的创新之处在于,它利用虚拟DOM的概念和diff实现了对页面的"按需更新",Vue-router很好地继承了这一点,譬如上图所示,导航组件和三个Tab组件(通过…,通过…,通过…)的重渲染是我们不希望看到的,因为无论跳转到页面一或是页面二,它只需要渲染一次就够了。组件帮助我们实现了这个愿望,反观标签,每次跳转都重渲染了导航组件和Tab组件试想一下,在一个浩大的项目里,这多么可怕!我们的"渲染"做了许多"无用功",而且消耗了大量弥足珍贵的DOM性能!
什么是插槽?
插槽(Slot)是Vue提出来的一个概念,正如名字一样,插槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性。插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制
怎么用插槽?
父组件
<template>
<div>
我是父组件
<slotOne1>
<p style="color:red">我是父组件插槽内容p>
slotOne1>
div>
template>
在父组件引用的子组件中写入想要显示的内容(可以使用标签,也可以不用)
子组件(slotOne1)
<template>
<div class="slotOne1">
<div>我是slotOne1组件div>
<slot>slot>
div>
template>
在子组件中写入slot,slot所在的位置就是父组件要显示的内容
具名插槽
子组件
<template>
<div class="slottwo">
<div>slottwodiv>
<slot name="header">slot>
<slot>slot>
<slot name="footer">slot>
div>
template>
在子组件中定义了三个slot标签,其中有两个分别添加了name属性header和footer
父组件
<template>
<div>
我是父组件
<slot-two>
<p>啦啦啦,啦啦啦,我是卖报的小行家p>
<template slot="header">
<p>我是name为header的slotp>
template>
<p slot="footer">我是name为footer的slotp>
slot-two>
div>
template>
在父组件中使用template并写入对应的slot值来指定该内容在子组件中现实的位置(当然也不用必须写到template),没有对应值的其他内容会被放到子组件中没有添加name属性的slot中
插槽的默认内容
父组件
<template>
<div>
我是父组件
<slot-two>slot-two>
div>
template>
子组件
<template>
<div class="slottwo">
<slot>我不是卖报的小行家slot>
div>
template>
可以在子组件的slot标签中写入内容,当父组件没有写入内容时会显示子组件的默认内容,当父组件写入内容时,会替换子组件的默认内容
根据vue-cli脚手架规范,一个js文件,一个CSS文件。
Vue1.0升级2.0有很多坑:
Vue路由在Android机上有问题,babel问题,安装babel polypill 插件解决。
事件被阻止
如果当前router-link的tag不是a的话(官方文档中说明:默认为a,可以通过tag改变渲染后的标签),那么就会往自己的子元素(组件)找,找到的第一个就会把一些属性及时间覆盖过去。
所以此时事件是被阻止了。个人看法:因为router-link的作用是单纯的路由跳转,如果不阻止事件的话,也许会有很多坑,所以作者干脆阻止了其他事件的触发
如何解决
<router-link @click.native="change"><span>{{item.text}}span>router-link>
只需要在@click后面加上native就可以了
原生事件
添加native事件修饰符之后变为原生事件
$element.addEventListener(click, callback);
此时a标签并不会阻止,至此便可以解决绑定在router-link身上的事件不会触发的问题。
其次,想要实现tab切换添加class可以在router中添加如下配置:
export default new Router({
routes: [],// 路由跳转配置
linkActiveClass:'active' // 在路由中添加配置 active 为跳转到该路由时添加的激活类名
})
问题描述
假设路由已配置好,但运行如下代码后,点击 Admin 按钮却没有跳转至 admin 界面。(clrDropdownItem为按钮样式)
main.component.html
<button type="button" clrDropdownItem >
<a routerLink="/admin"> Admin a>
button>
解决方法
将 clrDropdownItem 样式放在标签,不使用
标签
<a clrDropdownItem routerLink="/admin"> Admin a>
使用标签和Router.navigate方法
main.component.html
<button type="button" clrDropdownItem (click)="gotoAdmin()">
Admin
button>
复制代码
main.component.ts
import { Router } from '@angular/router';
...
export class MainComponent {
constructor(
private router: Router
) {}
gotoAdmin() {
this.router.navigate(['/admin']);
}
}
axios是一个基于promise的HTTP库,支持promise的所有API
它可以拦截请求和响应
它可以转换请求数据和响应数据,并对响应回来的内容自动转换为json类型的数据
它安全性更高,客户端支持防御XSRF
首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。
然后,使用Vue.extend方法创建一个组件,然后使用Vue.component方法注册组件。子组件需要数据,可以在props中接受定义。而子组件修改好数据后,想把数据传递给父组件。可以采用emit方法。
父组件向子组件传递数据通过prop传递
子组件传递数据给父组件通过$emit
触发事件
父组件通过 ref 直接访问子组件实例的属性和方法
通过$parent
/$children
:访问父 / 子实例
$attrs
和$listeners
利用 provide / inject ,向所有子孙后代注入依赖
$boradcast
和$dispatch
借助于中央事件总线 event bus 进行通讯。
利用 vuex 进行通讯。
利用 cookie 和 localstorage 进行通讯。
利用 session 进行通讯。
传参:
this.$router.push({
path:'/xxx',
query:{
id:id
}
})
接收参数:
this.$route.query.id
注意:传参是this.$router
,接收参数是this.$route
,这里千万要看清了!!!
2.params方式传参和接收参数
传参:
this.$router.push({
name:'xxx',
params:{
id:id
}
})
接收参数:
this.$route.params.id
注意:params传参,push里面只能是 name:‘xxxx’,不能是path:‘/xxx’,因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined!!!
Query的值携带在url刷新也不会丢失
另外,二者还有点区别,直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示
在根目录下写data.json
{
"errno": 0,
"data": [
{
"Findex": "Z",
"Fsinger_mid": "static/img/timg.jpg",
"Fsinger_name": "张三",
"Fsinger_tag": "541,555",
"Fsort": "1",
"Ftrend": "0",
"Ftype": "0",
"voc": "0"
}, {
"Findex": "Z",
"Fsinger_mid": "static/img/timg.jpg",
"Fsinger_name": "张三",
"Fsinger_tag": "541,555",
"Fsort": "2",
"Ftrend": "0",
"Ftype": "0",
"voc": "0"
}
]
}
2.将data.json 转成模拟数据
在webpack.dev.conf.js进行配置
const express = require('express')
const app = express()
var appData = require('../data.json')//加载本地数据文件
var list = appData.data//获取对应的本地数据
var apiRoutes = express.Router()
app.use('/api', apiRoutes)
devServer: {
...
before(app) {
app.get('/api/list', (req, res) => {
res.json({
errno: 0,
data: list
})//接口返回json数据,上面配置的数据seller就赋值给data请求后调用
})
}
},
3.vue页面调用
export default {
data() {
return {
singers: []
}
},
created() {
this._getSingerList()
},
methods: {
_getSingerList() {
this.$http.get('api/list').then((res) => {
this.singers = this._normalizeSinger(res.data.data)
})
},
}
}
vue组件三要素
基本用法
在使用 vue-cli 创建的项目中,组件的创建非常方便,只需要新建一个 .vue 文件,然后在 template 中写好 HTML 代码,一个简单的组件就完成了 一个完整的组件,除了 template 以外,还有 script和 style
<template>
<div class="headComponent">
子组件{{{myData}}
div>
template>
<script>
export default {
props:['data','type'],
inheritAttrs: false,
data(){
return{
myData:'',
}
},
mounted(){
},
methods:{
}
}
script>
<style scoped>
style>
然后在其他文件的 js 里面引入并注册,就能直接使用这个组件了
import list from '../components/headComponent.vue'
一、props:数据父组件传入子组件
父对子传参,就需要用到 props,通常的 props 是这样的:
props:['data','type']
但是通用组件的的应用场景比较复杂,对 props 传递的参数应该添加一些验证规则,常用格式如下:
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
propA: Number,
// 多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数字,有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
对于通过 props 传入的参数,不建议对其进行操作,因为会同时修改父组件里面的数据 // vue2.5已经针对 props 做出优化,这个问题已经不存在了 如果一定需要有这样的操作,可以这么写:
let copyData = JSON.parse(JSON.stringify(this.data))
为什么不直接写 let myData = this.data 呢? 因为直接赋值,对于对象和数组而言只是浅拷贝,指向的是同一个内存地址,其中一个改变另一个也会改变。而通过 JSON颠倒转换之后,实现了深拷贝,则可以互不影响。
二、子组件触发父组件事件
在通用组件中,通常会需要有各种事件,比如复选框的 change 事件,或者组件中某个按钮的 click 事件,有时子组件需要触发一个事件,并传递给父组件
// 子组件方法:触发父组件方法,并传递参数data到父组件
handleSubmit(data){
this.$emit('submitToParent', data)
}
// 父组件调用子组件
<child-component @submitToParent="parentSubmit"></child-component>
... ...
// 父组件中被触发的方法,接受到子组件传来的参数
parentSubmit(data){
// 父组件的逻辑处理
}
父组件中的逻辑要放在父组件处理,子组件基于父组件的数据做的逻辑放在子组件中处理; 这样既降低了耦合性,也保证了各自的数据不被污染。
三、记得留一个 slot
一个通用组件,往往不能够完美的适应所有应用场景 所以在封装组件的时候,只需要完成组件 80% 的功能,剩下的 20% 让父组件通过 solt 解决
上面是一个通用组件,在某些场景中,右侧的按钮是 “处理” 和 “委托”。在另外的场景中,按钮需要换成 “查看” 或者 “删除” 在封装组件的时候,就不用写按钮,只需要在合适的位置留一个 slot,将按钮的位置留出来,然后在父组件写入按钮
子组件
<div class="child-btn">
<slot name="button">slot>
<slot><slot>
div>
父组件
<child>
<button slot="button">slot按钮button>
child>
开发通用组件的时候,只要不是独立性很高的组件,建议都留一个 slot,即使还没想好用来干什么。
再来个例子:
开发过程中,常常需要在子组件内添加新的内容,这时候可以在子组件内部留一个或者多个插口
然后在调用这个子组件的时候加入内容
添加的内容就会分发到对应的 slot 中
slot 中还可以作为一个作用域,在子组件中定义变量,然后在父组件中自定义渲染的方式 子组件:
父组件:
这个示例中,首先在子组件中添加 slot,并在子组件中定义了数组变量 navs 然后在父组件中以作用域 template 添加内容,其中 scope 是固有属性,它的值对应一个临时变量 props 而 props 将接收从父组件传递给子组件的参数 navs
当网速较慢,vuejs文件还没有加载完时,在页面上会显示{{message}}的字样,直到vue创建实例、编译模板时,DOM才会被替换,所以这个过程屏幕是闪动的。
解决:加一句CSS,如下图
v-clock不需要表达式,它会在vue实例结束编译时从绑定的HTML元素上移除。
在一般情况下,v-clock是一个解决初始化慢导致页面闪动的最佳实践,对于简单的项目很实用。
但是,在具有工程化的项目里,比如使用了webpack和vue-router的项目中,HTML结构只是一个空的div元素,剩余的内容都是由路由去挂载不同的组件完成的,所以不需要v-cloak。
阻止事件冒泡
methods : {
//禁止滚动
stop(){
var mo=function(e){e.preventDefault();};
document.body.style.overflow='hidden';
document.addEventListener("touchmove",mo,false);//禁止页面滑动
},
/*取消滑动限制*/
move(){
var mo=function(e){e.preventDefault();};
document.body.style.overflow='';//出现滚动条
document.removeEventListener("touchmove",mo,false);
}
}
直接修改数组元素是无法触发视图更新的,如
this.array[0] = {
name: 'meng',
age: 22
}
修改array的length也无法触发视图更新,如
this.array.length = 2;
触发视图更新的方法有如下几种
1. Vue.set
可以设置对象或数组的值,通过key或数组索引,可以触发视图更新
数组修改
Vue.set(array, indexOfItem, newValue)
this.array.$set(indexOfItem, newValue)
对象修改
Vue.set(obj, keyOfItem, newValue)
this.obj.$set(keyOfItem, newValue)
2. Vue.delete
删除对象或数组中元素,通过key或数组索引,可以触发视图更新
数组修改
Vue.delete(array, indexOfItem)
this.array.$delete(indexOfItem)
对象修改
Vue.delete(obj, keyOfItem)
this.obj.$delete(keyOfItem)
3. 数组对象直接修改属性,可以触发视图更新
this.array[0].show = true;
this.array.forEach(function(item){
item.show = true;
});
4. splice方法修改数组,可以触发视图更新
this.array.splice(indexOfItem, 1, newElement)
5. 数组整体修改,可以触发视图更新
var tempArray = this.array;
tempArray[0].show = true;
this.array = tempArray;
6. 用Object.assign或lodash.assign可以为对象添加响应式属性,可以触发视图更新
//Object.assign的单层的覆盖前面的属性,不会递归的合并属性
this.obj = Object.assign({},this.obj,{a:1, b:2})
//assign与Object.assign一样
this.obj = _.assign({},this.obj,{a:1, b:2})
//merge会递归的合并属性
this.obj = _.merge({},this.obj,{a:1, b:2})
7.Vue提供了如下的数组的变异方法,可以触发视图更新
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Vant
有赞前端团队提供的轻量、可靠的移动端 Vue 组件库
Mint UI
基于 Vue.js 的移动端组件库
Vux
基于 WeUI 和 Vue(2.x) 开发的移动端 UI 组件库,主要服务于微信页面
Element
饿了么前端推出的基于 Vue.js 2.0 的后台组件库,帮助你更轻松更快速地开发 web 项目
iView
一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产品
Vue Antd
Ant Design 的 Vue 实现,开发和服务于企业级后台产品
Vue Admin
Vue 后台控制面板
基于 Vue2.0 和 Bulma0.2
响应式和弹性布局
vue-element-admin
后台集成解决方案,基于 Vue.js 和 element。使用了最新的前端技术栈,内置了i18国际化解决方案,动态路由,权限验证等很多功能特性。
Muse-UI
基于 Vue 2.0 和 Material Design 的 UI 组件库
vuetify
Material Component Framework for Vue.js 2
Keen-UI
A lightweight collection of essential UI components written with Vue and inspired by Material Design.(由Vue编写并受MaterialDesign启发的基本UL组件组成的轻量级集合。)
第一种方法:通过import引入
首先,引入要使用的背景图片:
<script type="text/javascript">
import cover from "../assets/images/cover.png";
export default{
...
}
script>
然后,通过v-bind:style使用:
<div :style="{ backgroundImage:'url(' + cover + ')' }">div>
第二种方法:通过require引入:
直接通过v-bind和require配合使用
<div :style="{ backgroundImage:'url(' + require('../assets/images/couver.png') + ')' }">div>
1、创建一个基于 webpack 模板的新项目
$ vue init webpack myvue
2、在当前目录下,安装依赖
$ cd myvue
$ npm install
3、安装sass的依赖包
npm install --save-dev sass-loader
//sass-loader依赖于node-sass
npm install --save-dev node-sass
4、在build文件夹下的webpack.base.conf.js的rules里面添加配置
{
test: /\.sass$/,
loaders: ['style', 'css', 'sass']
}
5、在APP.vue中修改style标签
<style lang="scss">
6、然后运行项目
$ npm run dev
7、修改APP.vue的样式,可以看下效果
进入config ⇒ index.js,在build中将dist关键字改成其他名称即可。
assetsPublicPath:‘/mydist/’,这里是存放静态资源的路径
vue init webpack vue-project
cd vue-project
npm run dev
npm run build
scripts:npm run xxx 命令调用node执行的 .js 文件
dependencies:生产环境依赖包的名称和版本号,即这些 依赖包 都会打包进 生产环境的JS文件里面
devDependencies:开发环境依赖包的名称和版本号,即这些 依赖包 只用于 代码开发 的时候,不会打包进 生产环境js文件 里面。
<template comments>
...
template>
因为箭头函数默绑定父级作用域的上下文,所以不会绑定vue实例,所以 this 是undefind
ES6的转码。IE的兼容
分为errorCaptured与errorHandler。
errorCaptured是组件内部钩子,可捕捉本组件与子孙组件抛出的错误,接收error、vm、info三个参数,return false后可以阻止错误继续向上抛出。
errorHandler为全局钩子,使用Vue.config.errorHandler配置,接收参数与errorCaptured一致,2.6后可捕捉v-on与promise链的错误,可用于统一错误处理与错误兜底。
e.currentTarget指的是注册了事件监听器的对象,而e.target指的是该对象里的子对象,也是触发这个事件的对象!
强制重新渲染
this.$forceUpdate()
强制重新刷新某组件
//模版上绑定key
<SomeComponent :key="theKey"/>
//选项里绑定data
data(){
return{
theKey:0
}
}
//刷新key达到刷新组件的目的
theKey++;
报错,语法错误
加入.native修饰符
input标签v-model用lazy修饰之后,vue并不会立即监听input Value的改变,会在input失去焦点之后,才会触发input Value的改变
1.new Vue({el:‘#app’})
当我们实例化Vue的时候,填写一个el选项,来指定我们的SPA入口:
let vm = new Vue({
el:'#app'
})
同时我们也会在body里面新增一个id为app的div
<body>
<div id='app'>div>
body>
这很好理解,就是为vue开启一个入口,那我们不妨来想想,如果我在body下这样
<body>
<div id='app1'>div>
<div id='app2'>div>
body>
Vue其实并不知道哪一个才是我们的入口,因为对于一个入口来讲,这个入口就是一个‘Vue类’,Vue需要把这个入口里面的所有东西拿来渲染,处理,最后再重新插入到dom中。
如果同时设置了多个入口,那么vue就不知道哪一个才是这个‘类’。
在组件内的beforeRouteLleave中移除事件监听
vue框架中状态管理。在main.js引入store,注入。新建一个目录store,…… export 。场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车
main.js:
import store from './store'
new Vue({
el:'#app',
store
})
State、Getter、Mutation 、Action、Module 五种
总结
vuex的State特性是?
state就是存放数据的地方,类似一个仓库 , 特性就是当mutation修改了state的数据的时候,他会动态的去修改所有的调用这个变量的所有组件里面的值( 若是store中的数据发生改变,依赖这个数据的组件也会发生更新 )
vuex的Getter特性是?
getter用来获取数据,mapgetter经常在计算属性中被使用
vuex的Mutation特性是?
Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作
可维护性会下降,想修改数据要维护三个地方;
可读性会下降,因为一个组件里的数据,根本就看不出来是从哪来的;
增加耦合,大量的上传派发,会让耦合性大大增加,本来Vue用Component就是为了减少耦合,现在这么用,和组件化的初衷相背。
一、如果请求来的数据不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
二、如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用,并包装成promise返回,在调用处用async await处理返回的数据。如果不要复用这个请求,那么直接写在vue文件里很方便。
1.首先定义两个页面
两个页面有一个公共的名字(大前端)
当第一个页面点击对应的按钮时 两个页面的公共名字会变成(vue)
当第二个页面点击对应的按钮时 两个页面的公共名字会变成(I love you)
2.开始使用vuex,新建一个 sotre文件夹,分开维护 actions mutations getters
3.在store/index.js文件中新建vuex 的store实例
*as的意思是 导入这个文件里面的所有内容,就不用一个个实例来导入了。
import Vue from 'vue'
import Vuex from 'vuex'
import * as getters from './getters' // 导入响应的模块,*相当于引入了这个组件下所有导出的事例
import * as actions from './actions'
import * as mutations from './mutations'
Vue.use(Vuex)
// 首先声明一个需要全局维护的状态 state,比如 我这里举例的resturantName
const state = {
resturantName: '大前端' // 默认值
// id: xxx 如果还有全局状态也可以在这里添加
// name:xxx
}
// 注册上面引入的各大模块
export default new Vuex.Store({
state, // 共同维护的一个状态,state里面可以是很多个全局状态
getters, // 获取数据并渲染
actions, // 数据的异步操作
mutations // 处理数据的唯一途径,state的改变或赋值只能在这里
})
4.actions
// 给action注册事件处理函数。当这个函数被触发时候,将状态提交到mutations中处理
export function modifyAName({commit}, name) { // commit 提交;name即为点击后传递过来的参数,此时是 'vue'
return commit ('modifyAName', name)
}
export function modifyBName({commit}, name) {
return commit ('modifyBName', name)
}
// ES6精简写法
// export const modifyAName = ({commit},name) => commit('modifyAName', name)
5.mutations
// 提交 mutations是更改Vuex状态的唯一合法方法
export const modifyAName = (state, name) => { // A组件点击更改名称为 vue
state.resturantName = name // 把方法传递过来的参数,赋值给state中的resturantName
}
export const modifyBName = (state, name) => { // B组件点击更改名称为I love you
state.resturantName = name
}
6.getters
// 获取最终的状态信息
export const resturantName = state => state.resturantName
7.在main.js中导入 store实例
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store, // 这样就能全局使用vuex了
components: { App },
template: ' '
})
8.在组件A中,定义点击事件,点击 修改 名称,并把vue名称在事件中用参数进行传递。
…mapactions 和 …mapgetters都是vuex提供的语法糖,在底层已经封装好了,拿来就能用,简化了很多操作。
其中…mapActions([‘clickAFn’]) 相当于this.$store.dispatch(‘clickAFn’,{参数}),mapActions中只需要指定方法名即可,参数省略。
…mapGetters([‘resturantName’])相当于this.$store.getters.resturantName
<template>
<div class="componentsA">
<P class="title">组件AP>
<P class="titleName">{{resturantName}}P>
<div>
<button class="btn" @click="modifyAName('vue')">修改为vuebutton>
div>
div>
template>
<script>
import {mapActions, mapGetters} from 'vuex'
export default {
name: 'A',
data () {
return {
}
},
methods:{
...mapActions( // 语法糖
['modifyAName'] // 相当于this.$store.dispatch('modifyName'),提交这个方法
)
},
computed: {
...mapGetters(['resturantName']) // 动态计算属性,相当于this.$store.getters.resturantName
}
}
script>
<style scoped>
.title,.titleName{
color: blue;
font-size: 20px;
}
.btn{
width: 160px;
height: 40px;
background-color: blue;
border: none;
outline: none;
color: #ffffff;
border-radius: 4px;
}
.marTop{
margin-top: 20px;
}
style>
actions和之前讲的Mutations功能基本一样,不同点是,actions是异步的改变state状态,而Mutations是同步改变状态。
1.看一个简单的action
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action函数接受一个与store实例具有相同属性和放法的contex对象,因此你可以调用contex.commit提交一个mutation,或者通过context.state和contex.getters来获取state和getters。当我们在之后的Modules时,你就知道context对象为什么不是stroe实例本身了。
实践中,我们经常用到ES2015的参数结构来简化代码
actions: {
increment ({ commit }){
commit('increment')
}
}
2.组合Actions
Action通常是异步的,那么如何知道action什么时候结束呢,更重要的是,我们如何组合多个action,以便处理更加复杂的流程?
首先你得知道,store.dispatch可以处理被触发的action的回调函数返回的Promise,并且store.dispath仍旧返回Promise
actions: {
actionA ({ commit }) {
return New Promise ( (resolve, reject ) => {
setTimeout( () => {
commit('increment')
resolve()
},1000)
})
}
}
这样就可以:
store.dispath('actionA').then(() => {})
在另一个action里你可以这样:
actions: {
actionB ({dispatch, commit}) {
return dispatch('actionA').then(() => {
commit('increment')
})
}
}
最后,如果我们利用async和await这个es7新特性,我们可以像这样组合代码:
// 假设getData()和getOtherData()返回的是Promise
actions: {
async actionA ({commit}) {
commit('gotData', await getData())
},
async actionB ({commit}) {
await dispatch('actionA') // 等待 actionA完成
commit('getOtherData', await getOtherData() )
}
}
async和await是最新的es7语法,但是还没完全确定下来,在大部分用户手上都不能使用,仅限在内部用哦~
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作
mutations只能是同步操作
localStorage 或者就是sessionStorage ,或者借用辅助插vuex-persistedstate
通过$watch监听mutation的commit函数中_committing是否为true
html代码
<template>
<div class="fixtop2">
<header class="header" ref="header">header>
<div class="nav" ref="nav" :class="{isFixed:isFixed}">
<div class="box" v-for="(item,index) in list" :key="index">
{{item.title}}
div>
div>
<ul class="content">
<li v-for="(item,index) in new Array(50)" :key="index">{{index+1}}li>
ul>
div>
template>
script代码
<script>
var throttle = require('lodash/throttle'); //从lodash中引入的throttle节流函数
(记得安装lodash :npm install lodash )
export default {
name: 'navScroll2',
data() {
return {
list: [
{ title: 'AAAA', id: 1 },
{ title: 'BBBB', id: 2 },
{ title: 'CCCC', id: 3 },
{ title: 'DDDD', id: 4 },
],
isFixed: false, //是否固定的
throttleScroll: null, //定义一个截流函数的变量
};
},
methods: {
//滚动的函数
handleScroll() {
let h = $(this.$refs.header).outerHeight(); //header的高度
let wh = $(window).scrollTop(); //滚动的距离的,为什么这里使用的jq,因为不用考虑的什么的兼容问题
(记得引进jq)
let navH = $(this.$refs.nav).outerHeight(); //nav的高度
if (wh > h) {
this.isFixed = true;
} else {
this.isFixed = false;
}
},
},
mounted() {
//写在掉接口的里面的
this.$nextTick(() => {
//这里使用监听的scroll的事件,为什么要使用的节流函数,如果不使用的,页面一直在滚动计算的,这样在
//使用手机时候,出现非常卡的,隔一段时间计算,大大降低了性能的消耗(具体的好处自己去查资料)
window.addEventListener('scroll', this.throttleScroll, false);
});
this.throttleScroll = throttle(this.handleScroll, 100);
},
deactivated() {
//离开页面需要remove这个监听器,不然还是卡到爆。
window.removeEventListener('scroll', this.throttleScroll);
},
};
script>
style
<style scoped lang="stylus" rel="stylesheet/stylus">
.fixtop2 {
min-height: 100vh;
}
.header {
height: 5rem;
width: 100%;
background-color: red;
}
.nav {
display: flex;
width: 100%;
background-color: pink;
&.isFixed {
position: fixed;
left: 0;
top: 0;
z-index: 9999;
}
.box {
font-size: 0.3rem;
height: 0.9rem;
line-height: 0.9rem;
color: #333333;
flex: 1;
}
}
.content {
height: 20rem;
li {
width: 100%;
height: 1rem;
border-bottom: 1px solid #000;
}
}
style>
第一步安装依赖
$ npm install axios
第二步进行配置
在build/webpack.dev.conf.js里进行配置
a.头部添加变量
var express = require('express')
var axios = require('axios')
var app = express()
var apiRoutes = express.Router()
app.use('/api', apiRoutes)
b.在devServer中进行代理编写
before(apiRoutes) {
apiRoutes.get('/api/getDiscList', function (req, res) {
var url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg' // 原api
axios.get(url, {
headers: {
referer: 'https://c.y.qq.com/',
host: 'c.y.qq.com'
},
params: req.query
}).then((response) => {
res.json(response.data)
}).catch((e) => {
console.log(e)
})
})
}
第三步api.js调用
import axios from 'axios'
export function getSingerList(mid) {
const url = '/api/getDiscList'
const data = Object.assign({}, {
g_tk: 1928093487,
inCharset: 'utf-8',
outCharset: 'utf-8',
notice: 0,
format: 'jsonp',
songmid: mid,
platform: 'yqq',
hostUin: 0,
needNewCode: 0,
categoryId: 10000000,
pcachetime: +new Date(),
format: 'json'
})
return axios.get(url, {
params: data
}).then((res) => {
return Promise.resolve(res.data)
})
}
第四步页面调用
import {getSingerList} from 'api/singer'
data() {
return {
singers: []
}
},
created() {
this._getSingerList()
},
methods: {
selectSinger(singer) {
this.$router.push({
path: `/singer/${singer.id}`
})
this.setSinger(singer)
},
_getSingerList() {
getSingerList().then((res) => {
if (res.code === ERR_OK) {
this.singers = this._normalizeSinger(res.data.list)
}
})
}
}
transition
首先看看官网淡入淡出动画的例子:
CSS部分
我们要实现的是左右切换的效果,所以要定义两种动画(左滑和右滑)
.transitionBody{
transition: all 0.15s ease; /*定义动画的时间和过渡效果*/
}
.transitionLeft-enter,
.transitionRight-leave-active {
-webkit-transform: translate(100%, 0);
transform: translate(100%, 0);
/*当左滑进入右滑进入过渡动画*/
}
.transitionLeft-leave-active,
.transitionRight-enter {
-webkit-transform: translate(-100%, 0);
transform: translate(-100%, 0);
}
HTML部分
这里的 keep-alive 是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM,我们要把它也包裹在transition标签内,否则页面将重新渲染,切换的动画也会卡顿
<transition :name="transitionName">
<keep-alive>
<router-view class="transitionBody">router-view>
keep-alive>
transition>
JS部分
在Vue组件中,data必须是一个函数,将对象 {transitionName: ‘transitionLeft’} 挂载到Vue实例中,然后我们可以监听路由的 to 和 from 来判断此时应该左滑还是右滑,来动态切换transition的name值。
export default {
data() {
return {
transitionName: 'transitionLeft'
};
},
watch: {
'$route' (to, from) {
const arr = ['/goods','/ratings','/seller'];
const compare = arr.indexOf(to.path)>arr.indexOf(from.path);
this.transitionName = compare ? 'transitionLeft' : 'transitionRight';
}
}
}