VUE学习整理
http://doc.liangxinghua.com/vue-family/1.html
http://doc.liangxinghua.com/vue-family/1.3.html
vue的优点
低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
vue和react对比
两者本质的区别:模板和组件化的区别
Vue本质是MVVM框架,由MVC发展而来;
React是前端组件化框架,由后端组件化发展而来;
Vue使用模板
React使用JSX
React本身就是组件化
Vue是在MVVM上扩展的
共同点:
都支持组件化,都是数据驱动视图
上面主要梳理了react和vue的4点不同:
1.数据是不是可变的
2.通过js操作一切还是各自的处理方式
3.什么功能内置,什么交给社区去做
react整体的思路就是函数式,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以做到,比如结合redux-form,而vue是基于可变数据的,支持双向绑定。react组件的扩展一般是通过高阶组件,而vue组件会使用mixin。vue内置了很多功能,而react做的很少,很多都是由社区来完成的,vue追求的是开发的简单,而react更在乎方式是否正确
监听数据变化的实现原理不同
Vue 通过 getter/setter 以及一些函数的劫持,能精确快速的计算出 Virtual DOM 的差异。这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
React 默认是通过比较引用的方式进行的,如果不优化,每当应用的状态被改变时,全部子组件都会重新渲染,可能导致大量不必要的 VDOM 的重新渲染。
Vue 不需要特别的优化就能达到很好的性能,而对于 React 而言,需要通过 PureComponent/shouldComponentUpdate 这个生命周期方法来进行控制。如果你的应用中,交互复杂,需要处理大量的 UI 变化,那么使用 Virtual DOM 是一个好主意。如果你更新元素并不频繁,那么 Virtual DOM 并不一定适用,性能很可能还不如直接操控 DOM。
为什么 React 不精确监听数据变化呢?
这是因为 Vue 和 React 设计理念上的区别,Vue 使用的是可变数据,而 React 更强调数据的不可变。
数据流的不同
Vue 中默认支持双向绑定,组件与 DOM 之间可以通过 v-model 双向绑定。但是,父子组件之间,props 在 2.x 版本是单向数据流
React 一直提倡的是单向数据流,他称之为 onChange/setState()模式。
不过由于我们一般都会用 Vuex 以及 Redux 等单向数据流的状态管理框架,因此很多时候我们感受不到这一点的区别了。
模板渲染方式的不同
在表层上, 模板的语法不同
React 是通过 JSX 渲染模板
而 Vue 是通过一种拓展的 HTML 语法进行渲染
在深层上,模板的原理不同,这才是他们的本质区别:
React 是在组件 JS 代码中,通过原生 JS 实现模板中的常见语法,比如插值,条件,循环等,都是通过 JS 语法实现的
Vue 是在和组件 JS 代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现
对这一点,我个人比较喜欢 React 的做法,因为他更加纯粹更加原生,而 Vue 的做法显得有些独特,会把 HTML 弄得很乱。举个例子,说明 React 的好处:react 中 render 函数是支持闭包特性的,所以我们 import 的组件在 render 中可以直接调用。但是在 Vue 中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们 import 一个组件完了之后,还需要在 components 中再声明下,这样显然是很奇怪但又不得不这样的做法。
vue全家桶
vue + vue-router + vuex + axios
https://www.cnblogs.com/Nutrient-rich/p/7063058.html
组件里面, data必须是一个函数
类比引用数据类型 Object是引用数据类型, 每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
那么用什么方法可以使每个组件的data相互独立,不受影响呢?
当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
vue中的nextTick
methods/computed/watch
计算属性:自动监听依赖值的变化,从而动态返回内容,主要目的是简化模板内的复杂运算,只需要动态值的时候,用计算属性,计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值,不支持异步,当computed内有异步操作时无效,无法监听数据的变化,适用场景:
重新计算开销很大的话,选computed; 不希望有缓存的选methods
watch:需要知道值改变后执行业务逻辑用watch,watch有新旧值两个参数, 计算属性没有,但是计算属性可以从setter获得新值,Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的
methods:是一个方法,可以接受参数,计算属性不能,计算属性是可以缓存的
在执行简单的计算操作时,computed比watch更简洁易读。
总结:
1.如果一个数据依赖于其他数据的简易计算处理的,那么使用computed比较合适。
2.如果需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。这是和computed最大的区别
关于computed
对于计算属性要特别说明一点: vue的计算属性computed默认只有getter,需要使用getter的时候需要自己加一个setter
export default {
data () {
return {
firstName: '张',
lastName: '三',
};
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
},
},
methods: {
changeFullName () {
this.fullName = '李 四';
}
},
};
其中computed里的代码完整写法是
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
}
},
执行 changeFullName 发现报错[Vue warn]: Computed property “fullame” was assigned to but it has no setter.
我们需要给计算属性fullName添加一个setter
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
vue中如何监控某个属性值的变化
比如现在需要监控data中,obj.a 的变化。Vue中监控对象属性的变化你可以这样:
watch: {
obj: {
handler (newValue, oldValue) {
console.log('obj changed')
},
deep: true
}
}
deep属性表示深层遍历,但是这么写会监控obj的所有属性变化,并不是我们想要的效果,所以做点修改:
watch: {
'obj.a': {
handler (newName, oldName) {
console.log('obj.a changed')
}
}
}
还有一种方法,可以通过computed 来实现,只需要:
computed: {
a1 () {
return this.obj.a
}
}
利用计算属性的特性来实现,当依赖改变时,便会重新计算一个新值。
vue中组件通信
—props emit bus
—vuex slot都可以传值
Vue 组件间通信六种方式
props/$emit
emit/ emit/emit/on
vuex
attrs/ attrs/attrs/listeners
provide/inject
parent/ parent/parent/children 与 ref
非父子通信
简单情况下我们可以通过使用一个空的Vue实例作为中央事件总线
在main.js文件夹下建一个vue空实例
let bus = new Vue()
Vue.prototype.bus = bus
在一个组件中定义this.bus.$emit('toChangeTitle','从首页来')
toChangeTitle自定义事件名称,后面是要传的值
在另一个组件中的接收
mounted () {
this.bus.$on('toChangeTitle', function (title) {
toChangeTitle //前一个组件定义的自定义事件名
title //值 把他赋值给data里的变量就可以在模板中使用了
console.log(title)
})
}
data赋值与computed赋值的区别:
data赋值:data:{return {aaa: this.aaa}如果是在data中进行赋值,当父组件的aaa值发生改变时,不会在重新赋给子组件中的aaa。
computed赋值:如果想让子组件跟着父组件修改,需要将赋值操作写在computed中。computed:{aaa(){return this.aaa}
vue中操作dom,获取dom
//选择器获取 <template>
<div>
<canvas id='cvs' >
</div>
</template>
<script>
export default{
mounted(){
let canvas=document.querySelector('#cvs');
}
}
</script>
ref获取 <template>
<div>
<canvas ref='cvs' >
</div>
</template>
<script>
export default{
mounted(){
let canvas=this.$refs.cvs;
}
}
</script>
在watch或者created里面操作dom,用this.$nextTick(function(){
xxxx
})
vue中数据双向绑定原理及如何实现
原理:
vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调
1、实现一个数据监听器Obverser,对data中的数据进行监听,若有变化,通知相应的订阅者。
2、实现一个指令解析器Compile,对于每个元素上的指令进行解析,根据指令替换数据,更新视图。
3、实现一个Watcher,用来连接Obverser和Compile, 并为每个属性绑定相应的订阅者,当数据发生变化时,执行相应的回调函数,从而更新视图。
4、构造函数 (new MVue({}))
实现思路:
<div id="app">
<input type="text" v-model="text">
输入的值为:{{text}}
<div>
<input type="text" v-model="text">
</div>
</div>
<script>
var vm = new MVue({
el: '#app',
data: {
text: 'hello world'
}
})
</script>
1、输入框以及文本节点和data中的数据进行绑定
2、输入框内容变化时,data中的对应数据同步变化,即 view => model
3、data中数据变化时,对应的文本节点内容同步变化 即 model => view
vue过滤器作用和使用场景
https://011.vuejs.org/api/filters.html
https://www.cnblogs.com/yanwuming/p/10603058.html
https://www.cnblogs.com/xuqp/p/9395269.html
https://blog.csdn.net/Shauna_Wu/article/details/79496101
Vue过滤器的作用和使用场景
主要用来实现数据的筛选、过滤、格式化。通常用于时间戳转时间,价格标签的转换等。过滤器分为全局过滤器和局部过滤器;
Vue 1.x 版本借鉴了 angular , 提供 10 个过滤器, 包括有: 日期 小数点位数保留 货币 大小写 等
使用特点
(1)可串联
{{ message | filterA | filterB }}
含义:message作为参数传到过滤器A,过滤器A的结果传到过滤器B。
(2)多个参数
{{ message | filterA(‘arg1’, arg2) }}
含义: 过滤器A有message,‘arg1’, ‘arg2’三个参数
过滤器代码实现
全局过滤器的定义语法
Vue.filter('过滤器的名称',function(){})
项目中的具体使用,局部过滤器为例
先定义个局部过滤器
filters: { //这个定义方式和上面的过滤器定义同理,这里formatDate是从JS中引入进来的,将时间戳转为时间格式
formatDate (time) {
if (!time) {
return;
}
var date = new Date(time);
return formatDate(date, 'yyyy-MM-dd hh:mm:ss');
}
},
在template中使用该过滤器
{{item.createTime | formatDate}} {{ 需要过滤的参数| 过滤器}}
如果在main.js中定义一个这样的全局过滤器,这样在任何地方需要用到这个过滤器,都可以用这个方式使用
1.3、使用过滤器需要注意事项
一、过滤器要想获得我们的数据,要通过一个叫做 ‘管道符 | ’ 来获取数据
二、过滤器是对已经有的数据进行格式化,也就是必须先有数据,在去格式化(如果支持空数据的形式,可自己在过滤器进行处置)
1.4、多个过滤器和多个参数的过滤器如何使用
直接看代码:
首先说全局,多个值的过滤器
Vue.filter('demo',function(val,first,second){ //多个参数的过滤器定义
return val + first + second;
})
<!--这里把其他参数括号括起来-->
{{message | demo('canshu1','canshu2')}} //多个参数的过滤器使用
全局传多个过滤器
Vue.filter('demo',function(val){ //多个过滤器定义
return val + ' demo1 ';
})
Vue.filter('demo2',function(val){
return val + ' demo2!';
})
<!--多个全局过滤器使用-->
{{message | demo1 | demo2}
是这样的,很多时候我们在项目中会需要从后台获取到时间这个字段,像带有评论的会有用户的评论时间,发表文章的会有发表文章的时间,虽然都是时间,但是有的时候需求不会完全统一。有的只需要年月日,有的要精确到时分秒。
还有一个是路径的问题,比如,我们页面上的图片,音频,视频等这些外部资源,目前我们从后台上传的都不是全路径的,所以这个需要我们自己拼接,但是有的时候又是全路径的,不需要拼接,以图片为例,也就是同一个img标签的src它不紧要支持全路径还要支持非全路径的资源,但是这个东西后台一般不会给你做区分处理,前端可以获取到之后自己做处理,如果用传统方式解决就要加各种判断,如果页面上既有图片又有音频,甚至还有视频等,每种类型都要判断,很繁琐。但是vue给我们提供了一个非常好的工具filters,可以让我们很简单的解决这种问题
过滤器是可以叠加的,后面过滤器接收前面过滤器的返回值
需要用v-if进行判断的都可以用过滤器来实现
filter的使用场景,一般为当后端返回数据为判断显示某个内容时,原来通常使用v-if现在改用filter便于管理
过滤器的作用:实现数据的筛选、过滤、格式化
使用场景:
1.时间过滤器
在项目中使用到的经常用到过滤器,比如时间,数据截取等过滤器,如果在每个.vue中都可以复制同一个过滤器,这可以达到目的,但是遇到方法有bug时就需要诸葛修改进入不同的页面修改,这样既费时又费力,优先可以考虑注册全局的过滤器。
定义方法如下:
新建filters/index.js
const isNullOrEmpty = function(val) {
if (val == null || val == "" || typeof(val) == undefined) {
return true;
} else {
return false;
}
}
const timeFormat = (value, format) => {
let date = new Date(value);
let y = date.getFullYear();
let m = date.getMonth() + 1;
let d = date.getDate();
let h = date.getHours();
let min = date.getMinutes();
let s = date.getSeconds();
let result = "";
if (format == undefined) {
result = `${y}-${m < 10 ? "0" + m : m}-${d < 10 ? "0" + d : d} ${
h < 10 ? "0" + h : h
}:${min < 10 ? "0" + min : min}:${s < 10 ? "0" + s : s}`;
}
if (format == "yyyy-mm-dd") {
result = `${y}-${m < 10 ? "0" + m : m}-${d < 10 ? "0" + d : d}`;
}
if (format == "yyyy-mm") {
result = `${y}-${m < 10 ? "0" + m : m}`;
}
if (format == "mm-dd") {
result = ` ${mm < 10 ? "0" + mm : mm}:${ddmin < 10 ? "0" + dd : dd}`;
}
if (format == "hh:mm") {
result = ` ${h < 10 ? "0" + h : h}:${min < 10 ? "0" + min : min}`;
}
if (format == "yyyy") {
result = `${y}`;
}
return result;
};
export {
isNullOrEmpty,
timeFormat
}
在main.js中引入和注册全局过滤器
import * as filters from '../filters/index'
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
此时就可以在不同的.vue中使用定义的全局过滤器了
{{date|isNullOrEmpty}}是否为空<br/>
{{date|timeFormat('yyyy-mm-dd')}} 时间过滤器<br>
{{date|timeFormat('yyyy-mm')}} 时间过滤器yyyy-mm<br>
{{date|timeFormat('hh:mm')}} 时间过滤器hh:mm<br>
{{date|timeFormat('yyyy')}} 时间过滤器yyyy<br>
{{date|timeFormat}} 时间过滤器yyyy<br>
2.
1.创建filter文件
import Vue from 'vue';
/*订单页面详情页面,状态栏*/
Vue.filter('settleAccountsType',(val)=>{
if(val==4){
return `订单已取消!`
}else if(val==5) {
return `订单已作废!`
}
else if(val==6) {
return `订单申请退款`
}else{
return ``
}
});
export default {
}
在main中挂载
import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App'
import router from './router'
import $ from 'jquery'
import axios from 'axios'//引入axios
import Vuex from 'vuex'
import store from './store/store';
import filter from '@/assets/js/filter.js';//过滤器全局
Vue.config.productionTip = false;
Vue.use(ElementUI);
Vue.use(Vuex);
// Vue.use(axios);
Vue.prototype.axios = axios;
Vue.use(filter);//过滤器全局挂载
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
filter,//过滤器全局挂载
store,//使用store
components: { App },
template: '<App/>'
});
3.在页面使用
<!--过滤器使用-->
<div class="statusIf">{{ status | settleAccountsType }}</div>
解释:status :vue页面绑定的数据,就是原来v-if判断的数据
settleAccountsType :过滤器js中的方法名
3.例如一个购物车界面,读取json文件获取购物车内物品的相关信息。对于单价的变量,读取后需要加美元符号或者加小数点,可以使用过滤器
1、局部过滤器
在js代码中
var vm = new Vue({
el:"#app",
data:{
totalMoney:0,
productList:[]
},
filters:{
formatMoney:function(value){
return "¥"+value.toFixed(2)
}
},
...
在HTML代码中:
<div class="cart-tab-2">
<div class="item-price">{{item.productPrice | formatMoney}}</div>
</div>
4.路径拼接过滤器
路径过滤(涉及传参的形式):
需要自己拼接的写法html:
<img src="../assets/image/img_article.png" alt=""
v-if="subject.image===''||subject.image==='null'"/>
<img :src="imgUrl+subject.image" alt="" v-if="subject.image !== ''&&subject.image!=='null'"/>
对应的js:
data() {
return {
imgUrl:" http://app.chuxinketing.com/api/",
};
},
在这种写法上,如果后台返回的图片本身就是一个全路径的链接,比如:http://app.chuxinketing.com/api/123.jpg,在拼上imgUrl,就变成了http://app.chuxinketing.com/api/http://app.chuxinketing.com/api/123.jpg。这种是无法正确打开图片的,你可以自己在浏览器中打开尝试一下,所以这时候,过滤器又变成了了一个化繁为简解决问题的小能手。我们只需要这么做:
<img src="../assets/image/img_avatar.png" alt=""
v-if="item.image===''||item.image==='null'" />
<img :src="item.image|urlFilter(imgUrl)" alt="" v-if="item.image !== ''&&item.image!=='null'"/>
在filters中添加一个urlFilter方法:
filters:{
urlFilter(value,imgUrl){
const self = this;
if (value) {
let include = value.indexOf("http");
if (include > -1){
return value;
} else {
return imgUrl+value;
}
}
},
}
注意,urlFilter()方法中的value其实就是后台返回的被过滤对象。这个大伙可以自己用chrome等调试工具断点调试哦,这样是不是就就轻松解决啦。另外,细心的小伙伴可能会发现为什么,要写两个img标签呢,其实,仔细看就会发现,第一个img标签的src前面没有“:”,而且图片路径也是一个固定的图片,其实这是为了提高用户体验所做的优化,如果后台没有返回图片,像一些需要用户上传头像的地方,如果用户没上传之类的,就可以用一张默认图片展示了。
vue过滤器中如何使用vue-i18n进行多语言的国际化翻译
//单个过滤起的文件,在main.js中引入,全局使用
import Vue from ‘vue’
import moment from ‘moment’
import accounting from ‘accounting’
import i18n from ‘…/i18n/i18n’ //引入国际化语言的语言包
Vue.filter(‘projectDate’, date => moment(date).format(‘YYYY-MM-DD’))
Vue.filter(‘logDate’, date => moment(date).format(‘YYYY-MM-DD HH:mm:ss’))
Vue.filter(‘developerStatus’, state => {
switch (state) {
case 4:
return i18n.t(‘register.recruit’) // 语言包中定义的json对象
case 5:
return i18n.t(‘register.develop’) //其他书写方式一致
case 51:
return ‘测试中’
case 6:
return ‘质保中’
case 7:
return ‘已完成’
case 8:
return ‘已取消’
case 9:
return ‘已中止’
default:
return ‘’
}
})
Vue.filter(‘teamSize’, state => {
switch (state) {
case 1:
return ‘1-3人’
case 2:
return ‘4-6人’
case 3:
return ‘7-9人’
case 4:
return ‘10人以上’
default:
return ‘’
}
})
Vue.filter(‘budget’, money => accounting.formatNumber(money))
Vue.filter(‘totalPrice’, money => accounting.formatMoney(money, ‘’, 2))
Vue.filter(‘payment’, state => {
switch (state) {
case 1:
return ‘线下转账’
case 2:
return ‘支付宝’
case 3:
return ‘微信支付’
default:
return ‘’
}
})
//使用示例
{{detail.developerStatus | developerStatus}}过滤器的名字
**vue中生命周期函数**
**什么是vue生命周期**
Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期
**vue生命周期的作用是什么**
它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
**created阶段的ajax请求与mounted请求的区别**
前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态
mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染
完毕,可以用 vm.$nextTick
vue2.0之后主动调用$destroy()不会移除dom节点,作者不推荐直接destroy这种做法,如果实在需要这样用可以在这个生命周期钩子中手动移除dom节点
**vue获取后端数据应该在created还是mounted**
看情况了,一般放到created里面就可以了,这样可以及早发请求获取数据,如果有依赖dom必须存在的情况,就放到mounted(){this.$nextTick(() => { /* code */ })}里面
**第一次页面加载会触发哪几个钩子**
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
**DOM 渲染在 哪个周期中就已经完成**
DOM 渲染在 mounted 中就已经完成了
**简单描述每个周期具体适合哪些场景**
答:生命周期钩子的一些使用方法:
beforecreate : 可以在这加个loading事件,在加载实例时触发
实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据
常用于初始化非响应式变量
created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用
实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到el属性, el属性,el属性,ref属性内容为空数组
常用于简单的ajax请求,页面的初始化
mounted : 挂载元素,获取到DOM节点
$ref属性可以访问
常用于获取VNode信息和操作,ajax请求
updated : 如果对数据统一处理,在这里写上相应函数
虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作
避免在这个钩子函数中操作数据,可能陷入死循环
beforeDestroy : 可以做一个确认停止事件的确认框
实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例
常用于销毁定时器、解绑全局事件、销毁插件对象等操作
destroyed
实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁
nextTick : 更新数据后立即操作dom
beforeCreated: el和data并未初始化 created: 完成data数据的初始化,el没有 beforeMount: 完成了el和data初始化 mounted: 完成挂载
**什么时候从后台获取数据组好?**
其实Vue并没有规定什么时候获取数据最好,我们从每个钩子函数定义便可知,只要在Vue实例创建之后,也就是created中及以后的所有钩子函数里都可以从后台获取数据。但是,这里我建议大家还是在created钩子函数就获取数据,然后进行数据操作。原因嘛就是,从beforeCreate到mounted,这中间是一个流程,而且是不受任何东西影响的,并且数据请求是个异步的过程,而生命周期并不是等待数据返回再接着运行。举个例子:
updated是个好东西
通过上文即可知道,updated是不需要渲染真实DOM之后才能调用的钩子。也就是说,我们不需要担心什么时候真实DOM才会被渲染完成。上述代码改成这样:
<ul>
<li v-for="item of source" :key="item" ref="item"></li>
</ul>
data(){
return{
source:[]
}
},
created(){
...从后台获取数据
this.source = response//将data里数据赋值为获取到的数据
},
updated(){
this.$nextTick(()=>{
//假如有一个数组项值为'Alan'
console.log(this.$refs.Alan)
//这里可能会报错,此时的refs显示为未定义的Object的属性
})
}
只要原始数据source发生了改变,就可以在updated里执行代码。这里要注意的是,我使用了一个Vue的自带方法 nextTick就是会将方法内回调函数的操作延迟到队列里下一个DOM更新后执行。也就是说,会等到source获取完毕,然后真实DOM渲染完成后才执行。 nextTick!
vue中keep-alive
vue中slot
vue中的vuex
https://blog.csdn.net/weixin_40402192/article/details/80052887
https://blog.csdn.net/Evan_QB/article/details/81358352
https://blog.csdn.net/awake720/article/details/79657719
https://blog.csdn.net/weixin_38483133/article/details/89327362
https://www.csdn.net/gather_29/MtTaAg3sOTg3Mi1ibG9n.html
https://www.jianshu.com/p/4547221704d8
原理:
vuex的方案是,在vue中构建一个用于存储state、定义操作state方法的仓库(即store)。通过在多个(不一定是全部)组件中引用需要的state、调用“操作state的方法”来实现对给共享变量的处理。且由于各个组件对state是引用的,单个组件改变了某个state后,其他组件可以实时的响应变化。
Vuex 是什么?
是专门针对vue提供的状态管理模式。
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
为什么要用vuex?
为了更好的管理状态,在之前组件内部状态的应用中发现,每个组件的状态都是独立的,如果存在好多组件公用的状态通讯起来比较麻烦
vuex的出现,他是将所有数据状态集中式存储管理,变成了一个,在组件中以相应的规则去读取改变。
vuex的核心概念
state: 唯一数据源, 定义在store中的state选项,在组件中可以通过this.$store.state读取,一半都写在计算属性中,可以通过mapState()函数生成计算属性
getters:相当于组件中的计算属性, 定义在store中getters选项中,在组件中通过$store.getters获取,一半都写在计算属性中,可以通过mapGetters()函数生成计算属性
getters: {
计算属性名称 (state, getters) => {
return 返回值
}
}
mutation: 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,
并且它会接受 state 作为第一个参数, 其他参数为第二个, 通过commit可以执行他
注意:Mutation 必须是同步函数
因为mutation中除了改变state之外还将state改变的过程快照记录了下来,如果在mutation中写异步逻辑的话就无法记录快照了
actions: 用来提交mutations,可以处理异步逻辑,但是不能直接修改state,只能通过commit(‘mutation’)的形式改变state,actions函数接收一个context对象(类似于store实例)
vuex实现的作用
数据共享机制,通过统一的数据中心store维护状态数据,每个组件进行更新的时候,通知数据中心 store。再由stroe将共享的状态,触发每一个调用它的组件的更新。
vuex的流程
页面通过mapAction异步提交事件到action。action通过commit把对应参数同步提交到mutation。mutation会修改state中对于的值。
最后通过getter把对应值跑出去,在页面的计算属性中,通过mapGetter来动态获取state中的值
vuex的Mutation特性是?
一、Action 类似于 mutation,不同在于:
二、Action 提交的是 mutation,而不是直接变更状态。
三、Action 可以包含任意异步操作
vuex的优势
状态管理工具 核心是响应式的做到数据管理
一个页面发生数据变化。动态的改变对应的页面
相比使用localStorage ,localstorge只能纯属字符串数据格式,因此还得封装自己的写入写出,localstorage的优势是永久存储,兄弟之间组件有大量通信的,建议一定要用VUEX,不管大项目和小项目
vuex的工作流程
1.在vue组件里面,通过dispatch来触发actions提交修改数据的操作。
2.然后再通过actions的commit来触发mutations来修改数据。
3.mutations接收到commit的请求,就会自动通过Mutate来修改state(数据中心里面的数据状态)里面的数据。
4.最后由store触发每一个调用它的组件的更新
store.js中的基本结构是
import Vue from 'vue'
mport Vuex from 'vuex'
Vue.use(Vuex)
const state = { //状态 在视图里面渲染的基本都放在state里
list:[],
num:0
//存放共享组件
}
const getters = { //计算过滤操作
state中的变量:(state)=>{
return state.变量名+一些列的操作
}
}
const mutation = { //同步改变状态
add(state){
state.num++
}
//可以放一些改变state的方法
}
const actions ={ //异步改变状态 在视图中调用actions里的方法一般使用
this,$store.dispatch('方法名')
方法名(context){ //这里的context是一个上下文对象 相当于当前的store 也可以直接
使用{commit}
context.commit('调用mutation里的方法',自由参数)
}
}
export default new Vuex.store({
state,
mutations,
actions,
getters,
})
视图应用
如果想把state里的数据映射在视图中,如liste:[]需要以下操作
第一种:可以直接在组件中{{this.$store.state.list}} //注意list是个数组需要循环
第二种 可以在视图中引入mapState方法
import {mapState} from 'Vuex'
然后在视图计算属性中
computed:{
...mapState(['list']) //es6的扩展符
}
然后在组件中直接使用 {{list}} //注意list是个数组需要循环
如果想把mutation里的方法想应用在视图中,如add需要以下操作
第一种:可以直接在组件中事件的函数中“this.$store.commit("add",自由参)”
第二种 可以在视图中引入mapMutation方法
import {mapMutation} from 'Vuex'
然后在视图的方法中
methods:{
...mapMutation(['add'])
}
可以直接在组件中事件的函数中"add",自由参
actions的应用与mutation类似
getters的应用就是把state引进修改后return出去就好
vue导航钩子
vue-router 提供的导航钩子主要用来拦截导航,让它完成跳转或取消。有多种方式可以在路由导航发生时执行钩子:全局的, 单个路由独享的, 或者组件级的。
全局钩子
const router = new VueRouter({ … }) router.beforeEach((to, from, next) => { // do something next(); }); router.afterEach((to, from, next) => { console.log(to.path); });
每个钩子方法接收三个参数:
to: Route : 即将要进入的目标 [路由对象]
from: Route : 当前导航正要离开的路由
next: Function : 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next方法的调用参数。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from路由对应的地址。
next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
确保要调用 next方法,否则钩子就不会被 resolved。
组件内的钩子
你可以在路由组件内直接定义以下路由导航钩子:
beforeRouteEnter beforeRouteUpdate (2.2 新增) beforeRouteLeave
const Foo = { template: `...`, beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this` } }
beforeRouteEnter 钩子 不能 访问 this,因为钩子在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => { // 通过 `vm` 访问组件实例 }) }
你可以 在 beforeRouteLeave 中直接访问 this。这个 leave 钩子通常用来禁止用户在还未保存修改前突然离开。可以通过 next(false) 来取消导航。
history和hash区别
需要使用 history模式,才能使用 scrollBehavior
实现的原理:
hash 模式的原理是 onhashchange 事件,可以在 window 对象上监听这个事件。
history :hashchange 只能改变 # 后面的代码片段,history api (pushState、replaceState、go、back、forward) 则给了前端完全的自由,通过在window对象上监听popState()事件。
前端路由的核心是:改变视图的同时不会向后端发出请求。
pushState()、replaceState() 方法接收三个参数:stateObj、title、url。
// 设置状态
history.pushState({color: "red"}, "red", "red");
// 监听状态
window.onpopstate = function(event){
console.log(event.state);
if(event.state && event.state.color === "red"){
document.body.style.color = "red";
}
}
// 改变状态
history.back();
history.forward();复制代码
应用场景:
通过 pushState 把页面的状态保存在 state 对象中,当页面的 url 再变回到这个 url 时,可以通过 event.state 取到这个 state 对象,从而可以对页面状态进行还原,如页面滚动条的位置、阅读进度、组件的开关等。
调用 history.pushState() 比使用 hash 存在的优势:
pushState 设置的 url 可以是同源下的任意 url ;而 hash 只能修改 # 后面的部分,因此只能设置当前 url 同文档的 url
pushState 设置的新的 url 可以与当前 url 一样,这样也会把记录添加到栈中;hash 设置的新值不能与原来的一样,一样的值不会触发动作将记录添加到栈中
pushState 通过 stateObject 参数可以将任何数据类型添加到记录中;hash 只能添加短字符串
pushState 可以设置额外的 title 属性供后续使用
劣势:
history 在刷新页面时,如果服务器中没有相应的响应或资源,就会出现404。因此,如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面
hash 模式下,仅 # 之前的内容包含在 http 请求中,对后端来说,即使没有对路由做到全面覆盖,也不会报 404
history模式的问题
通过history api,我们丢掉了丑陋的#,但是它也有个问题:不怕前进,不怕后退,就怕刷新,f5,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。
在hash模式下,前端路由修改的是#中的信息,而浏览器请求时不会将 # 后面的数据发送到后台,所以没有问题。但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,则会刷新出来404页面。
hash模式原理是利用了window可以监听onhashchange事件,也就是说url中的哈希值
如果变化了,前端可以做到监听并做一些响应,这样,即使前端没有发起http请求,也能够找到对应页面代码进行按需加载
hash 虽然出现在 URL 中,但不会被包含在 http 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面
hisory模式,有5个api,go,back,forward,pushState,replaceState,其中
pushstate和replace作用是可以将url替换并且不刷新页面,http也并没有去请求服务器该
路径下的资源,
history 利用了 html5 history interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求
但是history模式下刷新会报404,需要在服务器那端,将不存在的路径请求重定向到入口文件
总之,pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应
总结
传统的路由指的是:当用户访问一个url时,对应的服务器会接收这个请求,然后解析url中的路径,从而执行对应的处理逻辑。这样就完成了一次路由分发。
而前端路由是不涉及服务器的,是前端利用hash或者HTML5的history API来实现的,一般用于不同内容的展示和切换。
----------------------------- 补充 -----------------------------
history模式下,build之后本地 index.html 打开是无效的。
hash模式下,build之后本地 index.html 打开正常!
大牛解答:hash模式url里面永远带着#号,我们在开发当中默认使用这个模式。那么什么时候要用history模式呢?如果用户考虑url的规范那么就需要使用history模式,因为history模式没有#号,是个正常的url适合推广宣传。当然其功能也有区别,比如我们在开发app的时候有分享页面,那么这个分享出去的页面就是用vue或是react做的,咱们把这个页面分享到第三方的app里,有的app里面url是不允许带有#号的,所以要将#号去除那么就要使用history模式,但是使用history模式还有一个问题就是,在访问二级页面的时候,做刷新操作,会出现404错误,那么就需要和后端人配合让他配置一下apache或是nginx的url重定向,重定向到你的首页路由上就ok啦
vue路由钩子函数
https://juejin.im/post/5b10b46df265da6e2a08a724
全局前置守卫 router.beforeEach
全局解析守卫 router.beforeResolve
全局后置钩子 router.afterEach
路由独享的守卫 beforeEnter
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不能获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
beforeRouteEnter 钩子 不能 访问 this,因为钩子在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
你可以 在 beforeRouteLeave 中直接访问 this。这个 leave 钩子通常用来禁止用户在还未保存修改前突然离开。可以通过 next(false) 来取消导航。
同时注意必须有这个next(),相当于一个按钮开启一样。
1、beforeRouteEnter(to,from,next)
beforeRouteEnter 函数内部 this 是undefined,这是因为在执行路由钩子函数beforRouteEnter时候,组件还没有被创建出来;先执行beforRouteEnter,再执行组件周期钩子函数beforeCreate。我们可以通过 next 获取组件的实例对象,如:next( (vm)=>{} ),参数vm就是组件的实例化对象。
使用场景:
比如在进入列表页前需要提前对列表页的组件路由进行判断,判断是否需要刷新数据,获取新的列表内容,如果滚动行为是undefined(说明是刷新页面或是第一次进入页面)或者null(说明是点击了导航连接),此时需要刷新数据,获取新的列表内容,否则的话什么都不需要做
router/index.js 的配置如下:
import Vue from 'vue';
import Router from 'vue-router';
// import HelloWorld from '@/views/HelloWorld';Vue.use(Router);
scrollBehavior (to, from, savedPosition) {
// 保存到 meta 中,备用
to.meta.savedPosition = savedPosition;
if (savedPosition) {
return { x: 0, y: 0 };
}
return {};
}
});
list.vue 代码如下
<template> <div class="hello"> <h1>vue</h1> <h2>{{msg}}</h2> <router-link to="/detail">跳转到detail页</router-link> </div></template> <script>export default { name: 'helloworld', data () { return { msg: 'Welcome to Your Vue.js App' }; }, methods: { ajaxRequest() { const obj = { 'aa': 1 }; Promise.all([this.$store.dispatch('testUrl', obj)]).then((res) => { console.log(res); }); } },
beforeRouteEnter(to, from, next) {
next(vm => {
/*
如果 to.meta.savedPosition === undefined 说明是刷新页面或可以叫第一次进入页面 需要刷新数据 如果savedPosition === null, 那么说明是点击了导航链接; 此时需要刷新数据,获取新的列表内容。 否则的话 什么都不做,直接使用 keep-alive中的缓存 */
if (to.meta.savedPosition === undefined) {
vm.ajaxRequest();
}
if (to.meta.savedPosition === null) {
vm.ajaxRequest();
}
})
}
};
</script>
2、beforeRouteUpdate(to,from,next)
About组件是有二级导航的,在切换二级导航的时候,对应的内容是在变化的;但是about组件是复用的,只会生成一次,切换二级导航的时,如何知道导航在更新呢?
一个组件有二级导航的时候,点击二级导航的时候导航路径更新了,会触发路由钩子函数beforeRouteUpdate。
3、beforeRouteLeave(to,from,next)
当在about切换到user时,about页面有些数据还没有加载完成,这时候我们不让它切换到user。
<template>
<div>
我是about
<hr>
<ul class="subnave f-cb">
<!-- a标签中href属性不需要写地址,页面编译完成后自动会在href中加入对应的路劲 -->
<router-link :to='{name:"About"}' exact tag="li">
<a>study</a>
</router-link>
<router-link :to='{name:"Work"}' tag="li">
<a>work</a>
</router-link>
<router-link :to='{name:"Hobby"}' tag="li">
<a>hobby</a>
</router-link>
</ul>
测试数据:{{test}}
<router-view></router-view>
</div>
</template>
<script>
export default {
data(){
return {
test:'改变之前'
}
},
beforeCreate(){//组件生命周期函数
console.log('beforeCreate')
},
//当进入组件之前,执行 beforRouteEnter 路由钩子函数
beforeRouteEnter(to,from,next){
console.log('beforRouteEnter')
console.log(this) // 结果为undefined,因为在执行beforRouteEnter时候,组件还没有被创建出来;先执行beforRouteEnter,再执行beforeCreate
next((vm)=>{ //参数vm就是当前组件的实例。
vm.test = '我被改变了'
})
},
beforeRouteUpdate(to,from,next){
console.log('beforeRouteUpdate')
next()
},
beforeRouteLeave(to,from,next){//离开组件的时候触发
//什么都不写的时候,不会离开(走下一步)
next()
}
}
</script>
路由钩子在实际开发中的应用场景
路由钩子在实际的开发过程中使用较少, 我在实际的项目中只在组件内使用过beforeRouteLeave, 使用场景分别为一下三类情况:
1、清除当前组件中的定时器
当一个组件中有一个定时器时, 在路由进行切换的时候, 可使用beforeRouteLeave将定时器进行清楚, 以免占用内存:
beforeRouteLeave (to, from, next) {
window.clearInterval(this.timer) //清除定时器
next()
}
2、当页面中有未关闭的窗口, 或未保存的内容时, 阻止页面跳转
如果页面内有重要的信息需要用户保存后才能进行跳转, 或者有弹出框的情况. 应该阻止用户跳转
beforeRouteLeave (to, from, next) {
//判断是否弹出框的状态和保存信息与否
if (this.dialogVisibility === true) {
this.dialogVisibility = false //关闭弹出框
next(false) //回到当前页面, 阻止页面跳转
}else if(this.saveMessage === false) {
//弹出警告
next(false) //回到当前页面, 阻止页面跳转
}else {
next() //否则允许跳转
}
}
3、保存相关内容到Vuex中或Session中
当用户需要关闭页面时, 可以将公用的信息保存到session或Vuex中
beforeRouteLeave (to, from, next) {
localStorage.setItem(name, content); //保存到localStorage中
next()
}
go,replace,push区别
route 和 router 的区别是什么?
route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
router是“路由实例对象”,包括了路由的跳转方法(push、replace),钩子函数等。
vue指令
v-text
解释:更新元素的 textContent
v-html
解释:更新元素的 innerHTML
v-bind
作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
语法:v-bind:title=“msg”
简写::title=“msg”
v-on
作用:绑定事件
语法:v-on:click=“say” or v-on:click=“say(‘参数’, $event)”
简写:@click=“say”
说明:绑定的事件从methods中获取
事件修饰符
.stop 阻止冒泡,调用 event.stopPropagation()
.prevent 阻止默认事件,调用 event.preventDefault()
.capture 添加事件侦听器时使用事件捕获模式
.self 只当事件在该元素本身(比如不是子元素)触发时触发回调
.once 事件只触发一次
v-model
作用:在表单元素上创建双向数据绑定
说明:监听用户的输入事件以更新数据
v-for
作用:基于源数据多次渲染元素或模板块
v-for循环key属性
vue中的v-for循环最好加上key属性,否则在高版本(2.2.0+)的vue中控制台会报错。
key属性需要唯一,理想的 key 值是每项都有唯一 id,全局不需唯一,但在一个循环中需要唯一
为什么使用key?
当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容
key属性
推荐:使用 v-for 的时候提供 key 属性,以获得性能提升。
说明:使用 key,VUE会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
样式处理 -class和style
说明:这两个都是HTML元素的属性,使用v-bind,只需要通过表达式计算出字符串结果即可
表达式的类型:字符串、数组、对象
语法:
===>
为什么避免 v-if 和 v-for 用在一起
当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,通过v-if 移动到容器元素,不会再重复遍历列表中的每个值。取而代之的是,我们只检查它一次,且不会在 v-if 为否的时候运算 v-for。
v-if 和 v-show
条件渲染
v-if:根据表达式的值的真假条件,销毁或重建元素
v-show:根据表达式之真假值,切换元素的 display CSS 属性
不同点:
1 . v-if 当值为 true时,显示div ,
当值为false时,改元素消失,代码也会消失,
相当于将代码删除了,当在为true时,页面会重新渲染div;
支持加在<template>标签上
而v-show 控制的隐藏出现,
只是将css属性设为了display:none 或block;
不支持加在标签上
2.v-if 后还有 v-else 和 v-else-if 条件渲染,
这里需要注意的是v-else 必须紧跟 v-if 或v-else-if
3.v-if是真真正正的条件渲染;
然而他是惰性的,
在初始值是false的时候,他就什么都不做,
在为真的时候才会开始局部变异
相比之下v-show则是更简单一下,仅仅是css上的切换
所以,v-if有更高的切换消耗,
而v-show有更高的初始渲染消耗;
因此,如果是频繁切换,就用v-show;
在条件很难改变,比如某个模块在用户a出显示,就用v-if
提升用户体验:v-cloak
这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
防止刷新页面,网速慢的情况下出现{{ message }}等数据格式
{{ message }}
提升性能:v-pre
说明:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
{{ this will not be compiled }}
提升性能:v-once
说明:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
This will never change: {{msg}}
vue框架的理解
vue是(渐进式表现:)声明式渲染—组件系统—客户端路由—大数据状态管理—构建工具的一款渐进式框架
vue的两个核心点
(1)响应式数据绑定
当数据发生变化的时候,视图自动更新,即双向数据同步,原理利用了ES6中的 Object.definedProperty 中的setter/getter 代理数据,监控对数据的操作。
(2)组合的视图组件
即页面最终映射为一个组件树,采用树形数据结构进行设计,方便维护,重用
vue路由params和query区别
传参可以使用params和query两种方式。
使用params传参只能用name来引入路由,即push里面只能是name:’xxxx’,不能是path:’/xxx’,因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined!!!。
使用query传参使用path来引入路由。
params是路由的一部分,必须要在路由后面添加参数名。query是拼接在url后面的参数,没有也没关系。
二者还有点区别,直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示。
https://blog.csdn.net/mf_717714/article/details/81945218
vue-diff算法-虚拟dom
为什么使用 diff 算法?
1、页面结构庞大时,DOM 操作代价太高,可维护性差,因此要减少 DOM 操作;
2、虚拟 DOM 很轻量,对虚拟 DOM 操作快;
3、diff 算法 是找出本次 DOM 需要更新的节点进行更新,
其余不更新,对 DOM 进行原地复用,减少 DOM 创建性能耗费,
可以减少浏览器页面的重绘。
diff算法就是虚拟dom
1.虚拟DOM
virtual DOM和真实DOM的区别?
虚拟dom是将真实dom数据抽取出来,以对象的形式模拟树形结构
比如:
真实dom是这样的
<div>
<p>123</p>
</div>
对应的虚拟dom:
var Vnode = {
tag:'div'
children:[
{
tag:'p',text:'123'
}
]
}
渲染真实dom的开销很大,比如有时候我们修改了某个数据,如果直接渲染到真实dom上,会
引起整个dom数的重绘和重排,有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom呢,diff算法可以实现
我们先根据真实dom生成一个虚拟dom,当虚拟dom某个节点的数据改变后会生成一个新的vnode,然后vnode和oldvnode对比,发现有不一样的地方就直接修改在真实的dom上,然后使oldvode的值为vnode
diff的过程就是调用名为patch的函数,比较新旧节点,一遍比较一边给真实的dom打补丁
二、diff算法
传统的diff算法
传统的Diff算法通过循环递归对节点进行比较,然后判断每个节点的状态以及要做的操作(add,remove,change),最后 根据Virtual DOM进行DOM的渲染。
Vue的Diff算法与上面的思路大体相似,只比较同级的节点,若找不到与新节点类型相同的节点,则插入一个新节点,若有相同类型的节点则进行节点属性的更新,最后删除新节点列表中不包含的旧节点。具体的实现在vue源码的src/core/vdom/patch.js中的updateChildren方法中,由于代码较长,下面简单说一下整个的比较流程
三、vue中diff实现
vnode分类
EmptyVNode: 没有内容的注释节点
TextVNode:文本节点
ElementVNode: 普通元素节点
ComponentVNode: 组件节点
CloneVNode: 克隆节点,可以是以上任意类型的节点,唯一的区别在于isCloned属性为true
** patch函数接收6个参数:**
oldVnode: 旧的虚拟节点或旧的真实dom节点
vnode: 新的虚拟节点
hydrating: 是否要跟真实dom混合
removeOnly: 特殊的flag,用于
parentElm: 父节点
refElm: 新节点将插入到refElm之前
代码逻辑
如果vnode不存在,但是oldVnode存在,说明是需要销毁旧节点,则调用invokeDestroyHook(oldVnode)来销毁oldVnode。
如果vnode存在,但是oldVnode不存在,说明是需要创建新节点,则调用createElm来创建新节点。
当vnode和oldVnode都存在时
oldVnode和vnode是同一个节点,就调用patchVnode来进行patch
当vnode和oldVnode不是同一个节点时, 如果oldVnode是元素节点,需要用hydrate函数将虚拟dom和真是dom进行映射
如果oldVnode是真实节点时或vnode和oldVnode不是同一节点时,vnode替换oldVnode。如果组件根节点被替换,遍历更新父节点element。然后移除旧节点。
createElm 创建真实的 DOM 对象
vnode 根据vnode的数据结构创建真实的dom节点,如果vnode有children则会遍历这些子节点,递归调用createElm方法
insertedVnodeQueue记录子节点创建顺序的队列,每创建一个dom元素就会往队列中插入当前的vnode,当整个vnode对象全部转换成为真实的dom 树时,会依次调用这个队列中vnode hook的insert方法
新节点将插入到refElm之前
** 代码逻辑 **
从分析patch方法中,我们知道当vnode和oldVnode都存在,并且vnode和oldVnode是同一节点时,才会调用patchVnode进行patch。
下面来看来patchVnode执行原理
如果oldVnode和vnode完全一致,则可认为没有变化,return;
如果oldVnode的isAsyncPlaceholder属性为true时,跳过检查异步组件,return;
如果oldVnode跟vnode都是静态节点(实例不会发生变化),且具有相同的key,并且当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上,也不用再有其他操作,return;
否则,如果vnode不是文本节点或注释节点
如果vnode和oldVnode都有子节点并且两者的子节点不一致时,就调用updateChildren更新子节点
如果只有vnode有子节点,则调用addVnodes创建子节点
如果只有oldVnode有子节点,则调用removeVnodes把这些子节点都删除
如果vnode文本为undefined,则清空vnode.elm文本;
如果vnode是文本节点但是vnode.text != oldVnode.text时只需要更新vnode.elm的文本内容就可以。
key的作用
不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key可以更高效的利用dom。
举个例子:
我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的
vue中的key属性
MVVM模式和MVC模式
什么是MVVM呢?
答:M:后端实体,V:前端HTML,VM:前端实体。
后端M用来从数据库装载数据给前端VM,前端VM用来替换V中插值表达式来填充数据,同时V的变化可以动态更新到VM,V再加上指令,就可以基本不用操作DOM,靠前端MVVM框架通过指令去渲染页面了。
MVVM 是 Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式, 其核心是提供对 View 和 ViewModel 的双向数据绑定,这使得 ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定。
M:Model(数据层,也就是指数据(前端是js))
V:View ( 也就是指DOM层 或用户界面 )
VM : ViewModel (处理数据和界面的中间层,也就是指Vue) Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
mvvm和mvc区别?它和其它框架(jquery)的区别是什么?哪些场景适合?
主要就是mvc中Controller演变成mvvm中的viewModel。mvvm主要解决了mvc中大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
区别:vue数据驱动,通过数据来显示视图层而不是节点操作。
场景:数据操作比较多的场景,更加便捷
VNode是什么?虚拟 DOM是什么?
Vue在 页面上渲染的节点,及其子节点称为“虚拟节点 (Virtual Node)”,简写为“VNode”。“虚拟 DOM”是由 Vue 组件树建立起来的整个 VNode 树的称呼。
vue-loader是什么?使用它的用途有哪些?
解析.vue文件的一个加载器。
用途:js可以写es6、style样式可以scss或less、template可以加jade等
vue中 key 值的作用
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
所以概括来说key的作用主要是为了高效准确的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
使用key来给每个节点做一个唯一标识
Vue 组件中 data 为什么必须是函数
在 new Vue() 中,data 是可以作为一个对象进行操作的,然而在 component 中,data 只能以函数的形式存在,不能直接将对象赋值给它。
当data选项是一个函数的时候,每个实例可以维护一份被返回对象的独立的拷贝,这样各个实例中的data不会相互影响,是独立的
v-for 与 v-if 的优先级
v-for的优先级比v-if高
计算属性的 set get 如何使用
每一个计算属性都包含一个getter 和一个setter ;
绝大多数情况下,我们只会用默认的getter 方法来读取一个计算属性,在业务中很少用到setter,所以在声明一个计算属性时,可以直接使用默认的写法,不必将getter 和setter 都声明。
但在你需要时,也可以提供一个setter 函数, 当手动修改计算属性的值就像修改一个普通数据那样时,就会触发setter 函数,
计算属性和watch的区别
在我们运用vue的时候一定少不了用计算属性computed和watch
computed计算属性是用来声明式的描述一个值依赖了其它的值。当你在模板里把数据绑定到一个计算属性上时,Vue 会在其依赖的任何值导致该计算属性改变时更新 DOM。这个功能非常强大,它可以让你的代码更加声明式、数据驱动并且易于维护。
watch监听的是你定义的变量,当你定义的变量的值发生变化时,调用对应的方法。
就好在div写一个表达式name,data里写入num和lastname,firstname,在watch里当num的值发生变化时,就会调用num的方法,方法里面的形参对应的是num的新值和旧值,
而计算属性computed,计算的是Name依赖的值,它不能计算在data中已经定义过的变量
Vue中如何在组件内部实现一个双向数据绑定?
假设有一个输入框组件,用户输入时,同步父组件页面中的数据
具体思路:父组件通过 props 传值给子组件,子组件通过 $emit 来通知父组件修改相应的props值,具体实现如下
解决Vue同路由跳转后数据不更新
场景:需要根据不同的 $route.query 请求不同的数据
问题:由于使用 VueRouter 跳转时组件实例会被复用,跳转后无法拿到最新的 $route.query,并且地址栏的 url 不会变化
解决方法1
此方法一劳永逸,但是页面渲染速度会降低
指定页面
<!-- :key="$route.fullPath" 解决同路由但不同查询参数跳转后数据不更新 -->
<router-view
v-if="$route.name === 'match_id-match'"
:key="$route.fullPath"
/>
<router-view v-else />
所有页面
<!-- :key="$route.fullPath" 解决同路由但不同查询参数跳转后数据不更新 -->
<router-view :key="$route.fullPath" />
解决方法2
beforeRouteUpdate()
使用 VueRouter 的导航守卫钩子 beforeRouteUpdate(),在里面写上路由更新前你需要重新执行的代码
示例:
beforeRouteUpdate(to, from, next) {
this.zoneId = to.query.zone_id || null // 赛区id
this._getMatchZones() // 根据this.zoneId,请求不同数据
next()
},
本人决定使用了解决方法2,因为不想降低页面渲染速度
vue监听路由的变化,跳转到同一个页面时,Url改变但视图未重新加载问题
解决方法:
添加路由监听,路由改变时执行监听方法
methods:{
fetchData(){
console.log('路由发送变化doing...');
}
},
created() {
var self = this;
self.fetchData();
},
watch:{
'$route':'fetchData'
},
vue-router在同一个路由下切换,取不到变化的路由参数
最近用vue写项目的时候碰到一个问题,在同一个页面下跳转,路由地址不变,路由参数有变化,一开始只是在data里取路由的参数,发现根本取不到变化的路由参数。
在网上查找了一番后发现可以这样写:
watch: {
'$route' (to, from) {
//这样就可以获取到变化的参数了,然后执行参数变化后相应的逻辑就行了
console.log(this.$route.query)
}
}
https://www.cnblogs.com/sophie_wang/p/7880261.html
https://www.jb51.net/article/144337.htm
https://www.jb51.net/article/131110.htm