前端基础框架之vue(持续更新中)

2023年1月24日08:59:52

知识总结:

一,vue-cli 3.0 (2.0到3.0的过渡)

  1. 跨域配置 -- 详见1.1
  2. vue-cli2.0与3.0的区别 -- 详见1.2
  3. webpack里的一些问题 --详见1.3
  4. vue项目你自己会进行一些常用的配置? --详见1.4
  5. vue项目性能优化 --详见1.5

二,vue基础知识

  1. 双向数据绑定 (v-model的原理)-- 详见2.1
  2. 虚拟dom --详见2.2-1和2.2-2
  3. template模板渲染语法和原理(vue-loader)--详见2.3
  4. 指令和自定义指令(v-if与v-show)--详见2.4
  5. methods,computed,watch,filters,data(为什么是函数) --详见2.5
  6. class、style(Vue中通过属性绑定为元素的class样式,Vue中通过属性绑定为元素绑定style行内样式)  --详见2.6
  7. 条件和循环渲染(key的作用)  --详见2.7
  8. 组件(属性,父子组件生命周期顺序,组件间通信的方式,自己封装组件举例)--详见2.8
  9. ref  --详见2.9
  10. 插槽 --详见2.10
  11. keep-alive组件 --详见2.11
  12. transition --详见2.12
  13. 渲染函数和jsx --详见2.13
  14. 插件编写 --详见2.14
  15. 混入 --详见2.15
  16. devtools  --详见2.16
  17. vue检测数组或者对象的变化($set()) --详见2.17
  18. nextTick的原理  --详见2.18

三、vue-router   --详见3.1

  • 动态路由
  • 编程式导航
  • 命名路由和命名容器
  • 导航守卫
  • HASH和BROWSER路由(比较单页应用和多页应用)

四、vuex

  • state  --详见2.10方法三
  • getter  --详见2.10方法三
  • mutations  --详见2.10方法三
  • action  --详见2.10方法三
  • module命名空间  --详见4.1
  • map辅助函数  --详见4.1
  • 实现原理  --详见2.10方法三
  • dispatch和commit的区别--详见4.1

具体解答:

1.1跨域问题的解决方案和实现原理。

原因:同源策略(SOP)是Web浏览器强制执行的一种安全策略,用于控制对网站和Web应用程序之间数据的访问。 没有SOP,任何网页都将能够访问其他页面的DOM。
跨域资源共享(CORS)是一种HTTP机制,使用HTTP请求头定义源的权限。 使用CORS标头,可以通知浏览器来自其他来源的资源有权访问页面上的资源。

以前:jsonp/iframe
现在:(本地)webpack本地配置devServer的proxy跨域代理实现
      (线上)服务允许跨域

1.2vue-cli2.0与3.0的区别。

//项目目录结构发生了变化:
vue-cli3.0移除了配置文件目录,config 和 build 文件夹,同时移除了 static 静态文件夹,新增了 public 文件夹
//配置项
在3.0中,vue.config.js中有关于mock的配置
要注意的是:mockjs是用来模拟产生一些虚拟的数据,方便前端在后端接口还没有开发出来时独立开发
即使使用了真实的url,但是mockjs拦截了ajax请求,返回的是设定好的本地数据
//可视化界面
找到项目,vue ui 命令会直接打开可视化界面,里面可以进行配置、依赖等操作

1.3webpack里的一些问题

1.有哪些常见的Loader?他们是解决什么问题的?
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
source-map-loader:加载额外的 Source Map 文件,以方便断点调试
image-loader:加载并且压缩图片文件
babel-loader:把 ES6 转换成 ES5
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
eslint-loader:通过 ESLint 检查 JavaScript 代码
2.有哪些常见的Plugin?他们是解决什么问题的?
define-plugin:定义环境变量
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
3.打开config下index.js文件,找到 productionSourceMap: true ,改为 false 即可。

1.4vue项目里配置

//在vue项目中的rem适配
需要安装两个插件库 lib-flexible和px2rem-loader
yarn add lib-flexible
yarn add px2rem-loader
flexible.js是淘宝官方H5移动适应解决方案。它做了三件事:
1、动态改写标签
2、给<html>元素添加data-dpr属性,并且动态改写data-dpr的值
3、给<html>元素添加font-size属性,并且动态改写font-size的值
我个人理解就是做了一个动态的媒体查询,实时设置根结点的font-size
px2rem的使用是需要我们简单的配置一下的。px2rem是一个loader,vue中所有的loader都是在一个utils.js文件中配置生成的,所以我们只需要找到cssLoader这个对象,在它下面再加一个px2remLoader,就可以让这个loader在编译中将我们的px转成rem了。

