Vue高级知识概括

  • Vue CLI
  • Vue-router
  • Promise
  • Vuex
  • axios
  • Vue高级

Vue CLI

什么是Vue CLI:

  • 如果你只是简单写几个Vue的Demo程序, 那么你不需要Vue CLI.
  • 如果你在开发大型项目, 那么你需要, 并且必然需要使用Vue CLI
    ①使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。
    ②如果每个项目都要手动完成这些工作,那无疑效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。
  • CLI是什么意思?
    ①CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架.
    ②Vue CLI是一个官方发布 vue.js 项目脚手架
    ③使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.

Vue CLI使用前提:

  • 安装NodeJS
    ①默认情况下自动安装Node和NPM
    ②NPM的全称是Node Package Manager
    ③是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。
  • Vue.js官方脚手架工具就使用了webpack模板
    ①对所有的资源会压缩等优化操作
    ②它在开发过程中提供了一套完整的功能,能够使得我们开发过程中变得高效。

Vue CLI的使用:

  • 安装Vue脚手架:npm install -g @vue/cli
    ①查看vue版本:vue --version
    ②注意:上面安装的是Vue CLI3的版本,如果需要想按照Vue CLI2的方式初始化项目时不可以的。
    ③拉取2.X模板(旧版本):npm install -g @vue/cli-init
  • Vue CLI2初始化项目:vue init webpack my-project
  • Vue CLI3初始化项目:vue create my-project

Vue CLI2详解:

  • 初始化命令使用:
  • 目录简介:
    ①build.js:构建入口
    ②index.js:变量,配置信息
    ③webpack.base.conf.js:公共webpack配置信息
    ④webpack.dev.conf.js:部署配置信息
    ⑤webpack.prod.conf.js:构建配置信息

(运行时+编译器)Runtime-Compiler和(只含有运行时版本)Runtime-only的区别:

  • 如果你需要在客户端编译模板(例如, 向template 选项传入一个字符串,或者需要将模 板中的非DOM的HTML挂载到一个元素),你需要带有编译器的版本,因而需要完整构建版本。
  • 在使用vue-loader或vueify 时,* . vue文件中的模板会在构建时(build time)预编译(pre-compile)为JavaScript.最终生成的bundle中你不再需要编译器(compiler),因此可以直接使用只含有运行时的构建版本(runtime- only)。
