十三、Vue 单文件组件
1.在前面学习的传统组件存在以下问题:
(1)全局定义的组件必须保证组件名不能重复。
(2)字符串的模板缺乏高亮语法,在写多行 HTML 片段的时候,需要用到丑陋的 "\"。
(3)不支持 CSS
(4)没有构造步骤限制,只能使用 HTML 和ES5 的 JavaScript,不能使用预处理器
2.针对传统组件存在的问题,Vue 提供了单文件组件的解决方案,单文件组件的文件扩展名为 .vue,文件内部包含了三部分内容:
(1)template:组件模板区域,用于放组件对应的 HTML 代码
(2)script:业务逻辑区域,写 JS 代码的区域
(3)style:样式区域

十四、Vue 的脚手架:Vue-CLI
1.Vue-CLI 是 Vue 官方提供的一种基于 Vue.js 进行快速开发的完整系统,可以快速搭建 Vue 的项目模板,使用 Vue 脚手架开发项目具有诸多的好处:
(1)统一的目录接口
(2)便捷的插件安装
(3)图形化的界面操作
2.Vue-CLI 的准备和安装
(1)安装node.js:
因为 Vue 脚手架的安装,启动,打包和后续项目中插件的安装都在 node.js 中进行,需要 node 的 npm 包管理器。
(2)在日常项目开发中,npm 有以下使用场景:
① 允许用户从 npm 服务器下载安装别人编写的命令行程序到本地使用
② 允许用户将自己编写的包或命令行程序上传到 npm
(3)安装 node.js:
一路向前下一步,由于后续使用 node.js 的项目需要下载一些依赖包,所以一般将 node.js 不安装在 C 盘,比如装在 D 盘

① 安装完毕,测试 node , node -v 测试 node 是否安装成功以及检查版本号;npm -v 测试 npm 是否安装成功以及检查 npm版本

② 配置 npm 下载插件的默认安装目录和缓冲目录
1)在 node 的安装目录中创建 node_global 和 node_cache 两个文件夹

a. 打开cmd,依次输入如下命令配置上面创建的文件夹

③ 配置 node 所需的环境变量
在系统环境变量中新建 NODE_PATH ,值是安装目录下 node_global 中的 node_modules 的路径:即:D:\Program Files\nodejs\node_global\node_modules

将用户环境变量的 Path 中添加 node 安装目录的 node_global 路径,即:
D:\Program Files\nodejs\node_global
注意:要将这个环境变量上移到第一个位置

(4)安装国内淘宝镜像
① 我们通过 npm 命令下载 node 插件的时候访问的是国外的网站,所以可能出现速度很慢或直接不能下载,我们可以通过配置国内镜像来解决,一般配置的是淘宝的 npm 镜像 cnpm
② 安装命令:npm install -g cnpm --registry=https://registry.npmmirror.com,注意,如果提示权限不足,那么可以使用管理员的方式来启动cmd来执行
管理员运行cmd:


③ 测试:cnpm -v

(5)安装Vue脚手架
① npm install -g @vue/cli:国外的方式,比较慢
② cnpm install -g @vue/cli:国内的方式,我们安装了淘宝镜像,所以用这个。
注意:如果安装不成功,关掉 cmd,再以管理员身份打开 cmd 安装

③ 测试命令:vue -V(这里的V大写)

④ 改变 npm 源地址(避免下载插件时卡住或失败): npm config set registry https://registry.npmmirror.com
⑤ 以后不用了卸载命令:npm uninstall -g @vue/cli
十五、使用 Vue 脚手架创建 Vue 项目
1.Vue 脚手架创建项目有两种方式:一种是命令方式,一种是图形化方式,我们这里讲解命令方式。
2.命令行创建项目的步骤:
(1)在创建 Vue 项目的文件夹上面运行 cmd,在 cmd 中创建 Vue 的项目
注意,如果此时 cmd 不是管理员的方式启动,创建 Vue 项目的过程可能失败,解决办法就是将整个 nodejs 的安装目录右键---属性---安全的 Authenticated Users 设置为完全控制就可以解决管理员的问题,设置之后普通的cmd 命令也能创建 Vue 的项目:


点击"确定",设置成功之后就可以使用普通的 cmd 命令创建 Vue 项目:
(2) 创建 Vue 项目,在要创建 Vue 项目的目录上运行 cmd:

使用命令"vue create 项目名" 创建 vue 基于 vue 脚手架的项目:

运行之后会进入 Vue 的配置界面:其中空格表示选中或取消,方向键用于选择,A 表示全选,回车表示下一步,运行之后,等待进入配置界面:

(3) 我们选择 Deault Vue2,回车

(4) 等待下载依赖完成

(5) 项目创建完成后

然后我们按照提示去编译项目:

(6) 然后我们通过上面的 URL 地址去访问一下,就可以看到 Vue 项目运行后的界面了

我们要结束项目的运行可以在已经运行 vue 项目的 cmd 中按 Ctrl + C 即可终止项目运行:

3. vue 脚手架项目的解读
(1) 我们可以把 vue 项目用 IDEA 集成开发环境打开
**注意:**如果有些同学在创建 vue 的项目时发生错误,那么就使用管理员方式打开 cmd 来创建,并且 IDEA 也要用管理员方式打开来查看 vue 的项目


(2) 打开的 vue 的项目的结构如下图:


vue 的示例工程源码分析:
main.js:

App.vue 根组件:

HelloWorld.vue 子组件:
<template>
<div class="hello">
<!-- 子组件 HelloWorld 通过表达式显示自己的 Vue 实例的属性 msg,而这个 msg 是父组件传递过来的属性-->
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {//导出子组件
name: 'HelloWorld',//子组件名称
props: { //这种写法和 props:["msg"] 是一样的,都是表示这个 msg 用来接收父组件传递过来的属性值,只是这种写法可以指明msg 属性的类型
msg: String //此时的 msg 属性将作为子组件 HelloWorld 对应 Vue 对象的一个属性
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
现在我们自己创建一个子组件来使用一下,注意,Vue 脚手架规定在创建组件的时候,组件的名字必须是两个单词,并且两个单词的首字母都要大写:

UserComponent.vue 代码:

然后在 根组件 App.vue 中引入我们自己的组件,我们可以删除自带的 HelloWorld 组件:

然后我们在 IDEA 的终端(Terminal)中运行:npm run serve

然后它会去编译,编译完成后会生成访问该 Vue 项目的 URL 地址:

点击上面的超链接就可以运行:

十六、组件之间共享数据的方式
1.组件之间共享数据的方式主要分为三种:
(1)父向子传值:使用 v-bind 属性绑定,在子组件上绑定参数,传递数据(props):
删除自带的 HelloWorld 组件,创建一个子组件 NewsItems:

在根组件 App.vue 中使用子组件 NewsItems,IDEA 会自动帮我们导入:

在父组件 App.vue 中传递属性值到子组件 NewsItems 中:
<template>
<div id="app">
<h1>我是父组件</h1>
<NewsItems :news_title="news.title" :news_content="news.content"></NewsItems>
</div>
</template>
<script>
import NewsItems from "@/components/NewsItems";
export default {
name: 'App', //根组件的名称
components: {
NewsItems //注册 UserComponent 组件,是一个局部组件
},
data() { //ES6的新语法,等效于在 data 后面跟一个匿名函数
return {
//父组件对应的 Vue 对象的属性
news: {
title: "新闻标题",
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci consequatur impedit saepe temporibus voluptas. Aspernatur consectetur dolore est inventore nam numquam, ullam voluptate? Adipisci est itaque, laudantium nisi repudiandae voluptatibus."
}
}
}
}
</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>
子组件 NewsItems 中通过 props 选项接收:
<template>
<div>
<h1>我是子组件</h1>
<h3>{{news_title}}</h3>
<p>{{news_content}}</p>
</div>
</template>
<script>
export default {
name: "NewsItems",
/**
* props 选项用于接收父组件传递过来的属性,属性名写在 "[]" 内,可以接收多个属性,此时这些属性 news_title,news_content 将
* 作为该子组件对应的 Vue 实例的属性,就可以使用表达式 {{}} 输出
*/
props: ["news_title","news_content"]
}
</script>
<style scoped>
</style>
运行起来看看:


(2)子向父传值:使用 v-on 事件绑定,在子组件上绑定自定义事件,传递数据,父组件中监听这个自定义事件,获取子组件传递过来的数据
创建子组件 SubComponent.vue:

父组件就是使用该子组件 SubComponent 的根组件 App.vue:
<template>
<div id="app">
<h1>我是父组件</h1>
<!--在父组件中监听子组件发送的 son 事件,当 son 事件发生的时候,事件函数 getparam 就自动执行了,就可以获取子组件的 son 事件
发送过来的参数(消息)-->
<SubComponent @son="getparam"></SubComponent>
<p>父组件接收到子组件传递过来的消息为:{{msg}}</p>
</div>
</template>
<script>
import SubComponent from "@/components/SubComponent";
export default {
name: 'App', //根组件的名称
components: {
SubComponent
},
data() { //ES6的新语法,等效于在 data 后面跟一个匿名函数
return {
//父组件对应的 Vue 对象的属性
msg: '' //父组件 App 的属性
}
},
methods: {
getparam: function (param) {//son 事件的事件函数,param 参数就是子组件的 son 事件发送过来的参数(消息)
this.msg = param;//把子组件的 son 事件发送的参数 param 赋值给父组件 App 的属性 msg
}
}
}
</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>
运行起来:npm run serve

(3)兄弟组件传值:创建共享的消息总线
1)接收数据的组件:$on 监听总线事件
2)发送数据的组件:$emit 发送总线事件
示例:
创建一个兄弟组件:

创建另一个兄弟组件:
<template>
<div>
<h3>{{msg}}</h3>
</div>
</template>
<script>
import Vue from 'vue';
/**
* 创建一个全局的 Vue 对象 Bus,作为消息总线,每个组件中都可以通过 this 来访问这个 Bus,然后使用这个全局的 Bus 对象来
* 监听事件(on)和发送事件(emit)
*/
Vue.prototype.Bus = new Vue();//一个全局的 Vue 对象作为消息总线 Bus
export default {
name: "OtherComponent",
data(){ //ES6
return {
msg:'' //当前组件的属性
}
},
mounted() {//ES6,生命周期方法,当 Vue 实例挂载到 DOM 上之后自动执行
//在生命周期方法 mounted() 中,this 指的是 OtherComponent 对应的 Vue 对象
var $this = this;
//$on() 方法是Vue实例的实例方法,第一个参数为监听的事件名,第二个参数为事件函数,事件函数的参数 param 就是接收到的事件参数值
this.Bus.$on("brother", function (param) {
/**
* 这里不能使用 this.msg = param,因为我们使用的是 Bus 对象来监听事件,并不是当前组件 OtherComponent 对应的 Vue 对象,
* 所以这里的 this.msg 不是指 OtherComponent 组件的 msg
*/
this.msg = param;//这里的 this 就是指 OtherComponent 对应的 Vue 对象
});
}
}
</script>
<style scoped>
</style>
然后在根组件 App.vue 中使用这两个兄弟组件:

测试:npm run serve
十七、Vue Router(Vue路由)
1.什么是路由
(1)Vue Router
是 Vue.js(Vue脚手架工程)官方的路由管理器,它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。SPA 单页面应用是指,整个网站只有一个页面,当 Vue 感知 URL 变化的时候,会动态的把当前页面的内容清除掉,再把下一个页面的内容挂载到当前页面。Vue 实现的单页面应用,页面组件片段切换快,用户体验好。
(2)基本使用步骤:
1)创建vue的项目:vue create 项目名

2)选择Manually select features,回车

3)按上下键移动,按空格可以选中或取消,按空格选中 Router,回车,其他保持默认值:

4)选择 Vue 的版本,我们选择 2.x 版本,回车

5)是否使用历史的路由模式,输入n回车:

6)直接回车

7)直接回车

8)直接回车

9)是否保存配置,输入 N,回车

10)等待创建

创建完成之后,使用 IDEA 打开,打开之后发现多了一些文件,这些文件就是 Vue 路由相关的文件:

(3)简单使用 Vue Router
1)找两幅图片放在 assets 文件夹中

2)删除 views 目录中原来的所有组件,然后在 views 目录中创建两个组件:ChengshiComponent 和 XingkongComponent:


3)在 router-->index.js 文件中配置路由,将原来的路由配置删除:

4)配置完路由之后,我们需要修改根组件 App.vue 来调用路由:
<template>
<div id="app">
<nav>
<!--
<router-link> 其实就是一个增强型的 a 标签,它的 to 属性相当于是 a 标签的 href 属性,to 属性值就是在 router--index.js
中配置的路径地址
-->
<router-link to="/chengshi">城市</router-link> |
<router-link to="/xingkong">星空</router-link>
</nav>
<!--
<router-view>:引用路由显示的地方,即你点击上面哪一个链接地址,<router-view> 就显示哪一个路由地址对应的组件内容
-->
<router-view/>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
}
nav a {
font-weight: bold;
color: #2c3e50;
}
nav a.router-link-exact-active {
color: #42b983;
}
</style>
测试:npm run serve

2.路由重定向
(1)页面打开时,其实也是有一个链接地址 "/",现在我们希望默认地址可以指向其他链接,要实现这个功能,我们需要用到路由重定向,路由重定向是指,用户在访问地址 A 的时候,强制用户跳转到地址 B,从而展示特定的组件页面。
(2)Vue 实现路由重定向是通过路由规则中的 redirect 属性,为 path 指定一个新的路由地址,这样就完成了路由重定向的设置,例如:

示例:实现打开网页默认显示城市图片,访问城市路由
router---index.js:

保存,会进行自动编译,直接运行即可:
3.嵌套路由
(1)引入
在前面的章节中,我们学习了组件的开发和应用,在实际应用开发界面中,通过由多层嵌套的组件组合而成,比如我们登录组件中,需要嵌套立即注册和忘记密码操作,这就需要使用到 Vue 的路由中的嵌套路由操作,嵌套路由是指通过路由规则层级嵌套,可以在页面上展示出复杂的组件结构关系。
(2)嵌套路由需求分析
点击父级路由链接显示模板内容,模板内容又有子级路由链接,点击子级路由链接显示子级模板内容。
(3)实现思路:
① 在父路由组件中添加子路由链接和路由占位符
② 在父路由规则中,通过 children 属性,添加子路由规则,例如:
1)第一步:

2)第二步:在父路由组件中添加子路由链接和路由占位符

示例:优化代码,当点击城市的时候,在图片的下方会出现两个超链接,点击任意一个,下面会出现对应的城市
开发步骤:
1)创建 HeNanComponent 组件,代表河南城市分类

2)创建 HeBeiComponent 组件,代表河北城市分类:

3)在 router--->index.js 中对这两个子路由进行配置:
import Vue from 'vue'
import VueRouter from 'vue-router'
import ChengshiComponent from "@/views/ChengshiComponent";
import XingkongComponent from "@/views/XingkongComponent";
import HeNanComponent from "@/views/HeNanComponent";
import HeBeiComponent from "@/views/HeBeiComponent";
Vue.use(VueRouter)
const routes = [{
/**
* 应用默认情况下访问的是 "/" 地址,通过 redirect: "/chengshi",在应用启动的时候重定向到 /chengshi 对应的组件
* 来显示
*/
path: "/",
redirect: "/chengshi"
},{ //第一个路由配置
//路由配置,path:路由的 URL 地址,component:路由对应的组件,还有一个 name 选项后面讲
path: '/chengshi',
component: ChengshiComponent,
//子路由配置
children:[{
path:'henan',//子路由的地址前面没有"/"
component: HeNanComponent
},{
path:'hebei',//子路由的地址前面没有"/"
component: HeBeiComponent
}
]
},{//第二个路由配置
path: '/xingkong',
component: XingkongComponent
}
]
const router = new VueRouter({
routes
})
export default router
4)子路由配置好之后,我们就可以在父组件 ChengshiComponent 中添加<router-link>来访问子路由(嵌套路由):
<template>
<div>
<img src="../assets/1.jpg" alt="" width="600" height="400">
<br>
<!--子路由的地址一定要跟上副路由的路径,要完整-->
<router-link to="/chengshi/henan">河南</router-link> |
<router-link to="/chengshi/hebei">河北</router-link>
<!--通过 <router-view> 显示对应的子组件-->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "ChengshiComponent"
}
</script>
<style scoped>
</style>
- 测试:运行 npm run serve

4.命名路由和编程式路由导航
(1)name 命名路由:
有时候,通过一个名称来标识一个路由会显得更方便一些,特别是在执行路由跳转,路由链接或路由传参的时候,我们可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。
(2)语法:
(3)调用路由:
调用路由分为编程式导航调用路由和声明式调用路由,编程式后面会讲,声明式导航就是用到前面的<router-link to="/路径"></router-link> 这种直接当成 a 标签引用的被称为声明式导航。
(4)声明式导航调用路由的方法有三种:
① 第一种:<router-link to="/path地址">提示文字</router-link>
② 第二种:使用 path 关键字,<router-link :to="{path:'path地址'}">提示文字</router-link>,这里的path地址不需要 "/"
③ 第三种:使用 name 关键字,<router-link :to="{name:'路由的name名称'}">提示文字</router-link>
示例:优化代码,给两个子路由添加 name 名称,使调用路由的时候,路径不用写那么长
第一步:修改 router--->index.js 配置文件,添加 name 属性;