1.5 vue项目性能优化

Vue 代码层面的优化;

一、代码层面的优化
1.1 v-if 和 v-show区分使用场景
1.2 computed 和 watch 区分使用场景
computed 是计算属性,依赖其他属性值,并且computed的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed的值时才会重新计算computed的值。
1.3 v-for 遍历必须为 item 添加key,且避免同事使用 v-if
1.4、长列表性能优化
我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间
可以通过 Object.freeze 方法来冻结一个对象
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
1.5、事件的销毁
Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。如果在 js 内使用addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露,如:
created() {
	addEventListener('click',this.click,false);
},
beforeDestroy() {
	removeEventListener('click',this.click,false);
}
1.6、图片资源懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。我们在项目中使用 Vue 的vue-lazyload插件:
1.7 路由懒加载
Vue是单页面应用,可能会有很多的路由引入,这样使用webpack打包后的文件很大,当进入首页时,加载的资源过多, 页面会出现白屏的情况, 不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。会大大提高首页白屏显示的速度,但是可能其他的页面的速度就会降下来。 路由懒加载
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
	routes:[
		{ path:'/foo', component: Foo }
	]
})
1.8 第三方插件的按需引入
我们在项目中经常会需要引入第三方插件,如果我们直接引入整个插件,会导致项目的体积太大,我们可以借助 babel-plugin-component ,然后可以只引入需要的组件,以达到减小项目体积的目的。以下为项目中引入 element-ui 组件库为例: 
(1) 首先,安装 babel-plugin-component:
(2) 然后将.babelrc修改为:
{
	"presets":[["es2015",{"modules":false}]],
	"plugins":[
		[
			"component",
			{
				"libraryName":"element-ui",
				"styleLibraryName":"theme-chalk"
			}
		]
	]
}
1.9 优化无限列表性能
如果你的应用存在非常长或者无限滚动的列表,那么需要采用 窗口化 的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。你可以参考以下开源项目 vue-virtual-scroll-list 和 vue-virtual-scroller 来优化这种无限列表的场景的。

Webpack 配置层面的优化;

2.1、Webpack 对图片进行压缩
在 vue 项目中除了可以在 webpack.base.conf.js 中 url-loader 中设置 limit 大小来对图片处理,对小于limit的图片转化为 base64 格式,其余的不做操作。所以对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用image-webpack-loader来压缩图片: 
(1) 首先安装 image-webpack-loader:
(2) 然后,在webpack.base.conf.js中进行配置:
{
	test:/\.(png|jpe?g|gif|svg)(\?.*)?$/,
	use:[
	    {
	    loader: 'url-loader',
	    options: {
	      limit: 10000,
	      name: utils.assetsPath('img/[name].[hash:7].[ext]')
	      }
	    },
	    {
	      loader: 'image-webpack-loader',
	      options: {
	        bypassOnDebug: true,
	      }
	    }
	  ]
}
2.2、减少 ES6 转为 ES5 的冗余代码
npm install babel-plugin-transform-runtime --save-dev
复制代码
(2) 然后,修改 .babelrc配置文件:
"plugins": [
    "transform-runtime"
]
2.3、 提取公共代码
所以我们需要将多个页面的公共代码抽离成单独的文件,来优化以上问题。Webpack内置专门用于提取多个Chunk中公共部分的插件 CommonsChunkPlugin,我们在项目中 CommonsChunkPlugin的配置如下:
// 所有在 package.json 里面依赖的包,都会被打包进 vendor.js这个文件中。
new webpack.optimize.CommonsChunkPlugin({
	name:'vendor',
	minChunks:function(module,count){
		return(
			module.resource && 
			/\.js$/.test(module.resource) &&
			module.resource.indexOf(
				path.join(__dirname,'../node_modules')
			) === 0
		);
	}
}),
//抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
	name:'manifest',
	chunks:['vendor']
})
2.4模板预编译
2.5构建结构输出分析
Webpack输出的代码可读性非常差而且文件非常大,让我们非常头疼。为了更简单、直观地分析输出结构,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展现出来,让我们快速了解问题所在。接下来讲解我们在Vue项目中用到的分析工具:webpack-bundle-analyzer.
我们在项目中 webpack.prod.conf.js进行配置:
if(config.build.bundleAnalyzerReport){
	var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}
执行 npm run build \--report后生成分析报告如下:
三、基础的Web技术优化
3.1、开启gzip压缩
3.2、浏览器缓存

基础的Web技术层面的优化;

2.1vue2.0/3.0双向数据绑定的实现原理,并实现。

v-model:v-model就是vue的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的data属性,也会在更新data绑定属性时候,更新页面上输入控件的值。

实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:

在vue的2.x版本中用object.defineProperty来实现双向数据绑定原理,而在vue3.0版本中用Proxy这个对象来代替object.defineProperty实现数据的双向绑定。

  数据劫持:当访问或者设置对象的属性的时候,触发相应的函数,并且返回设置属性的值。

  1.VUE2.0通过Object.defineProperty来劫持对象属性的getter和setter操作,当数据发生变化时发出通知

  2.VUE3.0通过Proxy来劫持数据,当数据发生变化时发出通知

  3.数据劫持的优势:

  ①不需要克隆;

  ②不需要对每一个属性进行监听,减少额外的diff操作,减少性能消耗

vue2.0双向数据绑定实现:

let obj = {
		name: ''
	};
	let newObj = JSON.parse(JSON.stringify(obj));
	Object.defineProperty(obj, 'name', {
		get: function() {
			return newObj.name;
		},
		set: function(val) {
			newObj.name = val;
			text.innerHTML = val;
			inputA.value = val;
		}
	});
	inputA.oninput=function(){
		obj.name = this.value;
	};
	setTimeout(function(){
		obj.name = '啦啦啦'
	},1000)

vue3.0双向数据绑定实现:

let obj = {};
	obj = new Proxy(obj, {
		get: function(target, prop) {
			return target[prop];
		},
		set: function(target, prop, value) {
			target[prop] = value;
			text.innerHTML = value;
			inputA.value = value;
		}
	})
	inputA.oninput=function(){
		obj.name = this.value;
	};
	setTimeout(function(){
		obj.name = '啦啦啦'
	},1000)

2.2-1mvc与mvvm的原理与区别。

M(Model)、 V(View)、 VM(ViewModel)

vue和react:mvvm;视图改变影响数据,数据影响视图。

angular:mvc;少个视图改变影响数据,即类似onchange,多了了contorlar。

前言:Vue.js 2.0引入Virtual DOM(虚拟dom),比Vue.js 1.0的初始渲染速度提升了2-4倍,并大大降低了内存消耗。

数据影响视图原理:

  • Vue.js通过编译将template 模板转换成渲染函数(render ) ,执行渲染函数就可以得到一个虚拟节点树。
  • 在对 Model 进行操作的时候,会触发对应 Dep 中的 Watcher 对象。Watcher 对象会调用对应的 update 来修改视图。这个过程主要是将新旧虚拟节点进行差异对比,然后根据对比结果进行DOM操作来更新视图。
  • 简单点讲,在Vue的底层实现上,Vue将模板编译成虚拟DOM渲染函数。结合Vue自带的响应系统,在状态改变时,Vue能够智能地计算出重新渲染组件的最小代价并应到DOM操作上。

2.2-2虚拟DOM即Virtual DOM的原理。

为了实现高效的DOM操作,一套高效的虚拟DOM diff算法显得很有必要。我们通过patch 的核心----diff 算法,找出本次DOM需要更新的节点来更新,其他的不更新。比如修改某个model 100次,从1加到100,那么有了Virtual DOM的缓存之后,只会把最后一次修改patch到view上。那diff 算法的实现过程是怎样的?

Vue的diff算法是基于snabbdom改造过来的,仅在同级的vnode间做diff,递归地进行同级vnode的diff,最终实现整个DOM树的更新。因为跨层级的操作是非常少的,忽略不计,这样时间复杂度就从O(n3)变成O(n)。

diff 算法包括几个步骤:

  • 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
  • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  • 把所记录的差异应用到所构建的真正的DOM树上,视图就更新了
  • diff 算法的实现过程

    两个核心函数实现流程:

  • patch(container,vnode) :初次渲染的时候,将VDOM渲染成真正的DOM然后插入到容器里面。
  • patch(vnode,newVnode):再次渲染的时候,将新的vnode和旧的vnode相对比,然后之间差异应用到所构建的真正的DOM树上。

2.3 Template模板渲染语法和原理(vue-loader)

//vue-loader
既然vue-loader的核心首先是将以为.vue为结尾的组件进行分析、提取和转换,那么首先我们要找到以下几个loader
selector–将.vue文件解析拆分成一个parts对象,其中分别包含style、script、template
style-compiler–解析style部分
template-compiler 解析template部分
babel-loader-- 解析script部分,并转换为浏览器能识别的普通js
//一个template模板,从vue实例化到展示到页面上,会经过怎样的过程呢,我们来分析一下。
vue模板语法解释器vue-template-compiler将模板(template)编译成虚拟dom渲染函数(render);
主要有三个步骤:
1,parser:模板解释器,将template模板转成AST(abstract syntac tree)抽象语法树。
2,optimizer:AST优化器,处理静态不参与重复渲染的模板片段。
3,codegen:代码生成器,基于AST生成JavaScript虚拟dom渲染函数,延迟到运行时执行,生成HTML。
//vue渲染函数(render)
vue渲染函数从vue实例化到展示到页面上过程要比使用vue模板简单的多,因为渲染函数执行的结果是直接生成了虚拟dom对象。
使用vue渲染函数不需要经过模板解析,AST优化,代码生成三个步骤,所以使用vue的渲染函数要比使用vue模板语法性能高的多。

2.4指令v-model等和自定义指令(v-if与v-show)

相同点:v-if与v-show都可以动态控制dom元素显示隐藏
不同点:
1.v-if显示隐藏是将dom元素整个添加或删除,而v-show隐藏则是为该元素添加css--display:none,dom元素还在。
2.因为v-if操作dom,所以对性能消耗较大,不能频繁切换;v-show只是操作样式性能消耗较小,可以频繁切换;
3.编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载:销毁和重建内部的事件监听和子组件); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
//自定义指令
使用 Vue.directive(id, [definition]) 定义全局的指令来进行自定义指令
参数1 : 指令的名称,注意,在定义的时候,指令的名称前面,不需要加 v-前缀,但是: 在调用的时候,必须在置顶的名称前加上 v-前缀来进行调用
参数2: 是一个对象, 这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作。
Vue.directive("focus", {
// 注意:在每个函数中, 第一个参数永远是el, 表示被绑定了指令的那个元素,这个el参数,是一个原生的JS对象
bind: function(el){ // 每当指令绑定到元素上的时候,会立即执行这个bind函数,只执行一次
},
inserted: function(el){ // inserted 表示元素插入到DOM中的时候,会执行inserted函数【触发一次】
el.focus()
},
updated: function(el) { // 当VNode更新的时候,会执行updated,可能会触发多次   },
})
调用:
<!-- 注意: Vue中所有的指令,在调用的时候,都以 v- 开头 -->
<input type="text" class="form-control" v-model="keywords" v-focus>

2.5 methods,computed,watch,filters,data(为什么是函数)

取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例

2.7v-for与key

用于循环的数组里面的值可以是对象,也可以是普通元素
//当 v-if 与 v-for 一起使用时
v-for 具有比 v-if 更高的优先级。
若for和if放在了同一个标签中 没有先后顺序的要求,但是先执行for
//key 起到的作用
key来给每个节点做一个唯一标识
key的作用主要是为了高效的更新虚拟DOM
解决方案: for循环时把数据跟创建的节点利用给元素绑定唯一key值

 2.8 组件(父子组件生命周期顺序,组件按需加载、异步加载,组件间通信的方式,自己封装组件)

Vue的组件系统
Vue组件的API主要包含三部分:prop、event、slot
props表示组件接收的参数,最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,此外还可以通过type、validator等方式对输入进行验证
slot可以给组件动态插入一些内容或组件,是实现高阶组件的重要途径;当需要多个插槽时,可以使用具名slot
event是子组件向父组件传递消息的重要途径
//自己封装组件
我们公司现在为了统一样式,用的是elementui框架,会自己根据一些需要修改样式,但是,我们自己系统的功能需求ui框架肯定是没有的,那么我们就需要封装了,比如,我们管理端的一个公共搜索组件,不同的页面调用组件都会传不同的type,不同的type初始化不同的数据,展示不同的输入选择框等等。
//vue组件异步加载
.vue文件就是一个组件,所以页面其实也是一个组件;
require: 运行时调用,理论上可以运用在代码的任何地方;import:编译时调用,必须放在文件开头;
1.vue-router配置路由 , 使用vue的异步组件实现按需加载,首页的用import就好了;
{
    path: '/home',
    name: 'Home', 
    component: resolve => require(['@/components/home'],resolve) 
}
2.使用import()加载组件
const test1 = () =>import('@/components/test1.vue') 
//父子组件生命周期顺序
父子组件在加载的时候,执行的先后顺序为父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。mounted->父mounted
//子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
//父组件更新过程
父beforeUpdate->父updated
//销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
//组件间通信的方式
方法一:props/$emit
只能父子传值
父组件用v-bind:users="users"向子组件传值;子组件在props里接收;
子组件用this.$emit("titleChanged","子向父组件传值");方法在父组件用v-on:xx='xxx'定义;
方法二、$emit/$on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
var Event=new Vue();
    Event.$emit(事件名,数据);
    Event.$on(事件名,data => {});
$on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。
方法三、vuex

1.简要介绍Vuex原理
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

//vuex示例与注释
var store = new Vuex.Store({
  state: {
    // 大家可以把 state 想象成 组件中的 data ,专门用来存储数据的
    // 如果在 组件中,想要访问,store 中的数据,只能通过 this.$store.state.*** 来访问
  },
  mutations: {
    // 注意: 如果要操作 store 中的 state 值,只能通过 调用 mutations 提供的方法,才能操作对应的数据,不推荐直接操作 state 中的数据,因为 万一导致了数据的紊乱,不能快速定位到错误的原因,因为,每个组件都可能有操作数据的方法;
    increment(state) {
      state.count++
    },
    // 注意: 如果组件想要调用 mutations 中的方法,只能使用 this.$store.commit('方法名')
    // 这种 调用 mutations 方法的格式,和 this.$emit('父组件中方法名')
    subtract(state, obj) {
      // 注意: mutations 的 函数参数列表中,最多支持两个参数,其中,参数1: 是 state 状态; 参数2: 通过 commit 提交过来的参数;
      console.log(obj)
      state.count -= (obj.c + obj.d)
    }
  },
  getters: {
    // 注意:这里的 getters, 只负责 对外提供数据,不负责 修改数据,如果想要修改 state 中的数据,请 去找 mutations
    optCount: function (state) {
      return '当前最新的count值是:' + state.count
    }
    // 经过咱们回顾对比,发现 getters 中的方法, 和组件中的过滤器比较类似,因为 过滤器和 getters 都没有修改原数据, 都是把原数据做了一层包装,提供给了 调用者;
    // 其次, getters 也和 computed 比较像, 只要 state 中的数据发生变化了,那么,如果 getters 正好也引用了这个数据,那么 就会立即触发 getters 的重新求值;
  }
})
Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
dispatch:操作行为触发方法,是唯一能执行action的方法。
actions:操作行为处理模块,由组件中的$store.dispatch('action 名称', data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
const user = {
  state: {
    token: ''
  },
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token ="test " +token 
    },
  },
  actions: {
    // 登录
    Login({commit}, userInfo) {
      return new Promise((resolve, reject) => {
        login(userInfo.account, userInfo.password).then(aa => {
          if(aa.status==200){
            const tokenSuccess = aa.data.token.tokenValue
            commit('SET_TOKEN', tokenSuccess )
            document.cookie=`cookie地址`;
            token="test"+tokenSuccess ;
            //setToken("test" +token)
            resolve();
          }
          
        }).catch(error => {
          console.log("登录失败")
          reject(error)
        })
      })
    },
  }
}
this.$store.dispatch('Login',arg).then(() => {})
方法四:$attrs/$listeners
$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件;