//这种情况需要编译器(compiler)
new Vue({
	template: '<div>({ hi }}</div>'
)

//这种情况不需要
new Vue({
	render (h) {
		return h('div', this.h1)
	}
})
  • 由于只含有运行时构建版本(runtime-only)比完整构建版本(ull-build)轻量大约30%,你应该尽可能使用只含有运行时的构建版本。如果你还是希望使用完整构建版本,则需要在打包器中配置别名。
  • 简单总结:
    ①如果在之后的开发中,你依然使用template,就需要选择Runtime-Compiler。
    ②如果你之后的开发中,使用的是.vue文件夹开发,那么可以选择Runtime-only。

render和template区别:

//Runtime-Compiler
new Vue({
	el :'#app',
	components: { App },
	template: '<App/>'
})

//Runtime-only
new Vue({
	el : '#app',
	render: h => h(App)
})

  • 为什么存在这样的差异呢?
    ①我们需要先理解Vue应用程序是如何运行起来的。
    ②Vue中的模板如何最终渲染成真实DOM。
    ③我们来看下面的一幅图。

npm run build与npm run dev详解:

  • npm run build:
  • npm run dev:

webpack.base.conf.js起别名:

  • ./表示相对路径,具体代表当前目录下的同级目录,遵从的是从后往前找文件
  • @/的意思表示的是相对路径(当然这也是简写啦),因为这个在根目录/build/webpack.base.conf.js文件中@是配置的,比如我的配置文件中@就代表src目录,遵从的是从前往后找,比如’@/components/login’ 就表示的是src/components/login文件
resolve: {
	extensions: ['.js', '.vue', '.json'],
	alias: {
		'@':resolve('src'),
		'pages': resolve('src/pages'),
		'common': resolve('src/common'),
		'components':resolve('src/components'),
		'network': resolve('src/network')
	}
},

Vue CLI3详解:

  • vue-cli 3 与 2 版本有很大区别:
    ①vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
    ②vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录
    ③vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
    ④移除了static文件夹,新增了public文件夹,并且index.html移动到public中
  • 初始化使用:
  • 目录结构详解:
  • 配置去哪里了:
    ①UI方面的配置:启动配置服务器:vue ui

    ②一大堆配置文件去哪里了?
  • vue config.js简介:vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。具体可以查阅参考:Vue CLI配置链接。比较常见的配置项有:
    ①baseUrl ( publicPath ):部署应用包时的基本 URL。
    ②outputDir:当运行 vue-cli-service build 时生成的生产环境构建文件的目录。
    ③assetsDir:放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
    ④pages:在 multi-page 模式下构建应用。每个“page”应该有一个对应的 JavaScript 入口文件。
    ⑤lintOnSave:是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码。这个值会在 @vue/cli-plugin-eslint 被安装之后生效。设置为 true 或 ‘warning’ 时,eslint-loader 会将 lint 错误输出为编译警告。默认情况下,警告仅仅会被输出到命令行,且不会使得编译失败。
    ⑥devServer:所有 webpack-dev-server 的选项都支持。注意:有些值像 host、port 和 https 可能会被命令行参数覆写。有些值像 publicPath 和 historyApiFallback 不应该被修改,因为它们需要和开发服务器的 publicPath 同步以保障正常的工作。
    ⑦devServer.proxy:如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置。devServer.proxy 可以是一个指向开发环境 API 服务器的字符串。
    ⑧pluginOptions:这是一个不进行任何 schema 验证的对象,因此它可以用来传递任何第三方插件选项。

Vue-router

什么是路由?

  • 路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动
  • 路由器提供了两种机制: 路由和转送.
    ①路由是决定数据包从来源到目的地的路径.
    ②转送将输入端的数据转移到合适的输出端.
  • 路由中有一个非常重要的概念叫路由表。路由表本质上就是一个映射表, 决定了数据包的指向。

后端路由阶段:

  • 早期的网站开发整个HTML页面是由服务器来渲染的:
    ①服务器直接生产渲染好对应的HTML页面, 返回给客户端进行展示.
  • 但是一个网站, 这么多页面服务器如何处理呢?
    ①一个页面有自己对应的网址, 也就是URL.
    ②URL会发送到服务器, 服务器会通过正则对该URL进行匹配, 并且最后交给一个Controller进行处理.
    ③Controller进行各种处理, 最终生成HTML或者数据, 返回给前端.
    ④这就完成了一个IO操作.
  • 上面的这种操作, 就是后端路由。
    ①当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户顿.
    ②这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化.
  • 后端路由的缺点:
    ①一种情况是整个页面的模块由后端人员来编写和维护的.
    ②另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码.
    ③而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情.

前端路由阶段:

  • 前后端分离阶段:
    ①随着Ajax的出现, 有了前后端分离的开发模式.
    ②后端只提供API来返回数据, 前端通过Ajax获取数据, 并且可以通过JavaScript将数据渲染到页面中.
    ③这样做最大的优点就是前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上.
    ④并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可.
    ⑤目前很多的网站依然采用这种模式开发.
  • 单页面富应用阶段:
    ①其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.
    ②也就是前端来维护一套路由规则.
  • 前端路由的核心是什么呢?
    ①改变URL,但是页面不进行整体的刷新。

URL的hash:

  • URL的hash也就是锚点(#), 本质上是改变window.location的href属性.
  • 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
执行:location.hash = '/foo'
返回:"/foo"

执行:location. href
返回:"http: //192.168.1.101: 8000/examples/urlChange/#/foo"

HTML5的history模式:

  • history接口是HTML5新增的, 它有五种模式改变URL而不刷新页面。
  • history接口底层的数据结构是栈。
  • 函数:
    ①history.pushState():插入锚点,改变url,支持前进后退。
    ②history.replaceState():插入锚点,改变url,不支持前进后退。
    ③history.go()
  • 补充说明:
    ①上面只演示了三个方法
    ②因为 history.back() 等价于 history.go(-1)
    ③history.forward() 则等价于 history.go(1)
    ④这三个接口等同于浏览器界面的前进后退
  • 与URL的hash相比,没有了#符号

vue-router认识:

  • 目前前端流行的三大框架, 都有自己的路由实现:
    ①Angular的ngRouter
    ②React的ReactRouter
    ③Vue的vue-router
  • 当然, 我们的重点是vue-router,vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。
  • vue-router是基于路由和组件的:
    ①路由用于设定访问路径, 将路径和组件映射起来.
    ②在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.

安装和使用vue-router:

  • 安装vue-router:npm install vue-router --save
  • 在模块化工程中使用它(因为是一个插件, 所以可以通过Vue.use()来安装路由功能)
    ①导入路由对象,并且调用 Vue.use(VueRouter)
    ②创建路由实例,并且传入路由映射配置
    ③在Vue实例中挂载创建的路由实例
  • 使用vue-router的步骤:
    ①创建路由组件
    ②配置路由映射: 组件和路径映射关系
    ③使用路由: 通过<router-link>和<router-view>
  • 路由标签详解:
    ①<router-link>: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个<a>标签.
    ②<router-view>: 该标签会根据当前的路径, 动态渲染出不同的组件。
    ③网页的其他内容, 比如顶部的标题/导航,或者底部的一些版权信息等会和<router-view>处于同一个等级。
    ④在路由切换时, 切换的是<router-view>挂载的组件,其他内容不会发生改变。
----创建router文件夹,然后创建index.js文件,创建router实例,配置组件和路径的映射关系:----
import Vue from 'vue'
import VueRouter from 'vue-router'
// 1.注入插件
Vue.use(VueRouter)
// 2.定义路由
const routes = [
	{
		path: '/home',
		component: Home
	}
	{	
		path: '/about', 
		component: About
	}
]

// 3.创建router实例
const router = new VueRouter({
	routes
})
// 4.导出router实例
export default router


----main.js文件中挂载到Vue实例中:----
import Vue  from 'vue'
import App from './App'
import router from './router'
new Vue({
	el: '#app',
	router,
	render: h => h(App)
})


----创建About和Home路由组件----

----使用路由:----
<template>
	<div id="app">
		<h1>我是网站的标题< /h1>
		<router-link to="/home">首页</router-link>
		<router-link to="/about">关于</router-link>
		<router-view></router-view>
		<h2>我是APP中一些底部版权信息</h2>
	</div>
</template>

----最终效果如下:----
一、当路径是根路径时,router-view没有渲染试图
二、点击首页,路径切换,router-view渲染home组件
伞、点击关于,路径切换,router-view渲染about组件

vue-router细节处理:

  • 路由的默认路径:
    ①默认情况下, 进入网站的首页, 我们希望<router-view>渲染首页的内容,但是我们的实现中, 默认没有显示首页组件, 必须让用户点击才可以。
    ②如何可以让路径默认跳到到首页, 并且<router-view>渲染首页组件呢,非常简单, 我们只需要配置多配置一个映射就可以了。
    ③我们在routes中又配置了一个映射,path配置的是根路径: /,redirect是重定向, 也就是我们将根路径重定向到/home的路径下, 这样就可以得到我们想要的结果了。
const routes =[
	{
		path: '/'
		redirect: '/home'
	}
]
  • HTML5的History模式:
    ①我们前面说过改变路径的方式有两种:URL的hash,HTML5的history。默认情况下, 路径的改变使用的URL的hash,如果希望使用HTML5的history模式, 非常简单, 进行如下配置即可:
const router =	new VueRouter({
	routes,
	mode: 'history'
})
  • router-link补充:
    ①在前面的<router-link>中, 我们只是使用了一个属性: to, 用于指定跳转的路径。
    ②<router-link>还有一些其他属性:
    <1>tag: tag可以指定<router-link>之后渲染成什么组件, 比如上面的代码会被渲染成一个<li>元素, 而不是<a>。
    <2>replace: replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中。
    <3>active-class: 当<router-link>对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置active-class可以修改默认的名称。在进行高亮显示的导航菜单或者底部tabbar时, 会使用到该类。但是通常不会修改类的属性, 会直接使用默认的router-link-active即可。
  • 修改linkActiveClass:
    ①该class具体的名称也可以通过router实例的属性进行修改。
const router = new VueRouter({
	routes,
	mode: 'history',
	linkActiveClass:'active'
})
  • 路由代码跳转:
    ①有时候, 页面的跳转可能需要执行对应的JavaScript代码, 这个时候, 就可以使用第二种跳转方式了。
<template>
	<div id="app">
		<h1>我是网站的标题< /h1>
		<button @click="li nkToHomel">首页</button>
		<button @click="l inkToAbout">关于</button>
		<router-view></router-view>
		<h2>我是APP中一些底部版权信息</h2>
	</div>
<template>
<script>
	export default{
		name:'App',
		methods: {
			linkToHome() {
				this.$router.push('/home')
			}
			linkToAbout() {
				this.$router.push('/about')
			}
		}
</script>

  • 动态路由:
    ①在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:/user/aaaa或/user/bbbb。除了有前面的/user之外,后面还跟上了用户的ID。这种path和Component的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式)。
-----路由配置页面---
{
	path: '/user/:id',
	component: User
}

----使用页面----
<div> 
	<h2>{{$route.params.id}}</h2> 
</div>

----组件页面----
<router-link to="/user/123">用户</router-link>

路由懒加载:

  • 官方给出了解释:当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
  • 官方在说什么呢?首先, 我们知道路由中通常会定义很多不同的页面.这个页面最后被打包在哪里呢? 一般情况下, 是放在一个js文件中.但是, 页面这么多放在一个js文件中, 必然会造成这个页面非常的大.如果我们一次性从服务器请求下来这个页面, 可能需要花费一定的时间, 甚至用户的电脑上还出现了短暂空白的情况.
  • 如何避免这种情况呢? 使用路由懒加载就可以了.路由懒加载做了什么?路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块.只有在这个路由被访问到的时候, 才加载对应的组件。
  • 懒加载的方式:
方式一: 结合Vue的异步组件和Webpack的代码分析.
const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};

方式二: AMD写法
const About = resolve => require(['../components/About.vue'], resolve);

方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
const Home = () => import('../components/Home.vue')
//使用懒加载
const routes =[
	{
		path: '/home', component: () => import(' ../components/Home')
	},
		path: '/about', component: () => import('../components/About')
	},
];

路由嵌套:

  • 嵌套路由是一个很常见的功能。比如在home页面中, 我们希望通过/home/news和/home/message访问一些内容。一个路径映射一个组件, 访问这两个路径也会分别渲染两个组件。路径和组件的关系如下:
/home --------> Home
/home/news --------> News
/home/message --------> Message
/about --------> About
  • 实现嵌套路由有两个步骤:
    ①创建对应的子组件, 并且在路由映射中配置对应的子路由。
    ②在组件内部使用<router-view>标签。
-----一、定义两个组件-----

-----二、配置嵌套路由映射-----
import Vue from 'vue'
import VueRouter from 'vue-router'
const Home = () => import('../components /Home')
const Message = () => import('../components/message')
const News = () => import('../components/news')
const About = ( ) => import('../components/About')
// 1.注入插件
Vue.use (VueRouter)
// 2.定义路由
const routes = [
	{
		path: '/home',
		component: Home,
		children: [
			{
				path: 'message'
				component: Message
			},
			{
				path: 'news',
				component: News
			}
		]
	}
]

-----三、在父组件模板中使用-----
<template>
	<div id="home">
		<h2>我是首页标题</h2>
		<router-link to="/home/message">消息</router-link>
		<router-link to="/home/news">新闻</ router-link>
		<router-view></router-view>
	</div>
</template>
  • 嵌套路由也可以配置默认的路径, 配置方式如下:
{
	path: '',
	redirect:'message'
}

路由传递参数:

  • 传递参数主要有两种类型: params和query
  • params的类型:
    ①配置路由格式: /router/:id
    ②传递的方式: 在path后面跟上对应的值
    ③传递后形成的路径: /router/123, /router/abc
  • query的类型:
    ①配置路由格式: /router, 也就是普通配置
    ②传递的方式: 对象中使用query的key作为传递方式
    ③传递后形成的路径: /router?id=123, /router?id=abc
  • 如何使用它们呢? 也有两种方式: 的方式和JavaScript代码方式
----传递参数方式一: <router-link>:----
<router-link 
	:to="{
		path: '/profile/' + 123,
		query: { name:'why',age: 18}
	}"
>档案</router-link>

----传递参数方式二: JavaScript代码:----
export default {
	name:'App',
	methods: {
		toProfile() {
			this.$router.push({
				path: '/profile/' + 123,
				query: {name: 'why',age: 18}
			})
		}
	}
}
  • 获取参数:
    ①获取参数通过$route对象获取的,在使用了vue-router 的应用中,路由对象会被注入每个组件中,赋值为 this.$route,并且当路由切换时,路由对象会被更新。
    ②通过$route获取传递的信息如下:
<p>params: {{ $route.params}}</p>
<p>query: {{$route.query}}</p>
  • $route和$router是有区别的:
    ①$router为VueRouter实例,想要导航到不同URL,则使用$router.push方法
    ②$route为当前router跳转对象里面可以获取name、path、query、params等

路由导航守卫:

  • 我们来考虑一个需求: 在一个SPA应用中, 如何改变网页的标题呢?
    ①网页标题是通过<title>来显示的, 但是SPA只有一个固定的HTML, 切换不同的页面时, 标题并不会改变.
    ②但是我们可以通过JavaScript来修改<title>的内容.window.document.title = ‘新的标题’.
    ③那么在Vue项目中, 在哪里修改? 什么时候修改比较合适呢?
  • 普通的修改方式:
    ①我们比较容易想到的修改标题的位置是每一个路由对应的组件.vue文件中.
    ②通过mounted声明周期函数, 执行对应的代码进行修改即可.
    ③但是当页面比较多时, 这种方式不容易维护(因为需要在多个页面执行类似的代码).
  • 有没有更好的办法呢? 使用导航守卫即可,什么是导航守卫?
    ①vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
    ②vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
  • 详解:
    ①我们可以在钩子当中定义一些标题, 可以利用meta来定义。
    ②导航钩子的三个参数解析:
    <1>to: 即将要进入的目标的路由对象.
    <2>from: 当前导航即将要离开的路由对象.
    <3>next: 调用该方法后, 才能进入下一个钩子
// 定义路由
const routes =[
	{
		path:"/home',
		component: Home,
		children: [...],
		meta:{
			title:'首页'
		}
	},
	{
		path: '/about',
		component: About,
		meta: {
			title:'关于'
		}
	},
	{
		path: '/profile/:id',
		component: Profile ,
		meta: {
			title:'档案'
		}
	}
]

//配置路由
const router = new VueRouter ({
	routes,
	mode: 'history',
	linkActiveClass: 'active',
})
router.beforeEach( (to, from, next) => {
	window.document.title = to.meta.title
	next()
})
  • 补充:
    ①如果是后置钩子, 也就是afterEach, 不需要主动调用next()函数.
    ②上面我们使用的导航守卫, 被称之为全局守卫.
    <1>路由独享的守卫.
    <2>组件内的守卫.

路由keep-alive:

  • keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。它们有两个非常重要的属性:
    ①include - 字符串或正则表达,只有匹配的组件会被缓存
    ②exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
  • router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
<keep-alive>
	<router-view>
	<!--所有路径匹配到的视图组件都会被缓存! - - >
	</router-view>
</keep-alive>
  • 通过create声明周期函数来验证

Promise

什么是Promise呢?

  • Promise到底是做什么的呢?
    ①Promise是异步编程的一种解决方案。
  • 那什么时候我们会来处理异步事件呢?
    ①一种很常见的场景应该就是网络请求了。
    ②我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。
    ③所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。
    ④如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。
  • 但是当网络请求非常复杂时就会出现回调地狱。
  • 我们更加期望的是一种更加优雅的方式来进行这种异步操作。Promise可以以一种非常优雅的方式来解决这个问题。

Promise基本使用:

  • new Promise很明显是创建一个Promise对象。小括号中((resolve, reject) => {})也很明显就是一个函数,而且我们这里用的是之前刚刚学习过的箭头函数。但是resolve, reject它们是什么呢?
  • 我们先知道一个事实:在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)
    resolve和reject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个。
  • 成功还是失败?
    ①如果是成功的,那么通常我们会调用resolve(messsage),这个时候,我们后续的then会被回调。
    ②如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调。
//普通方式
setTimeout( function () {
	let data = 'Hello World'
	console. log (content) ;
}, 1000)

//Promise方式
new Promise((resolve, reject) => {
	setTimeout( function () {
		resolve( 'Hello World')
		reject( 'Error Data')
	}, 1000)
}).then(data => {
	console. log(data);
}).catch(error => { 
	console. log(error);
})

Promise三种状态:

  • 当我们开发中有异步操作时, 就可以给异步操作包装一个Promise 异步操作之后会有三种状态
    ①pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
    ②fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
    ③reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()

Promise链式调用:

  • 我们在看Promise的流程图时,发现无论是then还是catch都可以返回一个Promise对象。所以,我们的代码其实是可以进行链式调用的:
    ①这里我们直接通过Promise包装了一下新的数据,将Promise对象返回了
    <1>Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
    <2>Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
//链式调用的代码
new Promise((resolve, reject) => {
	setTimeout(function () {
		resolve('Hello World')
	},1000 )
}). then(data => {
	console. log(data); // => Hello World
	return Promise. resolve(data + '111')
}). then(data => {
	console. log(data); // => Hello World111
	return Promise. resolve(data + '222')
}). then(data => {
	console. log(data); // => Hello World111222
	return Promise.reject(data + 'error')
}). then(data => {
	console. log(data); //这里没有输出,这部分代码不会执行
	return Promise. resolve(data + '333')
}).catch(data => {
	console. log(data); // => Hello World111222error
	return Promise. resolve(data + '444')
}).then(data => {
	console. log(data); // => Hello World111222error444
})
  • 简化版代码:如果我们希望数据直接包装成Promise.resolve,那么在then中可以直接返回数据
    注意下面的代码中,我将return Promise.resovle(data)改成了return data。结果依然是一样的。
//链式调用的简便写法
new Promise((resolve, reject) => {
	setTimeout(function () {
		resolve( 'Hello World')
	},1000)
}). then(data => {
	console. log(data); // => Hello World
	return data + '111 '
}). then(data => {
	console. log(data); // => Hello World111
	return data + '222'
}). then(data => {
	console. log(data); // => Hello World111222
	return Promise. reject(data + 'error')
}). then(data => 
	console. log(data); //这里没有输出,这部分代码不会执行
	return data + ' 333
}).catch(data => {
	console. log(data); // => Hello World111222error
	return data + ' 444
}). then(data => {
console. log(data); // => Hello World111222error444 
})
  • 如果希望返回的是reject方法,可以直接throw "字符串"。

Vuex

Vuex是做什么的?

  • 官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
    ①它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
    ②Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
  • 状态管理到底是什么?
    ①状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
    ②其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
    ③然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
    ④那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
  • 等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
    ①当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
    ②如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
    ③不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。

Vuex详解:

  • 但是,有什么状态时需要我们在多个组件间共享的呢?
    ①如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
    ②比如用户的登录状态、用户名称、头像、地理位置信息等等。
    ③比如商品的收藏、购物车中的物品等等。
    ④这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的(待会儿我们就可以看到代码了,莫着急)。
  • 单界面的状态管理:
    ①State:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)
    ②View:视图层,可以针对State的变化,显示不同的信息。(这个好理解吧?)
    ③Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。
  • 多界面状态管理:
    ①对于某些状态(状态1/状态2/状态3)来说只属于我们某一个试图,但是也有一些状态(状态a/状态b/状态c)属于多个试图共同想要维护的
    <1>状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。
    <2>但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!
    <3>没错,Vuex就是为我们提供这个大管家的工具。
    ②全局单例模式(大管家)
    <1>我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
    <2>之后,你们每个试图,按照我规定好的规定,进行访问和修改等操作。
    <3>这就是Vuex背后的基本思想。

Vuex基本使用:

  • 首先,我们需要在某个地方存放我们的Vuex代码: 这里,我们先创建一个文件夹store,并且在其中创建一个index.js文件,在index.js文件中写入如下代码:
import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

const store = new Vuex.Store({
	state: {
		count: 0
	},
	mutations: {
		increment(state) {
			state.count++
		},
		decrement(state) {
			state.count--
		}
	}
})
export default store
  • 其次,我们让所有的Vue组件都可以使用这个store对象 来到main.js文件,导入store对象,并且放在new Vue中这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
import Vue from 'vue'
import App from './App'
import store from './store'

new Vue({
	el: '#app',
	store,
	render: h => h(App) 
})
  • 使用Vuex的count:
<template>
	<div id="app">
		<p>{{count}}</p>
		<button @click="increment">+1</button>
		<button @click="decrement">-1</button>
	</div>
</template>

<script>
export default {
	name:" App',
	components: {
	},
	computed: {
		count: function () {
			return this. Sstore.state.count
		}
	},
	methods: {
		increment: function () {
			this.$store.commit('increment')
		},
		decrement: function () {
			this.$store.commit('decrement')
		}
	}
}
</script>
  • 总结:
    ①小结:
    <1>提取出一个公共的store对象,用于保存在多个组件中共享的状态
    <2>将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
    <3>在其他组件中使用store对象中保存的状态即可
    1、通过this.$store.state.属性的方式来访问状态
    2、通过this.$storemit(‘mutation中方法’)来修改状态
    ②注意事项:
    <1>我们通过提交mutation的方式,而非直接改变store.state.count。
    <2>这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值。

Vuex核心概念:

  • Vuex有几个比较核心的概念:
    ①State
    ②Getters
    ③Mutation
    ④Action
    ⑤Module

State简介:

  • Vuex提出使用单一状态树, 什么是单一状态树呢?英文名称是Single Source of Truth,也可以翻译成单一数据源。
  • 这个和我们在应用开发中比较类似:
    ①如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。所以Vuex也使用了单一状态树来管理应用层级的全部状态。
    ②单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

Getters简介:

  • 有时候,我们需要从store中获取一些state变异后的状态,我们可以在Store中定义getters。
  • getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数。
  • Getters类似计算属性。
getters: {
	//直接获取store数据
	greaterAgesStus: state => {
		return state.students.filter(s => s.age >= 20)
	},
	//获取getters数据
	greaterAgesCount: (state, getters) => {
		return getters.greaterAgesstus.length
	},
	stuByID: state => {
		return id => {
			return state.students.find(s => s.id === id)
		}
	}
}

Mutation简介:

  • Vuex的store状态的更新唯一方式:提交Mutation
  • Mutation主要包括两部分:
    ①字符串的事件类型(type)
    ②一个回调函数(handler),该回调函数的第一个参数就是state。
//multation的定义方式:
mutations: {
	increment(state) {
		state.count++
	}
}
//通过mutation更新:
increment: function () {
	this.$storemit('increment')
}
  • 在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数:参数被称为是mutation的载荷(Payload)。
------单参数:-------
//multation的定义方式:
decrement(state,n) {
	state.count -= n
}
//通过mutation更新:
decrement: function () {
	this.Sstoremit('decrement',2)
}

------对象:-------
//multation的定义方式:
changeCount (state,payload) {
	state.count = payload.count
}
//通过mutation更新:
changeCount: function () {
	this.$storemit('changeCount', {count: 0})
}
  • Mutation提交风格:
    ①上面的通过commit进行提交是一种普通的方式。
    ②Vue还提供了另外一种风格, 它是一个包含type属性的对象:
//通过mutation更新:
this.$storemit ({
	type:'changeCount'
	count: 100
})

//Mutation中的处理方式是将整个commit的对象作为payload使用, 所以代码没有改变, 依然如下:
changeCount (state,pay1oad) {
	state.count = payload.count
}
  • Mutation响应规则:
    ①Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
    ②这就要求我们必须遵守一些Vuex对应的规则:
    <1>提前在store中初始化好所需的属性.
    <2>当给state中的对象添加新属性时, 使用下面的方式:
    方式一: 使用Vue.set(obj, ‘newProp’, 123)
    方式二: 用心对象给旧对象重新赋值
  • Mutation常量类型:
    ①在mutation中, 我们定义了很多事件类型(也就是其中的方法名称),当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多,方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况。
    ②在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型,我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然。
    ③我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量,定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称。
文件名:mutation-typesjs
代码:export const UPDATE_ INFO = 'UPDATE_INFO'
  • Mutation同步函数:
    ①通常情况下, Vuex要求我们Mutation中的方法必须是同步方法,主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照。
    ②但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成。所以通常情况下, 不要再mutation中进行异步的操作。

Action简介:

  • 我们强调, 不要再Mutation中进行异步操作. 但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求,必然是异步的. 这个时候怎么处理呢?
  • Action类似于Mutation, 但是是用来代替Mutation进行异步操作的.
  • context是什么?
    context是和store对象具有相同方法和属性的对象.
    也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说。
  • 这样的代码是否多此一举呢?
    ①我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
    ②事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了.
  • Action的分发:
    ①在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch。
    ②同样的, 也是支持传递payload
  • Action返回的Promise:
    在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject。
\\定义:
const store = new Vuex.Store({
	state: {
		count: 0
	},
	mutations: {
		increment(state) {
			state.count++
		}
	},
	actions: {
		increment (context) {
			contextmit('increment')
		}
	}
})

\\使用:
methods: {
	increment() {
		this.$store.dispatch('increment') 
	}
}

Module简介:

  • Module是模块的意思, 为什么在Vuex中我们要使用模块呢?
    ①Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理,当应用变得非常复杂时,store对象就有可能变得相当臃肿。
    为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutation、action、getters等。
  • 注意:
    ①虽然, 我们的gettersmutation都是定义在对象内部的,但是在调用的时候, 依然是通过this.$store来直接调用的。
    ②actions:
    <1>局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
    <2>如果getters中也需要使用全局的状态, 可以接受更多的参数
const moduleA = {
	//...
	actions: {
		incrementIfoddonRootSum ({state,commit,rootState}) {
			if ((state.count + rootState.count) %2 ===1) {
				commit('increment')
			}
		}
	}
}
  • 代码结构:
const moduleA = {
	state: { ... },
	mutations: { ... },
	actions: { ... },
	getters: { ... }
}

const moduleB = {
	state: { ... },
	mutations: { ... },
	actions: { ... }
}

const store = new Vuex.Store({
	modules: {
		a: modu1eA,
		b: modu1eB
	}
})

store.state.a // -> moduleA的状态
store.state.b // -> moduleB的状态

项目结构:

  • 当我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰:

axios

选择什么网络模块?

  • 选择一: 传统的Ajax是基于XMLHttpRequest(XHR)
    ①为什么不用它呢?
    <1>非常好解释, 配置和调用方式等非常混乱.
    <2>编码起来看起来就非常蛋疼.
    <3>所以真实开发中很少直接使用, 而是使用jQuery-Ajax
  • 选择二: 在前面的学习中, 我们经常会使用jQuery-Ajax
    ①相对于传统的Ajax非常好用.
    ②为什么不选择它呢?
    <1>首先, 我们先明确一点: 在Vue的整个开发中都是不需要使用jQuery了.
    <2>那么, 就意味着为了方便我们进行一个网络请求, 特意引用一个jQuery, 你觉得合理吗?
    <3>jQuery的代码1w+行.
    <4>Vue的代码才1w+行.
    <5>完全没有必要为了用网络请求就引用这个重量级的框架.
  • 选择三: 官方在Vue1.x的时候, 推出了Vue-resource.
    ①Vue-resource的体积相对于jQuery小很多.
    ②另外Vue-resource是官方推出的.
    ③为什么不选择它呢?
    <1>在Vue2.0退出后, Vue作者就在GitHub的Issues中说明了去掉vue-resource, 并且以后也不会再更新.
    <2>那么意味着以后vue-reource不再支持新的版本时, 也不会再继续更新和维护.
    <3>对以后的项目开发和维护都存在很大的隐患.
  • 选择四: 在说明不再继续更新和维护vue-resource的同时, 作者还推荐了一个框架: axios为什么不用它呢?
    ①axios有非常多的优点, 并且用起来也非常方便.
    ②稍后, 我们对他详细学习.

jsonp:

  • 在前端开发中, 我们一种常见的网络请求方式就是JSONP
    ①使用JSONP最主要的原因往往是为了解决跨域访问的问题.
  • JSONP的原理是什么呢?
    ①JSONP的核心在于通过<script>标签的src来帮助我们请求数据.
    ②原因是我们的项目部署在domain1服务器上时, 是不能直接访问domain2服务器上的资料的.
    ③这个时候, 我们利用<script>标签的src帮助我们去服务器请求到数据, 将数据当做一个javascript的函数来执行, 并且执行的过程中传入我们需要的json.
    ④所以, 封装jsonp的核心就在于我们监听window上的jsonp进行回调时的名称.
  • JSONP如何封装呢?
    ①我们一起自己来封装一个处理JSONP的代码吧.

为什么选择axios?

  • 功能特点:
    ①在浏览器中发送 XMLHttpRequests 请求
    ②在 node.js 中发送 http请求
    ③支持 Promise API
    ④拦截请求和响应
    ⑤转换请求和响应数据

axiox请求方式:

  • 支持多种请求方式:
    ①axios(config)
    ②axios.request(config)
    ③axios.get(url[, config])
    ④axios.delete(url[, config])
    ⑤axios.head(url[, config])
    ⑥axios.post(url[, data[, config]])
    ⑦axios.put(url[, data[, config]])
    ⑧axios.patch(url[, data[, config]])
  • 发送get请求演示:
import axios from 'axios'

export default {
	name : 'app',
	created() {
		//提问:为什么我这里没有跨域的问题?//
		1.没有请求参数
		axios.get( 'http://123.207.32.32:8000/category')
		.then(res => {
			console.log(res);
		}).catch(err =>{
			console.log(err);
		})
		
		//2.有请求参数
		axios.get( 'http:/ /123.207.32.32:8000/home/data',
			{params: itype: 'sell', page: 1}})
		.then(res => {
			console.log ( res);
		}).catch(err => {
			console.log(err);
		})
	}
}
  • 发送并发请求:
    ①有时候, 我们可能需求同时发送两个请求
    ②使用axios.all, 可以放入多个请求的数组.
    ③axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2
