1. 定义Vue组件
什么是组件:组件的出现,就是为了拆分 Vue 实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可;
组件化和模块化的不同:
- 模块化:是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
- 组件化:是从 UI 界面的角度进行划分的;前端的组件化,方便 UI 组件的重用;
1.1. 全局组件定义的三种方式
- 使用 Vue.extend 配合 Vue.component 方法:
1var login = Vue.extend({
2 template: '<h1>登录</h1>'
3 });
4 Vue.component('login', login);
- 直接使用 Vue.component 方法:
1Vue.component('register', {
2 template: '<h1>注册</h1>'
3 });
- 将模板字符串,定义到 script 标签种:
1<script id="tmpl" type="x-template">
2 <div><a href="#">登录</a> | <a href="#">注册</a></div>
3 </script>
同时,需要使用 Vue.component 来定义组件:
1Vue.component('account', {
2 template: '#tmpl'
3 });
注意:组件中的 DOM 结构,有且只能有唯一的根元素(Root Element)来进行包裹!
1.2. 组件中展示数据和响应事件
- 在组件中,
data
需要被定义为一个方法,例如:
1Vue.component('account', {
2 template: '#tmpl',
3 data() {
4 return {
5 msg: '大家好!'
6 }
7 },
8 methods:{
9 login(){
10 alert('点击了登录按钮');
11 }
12 }
13 });
- 在子组件中,如果将模板字符串,定义到了script标签中,那么,要访问子组件身上的
data
属性中的值,需要使用this
来访问;
1.3. 【重点】为什么组件中的data属性必须定义为一个方法并返回一个对象
- 通过计数器案例演示
1.4. 使用 components 属性定义局部子组件
- 组件实例定义方式:
1<script>
2 // 创建 Vue 实例,得到 ViewModel
3 var vm = new Vue({
4 el: '#app',
5 data: {},
6 methods: {},
7 components: { // 定义子组件
8 account: { // account 组件
9 template: '<div><h1>这是Account组件{{name}}</h1><login></login></div>', // 在这里使用定义的子组件
10 components: { // 定义子组件的子组件
11 login: { // login 组件
12 template: "<h3>这是登录组件</h3>"
13 }
14 }
15 }
16 }
17 });
18 </script>
- 引用组件:
1<div id="app">
2 <account></account>
3 </div>
2. 使用 flag 标识符结合 v-if 和 v-else 切换组件
- 页面结构:
1<div id="app">
2 <input type="button" value="toggle" @click="flag=!flag">
3 <my-com1 v-if="flag"></my-com1>
4 <my-com2 v-else="flag"></my-com2>
5 </div>
- Vue 实例定义:
1<script>
2 Vue.component('myCom1', {
3 template: '<h3>奔波霸</h3>'
4 })
5
6 Vue.component('myCom2', {
7 template: '<h3>霸波奔</h3>'
8 })
9
10 // 创建 Vue 实例,得到 ViewModel
11 var vm = new Vue({
12 el: '#app',
13 data: {
14 flag: true
15 },
16 methods: {}
17 });
18 </script>
3. 使用 :is 属性来切换不同的子组件,并添加切换动画
- 组件实例定义方式:
1 // 登录组件
2 const login = Vue.extend({
3 template: `<div>
4 <h3>登录组件</h3>
5 </div>`
6 });
7 Vue.component('login', login);
8
9 // 注册组件
10 const register = Vue.extend({
11 template: `<div>
12 <h3>注册组件</h3>
13 </div>`
14 });
15 Vue.component('register', register);
16
17 // 创建 Vue 实例,得到 ViewModel
18 var vm = new Vue({
19 el: '#app',
20 data: { comName: 'login' },
21 methods: {}
22 });
- 使用
component
标签,来引用组件,并通过:is
属性来指定要加载的组件:
1 <div id="app">
2 <a href="#" @click.prevent="comName='login'">登录</a>
3 <a href="#" @click.prevent="comName='register'">注册</a>
4 <hr>
5 <transition mode="out-in">
6 <component :is="comName"></component>
7 </transition>
8 </div>
- 添加切换样式:
1 <style>
2 .v-enter,
3 .v-leave-to {
4 opacity: 0;
5 transform: translateX(30px);
6 }
7
8 .v-enter-active,
9 .v-leave-active {
10 position: absolute;
11 transition: all 0.3s ease;
12 }
13
14 h3{
15 margin: 0;
16 }
17 </style>
4. 父组件向子组件传值
- 组件实例定义方式,注意:一定要使用
props
属性来定义父组件传递过来的数据
1<script>
2 // 创建 Vue 实例,得到 ViewModel
3 var vm = new Vue({
4 el: '#app',
5 data: {
6 msg: '这是父组件中的消息'
7 },
8 components: {
9 son: {
10 template: '<h1>这是子组件 --- {{finfo}}</h1>',
11 props: ['finfo']
12 }
13 }
14 });
15 </script>
- 使用
v-bind
或简化指令,将数据传递到子组件中:
1<div id="app">
2 <son :finfo="msg"></son>
3 </div>
5. 子组件向父组件传值
- 原理:父组件将方法的引用,传递到子组件内部,子组件在内部调用父组件传递过来的方法,同时把要发送给父组件的数据,在调用方法的时候当作参数传递进去;
- 父组件将方法的引用传递给子组件,其中,
getMsg
是父组件中methods
中定义的方法名称,func
是子组件调用传递过来方法时候的方法名称
1<son @func="getMsg"></son>
- 子组件内部通过
this.$emit('方法名', 要传递的数据)
方式,来调用父组件中的方法,同时把数据传递给父组件使用
1<div id="app">
2 <!-- 引用父组件 -->
3 <son @func="getMsg"></son>
4
5 <!-- 组件模板定义 -->
6 <script type="x-template" id="son">
7 <div>
8 <input type="button" value="向父组件传值" @click="sendMsg" />
9 </div>
10 </script>
11 </div>
12
13 <script>
14 // 子组件的定义方式
15 Vue.component('son', {
16 template: '#son', // 组件模板Id
17 methods: {
18 sendMsg() { // 按钮的点击事件
19 this.$emit('func', 'OK'); // 调用父组件传递过来的方法,同时把数据传递出去
20 }
21 }
22 });
23
24 // 创建 Vue 实例,得到 ViewModel
25 var vm = new Vue({
26 el: '#app',
27 data: {},
28 methods: {
29 getMsg(val){ // 子组件中,通过 this.$emit() 实际调用的方法,在此进行定义
30 alert(val);
31 }
32 }
33 });
34 </script>
6. 评论列表案例
目标:主要练习父子组件之间传值
7. 使用 this.$refs 来获取元素和组件
1 <div id="app">
2 <div>
3 <input type="button" value="获取元素内容" @click="getElement" />
4 <!-- 使用 ref 获取元素 -->
5 <h1 ref="myh1">这是一个大大的H1</h1>
6
7 <hr>
8 <!-- 使用 ref 获取子组件 -->
9 <my-com ref="mycom"></my-com>
10 </div>
11 </div>
12
13 <script>
14 Vue.component('my-com', {
15 template: '<h5>这是一个子组件</h5>',
16 data() {
17 return {
18 name: '子组件'
19 }
20 }
21 });
22
23 // 创建 Vue 实例,得到 ViewModel
24 var vm = new Vue({
25 el: '#app',
26 data: {},
27 methods: {
28 getElement() {
29 // 通过 this.$refs 来获取元素
30 console.log(this.$refs.myh1.innerText);
31 // 通过 this.$refs 来获取组件
32 console.log(this.$refs.mycom.name);
33 }
34 }
35 });
36 </script>
8. 什么是路由
- 对于普通的网站,所有的超链接都是 URL 地址,所有的 URL 地址都对应服务器上对应的资源;
- 对于单页面应用程序来说,主要通过URL中的 hash(#号)来实现不同页面之间的切换,同时,hash 有一个特点:HTTP 请求中不会包含 hash相关的内容;所以,单页面程序中的页面跳转主要用 hash 实现;
- 在单页面应用程序中,这种通过 hash 改变来切换页面的方式,称作前端路由(区别于后端路由);
9. 在 vue 中使用 vue-router
- 导入 vue-router 组件类库:
1<!-- 1. 导入 vue-router 组件类库 -->
2 <script src="./lib/vue-router-2.7.0.js"></script>
- 使用 router-link 组件来导航
1<!-- 2. 使用 router-link 组件来导航 -->
2<router-link to="/login">登录</router-link>
3<router-link to="/register">注册</router-link>
- 使用 router-view 组件来显示匹配到的组件
1<!-- 3. 使用 router-view 组件来显示匹配到的组件 -->
2<router-view></router-view>
- 创建使用
Vue.extend
创建组件
1 // 4.1 使用 Vue.extend 来创建登录组件
2 var login = Vue.extend({
3 template: '<h1>登录组件</h1>'
4 });
5
6 // 4.2 使用 Vue.extend 来创建注册组件
7 var register = Vue.extend({
8 template: '<h1>注册组件</h1>'
9 });
- 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则
1// 5. 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则
2 var router = new VueRouter({
3 routes: [
4 { path: '/login', component: login },
5 { path: '/register', component: register }
6 ]
7 });
- 使用 router 属性来使用路由规则
1// 6. 创建 Vue 实例,得到 ViewModel
2 var vm = new Vue({
3 el: '#app',
4 router: router // 使用 router 属性来使用路由规则
5 });
10. 在路由规则中定义参数
- 在规则中定义参数:
1{ path: '/register/:id', component: register }
- 通过
this.$route.params
来获取路由中的参数:
1var register = Vue.extend({
2 template: '<h1>注册组件 --- {{this.$route.params.id}}</h1>'
3 });
11. 使用 children 属性实现路由嵌套
1 <div id="app">
2 <router-link to="/account">Account</router-link>
3
4 <router-view></router-view>
5 </div>
6
7 <script>
8 // 父路由中的组件
9 const account = Vue.extend({
10 template: `<div>
11 这是account组件
12 <router-link to="/account/login">login</router-link> |
13 <router-link to="/account/register">register</router-link>
14 <router-view></router-view>
15 </div>`
16 });
17
18 // 子路由中的 login 组件
19 const login = Vue.extend({
20 template: '<div>登录组件</div>'
21 });
22
23 // 子路由中的 register 组件
24 const register = Vue.extend({
25 template: '<div>注册组件</div>'
26 });
27
28 // 路由实例
29 var router = new VueRouter({
30 routes: [
31 { path: '/', redirect: '/account/login' }, // 使用 redirect 实现路由重定向
32 {
33 path: '/account',
34 component: account,
35 children: [ // 通过 children 数组属性,来实现路由的嵌套
36 { path: 'login', component: login }, // 注意,子路由的开头位置,不要加 / 路径符
37 { path: 'register', component: register }
38 ]
39 }
40 ]
41 });
42
43 // 创建 Vue 实例,得到 ViewModel
44 var vm = new Vue({
45 el: '#app',
46 data: {},
47 methods: {},
48 components: {
49 account
50 },
51 router: router
52 });
53 </script>
12. 命名视图实现经典布局
- 标签代码结构:
1<div id="app">
2 <router-view></router-view>
3 <div class="content">
4 <router-view name="a"></router-view>
5 <router-view name="b"></router-view>
6 </div>
7 </div>
- JS 代码:
1<script>
2 var header = Vue.component('header', {
3 template: '<div class="header">header</div>'
4 });
5
6 var sidebar = Vue.component('sidebar', {
7 template: '<div class="sidebar">sidebar</div>'
8 });
9
10 var mainbox = Vue.component('mainbox', {
11 template: '<div class="mainbox">mainbox</div>'
12 });
13
14 // 创建路由对象
15 var router = new VueRouter({
16 routes: [
17 {
18 path: '/', components: {
19 default: header,
20 a: sidebar,
21 b: mainbox
22 }
23 }
24 ]
25 });
26
27 // 创建 Vue 实例,得到 ViewModel
28 var vm = new Vue({
29 el: '#app',
30 data: {},
31 methods: {},
32 router
33 });
34 </script>
- CSS 样式:
1 <style>
2 .header {
3 border: 1px solid red;
4 }
5
6 .content{
7 display: flex;
8 }
9 .sidebar {
10 flex: 2;
11 border: 1px solid green;
12 height: 500px;
13 }
14 .mainbox{
15 flex: 8;
16 border: 1px solid blue;
17 height: 500px;
18 }
19 </style>
13. watch 属性的使用
考虑一个问题:想要实现 名
和 姓
两个文本框的内容改变,则全名的文本框中的值也跟着改变;(用以前的知识如何实现???)
- 监听
data
中属性的改变:
1<div id="app">
2 <input type="text" v-model="firstName"> +
3 <input type="text" v-model="lastName"> =
4 <span>{{fullName}}</span>
5 </div>
6
7 <script>
8 // 创建 Vue 实例,得到 ViewModel
9 var vm = new Vue({
10 el: '#app',
11 data: {
12 firstName: 'jack',
13 lastName: 'chen',
14 fullName: 'jack - chen'
15 },
16 methods: {},
17 watch: {
18 'firstName': function (newVal, oldVal) { // 第一个参数是新数据,第二个参数是旧数据
19 this.fullName = newVal + ' - ' + this.lastName;
20 },
21 'lastName': function (newVal, oldVal) {
22 this.fullName = this.firstName + ' - ' + newVal;
23 }
24 }
25 });
26 </script>
- 监听路由对象的改变:
1<div id="app">
2 <router-link to="/login">登录</router-link>
3 <router-link to="/register">注册</router-link>
4
5 <router-view></router-view>
6 </div>
7
8 <script>
9 var login = Vue.extend({
10 template: '<h1>登录组件</h1>'
11 });
12
13 var register = Vue.extend({
14 template: '<h1>注册组件</h1>'
15 });
16
17 var router = new VueRouter({
18 routes: [
19 { path: "/login", component: login },
20 { path: "/register", component: register }
21 ]
22 });
23
24 // 创建 Vue 实例,得到 ViewModel
25 var vm = new Vue({
26 el: '#app',
27 data: {},
28 methods: {},
29 router: router,
30 watch: {
31 '$route': function (newVal, oldVal) {
32 if (newVal.path === '/login') {
33 console.log('这是登录组件');
34 }
35 }
36 }
37 });
38 </script>
14. computed 计算属性的使用
- 默认只有
getter
的计算属性:
1<div id="app">
2 <input type="text" v-model="firstName"> +
3 <input type="text" v-model="lastName"> =
4 <span>{{fullName}}</span>
5 </div>
6
7 <script>
8 // 创建 Vue 实例,得到 ViewModel
9 var vm = new Vue({
10 el: '#app',
11 data: {
12 firstName: 'jack',
13 lastName: 'chen'
14 },
15 methods: {},
16 computed: { // 计算属性; 特点:当计算属性中所以来的任何一个 data 属性改变之后,都会重新触发 本计算属性 的重新计算,从而更新 fullName 的值
17 fullName() {
18 return this.firstName + ' - ' + this.lastName;
19 }
20 }
21 });
22 </script>
- 定义有
getter
和setter
的计算属性:
1<div id="app">
2 <input type="text" v-model="firstName">
3 <input type="text" v-model="lastName">
4 <!-- 点击按钮重新为 计算属性 fullName 赋值 -->
5 <input type="button" value="修改fullName" @click="changeName">
6
7 <span>{{fullName}}</span>
8 </div>
9
10 <script>
11 // 创建 Vue 实例,得到 ViewModel
12 var vm = new Vue({
13 el: '#app',
14 data: {
15 firstName: 'jack',
16 lastName: 'chen'
17 },
18 methods: {
19 changeName() {
20 this.fullName = 'TOM - chen2';
21 }
22 },
23 computed: {
24 fullName: {
25 get: function () {
26 return this.firstName + ' - ' + this.lastName;
27 },
28 set: function (newVal) {
29 var parts = newVal.split(' - ');
30 this.firstName = parts[0];
31 this.lastName = parts[1];
32 }
33 }
34 }
35 });
36 </script>
15. watch、computed 和 methods 之间的对比
computed
属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;methods
方法表示一个具体的操作,主要书写业务逻辑;watch
一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computed
和methods
的结合体;
16. nrm 的安装使用
作用:提供了一些最常用的 NPM 包镜像地址,能够让我们快速的切换安装包时候的服务器地址;
什么是镜像:原来包刚一开始是只存在于国外的 NPM 服务器,但是由于网络原因,经常访问不到,这时候,我们可以在国内,创建一个和官网完全一样的 NPM 服务器,只不过,数据都是从人家那里拿过来的,除此之外,使用方式完全一样;
- 运行
npm i nrm -g
全局安装nrm
包; - 使用
nrm ls
查看当前所有可用的镜像源地址以及当前所使用的镜像源地址; - 使用
nrm use npm
或nrm use taobao
切换不同的镜像源地址;