1.介绍及安装
1.1 介绍
Vue是一套构建用户界面的渐进式框架。Vue只关注视图层,采用自底向上增量开发的设计。Vue的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
学习vue之前主要掌握的知识:HTML、CSS、JavaScript、TypeScript
兼容性说明:Vue 不支持 IE8 及其以下版本,因为 Vue 使用了 IE8 不能模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器。
1.2 安装
1.2.1 安装NodeJS
官网地址下载安装 Download | Node.js
1.2.2 安装npm
npm是随同NodeJS一起安装的包管理工具,安装nodejs后,就无需安装,但是npm速度会慢一些,推荐使用淘宝镜像及其命令cnpm,使用如下命令安装淘宝镜像
npm install -g cnpm --registry=https://registry.npmmirror.com
cnpm -v 查看安装的淘宝镜像版本
1.2.3 安装vue
执行如下命令,安装vue的最新稳定版本
npm install vue
安装指定版本的vue
npm install vue@版本号
比如要安装2.6.12版本的vue,执行命令 npm install vue@2.6.12
1.2.4 安装vue命令行工具(CLI)
npm install --global vue-cli
详细使用说明参见:介绍 | Vue CLI
1.3 项目实例
1.3.1 项目创建
cmd cd命令到需要创建项目的目录下,执行 vue create 项目名称 创建vue项目
在F磁盘下myVue文件夹下创建项目名称为bookmanage
注:项目名称不能包含大写字母
回车,默认Vue2项目模板,然后回车,等待项目初始化完成
使用VSCode打开项目
执行命令npm run serve,运行项目
文件目录说明
- node_modules:项目依赖文件夹
- public:一般放置一些静态资源(图片),需要注意,放在public文件夹中的静态资源,webpack进行打包的时候会原封不动打包到dist文件夹中。
- src(程序员源代码文件夹):
- asstes:一般也是放置静态资源(一般放置多个组件共用的静态资源),需要注意,放置在asstes文件夹里面的静态资源,在webpack打包的时候,webpack会把静态资源当作一个模块,打包到js文件里面。
- components:一般放置非路由组件(全局组件)
- App.vue:唯一的根组件
- main.js:程序入口文件,也是整个程序当中最先执行的文件
- babel.config.js:配置文件(babel相关)
- package.json:记录项目做什么,有哪些依赖、项目怎么运行
- package-lock.json:缓存性文件
1.3.2 CLI服务命令
在一个 Vue CLI 项目中,@vue/cli-service 安装了一个名为 vue-cli-service 的命令。你可以在 npm scripts 中以 vue-cli-service、或者从终端中以 ./node_modules/.bin/vue-cli-service 访问这个命令。
这是你使用默认 preset 的项目的 package.json:
你可以通过 npm 或 Yarn 调用这些 script:
npm run serve
# OR
yarn serve
1.3.2.1 vue-cli-service serve
用法:vue-cli-service serve [options] [entry]
选项:
--open 在服务器启动时打开浏览器
--copy 在服务器启动时将 URL 复制到剪切版
--mode 指定环境模式 (默认值:development)
--host 指定 host (默认值:0.0.0.0)
--port 指定 port (默认值:8080)
--https 使用 https (默认值:false)
vue-cli-service serve 命令会启动一个开发服务器 (基于 webpack-dev-server) 并附带开箱即用的模块热重载 (Hot-Module-Replacement)。
除了通过命令行参数,你也可以使用 vue.config.js 里的 devServer 字段配置开发服务器。
命令行参数 [entry] 将被指定为唯一入口 (默认值:src/main.js,TypeScript 项目则为 src/main.ts),而非额外的追加入口。尝试使用 [entry] 覆盖 config.pages 中的 entry 将可能引发错误
1.3.2.2 vue-cli-service build
用法:vue-cli-service build [options] [entry|pattern]
选项:
--mode 指定环境模式 (默认值:production)
--dest 指定输出目录 (默认值:dist)
--modern 面向现代浏览器带自动回退地构建应用
--target app | lib | wc | wc-async (默认值:app)
--name 库或 Web Components 模式下的名字 (默认值:package.json 中的 "name" 字段或入口文件名)
--no-clean 在构建项目之前不清除目标目录的内容
--report 生成 report.html 以帮助分析包内容
--report-json 生成 report.json 以帮助分析包内容
--watch 监听文件变化
vue-cli-service build 会在 dist/ 目录产生一个可用于生产环境的包,带有 JS/CSS/HTML 的压缩,和为更好的缓存而做的自动的 vendor chunk splitting。它的 chunk manifest 会内联在 HTML 里。
这里还有一些有用的命令参数:
- --modern 使用现代模式构建应用,为现代浏览器交付原生支持的 ES2015 代码,并生成一个兼容老浏览器的包用来自动回退。
- --target 允许你将项目中的任何组件以一个库或 Web Components 组件的方式进行构建。更多细节请查阅构建目标。
- --report 和 --report-json 会根据构建统计生成报告,它会帮助你分析包中包含的模块们的大小。
1.3.2.3 vue-cli-service inspect
用法:vue-cli-service inspect [options] [...paths]
选项:
--mode 指定环境模式 (默认值:development)
你可以使用 vue-cli-service inspect 来审查一个 Vue CLI 项目的 webpack config。
1.3.2.4 缓存和并行处理
- cache-loader 会默认为 Vue/Babel/TypeScript 编译开启。文件会缓存在 node_modules/.cache 中——如果你遇到了编译方面的问题,记得先删掉缓存目录之后再试试看。
- thread-loader 会在多核 CPU 的机器上为 Babel/TypeScript 转译开启。
1.3.3 开发基础配置
1.3.3.1 vue.config.js
vue.config.js是vue项目的配置文件,专注于vue项目。使用脚手架安装项目的时候并没有创建vue.config.js,所有一般是需要修改webpack的时候才会自己创建一个vue.config.js,然后就可以修改默认的webpack。
配置说明参见:https://cli.vuejs.org/zh/guide/webpack.html
module.exports ={
lintOnSave: true, // 关闭eslint 默认是true
productionSourceMap: false, // 不需要生产时的源映射(即.js.map文件不会生成) 默认是true
// 部署应用包时的基本URL。 默认是/ 。如应用被部署在 https://www.my-app.com/my-app/,则设置publicPath为/my-app/
publicPath: process.env.NODE_ENV === 'production' ? '/' : '/', // ./是当前目录 ; /是根目录
outputDir: 'dist', // 默认dist 所以这句可以注释掉
assetsDir: "static",// 项目打包的静态资源存放目录,默认 "static"
indexPath: "index.html",// 项目打包的index.html输出路径,默认 "index.html"
pages: undefined,// 多页应用配置参数,默认 undefined
// 配置代理跨域
devServer: {
open: true, //启动项目自动弹出浏览器
port: 5200, // 开发运行时的端口
https: false, // 是否启用 https
host: '0.0.0.0', // 默认localhost 设置成'0.0.0.0',在同一个局域网下,可以通过http://ip:port/...访问项目
proxy: { // 拦截所有api开头的请求,请求时删除api(根据自己实际情况来)
"^/api/.*": {
target: 'http://127.0.0.1:8090', // 服务器接口地址
changeOrigin: true, // 是否跨域
rewrite: (path) => path.replace(/^\/api/, ''),
}
}
},
// css全局配置 参见 https://cli.vuejs.org/zh/guide/css.html#css-modules
//特别说明:配置前先安装好需要的依赖;如:“sass-loader”: “^10.2.0”, “scss”: “^0.2.4”,安装的如果版本不一致会有报错 参见 https://www.npmjs.com/package/node-sass 查看nodejs与node-sass对应版本
//如果使用scss/sass设置,那么需要在每个vue文件中,需要这样写<style lang="scss"></style> 全局样式才会生效
/**
* sass-loader与node-sass对应版本号
* sass-loader 4.1.1,node-sass 4.3.0
sass-loader 7.0.3,node-sass 4.7.2
sass-loader 7.3.1,node-sass 4.7.2
sass-loader 7.3.1,node-sass 4.14.1
sass-loader 10.0.1,node-sass 6.0.1
*/
css: {
// 是否将组件中的 CSS 提取至一个独立的 CSS 文件中、生产环境下默认 true,开发环境下默认 false
extract: true,
// 是否开启 css 的 source map 调试,默认 false
sourceMap: false,
//向 CSS 相关的 loader 传递选项(支持 css-loader postcss-loader sass-loader less-loader stylus-loader) 默认{}
loaderOptions: {
scss:{
// v8 以前的写法 data: `@import @/assets/css/index.scss;`,
// v8 以后的写法prependData: `@import @/assets/css/index.scss;`,
// v10 以后的写法
additionalData: `@import "@/assets/css/common.scss";`// v8中,这个选项名是 "prependData"
},
// sass:{
// additionalData: `@import "@/assets/css/common.scss";`// v8中,这个选项名是 "prependData"
// }
}
},
//新增/修改 webpack 的 plugins 或者rules 的简单配置方式使用configureWebpack
//参见 https://cli.vuejs.org/zh/guide/webpack.html
configureWebpack:config=>{
console.log(config);
if(process.env.NODE_ENV==="development"){
//开发换进改配置
}
else if(process.env.NODE_ENV==="test"){
//测试环境配置
}
else if(process.env.NODE_ENV==="production"){
//生产环境配置
}
},
//新增/修改 webpack 的 plugins 或者rules 的链式操作 (高级)方式使用chainWebpack
//参见 https://cli.vuejs.org/zh/guide/webpack.html
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
// 修改它的选项...
return options;
});
}
}
1.3.3.2 模式和环境变量
模式
模式是 Vue CLI 项目中一个重要的概念。默认情况下,一个 Vue CLI 项目有三个模式:
- development 模式用于 vue-cli-service serve
- test 模式用于 vue-cli-service test:unit
- production 模式用于 vue-cli-service build 和 vue-cli-service test:e2e
你可以通过传递 --mode 选项参数为命令行覆写默认的模式。例如,如果你想要在构建命令中使用开发环境变量:
vue-cli-service build --mode development
当运行 vue-cli-service 命令时,所有的环境变量都从对应的环境文件中载入。如果文件内部不包含 NODE_ENV 变量,它的值将取决于模式,例如,在 production 模式下被设置为 "production",在 test 模式下被设置为 "test",默认则是 "development"。
NODE_ENV 将决定您的应用运行的模式,是开发,生产还是测试,因此也决定了创建哪种 webpack 配置。
例如通过将 NODE_ENV 设置为 "test",Vue CLI 会创建一个优化过后的,并且旨在用于单元测试的 webpack 配置,它并不会处理图片以及一些对单元测试非必需的其他资源。
同理,NODE_ENV=development 创建一个 webpack 配置,该配置启用热更新,不会对资源进行 hash 也不会打出 vendor bundles,目的是为了在开发的时候能够快速重新构建。
当你运行 vue-cli-service build 命令时,无论你要部署到哪个环境,应该始终把 NODE_ENV 设置为 "production" 来获取可用于部署的应用程序。
NODE_ENV
如果在环境中有默认的 NODE_ENV,你应该移除它或在运行 vue-cli-service 命令的时候明确地设置 NODE_ENV。
环境变量
你可以在你的项目根目录中放置下列文件来指定环境变量:
.env # 在所有的环境中被载入
.env.local # 在所有的环境中被载入,但会被 git 忽略
.env.[mode] # 只在指定的模式中被载入
.env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略
一个环境文件只包含环境变量的“键=值”对:
FOO=bar
VUE_APP_NOT_SECRET_CODE=some_value
警告
不要在你的应用程序中存储任何机密信息(例如私有 API 密钥)!
环境变量会随着构建打包嵌入到输出代码,意味着任何人都有机会能够看到它。
请注意,只有 NODE_ENV,BASE_URL 和以 VUE_APP_ 开头的变量将通过 webpack.DefinePlugin 静态地嵌入到客户端侧的代码中。这是为了避免意外公开机器上可能具有相同名称的私钥。
想要了解解析环境文件规则的细节,请参考 dotenv。我们也使用 dotenv-expand 来实现变量扩展 (Vue CLI 3.5+ 支持)。例如:
FOO=foo
BAR=barCONCAT=$FOO$BAR # CONCAT=foobar
被载入的变量将会对 vue-cli-service 的所有命令、插件和依赖可用。
环境文件加载优先级
为一个特定模式准备的环境文件 (例如 .env.production) 将会比一般的环境文件 (例如 .env) 拥有更高的优先级。
此外,Vue CLI 启动时已经存在的环境变量拥有最高优先级,并不会被 .env 文件覆写。
.env 环境文件是通过运行 vue-cli-service 命令载入的,因此环境文件发生变化,你需要重启服务。
模式和环境变量配置示例
在实际开发过程中我们可能需要开发环境、测试环境和生产环境对应三个不同环境的地址,这里我们在项目根目录中新增.env、.env.development、.env.test、.env.production三个文件,分别对应所有环境、开发、测试、生产不同模式下的环境变量配置
.env
NODE_ENV=development
VUE_APP_API_URL= 'http://127.0.0.1:8080/' // 开发接口域
.env.development
NODE_ENV=development
VUE_APP_API_URL= 'http://127.0.0.1:8080/' // 开发接口域
.env.test
NODE_ENV=test
VUE_APP_API_URL= 'http://127.0.0.1:8089/' // 开发接口域
.env.production
NODE_ENV=production
VUE_APP_API_URL= 'http://127.0.0.1:9095/' // 开发接口域
然后在package.json中的scrips中配置service命令
分别执行 npm run serve 和npm run test命令,页面中显示process.env.NODE_ENV值的效果
1.3.3.3 路由基础配置
Vue路由是指根据url分配到对应的处理程序;作用就是解析URL,调用对应的控制器的方法,并传递参数。Vue路由有助于在浏览器的URL或历史记录与Vue组件之间建立链接,从而允许某些路径渲染与之关联的任何一个视图。
$route:一般获取路由信息【路径、query、params等等】
$router:一般进行编程式导航进行路由跳转【push|replace】
项目中创建router.js文件,创建路径src/router/router.js
import Vue from 'vue';//引入vue
import VueRouter from 'vue-router';//引入vue-router
Vue.use(VueRouter)//第三方库需要use一下才能用
//定义routes路由的集合,数组类型
//路由懒加载: 当打包构建应用时,JavaScript包会变得非常大,影响页面加载。如果能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件。
const routes = [
//路由配置
// { path: '/login', component: ()=> import('@/views/login/index.vue')},
// { path: '/index', component: ()=> import('@/views/home/index.vue')}
];
//实例化 VueRouter 并将 routes 添加进去
const router = new VueRouter({
//ES6简写,等于routes:routes
routes
});
//抛出这个这个实例对象方便外部读取以及访问
export default router;
然后在main.js中引入路由js文件
import Vue from 'vue'
import App from './App.vue'
import router from './router/router.js'//引用router.js
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
1.3.3.4 全局样式设置
样式文件包含sass、scss、css、less等,全局样式的设置方式如下:
1)main.js中引入
import '@/assets/css/common.scss' // 全局样式
2)vue.config.js 中配置
详情参见 1.3.3.1 vue.config.js中的关于全局css配置的相关注释说明及代码
2.基础知识汇总
2.1 应用实例
每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的:
import Vue from 'vue'//引入vue
var vm = new Vue({
// 选项
})
选项参数包含根组件、路由等的配置,我们可以在入口程序main.js中的代码中看出,在vue实例中调用根组件App.vue,根组件中调用子组件
main.js中的代码:
根组件App.Vue中的代码:
2.1.1 生命周期
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数
挂载阶段:beforeCreate、created、beforeMount、mounted
更新阶段:beforeUpdate、updated
销毁阶段:beforeDestroy、destroyed
- beforeCreate:在该函数执行时Vue实例仅仅完成自身事件绑定和生命周期函数的初始化工作,Vue实例中还没有Data,el,methods相关属性
- created:在该函数执行时Vue实例已经初始化data属性和methods中相关方法,可以在此阶段调取后端数据了
- beforeMount:在该函数进行模板编译,生成虚拟dom
- mounted:在该函数将数据渲染到页面上,生成真实dom
- beforeUpdate:数据更新时触发该函数,该函数执行时Vue实例中data数据变化,但页面数据仍为旧数据,即data数据与页面数据不同步
- updated:数据更新触发,但时机点位于beforeUpdated后,此时页面数据与Vue实例中data数据能够保持一致
- beforeDestroy:该函数执行时,Vue实例中所有数据、methods、component还未销毁
- destroyed:该函数执行时,Vue实例彻底销毁
钩子函数常见的使用场景
1)推荐在created()中发送请求,对服务端接口进行调用,此时data数据已经创建,可以获取服务端数据对其进行赋值,相比beforeMount()和mounted(),created()能更快获取到服务端数据,减少页面loading 时间;
2)在 mounted()中,Vue将编译好的模板挂在到页面上,此时为真实DOM,可以对DOM进行操作。此时可以做启动定时器、绑定自定义事件、订阅消息等初始化操作。
3)beforeDestory()可进行清除定时器、解绑自定义事件、取消消息订阅等操作
2.2 模板语法
Vue 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。
Vue的核心是一个允许你采用简洁的模板语法来声明式的将数据渲染进 DOM 的系统。
结合响应系统,在应用状态改变时, Vue 能够智能地计算出重新渲染组件的最小代价并应用到 DOM 操作上。
2.2.1 插值
2.2.1.1 文本
数据绑定最常见的形式就是使用 {{...}}(双大括号)的文本插值
通过使用v-once指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定
<template>
<div>
<h1>{{title}}</h1>
<p v-once>{{titleOnce}}</p>
<button @click="changeOnce">改变v-once指令绑定的数据</button>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
title:'欢迎来到vue2的世界',
titleOnce:"使用v-once指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新"
}
},
methods:{
changeOnce(){
this.titleOnce="使用v-once指令";
console.log(this.titleOnce);
}
}
}
</script>
<style scoped>
h1 {
color: red;
}
</style>
页面输出效果:
2.2.1.2 纯HTML
双大括号将会将数据插值为纯文本,而不是 HTML。若想插入 HTML,你需要使用v-html指令
<template>
<div>
<h2>HTML</h2>
<div v-html="htmlTxt"></div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
htmlTxt:"<p>你好v-html</p><p>这是一个纯html的插值语句</p>"
}
},
methods:{
}
}
</script>
<style scoped>
</style>
页面输出效果:
2.2.1.3 属性
双大括号不能在 HTML attributes 中使用,相应的,应该使用v-bind指令
注:v-bind可省略不写,直接:属性值,比如绑定id可以这样写:id="dynamicId"
<template>
<div>
<!-- v-bind 绑定id 如果绑定的值是null或者undefined,那么该 attribute 将会从渲染的元素上移除-->
<div v-bind:id="dynamicId" v-html="htmlTxt"></div>
<!-- 布尔值的属性绑定,比如disabled 如果 isButtonDisabled 的值是 null、undefined 或 false,则 disabled attribute 甚至不会被包含在渲染出来的 <button> 元素中-->
<button v-bind:disabled="isDisabled">disabled绑定</button>
<!-- 动态绑定多个值 -->
<div v-bind="multiAttr">动态绑定多个值</div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
dynamicId:'div2',
isDisabled:true,
htmlTxt:"<p>你好v-html</p><p>这是一个纯html的插值语句</p>",
multiAttr:{
id:"divMulti",
style:"font-size:50px",
class:"txt1"
}
}
},
methods:{
}
}
</script>
<style scoped>
h1 {
color: red;
}
</style>
2.2.1.4 表达式
对于所有的数据绑定,Vue都提供了完全的 JavaScript 表达式支持
在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
- 在文本插值中(双大括号)。
- 在任何 Vue 指令attribute(以v-开头的特殊 attribute)的值中。
表达式会被作为 JavaScript ,以组件为作用域,解析执行
<template>
<div>
<!--数字计算-->
<div>当前数值是:{{num+1}}</div>
<!--三元运算符-->
<div>当前状态是否ok:{{isOk?'是':'否'}}</div>
<!--split-->
<div>姓氏:{{message.split(',').join(' ')}}</div>
<!-- 模板语句-->
<div :id="`list-${id}`">动态绑定id</div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
num:15,
isOk:true,
message:"赵,钱,孙,李",
id:1
}
},
methods:{
}
}
</script>
<style scoped>
</style>
运行输出结果:
在绑定的表达式中,可以使用一个组件暴露的方法,调用函数
绑定在表达式中的方法,在组件每次更新时,都会被重新调用,因此不应该产生任何effect(副作用)。比如改变数据或触发异步操作。
<template>
<div>
<!--绑定的表达式中调用函数-->
<span :title="toTitleDate()">{{dateToStr()}}</span>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
currDate:new Date()
}
},
methods:{
toTitleDate(){
return this.currDate.toString();
},
dateToStr(){
return this.currDate.getFullYear()+"年"+(this.currDate.getMonth()+1)+"月"+this.currDate.getDate()+"日";
}
}
}
</script>
<style scoped>
</style>
运行效果:
注意事项
1)在绑定的表达式中,不支持语句 ,以下写法都是无效的
<!-- 这是一个语句,而非表达式 --> {{ var a = 1 }} <!-- 条件控制同样不会工作,请使用三元表达式 --> {{ if (ok) { return message } }}
2)受限的全局访问
模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中,会暴露常用的内置全局对象,比如Math和Date。
没有显式包含在列表中的全局对象,将不能在模板内表达式中访问。例如,用户附加在window上的 property。然而,你也可以自行在app.config.globalProperties上显式地添加他们,供所有的 Vue 表达式使用。
2.2.2 指令
指令是带有v-前缀的特殊 attribute。指令 attribute 的期望值,为一个 JavaScript 表达式(v-for、v-on和v-slot将会是例外)。使用指令是为了,在其表达式值变化时,响应式的更新 DOM。
以v-if为例:
<template>
<div>
<!--v-ifv-if 指令将根据表达式 seen 的值(true 或 false )来决定是否插入span元素-->
<span v-if="seen">v-if指令</span>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
seen:true
}
},
methods:{
}
}
</script>
<style scoped>
</style>
2.2.2.1 参数
一些指令能够接收一个“参数”,在指令名称之后以:
(冒号)隔开,做标识。例如,v-bind指令可以用于响应式地更新 HTML attribute:
<a v-bind:href="url"> ... </a>
<!-- 缩写 -->
<a :href="url"> ... </a>
这里href
就是一个参数,它告诉v-bind指令,将表达式url的值,绑定到元素的href
attribute 上。在缩写中,参数前的一切(例如v-bind:
)都会被缩略为一个:
字符。
另一个例子是v-on指令,它用于监听 DOM 事件:
<a v-on:click="doSomething"> ... </a>
<!-- 缩写 -->
<a @click="doSomething"> ... </a>
这里的参数是要监听的事件名称:click
。v-on
也是少部分含有缩写的指令之一,缩写字符为@
2.2.2.2 动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<!--注意,参数表达式有一些约束,参见下面“动态参数表达式约束”一节的解释。-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 缩写 -->
<a :[attributeName]="url"> ... </a>这里的attributeName会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的组件实例有一个data property attributeName,其值为"href",那么这个绑定将等价于v-bind:href。
同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 缩写 -->
<a @[eventName]="doSomething">
在这个示例中,当eventName的值为"focus"时,v-on:[eventName]将等价于v-on:focus
动态参数值限制:动态参数预期会求出一个字符串,异常情况下值为 null
。这个特殊的 null
值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告
动态参数表达式限制
动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:
<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>
如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式
当使用DOM 内嵌模板(直接写在 HTML 文件里的模板)时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写:
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
--><a :[someAttr]="value"> ... </a>
2.2.2.3 修饰符
修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():
<form v-on:submit.prevent="onSubmit">...</form>
2.2.3 过滤器
vue允许你自定义过滤器,被用作一些常见的文本格式化。由"管道符"指示, 格式如下:
<!-- 在两个大括号中 -->
{{ message | capitalize }}<!-- 在 v-bind 指令中 -->
<div v-bind:id="rawId | formatId"></div>
过滤器函数接受表达式的值作为第一个参数
以下示例代码中,将字符串转换为大写字符:
<template>
<div>
<!--字符转大写-->
<span>{{message|toUp}}</span>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
message:"vue"
}
},
methods:{
},
//过滤器
filters:{
toUp:function(value){
return value.toUpperCase();
}
}
}
</script>
<style scoped>
</style>
过滤器可以串联:
{{ message | filterA | filterB }}
过滤器是 JavaScript 函数,因此可以接受参数:{{ message | filterA('arg1', arg2) }}
这里,message 是第一个参数,字符串 'arg1' 将传给过滤器作为第二个参数, arg2 表达式的值将被求值然后传给过滤器作为第三个参数。
2.3 样式绑定
数据绑定的一个常见需求场景是操纵元素的CSS类列表和内联样式。因为它们都是attribute,我们可以使用v-bind来做这件事:我们只需要通过表达式计算出一个字符串作为最终结果即可。然而频繁操作字符串连接很闹心,也很容易出错的。因此 Vue 为class和style的v-bind使用提供了特殊的功能增强。除了字符串外,表达式的结果还可以是对象或数组。
2.3.1 绑定class类
2.3.1.1 对象方式
<template>
<div>
<!--是否绑定class 类名称active,取决于isActive的真假值-->
<!--渲染效果<div class="active"></div>-->
<div :class="{active:isActive}">根据字段值true/false绑定calss</div>
<!--多字段来动态切换多个class-->
<!--hasError为false时的渲染效果 <div class="static active"></div>-->
<!--hasError为true时的渲染效果 <div class="static active text-danger"></div>-->
<div class="static" :class="{active:isActive,'text-danger': hasError }">多字段来动态切换多个class</div>
<!--绑定数据对象来动态切换多个class-->
<!--text-danger为false时的渲染效果 <div class="active"></div>-->
<!--text-danger为true时的渲染效果 <div class="active text-danger"></div>-->
<div :class="classObject">绑定数据对象来动态切换多个class</div>
<!--通过计算属性来动态切换多个class-->
<div :class="classCal">通过计算属性来动态切换多个class</div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
isActive:true,
hasError:false,
classObject:{
active:true,
'text-danger':false
}
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
classCal:function(){
return {
active: this.isActive && !this.hasError,
'text-danger': this.hasError
}
}
}
}
</script>
<style scoped>
.active{
color: purple;
font-weight: 700;
}
.static{
font-size: 30px;
}
.text-danger{
color: red;
}
</style>
渲染效果:
2.3.2.1 数组方式
<template>
<div>
<!--绑定数组的方式动态切换多个class-->
<!--渲染效果<div class="active static"></div>-->
<div :class="[activeClass,staticClass]">绑定数组的方式动态切换多个class</div>
<!--根据条件来动态切换多个class,可以用三元表达式-->
<div :class="[activeClass,hasError?dangerClass:'']">根据条件来动态切换多个class</div>
<!--当判断条件较繁琐的时候,数组中使用对象的方式来动态切换多个class-->
<div :class="[{active:isActive},dangerClass]">数组中使用对象的方式来动态切换多个class</div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
isActive:true,
hasError:false,
activeClass:"active",
staticClass:"static",
dangerClass:"text-danger"
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
}
}
</script>
<style scoped>
.active{
color: purple;
font-weight: 700;
}
.static{
font-size: 30px;
}
.text-danger{
color: red;
}
</style>
渲染效果:
2.3.3.2 组件绑定calss
当在一个自定义组件上使用 class property 时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖
比如使用HelloWorld的组件,v-bind:class方式同样适用
<HelloWorld msg="Welcome to Your Vue.js App" :class="active:isActive"/>
2.3.2 绑定内联样式
2.3.2.1 对象方式
v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名
<template>
<div>
<!--对象方式设置内联样式-->
<div :style="{ color:'red', fontSize:'30px' }">对象方式设置内联样式</div>
<!--样式对象设置内联样式-->
<div :style="styleObject">样式对象设置内联样式</div>
<!--计算属性设置内联样式-->
<div :style="styleCal">计算属性设置内联样式</div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
styleObject:{
color:'purple',
fontSize:'20px',
fontWeight:600
},
isActive:true,
haError:false
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
styleCal:function(){
return{
color:this.haError?'red':'purple',
fontSize:this.isActive?'30px':'20px'
}
}
}
}
</script>
<style scoped>
</style>
渲染效果:
2.3.2.2 数组方式
内联方式的数组是数组对象
<template>
<div>
<!--数组方式设置内联样式-->
<div :style="[styleColor,styleSize]">数组方式设置内联样式</div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
styleColor:{color:'purple'},
styleSize:{fontSize:'20px'}
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
}
}
</script>
<style scoped>
</style>
渲染效果:
2.3.2.3 自动添加前缀
当你在:style中使用了需要浏览器特殊前缀的 CSS 属性时,Vue 会自动为他们加上相应的前缀。Vue 是在运行时检查该属性是否支持在当前浏览器中使用。如果浏览器不支持某个属性,那么将测试加上各个浏览器特殊前缀,以找到哪一个是被支持的
如 transform
2.3.2.4 多重值
从 2.3.0 起你可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex
2.4 条件语句
2.4.1 v-if
条件语句包含:
- v-if:v-if 指令用于条件性地渲染一块内容,这块内容只会在指令的表达式返回 truthy 值的时候被渲染
- v-else-if:表示v-if的“else-if 块”
- v-esle:表示 v-if 的“else 块”,v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别
注意:不推荐同时使用 v-if 和 v-for,当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级。
<template>
<div>
<!--v-if 和v-esle-->
<div v-if="isSuperAdmin">
<span>大家好,我是超级管理员</span>
</div>
<div v-else>
<span>大家好,我普通用户</span>
</div>
<!--v-if 和v-esle-if 、v-else-->
<div v-if="loginType==='admin'">
<span>大家好,我管理员角色</span>
</div>
<div v-else-if="loginType==='superadmin'">
<span>大家好,我超级管理员角色</span>
</div>
<div v-else-if="loginType==='incharge'">
<span>大家好,我主管角色</span>
</div>
<div v-else>
<span>大家好,我是普通用户角色</span>
</div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
loginType:"user",
isSuperAdmin:true
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
}
}
</script>
<style scoped>
</style>
渲染效果:
2.4.2 v-show
另一个用于根据条件展示元素的选项是 v-show 指令
不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中,v-show 只是简单地切换元素的 CSS property display。
注意:v-show 不支持 <template> 元素,也不支持 v-else。
<template>
<div>
<!--v-show-->
<div v-show="isSuperAdmin">
<span>大家好,我是超级管理员</span>
</div>
<div v-show="!isSuperAdmin">
<span>大家好,我是普通用户</span>
</div>
<!--v-if 和v-esle-if 、v-else-->
<div v-show="loginType==='admin'">
<span>大家好,我是管理员角色</span>
</div>
<div v-show="loginType==='superadmin'">
<span>大家好,我是超级管理员角色</span>
</div>
<div v-show="loginType==='incharge'">
<span>大家好,我是主管角色</span>
</div>
<div v-show="loginType==='user'">
<span>大家好,我是普通用户角色</span>
</div>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
loginType:"user",
isSuperAdmin:true
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
}
}
</script>
<style scoped>
</style>
渲染效果:
2.4.3 v-if和v-show对比
- v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
- v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
- 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
- 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
2.5 循环语句
循环语句使用v-for指令
循环语法
<!--v-for in遍历-->
v-for="item in items"
<!--v-for of遍历-->
v-for="item of items"
2.5.1 遍历数组和对象
v-for 可遍历数组和对象,在 v-for
块中,我们可以访问所有父作用域的 property
遍历数组,支持可选的第二个参数(当前项的索引)
<!--v-for in遍历-->
v-for="item in items"
<!--v-for in遍历 可选参数 当前索引-->
v-for="(item,index)in items"
遍历对象支持可选的第二个参数(键名),还支持 可选的第三个参数(当前索引)
<!--v-for in遍历-->
v-for="value in object"
<!--v-for in遍历 可选参数 键名-->
v-for="(value,name) in object"
<!--v-for in遍历 可选参数 键名、当前索引-->
v-for="(value,name,index) in object"
注意:v-for 遍历时,一定要绑定元素key值,否则编译报错 :key="item"
在遍历对象时,会按 Object.keys()
的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。
<template>
<div>
<h2>v-for遍历数组</h2>
<!--v-for遍历数组-->
<ul v-for="item in studentInfos" :key="item">
<li>{{item.studentName}}</li>
</ul>
<h2>v-for遍历数组——元素值和当前索引参数</h2>
<!--v-for遍历数组-->
<ul v-for="(item,index) in studentInfos" :key="index">
<li>{{index}}.{{item.studentName}}</li>
</ul>
<h2>v-for遍历对象</h2>
<ul v-for="value of house" :key="value">
<li>{{value}}</li>
</ul>
<h2>v-for遍历对象——值和键名参数</h2>
<ul v-for="(value,name) of house" :key="value">
<li>{{name}}:{{value}}</li>
</ul>
<h2>v-for遍历对象——值、键名、当前索引参数</h2>
<ul v-for="(value,name,index) of house" :key="value">
<li>{{index}}、{{name}}:{{value}}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
studentInfos:[
{studentName:"马云",age:18,gender:"男",classNo:"高三1班"},
{studentName:"马化腾",age:17,gender:"男",classNo:"高三2班"},
{studentName:"董明珠",age:15,gender:"女",classNo:"高三3班"},
{studentName:"郎朗",age:18,gender:"男",classNo:"高三4班"},
{studentName:"高峰",age:19,gender:"男",classNo:"高三5班"}
],
house:{
door:"white door",
roomNum:3,
area:100,
address:"常州市新北区太湖东路世茂香槟湖"
}
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
}
}
</script>
<style scoped>
ul{
display:flex;
margin-left: 45%;
}
</style>
渲染效果:
2.5.2 遍历整数
可以直接传给v-for
一个整数值。在这种用例中,将会将该模板基于1...n
的取值范围重复多次
<template>
<div>
<h2>v-for遍历整数</h2>
<!--v-for遍历数组-->
<ul v-for="n in 10" :key="n">
<li>{{n}}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
studentInfos:[
{studentName:"马云",age:18,gender:"男",classNo:"高三1班"},
{studentName:"马化腾",age:17,gender:"男",classNo:"高三2班"},
{studentName:"董明珠",age:15,gender:"女",classNo:"高三3班"},
{studentName:"郎朗",age:18,gender:"男",classNo:"高三4班"},
{studentName:"高峰",age:19,gender:"男",classNo:"高三5班"}
],
house:{
door:"white door",
roomNum:3,
area:100,
address:"常州市新北区太湖东路世茂香槟湖"
}
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
}
}
</script>
<style scoped>
ul{
display:flex;
margin-left: 45%;
}
</style>
渲染效果:
2.5.3 通过key管理状态
当 Vue 正在更新使用 v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>
建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
因为它是 Vue 识别节点的一个通用机制,key 并不仅与 v-for 特别关联。后面我们将在指南中看到,它还具有其它用途。
2.5.4 组件和<template>使用v-for
<template>使用v-for
在 <template>使用v-for 来循环渲染一段包含多个元素的内容。比如:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
组件中使用v-for
在使用组件时,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域,为了把迭代数据传递到组件里,我们要使用 prop:
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
我们定义一个helloworld的组件,组件参数为msg
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
引用helloworld组件,使用v-for
<template>
<div id="app">
<HelloWorld v-for="item in msgList" :msg="item.msg" :key="item.id"></HelloWorld>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'//引入子组件HelloWorld
export default {
name: 'App',
components: {
HelloWorld
},
//数据
data(){
return {
msgList:[
{msg:'你好',id:1},
{msg:'保存失败',id:2},
{msg:'保存成功',id:3}
]
}
}
}
</script>
<style>
/* #app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
} */
</style>
渲染效果:
2.5.5 数组更新检测
2.5.5.1 变更方法
变更方法,会变更原始数组。Vue 能够侦听响应式数组的变更方法,并在它们被调用时,触发相关的视图更新。这些变更方法包括:
push()
:向数组的末尾添加一个或多个元素,并返回新的长度。pop()
:删除数组的最后一个元素,并返回最后一个元素。shift()
:删除数组的第一个元素,并返回第一个元素的值。unshift()
:向数组的开头添加一个或更多元素,并返回新的长度。sort()
:对数组的元素进行排序。reverse()
:颠倒数组中元素的顺序。splice()
:向/从数组中添加/删除项目,然后返回被删除的项目。- 删除功能:array.splice(index,num)。
- index:必需。数组元素的下标,必须是数字。使用负数可从数组结尾处规定位置。
- num:可选。要删除的项目数量。如果设置为 0,则不会删除项目。如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素。
- 插入功能:array.splice(index,0,item)。
- index:必需。数组元素的下标,必须是数字。使用负数可从数组结尾处规定位置。
- 0:必需是 0。表示插入功能。
- item:必需。规定要添加到数组的新元素。从 index 所指的下标处开始插入。
- 替换功能:array.splice(index,num,item)。
- index:必需。数组元素的下标,必须是数字。使用负数可从数组结尾处规定位置。
- num:必需。规定应该替换多少元素。必须是正整数。
- item:必需。规定要添加到数组的新元素。从 index 所指的下标处开始替换。
- 删除功能:array.splice(index,num)。
2.5.5.2 替换数组
替换数组,不变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组,触发相关的视图更新。
- filter():创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
- concat():用于连接两个或多个数组。
- slice():从已有的数组中返回选定的元素。
- split():用于把一个字符串分割成字符串数组。
// `items` 是一个数组的 ref
items.value = items.value.filter((item) => item.message.match(/Foo/))
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
2.5.6 使用计算属性展示过滤或排序后的结果
当我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。
<template>
<div>
<h2>使用计算属性展示过滤或排序后的结果</h2>
<!--v-for遍历数组 使用计算属性展示过滤或排序后的结果-->
<ul v-for="n in evenNumbers" :key="n">
<li>{{n}}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
numbers:[1,2,5,6,7,9]
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
//过滤数据
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
}
</script>
<style scoped>
</style>
渲染效果:
在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个方法:
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
在计算属性中使用reverse()和sort()请保持谨慎!这两个方法将改变原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:
// 慎用:
return numbers.reverse()
// 推荐:
return [...numbers].reverse()
2.6 计算属性
当我们在模板中多次使用相同的表达计算方式或者计算逻辑复杂的,推荐使用计算属性(computed),来描述依赖响应式状态的复杂逻辑,计算属性会自动追踪响应式依赖。
使用计算属性可以减少代码的重复性,降低维护难度,增强代码的可读性。
使用计算属性实现反转字符串实例:
<template>
<div>
<h2>使用计算属性展实现反转字符串</h2>
<span>{{reverseMsg}}</span>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
message:"Hello World"
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
reverseMsg: function () {
return this.message.split(' ').reverse().join('');
}
}
}
</script>
<style scoped>
</style>
渲染效果:
2.6.1 计算属性的setter
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter
计算两个数据的占比实例:
<template>
<div>
<h2>计算属性的setter</h2>
<span>{{calPercent}}</span>
<button @click="changeNum">计算属性setter</button>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
num:30,
totalNum:500
}
},
methods:{
changeNum(){
//计算属性 传值 setter会被调用,更新num和totalNum的值,那么对应的计算结果会改变
this.calPercent=[25,400];
}
},
//过滤器
filters:{
},
//计算属性
computed:{
calPercent: {
get: function() {
return (this.num/this.totalNum*100).toFixed(2);
},
set:function(newValue){
this.num=newValue[0];
this.totalNum=newValue[1];
}
}
}
}
</script>
<style scoped>
</style>
2.6.2 计算属性缓存和方法对比
我们可以通过在表达式中调用方法来达到和计算属性同样的效果,可以将同一函数定义为一个方法而不是一个计算属,两种方式的最终结果确实是完全相同的。
然而,不同的是计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
2.7 监听属性
计算属性允许我们声明性地计算衍生值。然而,在有些情况下,为了应对一些状态的变化,我们需要运行些effect(副作用):例如更改 DOM,或者根据异步操作的结果,去修改另一处的状态。
我们可以使用watch()
函数,在每次响应式状态发生变化时,触发回调函数
以监听question变量为实例的代码:
<template>
<div>
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
<p>{{changeInfo}}</p>
<button @click="changeQuestion">改变question</button>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
question:'',
answer:'I cannot give you an answer until you ask a question!',
changeInfo:''
}
},
methods:{
//改变问题
changeQuestion(){
this.question='I have a question about this computer';
}
},
//过滤器
filters:{
},
//计算属性
computed:{
},
//监听属性
watch:{
question:function(newVal,oldVal){
this.changeInfo=`old question:${oldVal},new question:${newVal}`;
this.answer = 'Waiting for you to stop typing...';
//metods等方法调用
}
}
}
</script>
<style scoped>
</style>
点击“改变question”按钮或者在文本框中输入问题,页面显示:
question变更后:
2.8 事件处理器
2.8.1 监听事件
使用v-on指令(缩写为@符号)来监听 DOM 事件,并在触发事件时,执行一些 JavaScript
按钮点击事件代码示例:
<button @click="changeQuestion">改变question</button>
或者
<button v-on:click="changeQuestion">改变question</button>
事件处理器的值可以是:
- 内联事件处理器:事件被触发时,执行的内联 JavaScript 语句(与 onclick 类似)。
- 方法事件处理器:一个组件定义的方法名、或对某个方法的访问路径。
模板编译器,会通过检查v-on的值,是否是合法的 JavaScript 标识符或属性访问,来断定是何种形式的事件处理器。举个例子,foo、foo.bar和foo['bar']会被视为方法事件处理器,而foo()和count++会被视为内联事件处理器。
2.8.2 内联事件处理器
内联事件处理器的使用场景:
- 简单事件处理
- 调用方法
- 访问事件参数:有时需要在内联事件处理器中,访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的$event变量,或者使用内联箭头函数
- 多事件处理器:事件处理程序中可以有多个方法,这些方法由,(逗号)分隔
实例代码:
<template>
<div>
<h3>内联事件处理器—简单场景</h3>
<p>计数:{{ count }}</p>
<button @click="count++">内联事件简单场景</button>
<h3>内联事件处理器—调用方法</h3>
<p>问题:{{question}}</p>
<button @click="changeQuestion">改变question</button>
<button @click="changeQ('This is a new quesuion')" :style="{marginLeft:'20px'}">带参方法</button>
<h3>内联事件处理器—访问事件参数</h3>
<button @click="warnMsg('访问事件参数$event变量',$event)">访问事件参数$event变量</button>
<button @click="(event)=>{warnMsg('访问事件参数箭头函数',event)}" :style="{marginLeft:'20px'}">访问事件参数箭头函数</button>
<h3>内联事件处理器—多事件处理器</h3>
<button @click="oneEvent('1'),twoWvent()">多事件处理器</button>
</div>
</template>
<script>
export default {
name: 'example1',
//数据
data(){
return {
count:1,
question:'I cannot give you an answer until you ask a question!',
muntiMethod:'多事件处理器'
}
},
methods:{
//改变问题
changeQuestion(){
this.question='I have a question about this computer';
},
//带参返回发
changeQ(questionVal){
this.question=questionVal;
},
//事件参数
warnMsg(msg,event){
//这里可以访问DOM原生事件
if(event){
alert(msg);
event.preventDefault();
}
else{
alert('不是事件');
}
},
oneEvent(numVal){
this.muntiMethod+=numVal;
},
twoWvent(){
alert(this.muntiMethod);
}
},
//过滤器
filters:{
},
//计算属性
computed:{
},
//监听属性
watch:{
}
}
</script>
<style scoped>
</style>
2.8.3 事件处理方法
随着事件处理器的逻辑变得愈发复杂,内联代码方式变得不够灵活。因此v-on也可以接受一个方法名或对某个方法的调用
实例代码:
<template>
<div>
<h3>事件处理方法</h3>
<button @click="greet">Greet</button>
</div>
</template>
<script>
export default {
//数据
data(){
return {
name: 'Vue2',
}
},
methods:{
//事件参数
greet(event){
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
},
//过滤器
filters:{
},
//计算属性
computed:{
},
//监听属性
watch:{
}
}
</script>
<style scoped>
</style>
2.8.4 DOM事件冒泡
DOM 在触发事件后,会经历事件捕获和事件冒泡两个最重要阶段。在 W3C 标准中,任何事件发生时,先从顶层开始进行事件捕获,直到事件触发到达了事件源元素。然后,再从事件源往上进行事件冒泡,直到到达 document。浏览器 IE 只支持事件冒泡。
- 事件捕获:由最外层向最里层触发事件的过程,叫事件捕获。事件从最不精确的 document 对象,开始触发,然后到最具体的元素。
- 事件冒泡:事件会从最内层的元素开始发生,一直向外层(向上)传播,直到 document 对象。
- 阻止冒泡:事件冒泡过程,是可以被阻止的。防止事件冒泡而带来不必要的错误和困扰。阻止事件冒泡的三种手段:
- return false:可以阻止默认事件和冒泡事件。
- event.stopPropagation:可以阻止冒泡事件,但是允许默认事件。IE 下event.cancelBubble = true
- event.preventDefault():可以阻止默认事件,但是允许冒泡事件。IE 下event.returnValue = false
- 事件委托:事件委托也叫事件代理,“事件代理”即是把原本需要绑定在子元素的响应事件(onclick,onmouseover,onmouseout等)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是 DOM 元素的事件冒泡。
2.8.5 事件修饰符
在处理事件时调用event.preventDefault()或event.stopPropagation()是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑,而不用去处理 DOM 事件的细节,会更好。
为解决这一问题,Vue 为v-on提供了事件修饰符。修饰符是用.(点)表示的指令后缀。
- .stop:阻止事件冒泡。阻止事件向上级 DOM 元素传递。
- .prevent:阻止默认事件。默认事件,指对 DOM 的操作会引起自动执行的动作。比如,点击超链接的时候会进行页面的跳转,点击表单提交按钮时会重新加载页面等。
- .capture:使用事件的捕获模式。使事件触发,从包含这个元素的顶层,开始往下触发。
- .self:只有 event.target 是当前操作的元素时,才触发事件。
- .once:事件只触发一次。
- .passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
注意:使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
.capture,.once,和.passive修饰符与原生addEventListener事件相同:
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
.passive修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。
注意:请勿同时使用.passive和.prevent,因为.prevent会被忽略并且你的浏览器可能会抛出警告。请记住,.passive是向浏览器表明你不想阻止事件的默认行为。并且如果你这样做,可能在浏览器中收到一个警告。
2.8.6 按键修饰符
在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在v-on或@监听按键事件时,添加按键修饰符。
<!-- 仅在 `key` 为 `Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />
你可以直接使用KeyboardEvent.key暴露的按键名称作为修饰符,但需要转为kebab-case形式。
<input @keyup.page-down="onPageDown" />
在上述示例中,处理函数只会在$event.key等于'PageDown'时被调用。
Vue 为最常用的键提供了别名:
- .enter
- .tab
- .delete(捕获“删除”和“退格”键)
- .esc
- .space
- .up
- .down
- .left
- .right
注意:有一些按键 (.esc 以及所有的方向键) 在 IE9 中有不同的 key 值, 如果你想支持 IE9,这些内置的别名应该是首选。
2.8.7 系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
- .ctrl
- .alt
- .shift
- .meta
注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
请注意修饰键与常规按键不同,在和keyup事件一起用时,事件触发时,修饰键必须处于按下状态。换句话说,keyup.ctrl只有在按住ctrl的情况下,但松开了另一个键时,才被触发。而单单释放ctrl也不会触发事件。
2.8.7.1 .exact修饰符
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
2.8.7.2 鼠标按钮修饰符
- .left
- .right
- .middle
这些修饰符会限制处理函数仅响应特定的鼠标按钮。
2.9 表单
在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,v-model指令帮我们简化了这一步骤。
另外,v-model还可以用于各种不同类型的输入元素。它会根据所使用的元素自动扩展到不同的 DOM 属性和事件组合:
- 文本类型的<input>、<textarea>元素会使用到value属性和input事件;
- <input type="checkbox">和<input type="radio">使用checked property 和change事件;
- <select>使用的value属性和change事件。
v-model会忽略所有表单元素的 attribute 的初始值(比如:value、checked、selected)。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。你应该在 JavaScript 中声明该初始值,使用响应式数据。
对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件
2.9.1 基础用法
包含文本(text)、多行文本(textarea)、复选框(checkbox)、单选按钮(radio)、选择框(select)控件的数据绑定
代码实例:
<template>
<div>
<h3>文本绑定值:{{txtContent}}</h3>
<input v-model="txtContent" placeholder="请输入文本内容">
<h3>多行文本绑定值:{{txtAreaContent}}</h3>
<textarea v-model="txtAreaContent" placeholder="请输入多行文本内容"></textarea>
<h3>单个复选框</h3>
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">单个复选框{{checked}}</label>
<h3>多个复选框绑定到同一个数组或集合的值</h3>
<input type="checkbox" id="jack" value="Jack" v-model="multiCheck">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="multiCheck">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="multiCheck">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ multiCheck }}</span>
<h3>单选按钮</h3>
<input type="radio" id="one" value="One" v-model="radioVal">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="radioVal">
<label for="two">Two</label>
<br>
<span>Picked: {{ radioVal }}</span>
<h3>选择框</h3>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
<h3>选择框多选,绑定到一个数组,多选时需要按住ctrl选择多个</h3>
<select v-model="multiSelect" multiple style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ multiSelect }}</span>
<h3>v-for 动态渲染绑定选择框</h3>
<select v-model="selected">
<option v-for="sel in vforSelects" :value="sel.value" :key="sel.value">
{{ sel.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
</template>
<script>
export default {
//数据
data(){
return {
txtContent:'',
txtAreaContent:'',
checked:false,
multiCheck:[],
radioVal:'',
selected:'',
multiSelect:[],
vforSelects:[
{text:'请选择',value:''},
{text:'red',value:'1'},
{text:'balck',value:'2'},
{text:'white',value:'3'}
]
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
},
//监听属性
watch:{
}
}
</script>
<style scoped>
</style>
对于多行文本,在文本区域插值 (<textarea>{{text}}</textarea>) 并不会生效,应用 v-model 来代替。
对于选择框,如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。
2.9.2 值绑定
对于单选按钮,复选框及选择框的选项,v-model绑定的值通常是静态字符串(对于复选框也可以是布尔值),但有时我们可能希望将该值绑定到当前活动实例上的动态属性,那么可以使用v-bind来做到。此外使用v-bind还使我们可以将选项值,绑定为非字符串类型
<template>
<div>
<h3>复选框值绑定</h3>
<!-- 这里的 true-value 和 false-value attribute 并不会影响输入控件的 value attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮 -->
<input type="checkbox" id="checkbox" v-model="checked" false-value="no" true-value="yes">
<label for="checkbox">复选框值绑定{{checked}}</label>
<h3>单选按钮值绑定</h3>
<input type="radio" id="one" v-model="radioVal" :value="radioBind">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="radioVal">
<label for="two">Two</label>
<br>
<span>Picked: {{ radioVal }}</span>
<h3>选择框的值绑定</h3>
<select v-model="selected">
<option value="">请选择</option>
<option :value="selectBind">A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
</template>
<script>
export default {
//数据
data(){
return {
checked:false,
radioVal:'',
radioBind:'A',
selected:'',
selectBind:'AA'
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
},
//监听属性
watch:{
}
}
</script>
<style scoped>
</style>
效果:
2.9.3 修饰符
- .lazy:懒加载。在默认情况下,v-model在每次input事件触发后,将输入框的值与数据进行同步(除了上述输入法组织文字时)。你可以添加lazy修饰符,从而转为在change事件之后,更新数据同步
- .number:确保为数值。如果想自动将用户的输入值自动转为数值类型,可以给v-model添加number修饰符
- .trim:去除两端空格。如果要自动去除用户输入内容中两端的空格,可以给v-model添加trim修饰符
<template>
<div>
<h3>.lazy修饰符</h3>
<!-- 这里的 true-value 和 false-value attribute 并不会影响输入控件的 value attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮 -->
<input v-model.lazy="msg">
<span>{{msg}}</span>
<h3>.number修饰符</h3>
<input v-model.number="age" type="number" />
<span>{{age}}</span>
<h3>.trim修饰符</h3>
<input v-model.trim="trimMsg">
</div>
</template>
<script>
export default {
//数据
data(){
return {
msg:'',
age:18,
trimMsg:' trim修饰符去除两端空格 '
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
},
//监听属性
watch:{
}
}
</script>
<style scoped>
</style>
注意:如果使用了.lazy修饰符,文本改变需要点击一下页面空白处,页面中的变量值才会同步更新
2.10 组件
组件(Component)是 Vue 最强大的功能之一。
组件可以扩展 HTML 元素,封装可重用的代码。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
使用组件的步骤:
1.定义组件(创建组件实例)
2.注册组件
全局组件(component(‘组件名’,组件实例))
局部组件(components:{组件实例…})
3.使用组件(使用组件标签)
<组件名></组件名> 双标签写法
<组件名/> 单标签写法
组件命名方式:
使用 kebab-case
Vue.component('my-component-name', { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>。
使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
2.10.1 全局组件
我们使用Vue.component来注册全局组件
方式一
Vue.component('my-component-name', {
// ... 选项 ...
})
使用此方式注册的全局组件,如果出现以下错误
You are using the runtime-only build of Vue where the template compiler is not available.
Either pre-compile the templates into render functions, or use the compiler-included build.
解决方案:
在vue.config.js配置文件中将runtimeCompiler设置为true
module.exports ={
runtimeCompiler:true
}
方式二
import 组件别名 from 'xx.vue';
Vue.component('component-a', 引入的组件别名)
我们在main.js中注册全局组件,实例代码
import Vue from 'vue'
import App from './App.vue'//引入根组件
import router from './router/router.js'//引用router.js
import '@/assets/css/common.scss' // 全局样式
import allCountCom from './components/VueAllCom.vue'//引入组件
Vue.config.productionTip = false;
//注册全局组件——button-counter
Vue.component('button-counter', {
data:function() {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
});
//通过import vue文件方式注册全局组件
Vue.component('AllCounter',allCountCom);
//实例化vue
new Vue({
router,
render: h => h(App),//导入根组件
}).$mount('#app');//mount挂载应用 根组件id
VueAllCom.vue 代码
<template>
<div>
<button @click="count++">import方式注册全局组件</button>
<span>点击了{{count}}次</span>
</div>
</template>
<script>
export default{
data(){
return {
count:0
}
}
}
</script>
使用全局组件
<template>
<div>
<button-counter></button-counter>
<AllCounter></AllCounter>
</div>
</template>
<script>
export default {
//数据
data(){
return {
}
},
methods:{
},
//过滤器
filters:{
},
//计算属性
computed:{
},
//监听属性
watch:{
}
}
</script>
<style scoped>
</style>
效果:
当组件中使用模板字符串来定义页面元素时,当控件类型有多个时,必须要在模板字符串中设置div根元素,比如:
//JavaScript 对象来定义局部组件 ButtonCounter
var ButtonCounter={
data:function(){
return {
count:0
}
},
props:{
title:String,
author:String
},
template:`<div><button v-on:click="count++">You clicked me {{ count }} times.</button> <br> <p>标题{{title}},作者{{author}}</p></div>`
};
2.10.2 局部组件
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
局部组件的注册方式:
通过一个普通的 JavaScript 对象来定义组件,然后在components中注册
//定义组件
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
components中注册组件
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
注意: JavaScript 对象来定义组件,需要在vue.config.js中设置runtimeCompiler:true
使用import 引入vue文件,,然后在components中注册
import 组件别名 from 'xx.vue';//引入组件,局部注册
components中注册组件
components: {
组件别名
}
我们将上述全局组件中的两个组件实例转换成局部组件,代码如下:
<template>
<div>
<button-counter></button-counter>
<AllCounter></AllCounter>
</div>
</template>
<script>
import AllCounter from '../components/VueAllCom.vue';//引入组件,局部注册
//JavaScript 对象来定义局部组件 ButtonCounter
var ButtonCounter={
data:function(){
return {
count:0
}
},
template:`<button v-on:click="count++">You clicked me {{ count }} times.</button>`
};
export default {
//数据
data(){
return {
}
},
//注册局部组件
components:{
'button-counter':ButtonCounter,
AllCounter
}
}
</script>
<style scoped>
</style>
注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}如果两个子组件都是单独的vue文件,则需要在一个vue文件中,通过import的方式引入另一个子组件
import ComponentA from './ComponentA.vue'export default {
components: {
ComponentA
},
// ...
}
2.10.3 prop子组件传参
prop 是子组件用来接受父组件传递过来的数据的一个自定义属性。
父组件的数据需要通过 props 把数据传给子组件,子组件需要显式地用 props 选项声明 "prop"
2.10.3.1 prop的大小写
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
重申一次,如果你使用字符串模板,那么这个限制就不存在了
2.10.3.2 prop类型
prop类型:字符串数组、对象
/*数组类型*/
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
/*对象 每个 prop 都有指定的值类型*/
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
2.10.3.3 传递静态或动态prop
<!-- 静态传值 -->
<blog-post title="My journey with Vue"></blog-post>
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post v-bind:title="post.title + ' by ' + post.author.name"></blog-post>
实际上任何类型的值都可以传给一个 prop,无需在意prop定义的数据类型
传入数字
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:likes="post.likes"></blog-post>
传入布尔值
<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>
传入数组
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>
传入对象
<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
v-bind:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>
传入对象的所有property
<!-- 定义对象-->
post: {
id: 1,
title: 'My Journey with Vue'
}
<!--对象传值-->
<blog-post v-bind="post"></blog-post>
2.10.3.4 prop单项数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
这里有两种常见的试图变更一个 prop 的情形:
1.这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
2.这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
2.10.3.5 prop验证
为了定制 prop 的验证方式,你可以为 props
中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
}
}
})
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data
、computed
等) 在 default
或 validator
函数中是不可用的。
类型检查
type 可以是下列原生构造函数中的一个:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:
function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
你可以使用:
Vue.component('blog-post', {
props: {
author: Person
}
})
来验证 author prop 的值是否是通过 new Person 创建的。
2.10.3.6 传参实例
<template>
<div>
<button-counter v-bind="propsData"></button-counter>
<!-- <AllCounter></AllCounter> -->
</div>
</template>
<script>
// import AllCounter from '../components/VueAllCom.vue';//引入组件,局部注册
//JavaScript 对象来定义局部组件 ButtonCounter
var ButtonCounter={
data:function(){
return {
count:0
}
},
props:{
title:String,
author:String
},
template:`<div><button v-on:click="count++">You clicked me {{ count }} times.</button> <br> <p>标题{{title}},作者{{author}}</p></div>`
};
export default {
//数据
data(){
return {
//props传参对象
propsData:{
title:'Vue2',
author:'youyuxi'
}
}
},
//注册局部组件
components:{
'button-counter':ButtonCounter,
// AllCounter
}
}
</script>
<style scoped>
</style>
2.10.4 监听事件
2.10.4.1 使用事件抛出一个值
在我们开发组件时,组件的一些功能可能要求我们和父级组件进行沟通。
比如我们需要设置文本的字号
最简单的方式是我们在父组件中统一设置文本内容的字号,这样每个子组件中显示的字号都一致了,实例代码:
<template>
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post v-for="item in posts" v-bind:post="item" :key="item.id"></blog-post>
</div>
</template>
<script>
//JavaScript 对象来定义局部组件 blog-post
var BlogPost={
data:function(){
return {
}
},
props:['post'],
template:`<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>`
};
export default {
//数据
data(){
return {
//props传参对象
posts:[
{id:1, title:'Vue2',content:'<p>Vue是一套构建用户界面的渐进式框架。Vue只关注视图层,采用自底向上增量开发的设计。Vue的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。</p>'},
{id:2, title:'Vue3',content:'<p>如果你是初学者,可能会觉得这些概念有些复杂。别担心!理解教程和指南的内容只需要具备基础的 HTML 和 JavaScript 知识。即使你不是这些方面的专家,也能够跟得上</p>'},
],
postFontSize:2
}
},
//注册局部组件
components:{
'blog-post':BlogPost
}
}
</script>
<style scoped>
</style>
效果:
我们还可以通过监听子组件的事件,来动态事件文本字体的变化,我们需要在代码中做以下改变:
1)在子组建中添加事件 v-on:enlarge-text="postFontSize += 0.1"
2)在子组件中新增放大字体的按钮,通过调用内建的 $emit 方法并传入事件名称来触发组件的enlarge-text事件, <button @click="$emit('enlarge-text')">放大文本字号</button>
实例代码如下:
<template>
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post v-for="item in posts" v-bind:post="item" :key="item.id" v-on:enlarge-text="postFontSize += 0.1"></blog-post>
</div>
</template>
<script>
//JavaScript 对象来定义局部组件 blog-post
var BlogPost={
data:function(){
return {
}
},
props:['post'],
template:`<div class="blog-post">
<h3>{{ post.title }}</h3>
<button @click="$emit('enlarge-text')">放大文本字号</button>
<div v-html="post.content"></div>
</div>`
};
export default {
//数据
data(){
return {
//props传参对象
posts:[
{id:1, title:'Vue2',content:'<p>Vue是一套构建用户界面的渐进式框架。Vue只关注视图层,采用自底向上增量开发的设计。Vue的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。</p>'},
{id:2, title:'Vue3',content:'<p>如果你是初学者,可能会觉得这些概念有些复杂。别担心!理解教程和指南的内容只需要具备基础的 HTML 和 JavaScript 知识。即使你不是这些方面的专家,也能够跟得上</p>'},
],
postFontSize:1
}
},
//注册局部组件
components:{
'blog-post':BlogPost
}
}
</script>
<style scoped>
</style>
效果:
当我们要考虑让子组件自己决定字号的大小,那么此时我们需要做如下改变:
1)用事件来抛出一个特定的值,子组件中的按钮$emit方法添加字号固定变大值:
<button @click="$emit('enlarge-text',0.1)">放大文本字号</button>
2)子组件的事件代码改为:v-on:enlarge-text="postFontSize += $event"
实例代码如下:
<template>
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post v-for="item in posts" v-bind:post="item" :key="item.id" v-on:enlarge-text="postFontSize +=$event"></blog-post>
</div>
</template>
<script>
//JavaScript 对象来定义局部组件 blog-post
var BlogPost={
data:function(){
return {
}
},
props:['post'],
template:`<div class="blog-post">
<h3>{{ post.title }}</h3>
<button @click="$emit('enlarge-text',0.1)">放大文本字号</button>
<div v-html="post.content"></div>
</div>`
};
export default {
//数据
data(){
return {
//props传参对象
posts:[
{id:1, title:'Vue2',content:'<p>Vue是一套构建用户界面的渐进式框架。Vue只关注视图层,采用自底向上增量开发的设计。Vue的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。</p>'},
{id:2, title:'Vue3',content:'<p>如果你是初学者,可能会觉得这些概念有些复杂。别担心!理解教程和指南的内容只需要具备基础的 HTML 和 JavaScript 知识。即使你不是这些方面的专家,也能够跟得上</p>'},
],
postFontSize:1
}
},
//注册局部组件
components:{
'blog-post':BlogPost
}
}
</script>
<style scoped>
</style>
子组件事件也可通方法,完成字号放大的功能:
1)子组件事件改成调用方法onEnlargeText v-on:enlarge-text="onEnlargeText"
2)父组件methods中创建onEnlargeText 方法:
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
2.10.4.2 组件上使用v-model
以input的一个组件未示例,展示如何实现在组件中使用v-model
实例代码:
<template>
<div>
<custom-input v-model="searchText"></custom-input>
</div>
</template>
<script>
//JavaScript 对象来定义局部组件 custom-input 用来展示组件中v-model的使用
var CustomInput={
data:function(){
return {
}
},
props:['value'],
template:`<input v-bind:value="value" v-on:input="$emit('input', $event.target.value)"/>`
};
export default {
//数据
data(){
return {
searchText:"组件中使用v-model"
}
},
//注册局部组件
components:{
'custom-input':CustomInput
}
}
</script>
<style scoped>
</style>
为了让组件正常工作,这个组件内的 <input> 必须:
将其 value attribute 绑定到一个名叫 value 的 prop 上
在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
2.10.5 通过插槽分发内容
在prop子组件传参章节中,我们学习到了子组件可以接受 props,来接受父组件传递数据,它可以是任何类型的 JavaScript 值。
但是模板内容呢?在某些情况下,我们可能希望把模板片段传递给子组件,并让子组件在其自己的模板中渲染该片段,我们就要用到插槽了,插槽我们使用slot来进行占位
2.10.5.1 默认插槽
这里我们创建一个组件FilmClassify来展示分类信息,组件中写入插槽slot,
第一个在子组件组件film-classify插入模板<ul>...<ul>
第二个在子组件中插入一张风景图片
实例代码如下:
<template>
<div>
<film-classify title="热门电影">
<ul :style="{marginLeft:'45%',paddingInlineStart:'0'}">
<li :style="liStyle">肖申克的救赎</li>
<li :style="liStyle">1912</li>
<li :style="liStyle">零的执行人</li>
</ul>
</film-classify>
<div :style="{height:'20px'}"></div>
<film-classify title="风景">
<img src="../imgs/风景.jpg" alt="" :style="{width:'300px'}">
</film-classify>
</div>
</template>
<script>
//JavaScript 对象来定义局部组件
var FilmClassify={
data:function(){
return {
}
},
props:['title'],
template:`<div>
<h3>{{title}}</h3>
<slot></slot>
</div>`
};
export default {
//数据
data(){
return {
liStyle:{
textAlign:'left'
}
}
},
//注册局部组件
components:{
'film-classify':FilmClassify
}
}
</script>
<style scoped>
</style>
渲染效果:
DOM解析会把子组件中的<slot></slot>插槽被插入的<ul>和<img>替换掉了
2.10.5.2 具名插槽
具名插槽其实只是在默认插槽的基础上给每个插槽起了名字,作用为可以在组件中设置多个插槽,可以更具体细分。首先给组件插槽起名字,使用 name="xxx"。然后在子组件中插入模板时,将模板内容插入指定名称的插槽中(slot="插槽名称")
我们将默认插槽中实例的子组件插槽改成两个插槽,一个命名为films,一个命名为viewimg
修改后的代码:
<template>
<div>
<film-classify title="热门电影">
<ul :style="{marginLeft:'45%',paddingInlineStart:'0'}" slot="films">
<li :style="liStyle">肖申克的救赎</li>
<li :style="liStyle">1912</li>
<li :style="liStyle">零的执行人</li>
</ul>
<img src="../imgs/风景.jpg" alt="" :style="{width:'300px'}" slot="viewimg">
</film-classify>
</div>
</template>
<script>
//JavaScript 对象来定义局部组件
var FilmClassify={
data:function(){
return {
}
},
props:['title'],
template:`<div>
<h3>{{title}}</h3>
<slot name="films"></slot>
<slot name="viewimg"></slot>
</div>`
};
export default {
//数据
data(){
return {
liStyle:{
textAlign:'left'
}
}
},
//注册局部组件
components:{
'film-classify':FilmClassify
}
}
</script>
<style scoped>
</style>
渲染效果:
最终的DOM
2.10.5.3 作用域插槽
作用域插槽简单来说就是带有数据的插槽,把组件中的数据绑定给插槽,然后谁使用这个组件谁就能拿到这个数据使用,也相当于一种数据通信,其需要这样把数据绑定定义给组件插槽(名称没有要求)
然后就要使用组件时使用 slot-scope="xxx" 去接收,或者直接使用 scope,要注意的是此处标准一点最好写在 template 中
实例代码:
<template>
<div>
<film-classify title="我的爱好">
<template slot-scope="{hobby}">
<ul :style="{marginLeft:'45%',paddingInlineStart:'0'}">
<li :style="liStyle" v-for="(h,index) in hobby" :key="index">{{h}}</li>
</ul>
</template>
</film-classify>
</div>
</template>
<script>
//JavaScript 对象来定义局部组件
var FilmClassify={
data:function(){
return {
hobby:["吃饭","睡觉","看电视"]
}
},
props:['title'],
template:`<div>
<h3>{{title}}</h3>
<slot :hobby="hobby"></slot>
</div>`
};
export default {
//数据
data(){
return {
liStyle:{
textAlign:'left'
}
}
},
//注册局部组件
components:{
'film-classify':FilmClassify
}
}
</script>
<style scoped>
</style>
渲染效果:
2.10.6 动态组件
实现动态组件,需要使用内部组件component来实现,内部组件的概念和属性:
Vue内部提供的组件component组件作用是:实现动态的渲染组件,按需显示组件。
component 标签是 vue 内置的,作用:组件的占位符
is 属性的值,表示要渲染的组件的名字
is 属性的值,应该是组件在 components 节点下的注册名称
2.10.6.1 使用is 属性实现动态组件
我们会在一个多标签的界面中使用 is
attribute 来切换不同的组件,这里我们使用两个按钮来实现模拟tab动态切换的功能,实现组件的动态显示
实例代码:
<template>
<div>
<h3>动态组件</h3>
<div>
<button @click="comName = 'Left' ">展示Left组件</button>
<button @click="comName = 'Right' ">展示Right组件</button>
</div>
<div>
<!-- 要渲染的Left和Right组件 -->
<component :is="comName"></component>
</div>
</div>
</template>
<script>
//JavaScript 对象来定义局部组件
//left组件
var LeftCom={
data:function(){
return {
}
},
template:`<div>left组件</div>`
};
//right组件
var RightCom={
data:function(){
return {
}
},
template:`<div>right组件</div>`
};
export default {
//数据
data(){
return {
comName:'',
txtMulti:''
}
},
//注册局部组件
components:{
'Left':LeftCom,
'Right':RightCom
},
//方法
methods:{
}
}
</script>
<style scoped>
</style>
组件切换时的DOM解析:
2.10.6.2 在动态组件上使用keep-alive
使用<keep-alive>我们可以在组件动态切换的时候,使得组件实例能够被在它们第一次被创建的时候缓存下来,从而不用在每次动态切换组件时,都去创建一个新的组件实例
- keep-alive 会把内部的组件进行缓存,而不是销毁组件;
- 在使用 keep-alive 的时候,可以通过 include 指定哪些组件需要被缓存;
- 或者,通过 exclude 属性指定哪些组件不需要被缓存;但是:不要同时使用 include 和 exclude 这两个属性
<keep-alive>
<component :is="comName"></component>
</keep-alive>
2.10.7 异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
如你所见,这个工厂函数会收到一个 resolve
回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason)
来表示加载失败。这里的 setTimeout
是为了演示用的,如何获取组件取决于你自己。一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
你也可以在工厂函数中返回一个 Promise
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
当使用局部注册的时候,你也可以直接提供一个返回 Promise
的函数
components: {
'my-component': () => import('./my-async-component')
}
处理加载状态(Vue2.3.0+版本新增)
异步组件工厂函数也可以返回一个如下格式的对象:
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
注意如果你希望在
Vue Router的路由组件中使用上述语法的话,你必须使用 Vue Router 2.4.0+ 版本。
2.10.8 解析DOM模板时的注意事项
字符串模板来源:
- 单文件组件。以.vue后缀结尾的文件。
- 内联模板字符串,例如template:'...'。
- <script type="text/x-template">
DOM 模板来源:以.html后缀结尾的文件。
如果你想在 DOM 中直接书写 Vue 模板,Vue 则必须从 DOM 中获取模板字符串。因为浏览器的原生 HTML 解析行为,因此有一些需要注意的事项
大小写区分
HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。这意味着当你使用 DOM 内的模板时,无论是PascalCase形式的组件名称、camelCase形式的prop名称还是v-on的事件名称,都需要转换为相应等价的kebab-case(短横线连字符)形式
闭合标签
我们在上面的例子中已经使用过了闭合标签(self-closing tag) <MyComponent />,这是因为 Vue 的模板解析器将/>作为标签关闭的标志,无关其类型。然而在 DOM 模板中,我们必须显式地写出关闭标签:<my-component></my-component>。这是由于 HTML 只允许一小部分特殊的元素省略其关闭标签,最常见的就是<input>和<img>。对于其他的元素来说,如果你省略了关闭标签,原生的 HTML 解析器会认为开启的标签永远没有结束,用下面这个代码片段举个例子:
<my-component /> <!-- 我们想要在这里关闭标签... -->
<span>hello</span>
将被解析为:<my-component>
<span>hello</span>
</my-component> <!-- 但浏览器会在这里关闭标签 -->
元素位置限制
某些 HTML 元素对于放在其中的元素类型有限制,例如<ul>、<ol>、<table>和<select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如<li>、<tr>和<option>。
这将导致在使用带有此类限制元素的组件时出现问题。例如:
<table>
<blog-post-row></blog-post-row>
</table>
自定义的组件<blog-post-row>将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的is attribute 作为一种解决方案:
<table>
<tr is="vue:blog-post-row"></tr>
</table>
当使用在原生 HTML 元素上时,is的值必须加上前缀vue:才可以被解析为一个 Vue 组件。这一点是必要的,为了避免和原生的自定义内置元素相混淆。
3. 附录
vue官方学习文档
- 基础教程 https://v2.cn.vuejs.org/v2/guide/installation.html
- CLI服务学习 https://cli.vuejs.org/zh/guide/
- API学习 https://v2.cn.vuejs.org/v2/api/
- 代码风格指南 https://v2.cn.vuejs.org/v2/style-guide