为什么需要组件化?在实际开发中,我们经常也会封装很多公共方法,达到复用的目的,也便于维护。对于前端也是一样,那么什么是组件?
官方定义:组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。
组件机制的设计,可以让开发者把一个复杂的应用分割成一个个功能独立组件,降低开发的难度的同时,也提供了极好的复用性和可维护性。组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可。组件化和模块化的视角不同:
可复用组件,高内聚、低耦合。Vue中提供了少量的内置组件(keep-alive、component、transition、transition-group等),但可以自定义组件。Vue API中,提供了Vue.extend和Vue.component两个全局方法创建/注册组件,还有一个实例选项components,用来注册局部组件。
准备好开发工具Visual Studio Code,导入之前生成的项目案例。
项目目录:
目录/文件 说明
build 项目构建(webpack)相关代码
config 配置目录,包括端口号等。我们初学可以使用默认的。
node_modules npm 加载的项目依赖模块(根据package.json安装时候生成的的依赖安装包)
src 这里是我们要开发的目录,基本上要做的事情都在这个目录里。里面包含了几个目录及文件:
assets 脚手架自动会放入一个图片在里面作为初始页面的logo。平常我们使用的时候会在里面建立js,css,img,fonts等文件夹,作为静态资源调用
components 用来存放组件,合理地使用组件可以高效地实现复用等功能,从而更好地开发项目。
router 路由相关的内容。该文件夹下有一个叫index.js文件,用于实现页面的路由跳转
main.js 入口文件,主要作用是初始化vue实例并使用需要的插件,小型项目省略router时可放在该处
App.vue 主组件,可通过使用 开放入口让其他的页面组件得以显示。也可以直接将组件写这里,而不使用 components 目录。
static 静态资源目录,如图片、字体等。
.gitkeep git配置。
.babelrc es6解析的一个配置
.editorconfig 编辑器的配置文件
.eslintignore 忽略eslint语法规范检查配置文件
.eslintrc.js eslint(代码格式化检查工具)的配置文件,开启以后要严格遵照它规定的格式进行开发
.gitignore 忽略git提交的一个文件,配置之后提交时将不会加载忽略的文件
.postcssrc.js 文件是postcss-loader包的一个配置
index.html 首页入口文件,经过编译之后的代码将插入到这来。
package.lock.json 锁定安装时的包的版本号,并且需要上传到git,以保证其他人在npm install时大家的依赖能保证一致
package.json 项目配置文件。需要哪些npm包来参与到项目中来,npm install命令根据这个配置文件增减来管理本地的安装包。
README.md 项目的说明文档,markdown 格式
运行命令npm run dev启动项目(npm run 其实执行了package.json中的script脚本)。更多说明参考:https://blog.csdn.net/xiaoxianer321/article/details/114009647
webpack-将各项前端代码/资源打包编译成.js、.css、.png等浏览器可以加载的资源。
在使用过程中经常出现编写不符合eslint规范,空格,缩进,各种括号,导致启动报错,真是头疼。
2.3.1、方式一:安装插件ESLint
然后File(文件)->Preferences(首选项)->settings(设置)
编辑settings.json文件将文件替换为下面的选项,就可以保存时自动格式化。
{
// vscode默认启用了根据文件类型自动设置tabsize的选项
"editor.detectIndentation": false,
// 重新设定tabsize
"editor.tabSize": 2,
// #每次保存的时候自动格式化
"editor.formatOnSave": true,
// #让prettier使用eslint的代码格式进行校验
"prettier.eslintIntegration": true,
// #去掉代码结尾的分号
"prettier.semi": false,
// #使用单引号替代双引号
"prettier.singleQuote": true,
// #让函数(名)和后面的括号之间加个空格
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
// #这个按用户自身习惯选择
"vetur.format.defaultFormatter.html": "js-beautify-html",
// #让vue中的js按编辑器自带的ts格式进行格式化
"vetur.format.defaultFormatter.js": "vscode-typescript",
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
"wrap_attributes": "force-aligned"
// #vue组件中html代码格式化样式
}
},
// 格式化stylus, 需安装Manta's Stylus Supremacy插件
"stylusSupremacy.insertColons": false, // 是否插入冒号
"stylusSupremacy.insertSemicolons": false, // 是否插入分好
"stylusSupremacy.insertBraces": false, // 是否插入大括号
"stylusSupremacy.insertNewLineAroundImports": false, // import之后是否换行
"stylusSupremacy.insertNewLineAroundBlocks": false, // 两个选择器中是否换行
}
2.3.2、方式二:关闭校验规则
找到build\webpack.base.conf.js这个文件:
它是index.js中的一个配置:useEslint并将其值设置为: false,即关闭eslint规则校验。(并不推荐这么做)
关于组件的创建源码分析,可以参考这篇文章:https://blog.csdn.net/xiaoxianer321/article/details/114340405
前面介绍过Vue.extend
可以帮助我们对 Vue 实例进行扩展,扩展之后,就可以用此扩展对象创建新的 Vue 实例,Vue.extend
是为了创建可复用的组件模板,更主要的是服务于Vue.component组件注册。
案例1:
vue-test-big
new Vue(), 尽管在Vue官方文档上,在相当多的例子中使用到了创建Vue实例这个操作,实际上它的使用可能并没有你想象的那么频繁,在很多时候,它可能就只在挂载根实例的时候使用到。后面我们接触更多的是组件和路由的使用。
在Vue 中定义一个组件模板,可以有很多种方式,下面只列出常见的几种:
1)字符串
// 注册
Vue.component('my-component', {
template: 'A custom component!'
})
2)模板字面量
Vue.component('my-component', {
template: `A custom component!`
})
3)x-template
Vue.component('my-component', {
template: '#checkbox-template'
})
4)render 函数
Vue.component('my-component', {
render(createElement){
return createElement('div','A custom component!')
}
})
5)内联模板(inline-template 属性)
我是内联模板
6)单文件组件(.vue 文件)
一般浏览器不能识别.vue的文件,所以需要vue-loader来加载.vue文件,所以需要基于webpack来开发。
webpack:前端资源模块化加载器和打包工具,他能把各种资源当作模块进行加载,实际上就是通过不同的loader将这些资源加载打包然后输出打包文件。webpack是基于commonJs语法的。
vue-loader:
会解析文件,提取每个语言块,如有必要会通过其它 loader 处理,最后将他们组装成一个 ES Module,它的默认导出是一个 Vue.js 组件选项的对象。支持使用非默认语言,比如 CSS 预处理器,预编译的 HTML 模版语言,通过设置语言块的 lang
属性。
传统前端中一个网页应用是由很多html文件组成,每个html文件又分为三部分:
1)
用于展示视图;
2)
用于和用户交互
3)
用于控制视图的样式。
在vue中,每个单文件组件(.vue文件)也可分为三部分(例如:在App.vue中我们可以看到有:、
App.vue 案例效果: 一个简单的网页,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件,形成了类似的组件树结构,引用官网一张图: 组件与组件之间的嵌套使用避免不了数据之间的传递。那么Vue中组件的数据是如何传递的呢?组件间数据传递不同于Vue全局的数据传递,组件实例的数据之间是孤立的,不能在子组件的模板内直接引用父组件的数据。如果要把数据从父组件传递到子组件,就需要使用 通过 Prop向子组件传递数据,父子组件之间的数据传递相当于自上而下的下水管子,只能从上往下流,不能逆流。这也正是 Vue 的设计理念之单向数据流(当父组件的属性变化时,将传给子组件,但是反过来不会)。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。 示例: 1)我们先定义好一个父组件(Parent.vue)和一个子组件(Child.vue) 2)Child.vue:我们需要使用父组件的数据定义在props中:props: ['myName', 'myAge'] HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符,所以在模板中不能直接使用camelCase (驼峰命名法) 的 prop (myName,myAge),需要使用其等价的 kebab-case (短横线分隔命名) 命名。 3) Parent.vue:在父组件中导入子组件,并引用子组件 4)在main.js中注册父组件 5)在App.vue中使用父组件 示例效果:当父组件的属性变化时,将传导给子组件,但是不会反过来。修改子组件的 prop 值,是不会传回给父组件去更新视图的。 通过props传递给子组件的myName、myAge,不能在子组件内部修改props中的值。这样的操作破坏了单向数据流的设想,所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。但是如果prop改成对象类型,子组件直接修改数据,不会引起警告(因为当参数是对象的时候,传给子组件的应该是类似于指针的一个东西,指向内存中的一个数据空间,而子组件通过 ObjectName.prop 去操作Object的时候,更改了内存空间的数据,但是并没有更改内存空间的位置,指针仍然是指向这块内存空间的,所以不会有警告报错),但还是不建议这样做。 官网给出了解决方案: 1)定义一个局部变量并用prop的值初始化它; 2)定义一个计算属性,处理prop的值并返回。 我们对子组件进行改造: 案例效果: 现在修改子组件的,不会影响父组件的传递过来的prop。 子组件定义 props 有三种方式: 1.2.1、第一种数组方式 数组方式:可以是静态的传递字符串,也可以是数字、布尔值、数组或者对象,但是后者需要使用到 v-bind指令。前面介绍过(v-bind 主要用于属性绑定,比方你的class属性,style属性,value属性,href属性等等,只要是属性,就可以用v-bind指令进行绑定),在上面(1.1 通过 Prop 传递数据)的案例中我们就用到了v-bind传递一个对象变量。 1)静态字符串 运行结果: 2)动态对象 1.2.2、第二种对象方式 使用第二种对象的方式,可以验证参数的类型,如果不符合数据规格,Vue 会发出警告。把main.js中的props改成下面的格式,则控制台出现警告。 运行结果: 1.2.3、第三种对象嵌套对象方式 第三种嵌套对象中的属性(type、default、required、validator)均为非必须。 type可以是以下类型, 将main.js中替换成如下所示: 什么是非Prop 的 Attribute 呢?(props是properties的缩写,attrs是attributes的缩写)在介绍Vue基础的时候,也介绍过properties(属性)和 attributes(特性)。 在Vue中一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 prop 定义的 attribute。对于绝大多数 attribute 来说,从外部提供给组件的值会替换掉组件内部设置好的值。但是有两个比较特殊, 1.3.1、Attribute 继承 举个栗子: 在main.js中注册一个全局组件 在app.vue中 在使用子组件时,我们定义了很多属性:【content="hello world" a="3" :b="fatherMsg" c="4" class="ddd" style="font-size: 16px;" title="bt" value="fvalue" id="hh" name="vcom" dir="ltr"】 同时在子组件的根元素上定义了:【name='mydiv' id='mydiv' dir='rtl' title='l am div' style='color:blue' 】 而子组件中我们只接收props: ['name','content', 'b', 'title']四个属性 子组件中接收到了 ['name','content', 'b', 'title'],但是这四个属性就没有继承/替换到根元素上。根元素的id、dir被替换,a、c、value继承到了根元素上,而class 和 style进行了合并。 1.3.2、禁用 Attribute 继承 如果不希望组件上的根元素继承attribute,可以在组件的选项中设置 加上 1.3.3、多个根节点上的 Attribute 继承-$attrs 当我们知道了 prop实现了把父组件中的数据传递到子组件中,每次父组件更新时,子组件的所有 prop 都会更新为最新值。但是 vm.$emit( eventName, […args] ) 参数: 触发当前实例上的事件。附加参数都会传给监听器回调。 [每个Vue实例都实现了事件接口:使用 在上一个案例中其实也使用到了$emit。我们先看官网给出的一个例子。 子组件click事件,调用$emit,传入需要触发的父类自定义事件welcome,在父组件中使用子组件时对welcome绑定sayHi方法。 首先在main.js中定义一个子组件:v-testcom,并在this.$emit('childFn', this.message)中传递一个值this.message 在App.vue中使用子组件: 子组件如果只传递一个参数(这个值可以是字符、对象或者数组),接收方也只接收一个参数(这个值可以是字符、对象或者数组)。如果我就是想传递多个参数呢,其实也是可以对应接收的,同时也支持以数组的形式接收。 首先在子组件中this.$emit('childFn', this.message, this.message1, this.message2)传递三个参数 如果是多个参数,在App.vue中一种是绑定时默认不写,则按参数一个一个接收。如果加上(),则需以数组的形式接收(并且这个参数只能写arguments),$event默认为childFn的第一参数。 前面我们介绍了$attrs用来操作父子组件间的属性,与之类似的 举个栗子: 首先在components目录下新建一个events/ChildComponent.vue 然后在App.vue中使用局部注册的方式,使用ChildComponent子组件。 案例执行后 console.log(this.$attrs, this.$listeners) //输出了没有在prop中的name属性 和一个没有被.native修饰的方法:two this.$listeners.two() //执行two绑定的方法triggerTwo 输出two 现在,我想两个组件之间进行数据传递,即我A页面点击后跳转到B页面,同时我希望将(组件/页面)A上的某一些参数携带过去给(组件/页面)B。如果咱们的应用程序不需要类似Vuex这样的库来处理组件之间的数据通信,就可以考虑Vue中的事件总线 ,即 EventBus来通信。 EventBus原本是一个针对Android为了松耦合的基于事件发布/订阅模式(观察者模式)的开源库。EventBus 在vue中适用于跨组件简单通信,不适应用于复杂场景多组件高频率通信,它相当于一个全局的仓库,任何组件都可以去这个仓库里获取事件,确实很方便。但是vue是一个单页应用,当页面刷新了之后,与之相关的EventBus会被移除。如果反复操作的页面,EventBus 在监听的时候就会触发很多次,也是一个非常大的隐患。这时候我们就需要好好处理 EventBus 在项目中的关系,在vue页面销毁时,同时移除EventBus 事件监听。 3.1.1、单独使用一个Vue实例作为中央事件总线 新建一个EventBus.js,方式一: 导出使用 新建一个EventBus.js,方式二: 导出使用: 3.1.2、在根组件中注册bus 3.1.3、使用插件vue-bus 这里使用安装插件vue-bus的方式,来使用EventBus。 安装好了vue-bus,在main.js中使用插件。 在components下新建目录eventBus,然后新建一个A.vue 和 一个B.vue A.vue B.vue {{msgB}} App.vue A组件bus事件总线通过emit,触发当前实例上的事件aMsg,并附加参数this.MsgA传给监听器。B组件bus事件总线通过on,监听当前实例上的自定义事件aMsg,并将传递过来的参数赋值给:msgB (this.msgB = data ),当B组价销毁时应当要销毁事件总线上的监听,避免被其他人劫走或者多次触发。 在官网实例property我们可以看到Vue实例,提供了vm.$parent、vm.$root、vm.$children,分别可以获取父类实例、根实例、和子类实例,并访问其实例对象的某些属性和方法,但是很少会直接去修改他们的属性。 vm.$parent 类型: 我们用之前的Child.vue 和 Parent.vue组件,输出一下。 Child.vue Parent.vue 输出结果: vm.$parent、vm.$children 是不是比较简单粗暴, 但是他对需要通信的组件绑定性强,你必须得知道你的子或父是什么,或者说你必须知道你的子组件或者父组件有什么东西的时候才可以调用,这样会让你的子组件和父组件耦合,子组件不能单独使用。 vm.$children返回的是一个数组,如果我在中间再插入一个组件,这样通过数组下标获取子组件,确实比较不方便。那么Vue中也提供了另外一种方式,让我们去访问子组件实例或者元素,他就是vm.$refs vm.$refs:一个对象,持有注册过 在Parent.vue中多引入几个 provide/inject翻译为:提供/注入,是 Vue 在 2.2.0 版本新增的 API。官网介绍如下: 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。 2)provide/inject可以像import/export一样提供数据共享(Symbol起到一个句柄的作用,解决了有可能出现的依赖命名冲突问题),但是他的的价值更在于它能够提供一个多实例,并且在组件树上能按照规则覆盖的机制,主要用于组件封装。 3)Vue 不会对 provide 中的变量进行响应式处理。所以,要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的。 4.3.1、 provide / inject(2.2.0 新增) 类型: 4.3.2、 首先,我们在components/pd目录下新建grandfather.vue、father.vue、son.vue grandfather.vue father.vue son.vue 然后在main.js中注册组件:Vue.component('A-grandfather', gd),并在App.vue中使用 突然这事情被雍正知道了,对乾隆说,怎么能要爷爷的钱呢?我给你,你还给爷爷。雍正赶紧得提供啊,于是,打开了被注释的代码provide那部分。乾隆高兴的很,开始接收(顺便调整下宽高)son.vue: father.vue father提供的数据覆盖了grandfather提供的数据,注入的值可以来自不同的父类或者祖先,也就是说provide/inject在组件树中会有更好的体现,注入的不必管从哪里来的,提供的可以在任意父类层中改变,而不用改子类。变更按钮体现则是注入使用普通函数和箭头函数的区别。provide/inject它解决一些循环组件比如tree, menu, list等, 传参困难, 并且难以管理的问题, 主要用于组件封装, 常见于一些ui组件库。 前面介绍了很多关于组件间的通信:props 、events、$parent、$root、$children、provide / inject都只适用于关系型组件之间通信,非关系型使用EventBus(通过 Vue组件的另一个重要概念是插槽,其主要参照了当前Web Components规范草案,是组件的一块HTML模板,而这块模板显示不显示,以及怎么显示由父组件来决定。不过,插槽显示的位置由子组件自身决定, 简单的说:使用组件的时候,经常需要在父组件中为子组件中插入一些标签等。当然其实可以通过属性等操作,但是比较麻烦,直接写标签还是方便很多。 那么Vue提供了slot协助子组件对父容器写入的标签进行管理。插槽就是子组件中的提供给父组件使用的一个占位符,用 从 [email protected] 开始,Vue 为具名和范围插槽引入了一个全新的语法,即我们今天要讲的主角:v-slot 指令。目的就是想统一之前slot 和 slot-scope 语法,使代码更加规范和清晰。官方推荐我们使用 v-slot 来替代后两者。 为了理解插槽,我们先看Vue2.6之前的写法:新建一个子组件Date.vue 新建一个父组件Weather.vue,注册Vue.component('v-weather', weather),在App.vue中使用 案例效果: 插槽 有时候,也许子组件内的slot不止一个,那么我们如何在父组件中,精确的在想要的位置,插入对应的内容呢?那就取个名字,一个不带 跟 具名语法:插入内容(字符或标签), 匿名语法:插入内容(字符或标签) 或者(default可省略) 插入内容(字符或标签) 具名缩写:插入内容(字符或标签), 如果没有指定名称,子组件接收多个插入内容,就会被一个slot占用,这时我们可以使用具名插槽,给插槽起一个名字,对传递内容进行唯一性的标识。 vue2.6以前slot-scope作用域插槽:为了解决父组件模板的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译,父组件的模板是无法使用到子组件模板中的数据,slot-scope的出现却实现了父组件调用子组件内部的数据,子组件的数据通过slot-scope属性传递到了父组件。 语法: v-slot: 支持解构插槽 Prop语法: v-slot: 案例:在main.js中注册组件s-child和s-user 在App.vue中使用 组件的模板不会永远是固定的。应用可能会需要在运行期间加载一些新的组件或者内容。前面在介绍v-if指令的时候,对于切换简单组件或dom,直接使用 vue提供了一个内置组件component。 component Props: 用法:渲染一个“元组件”为动态组件。依 案例1: 1)在components目录下新建dtzj/Acom.vue和dtzj/Bcom.vue,然后在App.vue中使用。 Acom.vue Bcom.vue App.vue 案例2: 在vue中,模板的位置有两种,一种是在组件内部定义,一种是在组件外部定义。前面我们使用的都是内部定义:在创建组件的时候定义template的。Vue提供了一种内联模板的功能,在使用组件时,给标签加上inline-complate特性,组件就会把它的内容当作模板,而不是当内容分发。 我们对App.vue修改一下: 上诉案例中可见,内联的模板优先级更高。这种方式虽然灵活,但是给让我们组件的模版与其他属性难以分离。 我们先来看一个案例:在本章1.1的案例基础上,在A组件中新增一个方法,用于变更数据。 上诉案例中,当我们将A组件的数据变更后,在切换到B组件,但是当我们再回到A组件时,发现他的状态又复原了。如果在切回A组件的时候,我们要使其保持之前的状态呢?内置组件keep-alive就是用来做这个的。 keep-alive Props: 用法: 当组件在 案例:修改App.vue,对A组件缓存 案例效果: 只对A组件缓存,当从B组件切回A组件时,A组件的状态一直都保持在在切换前,而B组件被复原。include='Acom' Acom为组件A中的name属性。 案例: 当我们首次运行1.2中的案例,首次加载时间是193ms 现在使用异步加载的方式,修改App.vue,将组件B的加载方式改为:const Bcom = () => import('./components/dtzj/Bcom') 由于内容不多,时间上相差不大,但是异步的生成了一个1.js,这个就是在使用B组件时加载的Bcom.vue,由webpack帮我们处理。 异步组件也可以分为全局异步和局部异步。2.3.0+ 新增的异步组件工厂函数也可以返回一个如下格式的对象: 过渡(transition)和动画(animation)原是CSS3中具有颠覆性的特征之一,我们可以在不使用 Flash 动画或 JavaScript 的情况下,当元素从一种样式变换为另一种样式时为元素添加效果,他们都是随着时间改变元素的属性值。 过渡:就是使瞬间的样式变化,按照一定方式变得缓慢平缓(有时候平缓一些看着还是比较舒适的);过渡主要描绘的是transtion:过渡属性 过渡所需要时间 过渡动画函数 过渡延迟时间。 动画:就是一帧一帧图片连续切换实现的效果,关键帧就是里面主要的一些帧;(CSS3的动画是个很不错的技术,基本能取代一些gif,javascript,flash等)。动画主要描绘的是@keyframes(关键帧),通过控制关键帧来控制动画的每一步,实现更为复杂的动画效果。 过渡(transition)和动画(animation)的主要区别在于:1)transition需要触发一个事件才会随着时间改变其CSS属性;animation在不需要触发任何事件的情况下,也可以显式的随时间变化来改变元素CSS属性,达到一种动画的效果。2)过渡只有一组(两个:开始-结束) 关键帧,动画可以设置多个(多个百分比)。 而在Vue中,插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具: 在Vue中动画和过渡类似,正如官方所说,唯一的区别是:动画与过渡的区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。 其次就是动画和过渡transition对应的的css属性不同。 过渡的语法:transition: property duration timing-function delay; 即:transition: 过渡属性 过渡所需要时间 过渡动画函数 过渡延迟时间 默认值为:ease transition: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n); ease 规定慢速开始,然后变快,然后慢速结束的过渡效果 (等于cubic-bezier(0.25,0.1,0.25,1))。 linear 规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1)) 动画语法:animation: name duration timing-function delay iteration-count direction fill-mode play-state;即:animation:关键帧名 动画完成时间 完成周期 延迟时间 动画播放次数 方向 不播放时的样式 制定播放状态 默认值:ease animation: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n); 默认值:normal animation: normal|reverse|alternate|alternate-reverse|initial|inherit; 默认值:none animation: none|forwards|backwards|both|initial|inherit; 默认值:running animation: paused|running; Props: 事件: 用法: 扩展: Props: 事件: 用法: 注意,每个 Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡: Vue为过渡(Transitions)定义了6个class类名(进入/离开): 进入: 离开: 新建一个trans.vue,然后在App.vue中注册并使用。 hello ! 新建一个Anim1.vue,然后在App.vue中注册并使用。 hello ! 我们可以通过以下 attribute 来自定义过渡类名: 他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。 案例: hello 如果在一个组件或者标签中我们要同时使用过度和动画应该怎么做呢? 案例一:Anim1.vue,不指定类型,也不显示指定时间 首次进入有抖动效果。当我点击隐藏的时候,渐变执行5秒后隐藏,当我执行显示的时候抖动后渐变3秒显示。 案例二:Anim1.vue,指定动画时间 指定时间动画因为是animate.css执行动画的默认时间1s,指定动画时间,则覆盖了过渡的时间。 案例三:Anim1.vue,指定类型。官网说:给同一个元素同时设置两种过渡动效,比如 我们将Anim1.vue, 我们案例中指定类型为type="transition"呢?我们发现首次抖动不再执行了,是因为appear-active-class后面并没有指定过渡css,则使用其默认值0 内置组件transition除了提供了很多的props,支持 CSS 过渡和动画中自动应用 class,同时也提供了不少事件,即过渡钩子函数中使用 JavaScript 直接操作 DOM。这个和css的类似,就直接引用官网的例子。 案例:使用第三方 JavaScript 动画库,如 Velocity.js,没安装的可以先安装: 安装:npm install velocity-animate --save-dev 引入: import Velocity from 'velocity-animate'
Demo
官网上有一句话:当有相同标签名的元素切换时,需要通过 案例一:新建一个MoreCon.vue,并在App.vue中使用 案例一:使用transform(元素的2D或3D转换。这个属性允许你将元素旋转,缩放,移动,倾斜等)来实现平滑移动效果。 案例一中的过渡,看着感觉总有一点不和谐。同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式。 我们加上过渡模式再试下效果: 多个组件的过渡简单很多 - 我们不需要使用 案例: 我们主要介绍了 那么怎么同时渲染整个列表,比如使用 需要注意的是使用 FLIP 过渡的元素不能设置为 扩展: FLIP是一种记忆设备和技术,最早是由 案例一:新建一个List.vue v-bind:key="item",绑定的key值如果未发生变化,Vue是不会改变位置的,案例中的有些颜色不变的就是位置就是key值没变,只会更新DOM,这个在Vue详细介绍及使用第一篇文章中有介绍到。 案例二: 通过 data attribute 与 JavaScript 通信,就可以实现列表的交错过渡。 在 Vue 中即使是过渡也是数据驱动的!动态过渡最基本的例子是通过 过渡可以通过 Vue 的组件系统实现复用。要创建一个可复用过渡组件,你需要做的就是将 官网对于状态过渡的描述:Vue 的过渡系统提供了非常多简单的方法设置进入、离开和列表的动效。那么对于数据元素本身的动效呢,比如: 这些数据要么本身就以数值形式存储,要么可以转换为数值。有了这些数值后,我们就可以结合 Vue 的响应式和组件系统,使用第三方库来实现切换元素的过渡状态。那么什么是过渡状态呢?这还是很抽象,不过大致可以理解为从一个状态到另一种状态变化的过程。 通常我们将用户界面描述为一种状态。一个状态定义了一组属性的改变,并且会在一定的条件下被触发。另外在这些状态转化的过程中可以有一个过渡,定义了这些属性的动画或者一些附加的动作(变化过程),当进入一个新的状态时,动作也可以被执行。 官网使用了第三方动画库GSAP 来实现状态的过渡,GSAP JS是GreenSock公司新出的一个2D动画引擎,是一个JavaScript库,用于创建高性能、零依赖、跨浏览器动画,可以与React、Vue、Angular和vanilla JS协同工作。 GSAP有几大核心模块,它们是: 要了解什么是GSAP,就要了解Tweens(补间)和Timelines(时间轴)。前面我们接触的CSS Animations 和 Transitions,控制动画时序主要依赖的是动画的持续时间(Duration Timeline)和 延迟时间(Delay Timeline)。 Tweens:翻译成补间,是描述一帧一帧序列的术语 Timelines:主要是用来控制动画对象对应的动画效果的播放时间和持续时间。在GSAP的世界中,时间轴就非常的强大,除了GSAP自带的时间轴API(即 案例一: {{ animatedNumber }} 正常我们设置值是响应一个结果,而案例中通过第三方动画库,展示给我们的是整个过程,从0-20.这就是官网所说的状态过渡。 就像 Vue 的过渡组件一样,数据背后状态过渡会实时更新,这对于原型设计十分有用。当你修改一些变量,即使是一个简单的 SVG 多边形也可实现很多难以想象的效果。 案例: 把过渡放到组件里和赋予设计以生命可以在官网中查看。这里就不再介绍。 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。换句话说,mixins是对vue组件的一种扩展,将一些公用的常用数据或者方法,构建一个可被混入的数据结构,被不同的vue组件进行合并,就可以在不同的vue组件中使用相同的方法或者基础数据。 如果只是提取公用的数据或者通用的方法,并且这些数据或者方法,不需要组件间进行维护,就可以使用mixins。(类似于js中封装的一些公用的方法) 先新建MixA.vue、MixB.vue,两个组件 我们发现MixA.vue、MixB.vue两个组件都有相同的逻辑。如何对相同的逻辑做提取,达到组件间代码复用的效果? 我们对MixA.vue、MixB.vue相同的部分做一个提取。新建一个mymixin.js文件。 MixA.vue MixB.vue 修改MixA.vue。 案例: 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,如果发生冲突时以组件数据优先。上面案例中的msg组件中的优先级更高。 混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。我们在main.js中使用全局注册: 在前面我们说过,vue定义模板的方式有很多种,但是最终都是会通过render函数生成Virtual DOM。然而在一些场景中,需要使用JavaScript的编程能力和创建HTML,这就是 官网也给我们举了个栗子。大致描述一下他想表达的意思:比如当我们有一个新闻组件,在一个页面中出现了多条新闻,版式都差不多,只是内容有所不同。我们可能会想到应用插槽的形式去展示不同的新闻内容。 假如我们要着重突出某个新闻甚至想改字体变颜色大小等就显得没有那么灵活了。我们可以在渲染函数的帮助下做到这一点,渲染函数有助于使组件变得动态,并通过保持它的通用性和使用相同的组件传递参数来使用它。 案例:新建一个Myrender.vue 在App.vue中使用组件 渲染函数更接近于底层,虽然很灵活,但是相对于模板的写法要复杂多了。这个时候 案例一:新建一个MyJsx.vue 在App.vue中使用 关于jsx的语法可以在这里学习https://github.com/vuejs/jsx Vue 提供了一种称为函数式组件的组件类型,又叫非渲染组件,React中也有这个概念:用来定义那些没有响应数据,也不需要有任何生命周期的场景,它只接受一些props 来显示组件。我们可以把函数式组件想像成组件里的一个函数,入参是渲染上下文(render context),返回值是HTML。 函数组件特点:1)无状态(Stateless),组件自身是没有状态的;2、无实例(Instanceless),组件自身没有实例,也就是没有this。 由于函数式组件拥有的这两个特性,我们就可以把它用作高阶组件(High order components),所谓高阶,就是可以生成其它组件的组件。 语法格式: content包含的属性如下: 8.3.1、函数式组件与普通组件的区别 1)函数式组件需要在声明组件是指定 functional 2)没有this,this通过render函数的第二个参数来代替 3)没有生命周期钩子函数,不能使用计算属性,watch 4)不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件 5)函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止) 函数式组件的部分源码: 8.3.1、函数式组件的使用 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件。主要运用场景:比如一些详情页面,列表界面等,它们有一个共同的特点是只需要将外部传入的数据进行展现,不需要有内部状态,不需要在生命周期钩子函数里面做处理,这时候你就可以考虑使用函数式组件。 案例:实现一个登陆头像,有照片就使用照片,无照片使用默认照片。 案例一:新建一个函数式组件tx.vue 在App.vue中使用 关于图片的路径,有两种方式:一种是不经过webpack处理,直接写绝对路径拿static里的文件,直接写/static/xx/xxx.png;一种是经过使用webpack处理,不管是require还是import,声明为一个变量,后续使用这个变量。 什么是Vue插件,它和Vue组件有什么区别?通常插件是一种遵循一定规范的应用程序接口编写出来的程序,是一个库。而组件则更倾向于一个单一的功能,一个控件或对象。Vue官网给我们的解释是:插件通常用来为 Vue 添加全局功能、组件是可复用的 Vue 实例。似乎还是有点懵。 其实, 官网说到,插件的功能范围没有严格的限制——一般有下面几种: 1)添加全局方法或者 property。如:vue-custom-element 2)添加全局资源:指令/过滤器/过渡等。如 vue-touch 3)通过全局混入来添加一些组件选项。如 vue-router 4)添加 Vue 实例方法,通过把它们添加到 5)一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router 官网给了我们一个模板:Vue.js 的插件应该暴露一个 一个Vue插件可以是一堆Vue组件的集合(插件干的事就是把内部的组件帮你倒入到vue全局下),也可以是用来扩展Vue功能的,比如 Vuex, Vue-Router。你也可以写一个插件,在Vue原型上扩展方法、添加指令、注入选项...... 通过全局方法 例如:我们做一个图片轮播,找到一个可以使用的插件:Vue Carousel 3d,对应的使用说明:https://github.com/Wlada/vue-carousel-3d。 案例效果: 前面说到插件是对 案例一:写一个加载中插件 2)loading.vue 3)index.js 4)注册插件 案例二:写一个提示插件 1)新建一个mytoast.vue 2)新建一个index.js 3)注册插件 4)App.vue中使用插件 插件一其实也是一个全局组件,插件二在注册完成后就已经存在了,直接在js中操作元素。 关于过滤器,在这篇文章(https://blog.csdn.net/xiaoxianer321/article/details/111560355)中就已经介绍过了。 由于文章过长,关于路由将在这篇文章(https://blog.csdn.net/xiaoxianer321/article/details/116114007)中介绍。
三、组件间的通信
props
属性。在Vue中,父子组件的关系可以总结为:prop
向下传递,事件向上传递。父组件通过prop
给子组件下发数据,子组件通过事件给父组件发送消息。所以我们也会经常遇到组件之间需要传递数据的时候,大致分为四种情况:
$children
并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children
来进行数据绑定,考虑使用一个数组配合 v-for
来生成子组件,并且使用 Array 作为真正的来源。ref
attribute 的所有 DOM 元素和组件实例。访问子组件实例或子元素1、父组件向子组件传递数据
1.1、通过 Prop 传递数据
子组件child数据
父组件Parent数据
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import Parent from '@/components/props/Parent'
Vue.component('my-parent', Parent)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '
子组件child数据
1.2、Prop 类型
// 第一种数组方式
props: [xxx, xxx, xxx]
// 第二种对象方式
props: { xxx: Number, xxx: String}
// 第三种对象嵌套对象方式
props: {
xxx: {
// 类型不匹配会警告
type: Number,
// 对象或数组默认值必须从一个工厂函数获取
default: 0,
required: true,
// 自定义验证函数
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
//main.js
Vue.component('v-string', {
// 声明 props
props: ['message', 'myMessage'],
// 就像 data 一样,prop 也可以在模板中使用 myMessage驼峰命名法 在模板中使用 kebab-case (短横线分隔式命名)
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '{{ message }} | {{ myMessage }}'
})
//app.vue
//main.js
Vue.component('v-string', {
// 声明 props , 注意字面量和使用v-bind指令传递的不同,v-bind传递会当做JavaScript 表达式
props: ['fatherMsg', 'myAge', 'fatherSz', 'userInfo'],
template: '{{fatherMsg}} | {{myAge}} {{typeof myAge}} | {{fatherSz.length}} | {{userInfo.names}}'
})
//app.vue
//main.js
Vue.component('v-string', {
// 声明 props
props: {
fatherMsg: String,
myAge: String,
fatherSz: Array,
userInfo: Object
},
template: '{{fatherMsg}} | {{myAge}} {{typeof myAge}} | {{fatherSz.length}} | {{userInfo.names}}'
})
type
还可以是一个自定义的构造函数(如:author: Person)
String
Number
Boolean
Array
Object
Date
Function
Symbol
//main.js
Vue.component('v-string', {
// 声明 props
props: {
// 基础类型检查 (如果`null` 指允许任何类型)
fatherMsg: String,
// 可能是多种类型
myAge: [Number, String],
// 必传且是数组
fatherSz: {
type: Array,
required: true,
validator: function (value) {
console.log(value[0]) // 2 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
return value[0] > 2
}
},
// 数组/对象的默认值应当由一个工厂函数返回,如果没有userInfo对象 则userInfo,names 默认返回defaultName
userInfo: {
type: Object,
default: function () {
return { names: 'defaultName' }
}
}
},
template: '{{fatherMsg}} | {{myAge}} {{typeof myAge}} | {{fatherSz.length}} | {{userInfo.names}}'
})
1.3、非 Prop 的 Attribute
class
,style
,id
;class
,style 不会替换,但是会合并。其他的属性基本上都会继承或者替换。
Vue.component('v-com', {
props: ['name', 'content', 'b', 'title'],
template: `
inheritAttrs: false【inheritAttrs-
继承属性的意思】
在main.js中加上inheritAttrs: false
Vue.component('v-com', {
inheritAttrs: false,
props: ['name', 'content', 'b', 'title'],
template: `
inheritAttrs: false,
根元素的id、dir依旧不变,且a、c、value也没有继承到根元素上了。但是它始终不影响class 和 style的合并。inheritAttrs的作用:
是否继承来自父组件的属性(默认继承),那如果多个根节点或者不想到根节点继承呢?那就要使用到$attrs。我们再回头来看看官网给我们列举的案例:
//main.js
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
`
})
//App.vue
2、子组件向父组件传递数据
不能
在子组件内部改变 prop,
当然修改是有效的(可以通过this.$parent.name 访问到父组件中的属性),如果你这么做了,Vue 也会有警告提示。子组件向父组件传递数据,通过events - 自定义事件(回调参数)传递数据。
{string} eventName
[...args]
$on(evntName)
监听事件;使用$emit(eventName,optionalPayload)
触发事件。父组件可以在使用子组件的地方直接用v-on
来监听子组件触发的事件。]2.1、通过 events 传递数据
//在main.js 定义组件welcome-button
Vue.component('welcome-button', {
template: `
`
})
//在App.vue 中使用组件(APP就是welcome-button父组件)
2.2、子组件向父组件传递一个值
let testCom = Vue.extend({
template: `
子组件传来的值 : {{message}}
2.3、子组件向父组件传递多个值
//main.js
let testCom = Vue.extend({
template: `
//app.vue
子组件传来的值 : {{message}}
2.4、
$listeners的使用
$listeners用来操作父子组件间的事件。
$attrs属性收纳:
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class
和 style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和 style
除外),并且可以通过 v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。$listeners事件收纳:
包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。它可以通过 v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
我是一个 {{ professional }}
3、兄弟组件通信-EventBus
3.1、EventBus的使用方式($emit、$on、$off)
import Vue from 'vue'
export const EventBus = new Vue()
import { EventBus } from './EventBus.js'
//EventBus.$on()...
// 你也可以这么定义
export default (Vue) => {
const EventBus = new Vue()
Vue.prototype.$bus = {
/**
* @param {any} event 第一个参数是事件对象,第二个参数是接收到消息信息,可以是任意类型
* @method $on 事件订阅, 监听当前实例上的自定义事件。
* @method $off 取消事件订阅,移除自定义事件监听器。
* @method $emit 事件广播, 触发当前实例上的事件。
* @method $once 事件订阅, 监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。
*/
$on (...event) {
EventBus.$on(...event)
},
$off (...event) {
EventBus.$off(...event)
},
$once (...event) {
EventBus.$emit(...event)
},
$emit (...event) {
EventBus.$emit(...event)
}
}
return EventBus
}
import Vue from 'vue'
import EventBus from './EventBus.js'
//EventBus(Vue).$on()...
const bus = new Vue()
Vue.prototype.$bus = bus
//绑定到原型上,这样我们就不需要再自己写bus.js引入了,可以直接使用this.$bus.on(),this.$bus.$emit(),this.$bus.$off()
安装 npm install vue-bus --save
//如果安装不成功
//出现npm ERR! code ERR_TLS_CERT_ALTNAME_INVALID...错误
//试试(取消npm的https认证):npm config set strict-ssl false
//main.js中引入
import VueBus from 'vue-bus'
Vue.use(VueBus)
//然后就可以使用this.$bus.on()...
3.2、EventBus的使用
import VueBus from 'vue-bus'
Vue.use(VueBus)
4、其他通信方式
4.1、vm.$parent、vm.$root、vm.$children
Vue instance
vm.$root 类型:Vue instance
vm.$children 类型:Array
子组件child数据
父组件Parent数据
4.2、扩展vm.$refs代替vm.$children获取子组件
ref
attribute 的所有 DOM 元素和组件实例。它和原生JS中的document.querySelector('xxx')
功能一样,它可以在vue中获取元素匹配组件。
父组件Parent数据
4.3、多层级组件通信
provide
/inject
在组件式开发中,最大的痛点就在于组件之间的通信。Vue 提供了各种各样的组件通信方式,从基础的 props/$emit 到用于兄弟组件通信的 EventBus,还有vm.$parent、vm.$root、vm.$children ,再到用于全局数据管理的 Vuex(Vuex相对更复杂一点,后面再介绍)。也许人会问:使用 $root 都能获得根节点,也可做全局共享,那么我们何必使用 provide/inject 呢?不过 provide/inject 也是有它用武之地的。 provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol
和 Reflect.ownKeys
的环境下可工作。 inject
选项应该是一个字符串数组,或一个对象,对象的 key 是本地的绑定名,value 是:在可用的注入内容中搜索用的 key (字符串或 Symbol),或一个对象,该对象的:from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)default property 是降级情况下使用的 value。 provide
和 inject
主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。provide
和 inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。 看了官网的解释感觉有点迷惑,大致意思是:
1)只要一个组件使用了 provide
向下提供数据,那其下所有的子组件都可以通过 inject
来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据(不一定要从根组件开始);provide
/inject语法格式
Object | () => Object
Array
方式一:
provide为对象(provide:Object
)// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo' inject以数组的形式注入
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
//利用ES2015 Symbols foo 可以改写成
const foo = Symbol()
const Provider = {
provide () {
return {
[foo]: 'foo'
}
}
}
方式二:
provide为函数() => Object
如果需要用到实例对象,方式一,使用foo: this.bar, 访问不到Vue实例,我们需要使用函数的形式。
// 方式二:
provide () { // 函数形式
return{
tos: this.money // 返回实例的一个对象
}
}
provide () {
return {
fms: () => { // 返回一个匿名函数
return this.famsg
}
}
}
// provide 2.5.0以前,以数组的形式接收,2.5.0+ 也可以通过设置默认值使其变成可选项
const Child = {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
}
}
provide
/inject案例
红包签名:{{fmsfunc()}}
{{toather}}
vm.$on
或 vm.$once
进行订阅,vm.$emit
发布,vm.off
取消订阅),当然还有Vuex,这个最后面再介绍。四、vue插槽(slot分发内容)
1、插槽简介
slot
写在组件template
的哪块,父组件传过来的模板将来就显示在哪块。插槽提供了一个将内容放置到新位置或使组件更通用的出口。在实际项目开发当中,时常会把父组件的内容与子组件自己的模板混合起来使用,而这样的一个过程在Vue中被称为内容分发。
天气状况:
slot
和 slot-scope
这两个目前已被废弃但未被移除,后面都只介绍v-slot的使用。2、匿名/具名插槽
name
的
出口会带有隐含的名字“default”。v-on
和 v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
。例如 v-slot:header
可以被重写为 #header
//在main.js中注册组件,带有两个插槽,我们先不给插槽指定名称
Vue.component('news-content', {
template: `
//main.js
Vue.component('news-content', {
template: `
3、作用域插槽
slotName
="slotProps" [slotProps ---> vue作用域的映射会映射子组件v-bind的数据
]slotName
="{vue作用域的映射:别名
}" 语法一:
Vue.component('s-child', {
data: function () {
return {
'user': [
{ id: 1, name: '张三', age: 20 },
{ id: 2, name: '李四', age: 22 },
{ id: 3, name: '王五', age: 27 },
{ id: 4, name: '张龙', age: 27 },
{ id: 5, name: '赵虎', age: 27 }
]
}
},
template: `
{{uit.id+'-'+uit.name}}
{{us.users.id+'-'+us.users.name}}
五、动态组件和异步组件
1、动态组件
1.1、内置组件component的使用
v-if
会更加直观,但是如果是复杂一点的组件,如果写一堆v-if就会显得有些臃肿。
is
- string | ComponentDefinition | ComponentConstructorinline-template
- booleanis
的值,来决定哪个组件被渲染。
{{msg}}
{{msg}}
1.2、内置组件keep-alive的使用
{{msg}}
include
- 字符串或正则表达式。只有名称匹配的组件会被缓存。exclude
- 字符串或正则表达式。任何名称匹配的组件都不会被缓存。max
- 数字。最多可以缓存多少组件实例。
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和
相似,
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
内被切换,它的 activated
和 deactivated
这两个生命周期钩子函数将会被对应执行。
2、异步组件
Vue
作为单页面应用遇到最棘手的问题是首屏加载时间的问题,单页面应用会把页面脚本打包成一个文件,这个文件包含着所有业务和非业务的代码,而脚本文件过大也是造成首页渲染速度缓慢的原因。如果一个页面有成百上千个组件,倘若这些组件都要一起加载的话,进入页面就会显得很慢。我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
六、过渡&动画
1、过渡和动画的由来
img{
transition: height 0.5s linear 0.5s;
}
img{
animation mymove 1s infinite; //animation 所有动画属性的简写属性,除了 animation-play-state 属性。
}
@keyframes mymove //@keyframes 规定动画。一个动画中可以有很多个帧(创建多个百分比,从而达到一种不断变化的效果)
{
0% {top:0px;}
75% {top:300px;}
84% {top:240px;}
100% {top:300px;}
}
//0% 100%的百分号都不能省略,0%可以由from代替,100%可以由to代替。要让mymove动画有效果,就必须要通过CSS3 animation属性来调用它。
1.1、CSS3过渡的语法及属性
值
描述
语法
transition-property
指定CSS属性的name,transition效果
默认值为:all transition: none|all| property; 可具体制定css属性
transition-duration
transition效果需要指定多少秒或毫秒才能完成
默认值为:0 transition:time;
transition-timing-function
指定transition效果的转速曲线
transition-delay
定义transition效果开始的时候
默认值:0 transition:time;
1.2、CSS3动画的语法及属性
值
说明
语法
animation-name
指定要绑定到选择器的关键帧的名称
默认值:none animation:keyframename|none;
animation-duration
动画指定需要多少秒或毫秒完成
默认值:0 animation:time;
animation-timing-function
设置动画将如何完成一个周期
animation-delay
设置动画在启动前的延迟间隔。
默认值:0 animation:time;
animation-iteration-count
定义动画的播放次数。
默认值:1 animation:value;
animation-direction
指定是否应该轮流反向播放动画。
animation-fill-mode
规定当动画不播放时(当动画完成时,或当动画有一个延迟未开始播放时),要应用到元素的样式。
animation-play-state
指定动画是否正在运行或已暂停。
2、内置组件transition
name
- string,用于自动生成 CSS 过渡类名。例如:name: 'fade'
将自动拓展为 .fade-enter
,.fade-enter-active
等。默认类名为 "v"
appear
- boolean,是否在初始渲染时使用过渡。默认为 false
。css
- boolean,是否使用 CSS 过渡类。默认为 true
。如果设置为 false
,将只通过组件事件触发注册的 JavaScript 钩子。type
- string,指定过渡事件类型,侦听过渡何时结束。有效值为 "transition"
和 "animation"
。默认 Vue.js 将自动检测出持续时间长的为过渡事件类型。mode
- string,控制离开/进入过渡的时间序列。有效的模式有 "out-in"
和 "in-out"
;默认同时进行。duration
- number | { enter
: number, leave
: number } 指定过渡的持续时间。默认情况下,Vue 会等待过渡所在根元素的第一个 transitionend
或 animationend
事件。enter-class
- stringleave-class
- stringappear-class
- stringenter-to-class
- stringleave-to-class
- stringappear-to-class
- stringenter-active-class
- stringleave-active-class
- stringappear-active-class
- string
before-enter
before-leave
before-appear
enter
leave
appear
after-enter
after-leave
after-appear
enter-cancelled
leave-cancelled
(v-show
only)appear-cancelled
元素作为单个元素/组件的过渡效果。
只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中。
组件的实现是 export
出 一个对象,它会将预先设定好的 props
绑定到 transition
上,可以对transitionProps中定义的样式进行任意形式的重写。对于CSS过渡的,通过在适当的时机添加和移除transition class实现,对于js过渡的,只需要在适当的时间调用绑定注册的事件回调即可。export default {
name: 'transition',
props: transitionProps,
abstract: true,
render (h: Function) {
//render ...处理
}
}
export const transitionProps = {
name: String,
appear: Boolean,
css: Boolean,
mode: String,
type: String,
enterClass: String,
leaveClass: String,
enterToClass: String,
leaveToClass: String,
enterActiveClass: String,
leaveActiveClass: String,
appearClass: String,
appearActiveClass: String,
appearToClass: String,
duration: [Number, String, Object]
}
3、内置组件transition-group
tag
- string,默认为 span
move-class
- 覆盖移动过渡期间应用的 CSS 类。mode
,其他 attribute 和
相同。
相同。
元素作为多个元素/组件的过渡效果。
渲染一个真实的 DOM 元素。默认渲染 ,可以通过
tag
attribute 配置哪个元素应该被渲染。
的子节点必须有独立的 key,动画才能正常工作
支持通过 CSS transform 过渡移动。当一个子节点被更新,从屏幕上的位置发生变化,它会被应用一个移动中的 CSS 类 (通过 name
attribute 或配置 move-class
attribute 自动生成)。如果 CSS transform
property 是“可过渡”property,当应用移动类时,将会使用 FLIP 技术使元素流畅地到达动画终点。4、单元素/组件的过渡
v-if
)v-show
)4.1、过渡的类名
1)v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。--》"出现"开始的样子 2)v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。--》"出现"过程的定义(时间,延迟和曲线函数) 3)v-enter-to
:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter
被移除),在过渡/动画完成之后移除。-》"出现"最后的样子 1)v-leave
:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。--》"消失"开始的样子 2)v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。--》 "消失"过程的定义(时间,延迟和曲线函数) 3)v-leave-to
:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave
被删除),在过渡/动画完成之后移除。--》"消失"最后的样子4.2、CSS 过渡基本使用
4.3、CSS 动画基本使用
4.4、自定义过渡的类名
enter-class
enter-active-class
enter-to-class
(2.1.8+)leave-class
leave-active-class
leave-to-class
(2.1.8+)
4.5、同时使用过渡和动画
animation
很快的被触发并完成了,而 transition
效果还没结束。在这种情况中,你就需要使用 type
attribute 并设置 animation
或 transition
来明确声明你需要 Vue 监听的类型。什么意思呢?其实就是用type
类型的时间作为默认时间。
4.6、JavaScript 钩子
5、多个元素的过渡
key
attribute 设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在
组件中的多个元素设置 key 是一个更好的实践。
5.1、过渡模式
in-out
:新元素先进行过渡,完成之后当前元素过渡离开。 out-in
:当前元素先进行过渡,完成之后新元素过渡进入。
6、多个组件的过渡
key
attribute。相反,我们只需要使用动态组件。
7、列表过渡
v-for
?在这种场景中,使用
组件。在我们深入例子之前,先了解关于这个组件的几个特点:
,它会以一个真实元素呈现:默认为一个 。你也可以通过
tag
attribute 更换为其他元素。key
attribute 值。7.1、列表的进入/离开/排序过渡
元素作为多个元素/组件的过渡效果。可以通过 tag
attribute 配置哪个元素应该被渲染。简单的说:tag就是表示用何总元素进行包裹,默认使用span进行包裹。
组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。使动画显得更平缓,其内部的实现,Vue 使用了一个叫 FLIP 简单的动画队列,
使用 transforms 将元素从之前的位置平滑过渡到新的位置。要使用这个新功能只需了解新增的 v-move
class,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name
attribute 来自定义前缀,也可以通过 move-class
attribute 手动设置。v-move
对于设置过渡的切换时机和过渡曲线非常有用。display: inline
。作为替代方案,可以设置为 display: inline-block
或者放置于 flex 中。@Paul Lewis
提出的,FLIP是First、Last、Invert和Play四个单词首字母的缩写。FLIP 将一些性能低下的动画映射为 transform 动画。通过记录元素的两个快照,一个是元素的初始位置(First – F),另一个是元素的最终位置(Last – L),然后对元素使用一个 transform 变换来反转(Invert – I),让元素看起来还在初始位置,最后移除元素上的 transform 使元素由初始位置运动(Play – P)到最终位置。它就是通过这样一种高性能的方式来动态的改变DOM元素的位置和尺寸,而不需要管它的布局是如何计算或渲染的(比如,height、width、float、绝对定位、Flexbox和Grid等)。
7.2、列表的交错过渡
8、动态过渡和可复用过渡
name
attribute 来绑定动态值。当你想用 Vue 的过渡系统来定义的 CSS 过渡/动画在不同过渡间切换会非常有用。
或者
作为根组件,然后将任何子组件放置在其中就可以了。关于这部分可以在官网中查看(可复用的过渡、动态过渡)9、状态过渡
9.1、状态动画与侦听器
repeat
、repeatDelay
和yoyo
等等,
是指在两个独立对象之间创建过渡帧的过程。TweenLite包含了一些方法,这些方法可让您精确控制每个补间。随时播放,暂停,倒退和调整(速度)。gsap.timeline()
)之外,还有 TimelineLite()
、TimelineMax()
、 TweenLite
和 TweenMax
。
9.2、动态状态过渡
9.3、把过渡放到组件里
七、可复用性 & 组合
7.1、混入
hello!我是组件A
hello!我是组件B
7.1.1、基础
// 定义一个mixin 复用的代码 mixin
export const showMixin = {
data () {
return {
show: true,
msg: '我是mixin',
ol: '公共属性'
}
},
methods: {
toggleShow () {
this.show = !this.show
}
}
}
hello!我是组件A
hello!我是组件B
7.1.2、选项合并
hello!我是组件A
{{this.$data}}
7.1.3、全局混入
// 全局的mixin
Vue.mixin({
created () {
const myoption = this.$data.msg
if (myoption) {
console.log(myoption)
}
}
})
8、渲染函数 & JSX
8.1、渲染函数
render
函数,它比template
更接近编译器。Vue推荐使用模板来构建我们的应用界面,在底层实现中Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制。
JSX
就派上用场了。8.2、JSX
JSX
是一种 Javascript
的语法扩展,JSX = Javascript + XML
,即在 Javascript
里面写 XML
,因为 JSX
的这个特性,所以他即具备了 Javascript
的灵活性,同时又兼具 html
的语义化和直观性。这就是为什么会有一个 Babel 插件,用于在 Vue 中使用 JSX 语法,它可以让我们回到更接近于模板的语法上。
8.3、函数式组件
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文:data、props、slots、children 以及 parent 都可以通过 context 来访问
render: function (createElement, context) {
// ...
}
})
props
:提供所有 prop 的对象children
:VNode 子节点的数组slots
:一个函数,返回了包含所有插槽的对象scopedSlots
:(2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。data
:传递给组件的整个数据对象,作为 createElement
的第二个参数传入组件parent
:对父组件的引用listeners
:(2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on
的一个别名。injections
:(2.3.0+) 如果使用了 inject
选项,则该对象包含了应当被注入的 property。function createComponent (
Ctor,
data,
context,
children,
tag
) {
if (isUndef(Ctor)) {
return
}
var baseCtor = context.$options._base;
// ...中间省略N行
// functional component
if (isTrue(Ctor.options.functional)) { // 在此判断是否是函数式组件,如果是return 自定义render函数返回的Vnode,跳过底下初始化的流程
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// ...中间省略N行
// install component management hooks onto the placeholder node
installComponentHooks(data); // 正常的组件是在此进行初始化方法(包括响应数据和钩子函数的执行)
// return a placeholder vnode
var name = Ctor.options.name || tag;
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
return vnode
}
默认头像:
正常头像(方式一):
正常头像(方式二):// 在 2.5.0 及以上版本中,如果你使用了单文件组件,那么基于模板的函数式组件可以这样声明:
9、插件
Vue 插件
和 Vue组件
只是在 Vue.js
中包装的两个概念而已,不管是插件还是组件,最终目的都是为了实现逻辑复用。它们的本质都是对代码逻辑的封装,只是封装方式不同而已。在必要时,组件也可以封装成插件,插件也可以改写成组件,就看实际哪种封装更方便使用了。除此之外,插件是全局的,组件可以全局注册也可以局部注册。插件就是对Vue
功能的增强或补充。Vue.prototype
上实现。9.1、认识插件
install
方法。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象。MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
9.2、插件的使用
Vue.use()
使用插件。它需要在你调用 new Vue()
启动应用之前完成。语法:// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
//步骤1:安装
npm install -S vue-carousel-3d
//步骤2:注册插件
import Vue from 'vue'
import Carousel3d from 'vue-carousel-3d'
Vue.use(Carousel3d)
//步骤3:使用插件
9.3、插件的开发
Vue
功能的增强或补充,可以添加全局方法、全局资源、混入选项,也可以同时注册多个组件或功能。
import LoadingComponent from './loading.vue'
let Loading = {}
Loading.install = (Vue) => {
// 当然这里也可以做多个全局组件注册
Vue.component('loading', LoadingComponent)
}
export default Loading
import load from './components/aloading/index'
Vue.use(load)
import ToastComponent from './mytoast.vue'
const Toast = {}
// 注册Toast
Toast.install = function (Vue) {
// 生成一个Vue的子类
// 同时这个子类也就是组件
const ToastConstructor = Vue.extend(ToastComponent)
// 生成一个该子类的实例
const instance = new ToastConstructor()
// 将这个实例挂载在我创建的div上
// 并将此div加入全局挂载点内部
instance.$mount(document.createElement('div'))
document.body.appendChild(instance.$el)
// 通过Vue的原型注册一个方法
// 让所有实例共享这个方法
Vue.prototype.$toast = (msg, duration = 2000) => {
instance.message = msg
instance.show = true
setTimeout(() => {
instance.show = false
}, duration)
}
}
export default Toast
import Toast from './components/acj/index'
Vue.use(Toast)
10、过滤器
八、路由