import axios from 'axios'

export default {
	name : 'app',
	created () {[
		//发送并发请求
		axios.all([axios.get('http://123.207.32.32:8000/category'),
		axios.get('http://123.207.32.32:8000/home/data',
		{params : {type: 'sell', page: 1}})])
	.then(axios.spread((res1,res2) => {
		console.log(res1);
		console.log(res2);
	}))
}

全局配置:

  • 在上面的示例中, 我们的BaseURL是固定的
    ①事实上, 在开发中可能很多参数都是固定的.
    ②这个时候我们可以进行一些抽取, 也可以利用axiox的全局配置
created( {
	//提取全局的配置
	axios.defaults.baseURL = 'http://123.207.32.32:8000'
	
	//发送并发请求
	axios.all([
		axios.get('/category'),
		axios.get('/home/data',
		{params: {type: 'sell', page:1}})])
	.then(axios.spread((res1,res2) =>{
		console.log (res1);
		console.log(res2);
	}))
}
  • 常见的配置选项:
请求地址
url: '/user',

请求类型
method: 'get',

请根路径
baseURL: 'http://www.mt/api',

请求前的数据处理
transformRequest:[function(data){}],

请求后的数据处理
transformResponse: [function(data){}],

自定义的请求头
headers:{'x-Requested-With':'XMLHttpRequest'},

URL查询对象
params:{ id: 12 },

查询对象序列化函数
paramsSerializer: function(params){ }

request body
data: { key: 'aa'},

超时设置s
timeout: 1000,

跨域是否带Token
withCredentials: false,

自定义请求处理
adapter: function(resolve, reject, config){},

身份验证信息
auth: { uname: '', pwd: '12'},

响应的数据格式 json / blob /document /arraybuffer / text / stream
responseType: 'json',

axios的实例:

  • 为什么要创建axios的实例呢?
    ①当我们从axios模块中导入对象时, 使用的实例是默认的实例.
    ②当给该实例设置一些默认配置时, 这些配置就被固定下来了.
    ③但是后续开发中, 某些配置可能会不太一样.
    ④比如某些请求需要使用特定的baseURL或者timeout或者content-Type等.
    ⑤这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息.
//创建新的实例
const axiosInstance = axios.create({
	baseURL: 'http:/ /123.207.32.32:8000',
	timeout: 5000,
	headers: {
		'Content-Type' : 'application/x-www-form-urlencoded '
	}
})

//发送网络请求
axiosInstance({
	url: '/category',
	method: 'get'
}).then(res => {
	console. log(res);
}).catch(err => {
	console. log(err);
})
  • axios封装:
import originAxios from 'axios'

export default function axios(option) {
	return new Promise((resolve, reject) =>{
		//1.创建axios的实例
		const instance = originAxios.create({
			baseURL: '/api',
			timeout: 5000,
			headers:''
		});

		//2.传入对象进行网络请求
		instance(option).then(res => {
			resolve(res)
		}).catch(err => {
			reject(err)
		})
	})
}

axios拦截器:

  • axios提供了拦截器,用于我们在发送每次请求或者得到相应后,进行对应的处理。
  • 如何使用拦截器呢?
//配置请求和响应拦截
instance.interceptors.request.use(config =>{
	console.log('来到了request拦截success中');
	return config
}, err => {
	console.log('来到了request拦截failure中');
	return err
}

instance.interceptors.response.use(response =>{
	console.log('来到了response拦截success中');
	return response.data
}, err =>{
	console.log('来到了response拦截failure中');
	return err
})
  • 拦截器中都做什么呢?
    ①请求拦截中错误拦截较少,通常都是配置相关的拦截。可能的错误比如请求超时,可以将页面跳转到一个错误页面中。
    ②响应拦截中完成的事情:
    <1>响应的成功拦截中,主要是对数据进行过滤。
    <2>响应的失败拦截中,可以根据status判断报错的错误码,跳转到不同的错误提示页面。
//请求拦截
instance.interceptors.request.use(config =>{
	console.log('来到了request拦截success中');
	//1.当发送网络请求时,在页面中添加一个loading组件,作为动画

	//2.某些请求要求用户必须登录,判断用户是否有token,如果没有token跳转到login页面

	//3.对请求的参数进行序列化
	config.data = qs.stringify (config.data)
	console.log(config);
	
	//4.等等
	return config
})	

//响应的成功拦截
instance.interceptors.response.use(response => {
	console.log('来到了response拦截success中');
	return response.data
})

//响应的失败拦截
},err =>{
	console.log('来到了response拦截failure中');
	if (err && err.response) {
		switch (err.response.status) {
			case 400:
				err.message ='请求错误'
				break
			case 401:
				err.message ='未授权的访问'
				break
		}
	}
	return err

Vue高级

防抖和节流:

  • 本质上是优化高频率执行代码的一种手段。如:浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用throttle(防抖)和debounce(节流)的方式来减少调用频率。
  • 定义:
    ①节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
    ②防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
  • 比喻:
    ①想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应。
    ②假设电梯有两种运行策略 debounce 和 throttle,超时设定为15秒,不考虑容量限制。
    ③电梯第一个人进来后,15秒后准时运送一次,这是节流。
    ④电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖。
  • Vue 没有内置支持防抖和节流,但可以使用 Lodash 等库来实现。
----如果某个组件仅使用一次,可以在 methods 中直接应用防抖:----
<script src="https://unpkg/lodash@4.17.20/lodash.min.js"></script>
<script>
  Vue.createApp({
    methods: {
      // 用 Lodash 的防抖函数
      click: _.debounce(function() {
        // ... 响应点击 ...
      }, 500)
    }
  }).mount('#app')
</script>

----但是,这种方法对于可复用组件有潜在的问题,因为它们都共享相同的防抖函数。
为了使组件实例彼此独立,可以在生命周期钩子的 created 里添加该防抖函数:----
<script>
app.component('save-button', {
  created() {
    // 使用 Lodash 实现防抖
    this.debouncedClick = _.debounce(this.click, 500)
  },
  unmounted() {
    // 移除组件时,取消定时器
    this.debouncedClick.cancel()
  },
  methods: {
    click() {
      // ... 响应点击 ...
    }
  },
  template: `
    <button @click="debouncedClick">
      Save
    </button>
  `
})
</script>

自定义指令:

  • 现在让我们用指令来实现这个功能:
const app = Vue.createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
  // 当被绑定的元素挂载到 DOM 中时……
  mounted(el) {
    // 聚焦元素
    el.focus()
  }
})
  • 如果想注册局部指令,组件中也接受一个 directives 的选项:
directives: {
  focus: {
    // 指令的定义
    mounted(el) {
      el.focus()
    }
  }
}
  • 然后你可以在模板中任何元素上使用新的 v-focus attribute,如下:
<input v-focus />
  • 一个指令定义对象可以提供如下几个钩子函数 (均为可选):
    ①created:在绑定元素的 attribute 或事件监听器被应用之前调用。在指令需要附加在普通的 v-on 事件监听器调用前的事件监听器中时,这很有用。
    ②beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。
    ③mounted:在绑定元素的父组件被挂载后调用。
    ④beforeUpdate:在更新包含组件的 VNode 之前调用。
    ⑤updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。
    ⑥beforeUnmount:在卸载绑定元素的父组件之前调用
    ⑦unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次。
  • 动态指令参数:指令的参数可以是动态的。例如,在 v-mydirective:[argument]=“value” 中,argument参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。
const app = Vue.createApp({})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.value 是我们传递给指令的值——在这里是 200
    el.style.top = binding.value + 'px'
  }
})

app.mount('#dynamic-arguments-example')
  • 函数简写:
app.directive('pin', (el, binding) => {
  el.style.position = 'fixed'
  const s = binding.arg || 'top'
  el.style[s] = binding.value + 'px'
})
  • 对象字面量:如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>

app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})

Object.defineProperty()简介:

  • Object是在javascript中一个被我们经常使用的类型,而且JS中的所有对象都是继承自Object对象的。虽说我们平时只是简单地使用了Object对象来存储数据,并没有使用到太多其他功能,但是Object对象其实包含了很多很有用的属性和方法,尤其是ES5增加的方法,今天我们先探讨一下Object.defineProperty()。
  • Object.defineProperty 给一个对象添加或者修改属性 返回的是一个对象
    ①语法:Object.defineProperty(obj, prop, descriptor)
    ②参数:
    <1>obj要在其上定义属性的对象。
    <2>prop要定义或修改的属性的名称。
    <3>descriptor将被定义或修改的属性描述符。
  • 属性描述符是一个对象,参数如下:
    ①value: 设置属性值
    ②writeable: 设置当前属性是否允许被修改
    ③configurable:设置当前属性是否可以删除
    ④enumerable:设置当前属性是否可被枚举
    ⑤getter—get() 当获取属性值的时候触发的函数
    ⑥setter—set() 当设置属性的时候触发的函数
  • 注意:当使用了get()方法或者set()方法的时候就不能使用value和writable中的任何一个属性否则会报错
  • 例子:
    ①另一种方法是将库添加到vue原型中。所有组件都继承自此,因此它们现在都可以通过this关键字访问库。
import _ from 'lodash ';
object.defineProperty(Vue.prototype,'$_', { value: _ });

Vue.config.productionTip的作用:

  • productionTip设置为 false ,可以阻止 vue 在启动时生成生产提示
  • 开发环境下,Vue会提供很多警告来帮你对付常见的错误与陷阱。而在生产环境下,这些警告语句却没有用,反而会增加应用的体积。此外,有些警告检查还有一些小的运行时开销,这在生产环境模式下是可以避免的

vue中style scoped属性的作用和原理:

  • 作用:当style标签里面有scoped属性时,它的css只作用于当前组建的元素。在单页面项目中可以使组件之间互不污染,实现模块化(实现组件的私有化,不对全局造成样式污染,表示当前style属性只属于当前模块)。
  • 实现原理: style 标签中添加 scoped 属性后,vue 就会为当前组件中的 DOM元素添加唯一的一个自定义属性(唯一性的标记)【data-v-xxx】,即CSS带属性选择器,以此完成类似作用域的选择方式,从而达到样式私有化,不污染全局的作用。
  • 注意:实际开发中,建议在每个组件的 style 身上都加上 scoped 属性。

vue中this的指向详解:

  • 在Vue实例中,methods中如果用的是正常函数,那么它的this就指向Vue实例;如果是箭头函数,this就指向window对象。在Vue的官方文档是这么解释的:
methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this自动绑
定为 Vue 实例。

注意,不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++)。理由是箭头函数绑定了父级作用域的上
下文,所以 this 将不会按照期望指向 Vue 实例,this.a 将是 undefined。
  • Vue中生命周期钩子和自定义方法中的this指向当前的 Vue 实例:
所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对 
property 和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法 
例如 created: () => this.fetchTodos())。这是因为箭头函数绑定了父上下文,
this 与你期待的 Vue 实例不同,this.fetchTodos 的行为未定义。
  • Vue 中回调函数中的 this:
    ①若回调函数为匿名函数,非严格模式下指向 window,严格模式下为 undefined。
    ②若回调函数为自定义方法,则 this 指向 Vue 实例。
    ③若回调函数为 箭头函数,则 this 指向 Vue 实例。
  • Vue 中 addEventListener 中的 this:
    ①通常,事件监听函数中的 this 都指向绑定事件的那个元素, 但是在 Vue 中,监听函数中的 this 也指向 Vue 实例。
  • 总结:除了回调函数中的 this ,其它地方的 this 均指向 Vue 实例

更多推荐

Vue高级知识概括