方法五、provide/inject
Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

方法六、$parent / $children与 ref
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例

、、常见使用场景可以分为三类:
父子通信:
父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
兄弟通信:
Bus;Vuex
跨级通信:
Bus;Vuex;provide / inject API、$attrs/$listeners

2.9ref

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:
 整个用下来就是比较方便取DOM,方便操作DOM

2.10vue的插槽slot

let allSlots = this.$slots.default;会多出n-1个空节点。

2.11keep-alive组件

//vue中的keep-alive用法详解
keep-alive  保持活跃,在vue中我们可以用其来保存组件的状态,对组件进行缓存。
keep-alive  我们常在列表页使用,比如我们在业务上经常会有要求,当查看完某一列表详情页时,返回列表页,需要回到原来的位置,并保持页面的状态。
//新增需求,点击浏览器返回按钮或者详情页的返回按钮,需要返回列表页,并保存之前所有的状态。
用 keep-alive 及 scrollBehavior 完美解决。
第一,在 App.vue文件中,给路由加上 keep-alive

第二,在 路由文件中 :router/index.js,给被要被缓存的页面设置 meta 属性(这里就是列表页),不需要缓存的视图,不用添加
第三,在详情页里面设置 beforeRouteLeave

设置完这些,点击浏览器后退按钮,页面返回列表页,但没有之前的位置,下拉滚动条,发现所有的状态已经被保留了
最后一步, 实现滚动行为的代码:router/index.js

2.12transition 

//进入/离开 & 列表过渡
在 CSS 过渡和动画中自动应用 class
可以配合使用第三方 CSS 动画库,如 Animate.css
在过渡钩子函数中使用 JavaScript 直接操作 DOM
可以配合使用第三方 JavaScript 动画库,如 Velocity.js
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
条件渲染 (使用 v-if)/条件展示 (使用 v-show)/动态组件/组件根节点
<transition name="fade">
    <p v-if="show">hello</p>
</transition>
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
在进入/离开的过渡中,会有 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-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>

2.13渲染函数和jsx

render组件需要的一切都是通过 context 参数传递,它是一个包括如下字段的对象:
props:提供所有 prop 的对象
children: VNode 子节点的数组
slots: 一个函数,返回了包含所有插槽的对象
scopedSlots: 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
parent:对父组件的引用
listeners: 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
injections: 如果使用了 inject 选项,则该对象包含了应当被注入的属性。

2.14插件编写

vue插件的编写方法一般分为4类,如上图所示。主要注册与绑定机制如下:
export default {
    install(Vue, options) {
        Vue.myGlobalMethod = function () {  // 1. 添加全局方法或属性,如:  vue-custom-element
        // 逻辑...
        }
        Vue.directive('my-directive', {  // 2. 添加全局资源:指令/过滤器/过渡等,如 vue-touch
            bind (el, binding, vnode, oldVnode) {
                // 逻辑...
            }
        })
        Vue.mixin({
            created: function () {  // 3. 通过全局 mixin方法添加一些组件选项,如: vuex
                // 逻辑...
            }
        })    
        Vue.prototype.$myMethod = function (options) {  // 4. 添加实例方法,通过把它们添加到 Vue.prototype 上实现
            // 逻辑...
        }
    }
}
//插件使用
在plugins.js中我们仅仅编写了一个插件的空壳子,假如现在需要全局注册该插件,我们可以在入口文件,比如main.js中注册:
import Vue from 'vue'
import MyPlugin from './plugins/plugins.js'
Vue.use(MyPlugin);
//下面我们便来封装一下该组件:
import LoadingComponent from '../components/loading.vue'
let $vm
export default {
    install(Vue, options) {
        if (!$vm) {
            const LoadingPlugin = Vue.extend(LoadingComponent);
            $vm = new LoadingPlugin({
                el: document.createElement('div')
            });
            document.body.appendChild($vm.$el);
        }
        $vm.show = false;
        let loading = {
            show(text) {
                $vm.show = true;
                $vm.text = text;
            },
            hide() {
                $vm.show = false;
            }
        };
        if (!Vue.$loading) {
            Vue.$loading = loading;
        }
        // Vue.prototype.$loading = Vue.$loading;
        Vue.mixin({
            created() {
                this.$loading = Vue.$loading;
            }
        })
    }
}
以上我们新建一个loading.js文件,引入我们的loading.vue组件,然后通过Vue.extend()方法创建了一个构造器LoadingPlugin,其次我们再通过new LoadingPlugin()创建了$vm实例,并挂载到一个div元素上。最后我们需要通过document.body.appendChild($vm.$el)将其插入到DOM节点中。
当我们创建了$vm实例后,我们可以访问该实例的属性和方法,比如通过$vm.show就可以改变loading组件的show值来控制其显示隐藏。
最终我们通过Vue.mixin或者Vue.prototype.$loading来全局添加了$loading事件,其又包含了show和hide两个方法。我们可以直接在页面中使用this.$loading.show()来显示加载,使用this.$loading.hide()来关闭加载。
//插件发布
npm login
cd 目录
npm publish

2.15混入

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
  mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。

2.16devtools

vue调试工具vue-devtools安装及使用
详见

https://www.cnblogs.com/chenhuichao/p/11039427.html

2.17vue检测数组或者对象的变化

1、不能检测到对象属性的添加或删除
var vm = new Vue({
  data:{
      data111:{
          a = 1
      }
  }
})
data111.a = 2;//这个可以引起变化
但data111.b = 2;和vm.b = 2这个不能检测到变化
需要用
Vue.set(object, key, value)/$set(data111, b, 2)/this.data111.$set(key,value)
2.检测数组变化
下面两种情况不能检测到变化:
1、直接通过索引设置元素,如arr[0]=12;
2、直接修改数组的长度,如vm.arr.length
Vue.set( object, key, value )
用法:
this.$set(this.arr,0,12)

2.18nextTick的原理

定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
理解:nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当数据更新了,在dom中渲染后,自动执行该函数.
//什么时候需要用的Vue.nextTick()??
1、Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。
2、当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中;通俗的理解是:更改数据后当你想立即使用js操作新的视图的时候需要使用它
3、在使用某个第三方插件时 ,希望在vue生成的某些dom动态发生变化时重新应用该插件,也会用到该方法,这时候就需要在 $nextTick 的回调函数中执行重新应用插件的方法。
Vue.nextTick(callback) 使用原理:
原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

3.1vue-router

动态路由

注意: 从 /user/foo 到 /user/bar组件会被复用,组件不会卸载再加载不会触发生命周期钩子,可以使用监听器监听 $route 观察路径变化,组件内作出正确的响应,2.2版本提供 beforeRouteUpdate 路由钩子使用
const routes = [
{ name: 'users', path: '/users/:id', component: Users },
{ path: '/books', component: Books }
]
// params使用场景
router.push('/users/123') // 跳转时
router.push({ // 另一种方式跳转
 name: 'users',
 params: {
 id: 123
}
})
// 获取id
route.params.id // 123
// 实际URL地址 => localhost:8080/users/123
// query使用场景
router.push('books?q=123') // 跳转时
router.push({ // 另一种方式跳转
 path: '/books',
 query: {
 q: 123
}
})
// 获取query
route.query.q // 123
// 实际URL地址 => localhost:8080/books?q=123

编程式导航

有三种方法:
1、`this.$router.push`:转到下一个`url`,会把新传入的url添加到浏览器的`history`中。
1)字符串:直接就是路径
this.$router.push("/post")
2)对象:path和name都可以,但是使用`path`时,参数必须添加到`path`中,放到`params`中无效。
this.$router.push({
    name: "myprofile",
    params: {
        userID: "Xsan"
})
2、`this.$router.replace`:跟`push`一样,只不过是直接替换当前页面,不会添加到浏览器的`history`中。
 let currentPath = this.$route.fullPath
                    this.$router.replace({
                        path: "/login",
                        query: {
                            from: currentPath
                        }
                    })
   3、`this.$router.go`:传递的是步数,正数为下一步,负数为上一步。
                // 下一步
          gotoNext() {
                    this.$router.go(1)
                },
          // 上一步
                gotoPrevent() {
                    this.$router.go(-1)
                }

命名路由和命名容器

①命名路由就是在routers配置路由名称的时候给路由定义不同的名字,这样的好处就是可以在使用router-link的to属性跳转路由的时候传一个对象从而实现与router.push一样的效果:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
简单来说就是,给不同的router-view定义不同的名字,通过名字进行对应组件的渲染。

导航守卫

导航守卫分为:全局的、单个路由独享的、组件内的三种。分别来看一下:
//【全局的】:是指路由实例上直接操作的钩子函数,他的特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数,如下的写法。钩子函数按执行顺序包括beforeEach、beforeResolve(2.5+)、afterEach三个(以下的钩子函数都是按执行顺序讲解的):
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
  // ...
})
[beforeEach]:在路由跳转前触发,参数包括to,from,next(参数会单独介绍)三个,这个钩子作用主要是用于登录验证,也就是路由还没跳转提前告知,以免跳转了再通知就为时已晚。
[beforeResolve](2.5+):这个钩子和beforeEach类似,也是路由跳转前触发,参数也是to,from,next三个,和beforeEach区别官方解释为:
区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。
[afterEach]:和beforeEach相反,他是在路由跳转完成后触发,参数包括to,from没有了next(参数会单独介绍),他发生在beforeEach和beforeResolve之后,beforeRouteEnter(组件内守卫,后讲)之前。
//【路由独享的】是指在单个路由配置的时候也可以设置的钩子函数,
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
[beforeEnter]:和beforeEach完全相同,如果都设置则在beforeEach之后紧随执行,参数to、from、next
//【组件内的】:是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。钩子函数按执行顺序包括beforeRouteEnter、beforeRouteUpdate (2.2+)、beforeRouteLeave三个,
beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }

HASH和BROWSER路由(比较单页应用和多页应用)