第二步:修改父组件 ChengshiComponent,修改 to 中的值:

测试:

(5)编程式路由导航
1)Vue 路由中页面导航有两种方式:
a. 声明式路由导航:通过点击 <router-link> 超链接实现路由的导航的方式叫做声明式导航,例如上面的例子就是。
b. 编程式导航:通过调用 API 实现导航的方式,叫做编程式导航。
2)Vue 实现编程式导航,常见的 Vue 路由编程式导航的 API 如下:
a. this.$router.push("地址");
b. this.$router.go(n);
我们选择第一种方式,语法如下:
第一种:this.$router.push("路由配置的path地址");//注意没有"/"
第二种:this.$router.push({path:"路由配置的path地址"});//也没有"/"
第三种:this.$router.push({name:"路由的name名称"});
示例:使用编程式导航改造上面的代码
第一个:修改父组件 ChengshiComponent ,添加按钮并添加单击事件:
<template>
<div>
<img src="../assets/1.jpg" alt="" width="600" height="400">
<br>
<!--子路由的地址一定要跟上副路由的路径,要完整-->
<router-link to="/chengshi/henan">河南</router-link>
<router-link :to="{path:'/chengshi/henan'}">河南</router-link>
<router-link :to="{name:'hn'}">河南</router-link>
<button @click="henan">河南</button>
|
<router-link to="/chengshi/hebei">河北</router-link>
<router-link :to="{name:'hb'}">河北</router-link>
<!--通过 <router-view> 显示对应的子组件-->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "ChengshiComponent",
methods: {
henan() {//事件函数,ES6语法
/**
* 在进行编程式导航时, push() 方法的第二个参数要给出一个箭头函数,表示定义的回调函数,避免重复点击按钮时出现错误,
* 如果不写,就肯能会出错
*/
//注意这里的 path 地址没有"/"
//this.$router.push('henan',()=>{});
//地址 henan 不要 "/"
//this.$router.push({path:'henan'}, ()=>{});
this.$router.push({name:'hn'}, ()=>{});
}
}
}
</script>
<style scoped>
</style>
测试:

(6)路由导航传参
在 Vue 路由中,针对不同的路由导航有不同的传参方式,这里主要是根据 path 属性或 name 属性进行区分,无论是上面的声明式还是编程式操作基本一致。
1)声明式导航传参
第一种:path+query属性
<router-link :to="{path:'path地址',query:{参数1:值1,参数2:值2,...}}">提示文字</router-link>
组件中获取参数:{{this.$route.query.参数名}}
第二种:name+params属性
<router-link :to="{name:'name名称',params:{参数1:值1,参数2:值2,...}}">提示文字</router-link>
组件中获取参数:{{this.$route.params.参数名}}
示例:在河北和河南中添加 li,内容为调用时传递的路由参数
第一步:修改 HeNanComponent:

第二步:修改 HeBeiComponent:

第三步:修改父组件 ChengshiComponent,传递参数:
<template>
<div>
<img src="../assets/1.jpg" alt="" width="600" height="400">
<br>
<!--子路由的地址一定要跟上副路由的路径,要完整-->
<router-link to="/chengshi/henan">河南</router-link>
<router-link :to="{path:'/chengshi/henan', query:{henan:'query 参数值'}}">河南 args</router-link>
<router-link :to="{name:'hn'}">河南</router-link>
<button @click="henan">河南</button>
|
<router-link to="/chengshi/hebei">河北</router-link>
<router-link :to="{name:'hb', params:{hebei:'params 参数值'}}">河北 args</router-link>
<!--通过 <router-view> 显示对应的子组件-->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "ChengshiComponent",
methods: {
henan() {//事件函数,ES6语法
/**
* 在进行编程式导航时, push() 方法的第二个参数要给出一个箭头函数,表示定义的回调函数,避免重复点击按钮时出现错误,
* 如果不写,就肯能会出错
*/
//注意这里的 path 地址没有"/"
//this.$router.push('henan',()=>{});
//地址 henan 不要 "/"
//this.$router.push({path:'henan'}, ()=>{});
this.$router.push({name:'hn'}, ()=>{});
}
}
}
</script>
<style scoped>
</style>
测试:

2)编程式导航传参:编程式导航传参和声明式导航传参差不多,只不过不是用标签,而是在方法内传参:
示例:


测试:
