学习笔记-Vue基础

2021/12/18 Vue

本文记录Vue框架基础学习笔记

# 1. Vue介绍

MVVM

img

  • Model:负责数据存储
  • View:负责页面展示
  • View Model:负责业务逻辑处理(比如Ajax请求等),对数据进行加工后交给视图展示

Vue框架的特点

  • 模板渲染:基于 html 的模板语法,学习成本低。
  • 响应式的更新机制:数据改变之后,视图会自动刷新。【重要】
  • 渐进式框架
  • 组件化/模块化
  • 轻量:开启 gzip压缩后,可以达到 20kb 大小。(React 达到 35kb,AngularJS 达到60kb)。

利用 vue-cli 新建项目

Vue 提供一个官方命令行工具,可用于快速搭建大型单页应用。

  vue create demo
  cd demo
  npm run serve
1
2
3

img

  • build:打包配置的文件夹
  • config:webpack对应的配置
  • src:开发项目的源码
    • App.vue:入口组件,.vue文件都是组件。
    • main.js:项目入口文件。
  • static:存放静态资源
  • .babelrc:解析ES6的配置文件
  • .editorcofnig:编辑器的配置
  • .postcssrc.js:html添加前缀的配置
  • index.html:单页面的入口。通过 webpack打包后,会把 src 源码进行编译,插入到这个 html 里面来。
  • package.json:项目的基础配置,包含版本号、脚本命令、项目依赖库、开发依赖库、引擎等。

# 2. Vue的系统指令

  • 插值表达式 {{}}

    {{ number + 1 }}
    
    {{ ok ? 'YES' : 'NO' }}
    
    {{ name == '张三' ? 'true' : 'false' }}
    
    {{ message.split('').reverse().join('') }}
    
    1
    2
    3
    4
    5
    6
    7
  • v-text

    • 区别1:v-text 没有闪烁的问题,因为它是放在属性里的。
    • 区别2:插值表达式只会替换自己的这个占位符,并不会把整个元素的内容清空。v-text 会覆盖元素中原本的内容。
  • v-html

  • v-bind:属性绑定

    • 简写:”:“
  • v-on:事件绑定

    • 简写:”@“
    • .stop 阻止冒泡。本质是调用 event.stopPropagation()。
    • .prevent 阻止默认事件(默认行为)。本质是调用 event.preventDefault()。
    • .capture 添加事件监听器时,使用捕获的方式(也就是说,事件采用捕获的方式,而不是采用冒泡的方式)。
    • .self 只有当事件在该元素本身(比如不是子元素)触发时,才会触发回调。
    • .once 事件只触发一次。

举例:文字滚动显示(跑马灯效果)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文档标题</title>
</head>
<body>
<div id="app">
  <p>{{ name }}</p>
  <p>{{ info }}</p>
  <button @click='handleToRun'>开始</button>
  <button @click='handleToStop'>结束</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
  <script>
  let vm= new Vue({
  el:'#app',
  data(){
    return {
      name:'这是一条会滚动的字幕',
      info:'stop'
    }
  },
  methods:{ 
    handleToRun(){ 
      if(this.info!='run'){
        this.textScroll=setInterval(()=>{
        let start=this.name.slice(0,1)
        let end=this.name.slice(1)
        this.name=end+start
      },800)}
      this.info='run'
    },
    handleToStop(){
      clearInterval(this.textScroll)
      this.info='stop'
    }
  }
})
   </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 3. 列表功能

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
        <style>
            .table {
                width: 800px;
                margin: 20px auto;
                border-collapse: collapse; /*这一行,不能少:表格的两边框合并为一条*/
            }

            .table th {
                background: #0094ff;
                color: white;
                font-size: 16px;
                border: 1px solid black;
                padding: 5px;
            }

            .table tr td {
                text-align: center;
                font-size: 16px;
                padding: 5px;
                border: 1px solid black;
            }

            .form {
                width: 800px;
                margin: 20px auto;
            }

            .form button {
                margin-left: 10px;
            }
        </style>

        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
    </head>

    <body>
        <div id="app">
            <div class="form">
                编号:
                <input type="text" v-model="formData.id" /> 名称:
                <input type="text" v-model="formData.name" />
                <button @click="addData">添加</button>
                搜索:
                <input type="text" v-model="keywords" />
            </div>
            <table class="table">
                <th>编号</th>
                <th>名称</th>
                <th>创建时间</th>
                <th>操作</th>
                <tr v-show="list.length == 0">
                    <td colspan="4">列表无数据</td>
                </tr>
                <!-- 因为要渲染搜索结果,所以直接获取方法返回的对象,也可以在键入值的时候调用事件 -->
                <tr v-for="(item,index) in search(keywords)">
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.ctime}}</td>
                    <td>
                        <a href="#" @click="delData(index)">删除</a>
                    </td>
                </tr>
            </table>
        </div>
    </body>

    <script>
        var vm = new Vue({
            el: '#app',
            data() {
                return {
                    list: [
                        { id: 1, name: '奔驰', ctime: new Date() },
                        { id: 2, name: '宝马', ctime: new Date() }
                    ],
                    keywords: '',
                    formData: { id: '', name: '' }
                }
            },
            methods: {
              // 返回给列表循环
                search(keywords) {
                    let newList = this.list.filter((item) => {
                        if (item.name.includes(keywords)) return item
                    })
                    return newList
                },
                addData() {
                    if (this.formData.id && this.formData.name) {
                        this.formData.ctime = new Date()
                        // 浅拷贝
                        let obj = Object.assign({}, this.formData)
                        this.list.push(obj)
                    } else {
                        alert('输入为空')
                    }
                    this.formData.name = ''
                    this.formData.id = ''
                },
                delData(index) {
                    if (!confirm('是否要删除数据?')) {
                        //当用户点击的取消按钮的时候,应该阻断这个方法中的后面代码的继续执行
                        return
                    }
                    this.list.splice(index, 1)
                }
            }
        })
    </script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

# 4. 自定义指令

私有自定义指令

在每个vue组件中,可以在directives节点下声明私有自定义指令

<input type='text' v-color="'red'" />

// 自定义指令
directives:{
	color:{
        // 当指令第一次被绑定到元素时调用
		bind(el,binding){
			el.style.color=binding.value
		},
        // 每次DOM更新时被调用
        update(el,binding){
            el.style.color=binding.value
        }
	}
}

// 当bind和update的逻辑完全相同,可以简写成函数形式
directives:{
	color(el,binding){
        el.style.color=binding.value
        }
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

全局自定义指令

Vue.directive('color',function(el,binding){
	el.style.color=binding.value
})
1
2
3

# 5. 生命周期

image-20211216230626646

创建期间的生命周期函数

  • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
  • created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。我们可以在这里进行Ajax请求。
  • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
  • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示。(mounted之后,表示真实DOM渲染完了,可以操作DOM了

运行期间的生命周期函数

  • beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
  • updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。

PS:数据发生变化时,会触发这两个方法。不过,我们一般用watch来做。

销毁期间的生命周期函数

  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

PS:可以在beforeDestroy里清除定时器、或清除事件绑定

VUE生命周期

# 6. Vue组件

模块化和组件化的区别

  • 模块化:是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一
  • 组件化:是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用

image-20211217002135373

组件的定义和注册

import myAccount from '@/components/myAccount.vue'

// main.js中全局注册
Vue.component('account', myAccount);

// 私有
components:{
    myAccount
}
1
2
3
4
5
6
7
8
9

组件的切换

  • v-if和v-else
<button @click='flag=true'>登录</button>
<button @click='flag=false'>注册</button>

<login v-if='flag'></login>
<reg v-else></reg>

data(){
	return {
		flag=true
	}
}
1
2
3
4
5
6
7
8
9
10
11
  • <component>标签
<button @click="cName='login'">登录</button>
<button @click="cName='reg'">注册</button>

<!-- keep-alive可以保持组件状态,include里为会被缓存的组件 -->
<keep-alive include='reg'>
	<component :is="comName"></component>
</keep-alive>

data(){
	return {
		comName: 'login'
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

插槽

插槽Slot是vue为组件的封装者提供的能力,把不确定的、希望由用户指定的部分定义为插槽。

<!-- 父组件 -->
<!-- #top是v-slot='top'的简写 -->
<template #top>
	<p>这是Left组件的内容区域</p>
</template>

<template #mid='val'>
	<p>这是Left组件的内容区域</p>
    <p>{{ val.user.xxx }}</p>
</template>

<!-- 子组件 -->
<slot name='top'>
<!-- 具名插槽 -->
	<h6>这是插槽的默认内容</h6>
<slot>
<!-- 作用域插槽 -->    
<slot name='mid' :user='userinfo'>
	<h6>这是插槽的默认内容</h6>
<slot>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 7. 组件之间的数据共享

父向子传值

image-20211217113927437

注意:在子组件中的props是接受父组件传值的,且只读,如果要使用该数值,应该存放到子组件的data中。

子向父传值

image-20211217114757164

兄弟组件之间的数据共享

image-20211217115414003

通过ref获取DOM

ref是用来在不依赖原生和JQuery的情况下,获取DOM元素或组件的引用。 每个vue的组件实例上,都包含一个**$refs对象,里面储存着对应的DOM元素或组件**的引用,默认指向一个空对象。

<myComponent ref='myCom'></mycomponent>
<input type="button" value="点击按钮" @click="getRef">

methods:{
	getRef(){
		// 引用组件的实例之后,就可以调用组件上的方法了
		this.$refs.myCom.add()
	}
}

1
2
3
4
5
6
7
8
9
10

# 8. router路由

前端路由:Hash地址组件之间的对应关系

image-20211218161140785

// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'

// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'

import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'

import Login from '@/components/Login.vue'
import Main from '@/components/Main.vue'

// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
  routes: [
    // 重定向的路由规则
    { path: '/', redirect: '/home' },
    // 路由规则
    { path: '/home', component: Home },
    // 需求:在 Movie 组件中,希望根据 id 的值,展示对应电影的详情信息
    // 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
    { path: '/movie/:mid', component: Movie, props: true },
    {
      path: '/about',
      component: About,
      // redirect: '/about/tab1',
      children: [
        // 子路由规则
        // 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
        { path: '', component: Tab1 },
        { path: 'tab2', component: Tab2 }
      ]
    },
    { path: '/login', component: Login },
    { path: '/main', component: Main }
  ]
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

image-20211218170420349

声明式导航&编程式导航

  • 点击链接实现导航的方式,叫做声明式导航

    比如点击<a><router-link>

  • 调用API方法实现导航的方式,叫做编程式导航

    比如调用location.href跳转到新页面

image-20211218170924141

导航守卫

image-20211218171101172

// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
  // to 表示将要访问的路由的信息对象
  // from 表示将要离开的路由的信息对象
  // next() 函数表示放行的意思
  // 分析:
  // 1. 要拿到用户将要访问的 hash 地址
  // 2. 判断 hash 地址是否等于 /main。
  // 	2.1 如果等于 /main,证明需要登录之后,才能访问成功
  // 	2.2 如果不等于 /main,则不需要登录,直接放行  next()
  // 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
  // 	3.1 如果有 token,则放行
  // 	3.2 如果没有 token,则强制跳转到 /login 登录页
  if (to.path === '/main') {
    // 要访问后台主页,需要判断是否有 token
    const token = localStorage.getItem('token')
    if (token) {
      next()
    } else {
      // 没有登录,强制跳转到登录页
      next('/login')
    }
  } else {
    next()
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

路由元信息meta (opens new window)

Last Updated: 2021年12月24日星期五 22:23