//hash 模式和 history 模式
Browser包含window对象,Navigator对象,Screen对象,History对象,Location对象等,其中window对象表示浏览器打开的窗口;Navigator对象包含浏览器的相关信息,其常用的属性有navigator.userAgent获取浏览器内核等信息;Screen对象包含客户端显示屏幕的信息,如screen.height或screen.width获取宽高;history对象包含访问过的URL,是window对象的一部分,有三个方法,back(),forward(),go(),调用这三个方法,浏览器会加载对应页面;location对象包含当前URL有关的信息,是window对象的一部分,常用的属性有location.hash返回URL的hash值,location.port返回当前使用的端口号。详细信息可查阅文档
hash和history只是浏览器的两个特性
hash
hash即‘#/xxx’,是浏览器用来做页面定位的,例如a标签的描点功能,使用 window.location.hash 可以读取当前页面的hash值,也可以写入,hashchange事件可以响应hash的的改变,有两个特点: 1、改变hash值,浏览器不会重载页面,但会在历史访问里增加一条纪录 2、刷新重载页面时,hash值不会传给服务器端
history
HTML5 History Interface 中history对象新增了两个方法 pushState() 和 popState()。 这两个方法应用于浏览器的历史记录栈,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会重载页面。
vue-router的 history 模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

4.1 VUEX里的map辅助函数,dispatch()和commit(),

//vueX 里的 map辅助函数
mapState,mapGetters,mapMutations,mapActions
//mapState
import { mapState } from 'vuex';
computed: {
       ...mapState({
          add: state => state.add,
          counts: state => state.counts
       })
},
如上代码,我们使用 mapState工具函数会将store中的state映射到局部计算属性中。
我们在mounted方法内,直接使用 this.xx 即可使用到对应computed中对应的属性了。也就是 我们使用 this.add 就直接映射到 this.$store.state.add 了 。

//VUEX中的dispatch()和commit()
commit: 同步操作
存储this.$store.commit('Mutations里的方法',name)
dispatch: 异步操作
存储this.$store.dispatch('Actions里的方法',name).then(() => {})
例子:actions: {
    // 登录
    Login({commit}, userInfo) {
      return new Promise((resolve, reject) => {
        login(userInfo.account, userInfo.password).then(aa => {
          if(aa.status==200){
            const tokenSuccess = aa.data.token.tokenValue
            commit('SET_TOKEN', tokenSuccess )
            resolve();
          }
        }).catch(error => {
          console.log("登录失败")
          reject(error)
        })
      })
    },
// vuex 之 module
在项目开发过程中,随着项目逐渐增大,数据关联复杂度逐渐加大, 多人协同开发,人员变动等。 我们会遇到vuex数据更新时,执行某个action 导致同名/未预测到的关联数据发生了变化。 
vue 基本思想之一便是数据驱动,vuex 更是专门的数据状态关联库。 导致数据错误结果可想而知......
使用vuex module 命名空间概念则可以很好的解决这个问题!
const test1 = {
  // 当namespaced=true 时,vuex, 将会自动给各自module 添加访问路径名。 方便区分moduel
  namespaced: true,
  state: {
    name: 'moduleA',
    type: 'module A'
  },
  mutations: {
    updateNameByMutation(state, appendStr){
      state.name = state.name + " append Str: " + appendStr
    }
  },
  actions: {
    udpateNameByAction({commit}, appendStr) {
      commit("updateNameByMutation", appendStr)
    }
  },
  getters: {
    getNameA(state){
      return state.name
    }
  }
}
const test2 = {
  namespaced: true,
  state:{
    name: 'moduleB',
    type: 'module B'
  },
  mutations: {
    updateNameByMutation(state, appendStr){
      state.name = state.name + " append Str: " + appendStr
    }
  },
  actions: {
    // 如果不使用命名空间, 那么view 指向actions 的该方法时,会执行所有与指定action名相同的函数(即:这里module A,B 中该action都会执行)
    udpateNameByAction({commit}, appendStr){
      commit("updateNameByMutation", appendStr)
    }
  },
  getters: {
    getNameB(state){
      return state.name
    }
  }
}
const storeInstall =  new Vuex.Store({
   state: {
     name: 'i am root state name'
   },
   modules:{
    // 这里的路径名: test1, test2, 在view 中 通过 mapActions('test1', [actionName]) 使用并区分需要使用的module
    test1,
    test2
   }
})
export default storeInstall

  • 作者:蜗牛ha
  • 原文链接:https://blog.csdn.net/tan9374/article/details/107707068
    更新时间:2023年1月24日08:59:52 ,共 23800 字。