python 全栈开发,Day91(Vue实例的生命周期,组件间通信之中央事件总线bus,Vue Router,vue-cli 工具)
昨日内容回顾
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
0. 组件注意事项!!! data属性必须是一个函数! 1. 注册全局组件 Vue.component('组件名',{ template: `` }) var app = new Vue({ el: '#app' }) 2. 注册局部组件 var app = new Vue({ el: '#app', components:{ 局部组件名:{ template: `...` } } }) 3. 传值 1. 父组件 --> 子组件 1. 父组件通过 v-bind:变量='值' 2. 子组件需要通过props 声明我需要的变量 2. 子组件 --> 父组件 子组件通过触发自定义事件的方式向外传递信息 1. 子组件: this.$emit('自定义事件') 2. 父组件: v-on:自定义事件='方法名' 3. 组件间传值 1. 补充:Vue实例的生命周期钩子函数(共8个) 1. beforeCreate --> 数据属性声明但没有赋值 2. created --> 数据属性赋值 3. beforeMount --> 页面上的 {{name}} 还没有被渲染 4. mounted --> 页面上的 {{name}} 被替换成真正的内容 ... 2. 基于bus对象实现 4. 插槽(slot) 插槽是占位置的!!! 插槽多了可以起名字,进行区分! --> <span slot='heihei'>嘿嘿!</span> <alert>根本不显示</alert> 5. 组件的注意事项: 1. 特殊的组件需要使用is语法声明一下 比如 table、select、ul等内部使用的组件 2. 捕获子组件的原生事件
解析 DOM 模板时的注意事项
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:
<table> <blog-post-row></blog-post-row> </table>
这个自定义组件 <blog-post-row>
会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is
特性给了我们一个变通的办法:
<table> <tr is="blog-post-row"></tr> </table>
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:
template: '...'
) - 单文件组件 (
.vue
) <script type="text/x-template">
到这里,你需要了解的解析 DOM 模板时的注意事项——实际上也是 Vue 的全部必要内容,大概就是这些了。
举例:table标签插入一个tr
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <table border="1"> <tbody> <!--自定义组件--> <zsq></zsq> </tbody> </table> </div> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: {}, components:{ zsq: { template: `<tr>我是一个tr标签!</tr>` }, } }) </script> </body> </html>
打开网页,效果如下:
查看html代码,发现它提升到外部了。通俗来讲,就是漂移到table外面了
使用is特性
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <table border="1"> <tbody> <!--自定义组件--> <tr is=zsq></tr> </tbody> </table> </div> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: {}, components:{ zsq: { template: `<tr>我是一个tr标签!</tr>` }, } }) </script> </body> </html>
刷新网页,效果如下:
查看html代码,这样就正常了
将原生事件绑定到组件
也就是
触发原生事件
你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on
的 .native
修饰符:
<base-input v-on:focus.native="onFocus"></base-input>
在有的时候这是很有用的,不过在你尝试监听一个类似 <input>
的非常特定的元素时,这并不是个好主意。比如上述 <base-input>
组件可能做了如下重构,所以根元素实际上是一个 <label>
元素:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > </label>
这时,父级的 .native
监听器将静默失败。它不会产生任何报错,但是 onFocus
处理函数不会如你预期地被调用。
为了解决这个问题,Vue 提供了一个 $listeners
属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{ focus: function (event) { /* ... */ } input: function (value) { /* ... */ }, }
有了这个 $listeners
属性,你就可以配合 v-on="$listeners"
将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input>
的你希望它也可以配合 v-model
工作的组件来说,为这些监听器创建一个类似下述 inputListeners
的计算属性通常是非常有用的:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], computed: { inputListeners: function () { var vm = this // `Object.assign` 将所有的对象合并为一个新对象 return Object.assign({}, // 我们从父级添加所有的监听器 this.$listeners, // 然后我们添加自定义监听器, // 或覆写一些监听器的行为 { // 这里确保组件配合 `v-model` 的工作 input: function (event) { vm.$emit('input', event.target.value) } } ) } }, template: ` <label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on="inputListeners" > </label> ` })
现在 <base-input>
组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input>
元素一样使用了:所有跟它相同的特性和监听器的都可以工作。
举例:第一种写法,使用.native修饰符
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <ztz v-on:click.native='hehe'></ztz> </div> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { name: 'ztz', age: 24 }, components: { ztz: { template: `<button>赵天柱 逃课!</button>` }, }, methods:{ hehe:function(){ alert(123); } } }) </script> </body> </html>
刷新网页,点击按钮,效果如下:
举例:第二种写法,绑定自定义事件
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <!--<ztz v-on:click.native='hehe'></ztz>--> <ztzsb v-on:click='hehe'></ztzsb> </div> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { name: 'ztz', age: 24 }, components: { // ztz: { // template: `<button>赵天柱 逃课!</button>` // }, ztzsb: { template: `<button v-on:click='shangkele'>赵天柱 逃课!</button>`, methods:{ shangkele:function(){ this.$emit('click') } } } }, methods:{ hehe:function(){ alert(123); } } }) </script> </body> </html>
刷新网页,点击按钮,效果同上!
针对这2种写法,推荐使用第一种!
一、Vue实例的生命周期
实例生命周期钩子
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
比如 created
钩子可以用来在一个实例被创建之后执行代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
new Vue({ data: { a: 1 }, created: function () { // `this` 指向 vm 实例 console.log('a is: ' + this.a) } }) // => "a is: 1"
也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted
、updated
和 destroyed
。生命周期钩子的 this
上下文指向调用它的 Vue 实例。
不要在选项属性或回调上使用箭头函数,比如 created: () => console.log(this.a) 或 vm.$watch('a', newValue => this.myMethod())。因为箭头函数是和父级上下文绑定在一起的,this 不会是如你所预期的 Vue 实例,经常导致 Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之类的错误。
生命周期图示
下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
Vue的生命周期,就是从Vue实例被创建开始到实例销毁时的过程,整个过程主要可以分为八个阶段分别是:
- 创建前(beforeCreate)
- 已创建(created)
- 编译前(beforeMount)
- 编译后(mounted)
- 更新前(beforeUpdate)
- 更新后(update)
- 销毁前(beforeDestroy)
- 销毁后(destroyed)
以上各个阶分别会有对应的“钩子函数”以Vue实例的选项的形式存在。
Vue提供的生命周期钩子如下:
① beforeCreate
在实例初始化之后,数据观测(data observer,开始监控Data对象数据变化)和初始化事件(init event,Vue内部初始化事件)之前被调用。
② created
在实例已经创建完成之后被调用。实例已完成以下的配置:数据观测(data observer),属性和方法的运算,event事件回调。挂载阶段尚未开始,$el 属性不可见。
③ beforeMount
在挂载开始之前被调用。相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
④ mounted
在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。此时模板中的html渲染到了html页面中,此时一般可以做一些Ajax操作。注意mounted只会执行一次。
⑤ beforeUpdate
在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
⑥ updated
在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
⑦ beforeDestroy
在实例销毁之前调用。实例仍然完全可用。
⑧ destroyed
在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
关于Vue生命周期的详细解释,请参考文章:
https://www.cnblogs.com/penghuwan/p/7192203.html
这篇文章,写的非常详细,大家可以观摩一下!
如果有想做Vue开发的,vue生命周期必问。大概描述一下Vue实例被创建开始到实例销毁时的过程,就可以了!
二、组件间通信之中央事件总线bus
摘要
中央事件总线 - 就是一个名字可以叫做bus的vue空实例,里边没有任何内容; var bus = new Vue(); 人如其名,她就像一个公交车一样,来回输送人,将a站点的A输送到b站点,再将b站点的B输送到a站点; 这里a,b站点就像父、子组件,也像兄、弟组件,或者像两个没有任何亲戚关系的任何组件; 而A,B就像是各个组件内部要传输的数据或者要执行的命令信息,靠bus来通信。
中央事件总线 - 就是一个名字可以叫做bus的vue空实例,里边没有任何内容;
var bus = new Vue();
人如其名,她就像一个公交车一样,来回输送人,将a站点的A输送到b站点,再将b站点的B输送到a站点;
这里a,b站点就像父、子组件,也像兄、弟组件,或者像两个没有任何亲戚关系的任何组件;
而A,B就像是各个组件内部要传输的数据或者要执行的命令信息,靠bus来通信。
如果有父子组件通信知识基础的,应该记得当初父子组件通信,父组件中用$on监听,子组件中用$emit发射。
现如今父子组件(或任何其他关系的两个组件之间)达成一致协议:
将监听和发射的工作交给了bus来搞,就好像他们两头不自驾接送了,改乘公交了自己出发自己回家了。
那局面就是下边这样
$on和$emit现在绑在bus上了
bus.$emit(‘同名自定义事件名’,‘$on发送过来的数据’);
在a站点(第一个组件)中的methods方法里,准备用bus的$emit发射事件任务。
bus.$on(‘自定义事件名’,function(){ //然后执行什么你自己懂的。。。 });
在b站点(另一个组件)实例初始化(mounted钩子中)时,用bus的$on监听自家$emit触发的事件。
Bus实例扩展:
可以再添加data、methods、computed等选项,在初始化时让bus获取一下,任何组件都可以公用了。就像公交车上的座位,只要有座谁都能坐。
可以包括一些共享通用的信息:比如用户登录的姓名、性别、邮箱等,还有油壶授权的token等。
举例:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <component_1></component_1> <hr> <component_2></component_2> </div> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var bus = new Vue(); //中央事件总线 var app = new Vue({ el: '#app', data: { name: 'component_1' }, components:{ //注册局部组件 component_1:{ //v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码 template: `<button v-on:click='paolu'>点我</button>`, methods:{ paolu(){ alert(123); bus.$emit('taopao'); //绑定到bus上 } } }, component_2: { template: `<div>{{num}}</div>`, data:function(){ return { num: 0 } }, mounted:function(){ // 在这个作用域中 this 指的是 component_2 组件实例 console.log(this.num); var _this = this; // el被挂载之后就监听bus bus.$on('taopao', function(){ // 在这个作用域中 this 指的是 bus console.log(this.num); // undefined alert('赵天柱跑路了...'); // 修改component_2组件中的num值 // 此时this是谁?是bus // this.num += 1; // 有问题 _this.num += 1; }) } } } }) </script> </body> </html>
刷新网页,效果如下:
、
一、Vue Router
Vue Router中文文档
介绍
Vue全家桶:Vue + VueRouter + VueX
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于 Vue.js 过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的 CSS class 的链接
- HTML5 历史模式或 hash 模式,在 IE9 中自动降级
- 自定义的滚动条行为
先来看一个网页
点击不同的选项时,页面并没有刷新。如何判断页面有没有刷新呢?看浏览器左侧这个按钮,有没有闪动。
虽然看到url地址变化了,但它并没有刷新。这个,就是用vue实现的!
起步
用 Vue.js + Vue Router 创建单页应用,是非常简单的。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们。下面是个基本例子:
基本使用
必须先引用vue.js,再引用vue-router.js
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--路由的入口--> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <!--/index表示下面定义的path--> <router-link to="/index">index页面</router-link> <router-link to="/home">home页面</router-link> <hr> <!--路由的出口--> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <script> //写路由 const routeArray = [ { path:'/index', component:{ template:`<div><h1>这是index页面</h1></div>` }, }, { path:'/home', component:{ //component表示用什么渲染 //template 表示使用模板 template:`<div><h1>这是home页面</h1></div>` }, } ] //生成VueRouter实例 const routerObj = new VueRouter({ //routes是关键字,它的值必须是一个数组 routes:routeArray }) //创建和挂载根实例,记得要通过 router 配置参数注入路由 //从而让整个应用都有路由功能 var app = new Vue({ el:'#app', //router是关键字 router:routerObj //将路由实例挂载到vue实例中 }) </script> </body> </html>
刷新网页,效果如下:
动态路由匹配
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User
组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router
的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
const User = { template: '<div>User</div>' } const router = new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: '/user/:id', component: User } ] })
现在呢,像 /user/foo
和 /user/bar
都将映射到相同的路由。
一个“路径参数”使用冒号 :
标记。当匹配到一个路由时,参数值会被设置到 this.$route.params
,可以在每个组件内使用。于是,我们可以更新 User
的模板,输出当前用户的 ID:
const User = { template: '<div>User {{ $route.params.id }}</div>' }
你可以看看这个在线例子。
你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params
中。例如:
模式 | 匹配路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: 'evan' } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: 123 } |
除了 $route.params
外,$route
对象还提供了其它有用的信息,例如,$route.query
(如果 URL 中有查询参数)、$route.hash
等等。你可以查看 API 文档 的详细说明。
举例:显示xx的页面
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--路由的入口--> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <!--/index表示下面定义的path--> <router-link to="/user/zly">赵丽颖</router-link> <router-link to="/user/hx">韩雪</router-link> <hr> <!--路由的出口--> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <script> //写路由 const routeArray = [ { path: '/user/:name', component: { template: `<div> <h1>这是{{$route.params.name}}的主页页面!</h1> </div>` } } ] //生成VueRouter实例 const routerObj = new VueRouter({ routes: routeArray }) var app = new Vue({ el:'#app', data:{}, //router是关键字,它的值必须是数组 router:routerObj //将路由实例挂载到vue实例中 }) </script> </body> </html>
刷新网页,效果如下:
举例2: 获取url中的年龄和爱好
比如: file:///E:/python_script/Vue/test.html#/user/hx?age=18&hobby=sing
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--路由的入口--> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <!--/index表示下面定义的path--> <router-link to="/user/zsp">赵丽颖</router-link> <router-link to="/user/hx">韩雪</router-link> <hr> <!--路由的出口--> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <script> //写路由 const routeArray = [ { path: '/user/:name', component: { template: `<div> <h1>这是{{$route.params.name}}的主页页面!</h1> <p>芳龄: {{$route.query.age}}</p> <p>爱好: {{$route.query.hobby}}</p> </div>` } } ] //生成VueRouter实例 const routerObj = new VueRouter({ //routes是关键字参数,它必须对应一个数组 routes: routeArray }) var app = new Vue({ el:'#app', data:{}, //router是关键字,它的值必须是数组 router:routerObj //将路由实例挂载到vue实例中 }) </script> </body> </html>
刷新网页,访问以下url:
file:///E:/python_script/Vue/test.html#/user/hx?age=18&hobby=看电影
注意:后面的参数是手动加的!效果如下:
嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:
/user/foo/profile /user/foo/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
借助 vue-router
,使用嵌套路由配置,就可以很简单地表达这种关系。
接着上节创建的 app:
html代码
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<div id="app"> <router-view></router-view> </div>
js代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
const User = { template: '<div>User {{ $route.params.id }}</div>' } const router = new VueRouter({ routes: [ { path: '/user/:id', component: User } ] })
这里的 <router-view>
是最顶层的出口,渲染最高级路由匹配到的组件。同样地,一个被渲染组件同样可以包含自己的嵌套 <router-view>
。例如,在 User
组件的模板添加一个 <router-view>
:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
const User = { template: ` <div class="user"> <h2>User {{ $route.params.id }}</h2> <router-view></router-view> </div> ` }
要在嵌套的出口中渲染组件,需要在 VueRouter
的参数中使用 children
配置:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { // 当 /user/:id/profile 匹配成功, // UserProfile 会被渲染在 User 的 <router-view> 中 path: 'profile', component: UserProfile }, { // 当 /user/:id/posts 匹配成功 // UserPosts 会被渲染在 User 的 <router-view> 中 path: 'posts', component: UserPosts } ] } ] })
要注意,以 /
开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。
你会发现,children
配置就是像 routes
配置一样的路由配置数组,所以呢,你可以嵌套多层路由。
此时,基于上面的配置,当你访问 /user/foo
时,User
的出口是不会渲染任何东西,这是因为没有匹配到合适的子路由。如果你想要渲染点什么,可以提供一个 空的 子路由:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ // 当 /user/:id 匹配成功, // UserHome 会被渲染在 User 的 <router-view> 中 { path: '', component: UserHome }, // ...其他子路由 ] } ] })
举例:点击用户后,展示用户详细信息,并能点击查看
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--路由的入口--> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <!--/index表示下面定义的path--> <router-link to="/user/zsp">赵丽颖</router-link> <router-link to="/user/hx">韩雪</router-link> <hr> <!--路由的出口--> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <script> // 写路由 // route --> 路由 // routes --> 多个路由 // router --> 路由实例 const routeArray = [ { path: '/user/:name', component: { //append表示当前路由拼接url,比如/user/hx/info //router-view对应子路由的template的内容 template: `<div> <h1>这是{{$route.params.name}}的主页页面!</h1> <p>{{$route.query.age}}</p> <p>{{$route.query.hobby}}</p> <hr> <router-link to='info' append>用户详细信息</router-link> <router-view></router-view> </div>` }, // 定义子路由 children:[ { path: 'info', component:{ template: ` <div> <h1>钗头凤 唐婉</h1> <p>忠厚老实人的恶毒像饭里的砂砾或脱骨鱼片里未净的刺给人一种不期待的伤痛。</p> </div> ` } }, ] } ] //生成VueRouter实例 const routerObj = new VueRouter({ //routes是关键字参数,它必须对应一个数组 routes: routeArray }) var app = new Vue({ el:'#app', data:{}, //router是关键字,它的值必须是数组 router:routerObj //将路由实例挂载到vue实例中 }) </script> </body> </html>
刷新网页,效果如下:
总结:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1. 基本使用 1. 必须导入vue-router.js文件 2. 要有VueRouter()实例 3. 要把VueRouter实例挂载到Vue实例中 4. 路由的入口 <router-link to='/index'>index页面</router-link> 5. 路由的出口 <router-view></router-view> 2. 路由的参数 1. path: '/user/:name' --> 匹配路由 $route.params.name --> 取值 2. /user/alex?age=9000 --> url中携带参数 $route.query.age --> 取出url的参数 3. 子路由 children:[ { path: '', component: { template: `...` } } ] <router-link to='info' append></router-link>
四、webpack模板
官方文档:
https://vuejs-templates.github.io/webpack/
介绍
这个样板是针对大型,严肃的项目,并假设您有点熟悉Webpack和vue-loader
。请务必阅读常用工作流程配方vue-loader
的文档。
如果您只想尝试vue-loader
或制作快速原型,请使用webpack-simple模板。
快速开始
要使用此模板,请使用vue-cli构建项目。建议使用npm 3+以获得更高效的依赖树。
$ npm install -g vue-cli $ vue init webpack my-project $ cd my-project $ npm install $ npm run dev
vue-cli 是进行vue开发的脚手架工具,方便快速生成一个vue项目
先来查看node和npm的版本
C:\Users\xiao>node -v v10.7.0 C:\Users\xiao>npm -v 6.1.0
新建一个文件夹vue-cli_demo,进入到此目录
C:\Users\xiao>e: E:\>cd E:\python_script\Vue\vue-cli_demo
安装vue-cli,-g表示全局
E:\python_script\Vue\vue-cli_demo>npm install -g vue-cli npm WARN deprecated coffee-script@1.12.7: CoffeeScript on NPM has moved to "coffeescript" (no hyphen) C:\Users\xiao\AppData\Roaming\npm\vue -> C:\Users\xiao\AppData\Roaming\npm\node_modules\vue-cli\bin\vue C:\Users\xiao\AppData\Roaming\npm\vue-init -> C:\Users\xiao\AppData\Roaming\npm\node_modules\vue-cli\bin\vue-init C:\Users\xiao\AppData\Roaming\npm\vue-list -> C:\Users\xiao\AppData\Roaming\npm\node_modules\vue-cli\bin\vue-list + vue-cli@2.9.6 added 252 packages from 218 contributors in 29.948s
查看vue版本
E:\python_script\Vue\vue-cli_demo>vue -V
2.9.6
基于webpack模板创建项目mysite
E:\python_script\Vue\vue-cli_demo>vue init webpack mysite
提示项目名,直接回车
? Project name (mysite)
项目描述,直接回车
? Project name mysite
? Project description (A Vue.js project)
作者,直接回车
? Project name mysite
? Project description A Vue.js project
? Author (xiao <383697894@qq.com>)
这个时候,文字是有颜色的,表示选中状态,可以用光标上下移动。选择默认的Runtime + 即可!
是否安装vue-router,直接回车
? Project name mysite ? Project description A Vue.js project ? Author xiao <383697894@qq.com> ? Vue build standalone ? Install vue-router? (Y/n)
是否使用ESLint来编写代码?输入n
它是javascript代码检测工具,必须遵循一定的规则才行,否则会有提示!
对于初学者而言,不建议开启,否则写代码会很痛苦的!
? Project name mysite ? Project description A Vue.js project ? Author xiao <383697894@qq.com> ? Vue build standalone ? Install vue-router? Yes ? Use ESLint to lint your code? (Y/n) n
设置单元测试,输入n
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
为了快速开发,直接关闭掉
? Project name mysite ? Project description A Vue.js project ? Author xiao <383697894@qq.com> ? Vue build standalone ? Install vue-router? Yes ? Use ESLint to lint your code? No ? Set up unit tests (Y/n) n
是否使用Nightwatch设置e2e测试,输入n
Nightwatch.js是一个测试web app和web 站点的自动化测试框架, 使用Node.js编写, 基于Selenium WebDriver API.
它是一个完整的浏览器端真实用户场景测试解决方案, 致力于简化继续集成和编写自动化测试。
小白表示一脸懵逼,关闭掉!
? Project name mysite ? Project description A Vue.js project ? Author xiao <383697894@qq.com> ? Vue build standalone ? Install vue-router? Yes ? Use ESLint to lint your code? No ? Set up unit tests No ? Setup e2e tests with Nightwatch? (Y/n) n
最后提示,使用npm安装组件
直接回车即可
上面的选项,是一条最简单的路。初学时,使用最简单的即可!
下面就表示开始安装了,耐心等待几分钟!
出现finished,表示安装成功
根据提示,执行2个命令,启动项目
它会开启本机的8080端口,通过即可
提示运行的应用在 http://localhost:8080
访问url,效果如下:
项目结构
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
. ├── build/ # webpack 配置文件 │ └── ... ├── config/ │ ├── index.js # 主项目配置 │ └── ... ├── src/ │ ├── main.js # app 入口文件 │ ├── App.vue # 主应用程序组件 │ ├── components/ # ui组件 │ │ └── ... │ └── assets/ # 模块资产(由webpack处理) │ └── ... ├── static/ # 纯静态资产(直接复制) ├── test/ │ └── unit/ # 单元测试 │ │ ├── specs/ # 测试规范文件 │ │ ├── eslintrc # eslint的配置文件,仅用于单元测试的额外设置 │ │ ├── index.js # 测试构建入口文件 │ │ ├── jest.conf.js # 使用Jest进行单元测试时的配置文件 │ │ └── karma.conf.js # 使用Karma进行单元测试时测试运行器配置文件 │ │ ├── setup.js # 在Jest运行单元测试之前运行的文件 │ └── e2e/ # e2e测试 │ │ ├── specs/ # 测试规范文件 │ │ ├── custom-assertions/ # e2e测试的自定义断言 │ │ ├── runner.js # 测试运行脚本 │ │ └── nightwatch.conf.js # 测试运行器配置文件 ├── .babelrc # babel 配置 ├── .editorconfig # 缩进,空格/制表符和编辑器的类似设置 ├── .eslintrc.js # eslint 配置 ├── .eslintignore # eslint 忽略规则 ├── .gitignore # 默认的忽略设置 ├── .postcssrc.js # postcss 配置 ├── index.html # index.html 模板 ├── package.json # 构建脚本和依赖项 └── README.md # 默认 README 文件
主要几个文件说明:
build/
此目录包含开发服务器和生产webpack构建的实际配置。通常,您不需要触摸这些文件,除非您想要自定义Webpack加载器,在这种情况下您应该查看build/webpack.base.conf.js
。
config/index.js
这是主要配置文件,它公开了构建设置的一些最常见的配置选项。有关详细信息,请参阅开发期间的API代理和与后端框架集成。
src/
这是您的大多数应用程序代码所在的位置。如何构建此目录中的所有内容在很大程度上取决于您; 如果您使用的是Vuex,则可以参考Vuex应用程序的建议。
static/
此目录是您不希望使用Webpack处理的静态资产的转义窗口。它们将直接复制到生成webpack构建资产的同一目录中。
有关详细信息,请参阅处理静态资产。
test/unit
包含单元测试相关文件。有关详细信息,请参阅单元测试
test/e2e
包含e2e测试相关文件。有关更多详细信息,请参阅端到端测试。
index.html
这是我们单页面应用程序的模板 index.html
。在开发和构建期间,Webpack将生成资产,这些生成的资产的URL将自动注入此模板以呈现最终的HTML。
package.json
包含所有构建依赖项和构建命令的NPM包元文件。
构建命令
所有构建命令都通过NPM脚本执行。
npm run dev
启动Node.js本地开发服务器。有关详细信息,请参阅开发期间的API代理。
- Webpack +
vue-loader
用于单个文件Vue组件。 - 状态保持热重载
- 状态保留编译错误覆盖
- 使用ESLint进行Lint-on-save
- 来源地图
npm run build
为生产建立资产。有关更多详细信息,请参阅与后端框架集成。
- 使用UglifyJS v3缩小JavaScript 。
- 用html-minifier缩小HTML。
- 将所有组件中的CSS提取到单个文件中并使用cssnano缩小。
- 所有静态资产都使用版本哈希编译,以实现高效的长期缓存,并
index.html
使用适当的URL自动生成生产,以生成这些生成的资产。
npm run unit
- 在测试文件中支持ES2015 +。
- 轻松嘲笑。
npm run e2e
使用Nightwatch运行端到端测试。有关更多详细信息,请参阅端到端测试。
- 在多个浏览器中并行运行测试。
- 使用一个开箱即用的命令:
- 自动处理Selenium和chromedriver依赖项。
- 自动生成Selenium服务器。
npm run lint
运行eslint并报告代码中的任何linting错误。请参阅Linter配置
修改默认的页面
先停止vue项目
安装bootstrap,执行版本为3.3.7。-D表示当前项目安装
E:\python_script\Vue\vue-cli_demo\mysite>npm install bootstrap@3.3.7 -D
再开启vue项目
E:\python_script\Vue\vue-cli_demo\mysite>npm run dev
查看package.json,搜索bootstrap
它会自动增加一行
进入node_modules目录,里面有一个bootstrap文件夹
进入mysite\src,这里面才是我们需要编辑的文件
文件说明:
main.js # app 入口文件 App.vue # 主应用程序组件 components/ # ui组件 assets/ # 模块资产(由webpack处理)
查看App.vue文件内容,它由3部分组成
<template></template> html代码 <script></script> js代码 <style></style> css代码
修改App.vue,导入bootstrap的css文件
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<template> <div id="app"> <!--导航条--> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Brand</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">我的首页</a></li> <li><a href="#">我的笔记</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="#">登录</a></li> <li class="dropdown"> <li><a href="#">注册</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <img src="./assets/logo.png"> <router-view/> </div> </template> <script> //导入bootstrap import 'bootstrap/dist/css/bootstrap.min.css' export default { name: 'App' } </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>
查看网页,效果如下:
注意:修改代码时,网页会自动加载,不需要刷新页面!
作业:
使用vue-cli,实现下面的效果:
修改App.vue,代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<template> <div id="app"> <!--导航条--> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Brand</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">我的首页</a></li> <li><a href="#">我的笔记</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="#">登录</a></li> <li class="dropdown"> <li><a href="#">注册</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container-fluid text-left"> <div class="row clearfix"> <div class="col-md-12 column"> <div class="row clearfix"> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title"> 我的笔记 </h3> </div> <div class="panel-body"> <div class="col-md-2 column"> <ul class="list-group"> <li class="list-group-item"> 我的笔记1 <span class="glyphicon glyphicon-trash pull-right"></span> <span class="glyphicon glyphicon-pencil pull-right" style="margin-right: 15px"></span> </li> <li class="list-group-item"> 我的笔记2 <span class="glyphicon glyphicon-trash pull-right"></span> <span class="glyphicon glyphicon-pencil pull-right" style="margin-right: 15px"></span> </li> <li class="list-group-item"> 测试测试1 <span class="glyphicon glyphicon-trash pull-right"></span> <span class="glyphicon glyphicon-pencil pull-right" style="margin-right: 15px"></span> </li> <li class="list-group-item"> 测试测试2 <span class="glyphicon glyphicon-trash pull-right"></span> <span class="glyphicon glyphicon-pencil pull-right" style="margin-right: 15px"></span> </li> </ul> </div> <div class="col-md-5 column"> <form> <div class="input-group"> <span class="input-group-addon" id="basic-addon1">标题</span> <input type="text" class="form-control" placeholder="请输入标题" aria-describedby="basic-addon1"> </div> <div style="height: 20px"></div> <textarea class="form-control" style="width: 100%;height: 600px;" placeholder="Textarea"></textarea> </form> </div> <div class="col-md-5 column"> <button type="button" class="btn btn-default btn-success">添加</button> <div style="height: 20px"></div> <div style="border: #C1C1C1 solid 1px;height: 600px"> </div> </div> </div> <!--<div class="panel-footer">--> <!--Panel footer--> <!--</div>--> </div> </div> </div> </div> </div> <!--<img src="./assets/logo.png">--> <!--<router-view/>--> </div> </template> <script> //导入bootstrap import 'bootstrap/dist/css/bootstrap.min.css' export default { name: 'App' } </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>
访问网页,效果如下: