Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

前端Vue框架学习

2022/02/24 前端 Vue
Word count: 9,227 | Reading time: 43min

前端Vue框架学习(未完)

MVVM模型

vueify介绍

所谓vueify,就是使用.vue格式的文件定义组件,一个.vue文件就是一个组件。

在.vue文件定义的组件内容包括3部分:

  • <style></style>标签:定义组件样式
  • <template></template>标签:定义组件模板
  • <script></script>标签:定义组件的各种选项,比如data, methods等。

Vue生命周期

使用Vue的初次操作

创建一个Vue实例

1
2
3
4
5
6
var c = new Vue({			//初始化一个Vue对象
el: '#box', //对象,后面跟的是选择器 el(是element缩写, 指定挂载vue示例的元素
data : { //数据成员,必须用关键字data
msg:'welcome'
}
})

完整的html页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
window.onload=function (ev) {
new Vue({
el: '#box',
data:{
msg:'hello'
}
})
}

</script>
</head>
<body>
<div id="box">
{{msg}}
<!--<li></li>-->

</div>
</body>
</html>

注:template必须有且只能有一个div

常用指令

v-model一般是放在表单中,实现了双向绑定

1
2
3
4
5
6
//如上初始化一个Vue实例
<div id='box'>
<input type='text' v-model="msg" > //"双向绑定",修改输入框内容,msg内容也会改变
<br>
{{msg}}
</div>

v-model 被称为 Vue 的指令,指令可以用来做很多事,比如用于 if 条件判断的 v-if,用于绑定值的 v-bind、用于绑定监听事件的 v-on 等等,这在以后会接触到。而这个 v-model 指令的作用是将 input 元素 value 属性的值和我们创建的 Vue 对象中 value 的值进行绑定,我们知道 input 有一个 value 属性,它的值会在浏览器显示(例如后面那个 button 按钮的发送),Vue 将这个值绑定后,在 input 中引起的 value 值变化就会实时反映到关联的 Vue 对象,所以会看到下方引用的 也会跟着变化。

v-repeat===>v-for="变量名 in 数组"

使用变量的话,使用索引

1
2
3
4
5
6
7
/*            
data:{
msg:'hello',
array:['he','bo','ce']
}
*/
<li v-for="value in array">{{value}}</li>

v-text

设置标签的文本

1
2
3
4
5
6
7
8
9
10
11
<div id="app">
<h2 v-text="message"></h2> // 缺点是会将元素内的所有内容都替换成message
<h2> {{message}} </h2> // 插值表达式优点是可以拼接加表达式,更灵活
</div>

var app = new Vue({
el: "#app",
data:{
message: "Hello"
}
})

v-html

设置标签的innerHTML, 如果设置的是普通文本则效果跟v-text相同,如果是html语法则会被正确解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// vue3
<div id="example1" class="demo">
<p>使用双大括号的文本插值: {{ rawHtml }}</p>
<p>使用 v-html 指令: <span v-html="rawHtml"></span></p>
</div>

<script>
const RenderHtmlApp = {
data() {
return {
rawHtml: '<span style="color: red">这里会显示红色!</span>'
}
}
}

Vue.createApp(RenderHtmlApp).mount('#example1')
</script>

v-on侦听Dom事件

因为 Vue 并不知道我们点击了按钮,为了让 Vue 监听到我们点击按钮的事件,需要在被点击的元素上绑定一个 click 事件,前面说过绑定事件用 v-on。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
window.onload=function () {
new Vue({
el: '#box',
data:{
msg:'hello',
array:['he','bo','ce']
},
methods :{ //注意是methods , 而不是method
show:function () {
alert(1);
}
}
})
}

<body>
<input type="button" value="按钮" v-on:click="show()">
</body>

除了能绑定click以外,v-on还能绑定如下事件:

  • click
    • @click.stop: 阻止冒泡事件
    • @click.prevent: 阻止默认事件
  • mousedown
  • dblclick
  • mouseover
  • mouseout

注:v-on:click=func()可以直接省略写成@click="func()"

v-bind

用于绑定属性(值、类……),跟v-on一样有省略写法, v-bind:class–>:class=

1
2
3
4
5
6
<div id="app">
<input type="text"
v-bind:class='{empty: !count}'
v-model="value">
...
</div>

Vue 会根据 empty 后的表达式 !count 的真假来判断 class 的值是否为 empty,如果为真(即 count = 0 的情况),则 class 的值为 empty,否则为空。

v-for

一般运用在<ul>, <ol>上, 如<ul v-for="(item, index) in todoList" :key="index" >

  • 1
    2
    3
    <li v-for="item in arr">
    {{ item }}
    </li>
  • 1
    2
    3
     <li v-for="(item, index) in arr">    
    {{ index, item }}
    </li>

注:在v-for中必须要有:key,这个是li的标识符以区别, 一般是设置为索引。

v-if

可以运用在v-for的单个item下, 如<li v-if="item.done === true">

1
2
3
4
5
6
7
<ol class="demo-box" v-for="(item, index) in todoList" :key="index">
<li v-if="item.done === false">
<input type="checkbox" @change="changeTodo(index, true)">
<p>{{item.todo}}</p>
<a @click="deleteTodo(index, true)">-</a>
</li>
</ol>

注: v-if是直接的切换结构,所以通过变量来显示显示和隐藏 ==>v-show是个更好的选择(只能控制一个元素的显示)

v-slot

slot的作用是指定占位符,留下插槽让其他的内容填充进来

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

在向具名插槽提供内容的时候,有个等价的写法: 我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

Vue3(其实从2.6开始)中引入了一个新的指令v-slot,用来表示具名插槽和默认插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- default slot -->
// slot占位, modal.vue
<slot></slot>
// slot模板, used.vue
<foo v-slot="{ msg }">
{{ msg }}
</foo>


<!-- named slot -->
// slot占位, modal.vue
<slot name="one"></slot>
// slot模板, used.vue
<foo>
<template v-slot:one="{msg}">
{{ msg }}
</template>
</foo>

插槽指令的缩写: 和 v-bind(:attr)和v-on(@func)相似,缩写只有在存在参数时才生效,这就意味着v-slot没有参数时不能使用#xx=yy。对于默认插槽,可以使用#default来代替v-slot, 如#header="{ msg }"=>v-slot:header="{ msg }"

注:注意 v-slot 只能添加在 <template> 上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。

template标签

template的作用是模板占位符,可帮助我们包裹元素,但在循环过程当中,template不会被渲染到页面上

1
2
3
4
<template v-for="(item, index) in list" :key="item.id">
<div>{{item.text}}--{{index}}</div>
<span>{{item.text}}</span>
</template>

常见用途是:

  • 将for内容提供给包含的div
  • 为子组件占位

父子组件间通信数据

1、 父向子传递: Props

父组件有数据,需要通过子组件来展示

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
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<Test :list="list"/>
<Board/>
</div>
</template>

export default {
name: 'App',
components: {
HelloWorld,
Test
},
data(){
return {
list : ["he","we","en"]
}
}
}


<template>
<ul v-for="l in list" :key="l">
<li>{{ l }}</li>
</ul>
</template>
export default ({
name: "Test",
data(){
return {
a: 1
}
},
props:{
list: Array
},
...
})

2、子向父传递: $emit

在子组件上增添了数据,需要回传给父组件

Vue实例属性

  • data

    • ▲注意区别, new App{}中的data是属性, 但component中的是函数,``dataproperty in component must be a function
  • methods:{}

  • computed:{}

    • 需要通过计算得到新的变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    computed: {
    newName: function(){ // 默认的设置是get方法
    return this.firstName + this.lastName
    }
    // 等价于
    newName: {
    get: function(){ // 通过原有数据产生新数据
    return this.firstName + this.lastName
    }
    set: function(value){ // 通过新数据修改原有数据
    let arr = value.split(" ")
    this.firstName = arr[0]
    this.lastName = arr[1]
    }
    }
    }
  • watch

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    data(){
    x: 15,
    y: "T-shirt"
    },
    watch: {
    // 被监听的对象发生变化后执行函数
    x: function(){
    if (this.x >26 ) {
    this.y = "半袖"
    }else if (this.x <= 26 && this.x > 0){
    this.y = "T-shirt"
    }else{
    this.y = "None"
    }
    }
    }

computed 相 watch 区别

  • computed是计算属性,依赖其他属性计算值,并且computed的值有缓存,只有当计算值变化才会返回内容
  • watch,监听的值发生变化就会执行回调,在回调中可以进行一些逻辑操作。所以一般来说需要依赖别的属性来动态获得值的时候可以使用computed,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用

–>v-show切换上性能更好, v-if初次加载性能更好

一个较为完整的栗子

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

<template>
<div>
<header>
<section>
<label for="title">ToDoList</label>
<input type="text" v-model="todo" @keyup.enter="addTodo" placeholder="添加ToDo" />
</section>
</header>
<section>
<h2>正在进行
<span>1</span>
</h2>
<ol class="demo-box" v-for="(item, index) in todoList" :key="index">
<li v-if="item.done === false">
<input type="checkbox" @change="changeTodo(index, true)">
<p>{{item.todo}}</p>
<a @click="deleteTodo(index, true)">-</a>
</li>
</ol>
<h2>已经完成
<span>{{todoList.length - todoLen}}</span>
</h2>
<ul v-for="(item, index) in todoList" :key="index" >
<li v-if="item.done === true">
<input type="checkbox" @change="changeTodo(index,false)" checked='checked'>
<p>{{item.todo}}</p>
<a @click="deleteTodo(index,false)">-</a>
</li>
</ul>
</section>
<footer>Copyright &copy; 2014 todolist.cn<a @click="clearData()">clear</a></footer>
</div>
</template>



<script>
import * as Utils from '@/utils/utils'

export default {
name: 'HelloWorld',
props: {
msg: String
},
data () {
return {
todo: '',
todoList: [],
todoLen: 0
}
},
methods: {
addTodo () { // 等价于 addTodo: function (){
let todoObj = {
todo: this.todo,
done: false
}
var tempList = Utils.getItem('todoList')
if (tempList) {
tempList.push(todoObj)
Utils.setItem('todoList', tempList)
} else {
var tempData = []
tempData.push(todoObj)
Utils.setItem('todoList', tempData)
}
this.todoList.push(todoObj)
this.todoLen++
this.todo = ''
},
deleteTodo (index, done) {
if(done){
this.todoLen--
}
this.todoList.splice(index, 1)
Utils.setItem('todoList', this.todoList)

},
changeTodo (index, done) {
if (done) {
this.todoLen--
this.todoList[index].done = true
Utils.setItem('todoList', this.todoList)
} else {
this.todoLen++
this.todoList[index].done = false
Utils.setItem('todoList', this.todoList)
}
},
initTodo () {
var todoArr = Utils.getItem('todoList')
if (todoArr) {
for (let i = 0, len = todoArr.length; i < len; i++) {
if (todoArr[i].done === false) {
this.todoLen++
}
}
this.todoList = todoArr
}
},

clearData () {
localStorage.clear()
this.todoList = []
this.todoLen = 0
}

},
mounted () {
this.initTodo()
}
}
</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>

B教程:

网络通信axios

axios:官方推荐使用

教程:https://zhuanlan.zhihu.com/p/266977438

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 ppp(){
axios({
url:'http://123.207.32.32:8000/home/multidata',
// params是针对get请求的参数拼接
params:{
type:'pop',
page:1
}
}).then(res => {
console.log(res)
})
// 注:axios方法默认返回一个Promise对象,所以在后面可以直接用then处理请求回来的数据
// method:'get' 设置请求的类型,默认为get
}

GET方法:

1
2
3
4
5
6
7
8
9
10
11
axios.get(
'http://123.207.32.32:8000/home/multidata',
// params是针对get请求的参数拼接
{
params:{
type:'pop',
page:1}
}
).then(res => {
console.log(res)
})

POST方法:

1
2
3
4
5
6
7
8
9
10
axios.post('/user', {
firstName: 'Fred', // 参数 firstName
lastName: 'Flintstone' // 参数 lastName
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

并发请求

1
2
3
4
5
6
7
8
9
10
11
function getUserAccount() {
return axios.get('/user/12345');
}

function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// 两个请求现在都执行完成
}));

vue中的h函数总结

大家都知道render函数在vue中非常重要,但其实本质上执行渲染工作的是h函数,本质上也就是createElement函数!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  const app = new Vue({
··· ···
render: h => h(App)
})
// 等价写法
const app = new Vue({
··· ···
render:function(createElement){
return createElment(App)
}
})

//参数1:要传递的组件名字
//参数2:要传递组件的props,且props是什么格式,这里就尽量什么格式,如对象格式,就应该对象格式

h函数就是vue中的createElement方法,这个函数作用就是创建虚拟dom,追踪dom变化的。。。

虚拟dom简单来说就是一个普通的JavaScript对象,包含tag,props,children三个属性。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
<p className="text">lxc</p>
</div>
// 上边的HTML代码转为虚拟DOM如下:
{
tag:"div",
props:{
id:"app"
},
children:[
{
tag:"p",
props:{
className:"text"
},
children:[
"lxc"
]
}
]
}
// 该对象就是所谓的虚拟dom,因为dom对象是属性结构,所以使用JavaScript对象就可以简单表示。而原生dom有许多属性、事件,即使创建一个空div也要付出昂贵的代价。
// 而虚拟dom提升性能的点在于DOM发生变化的时候,通过diff算法对比,计算出需要更改的DOM,只对变化的DOM进行操作,而不是更新整个视图。。。

Good: vue 中的h函数

Vue-router

安装vue-cli和vue-router: 第一个vue程序(html集成vue)只需要script导入vue的js即可,如果是vue工程则通过vue-cli来建立

1.新建vue工程

  • @vue/cli-init

    vue init 是vue-cli2.x的初始化方式

    • vue init : 需要npm i -g @vue/cli-init
      • vue init webpack [project-name], webpack为模板<generate a project from a remote template>,然后按照交互信息提示输入即可完成工程创建
      • init创建工程时,交互信息中有一个比较有意思的是选择包管理工具: npm、yarn,两者
  • @vue/cli

    会让选择Vue2还是Vue3

    • vue create router_project: 需要npm install -g @vue/cli
    • vue ui通过UI创建: 需要npm install -g @vue/cli
      • vue create [project-name]

    注:经测试,Vue CLI v4.5.15会提示选择yarn还是npm;@vue/cli 4.5.13没提示==>后来发现貌似是平台的区别,linux上会提示选择,win上默认是yarn

    Vue CLI 的包名称由vue-cli已经改成了@vue/cli,如果通过vue-cli来构建Vue3项目则需要通过 npm uninstall vue-cli -gyarn global remove vue-cli 卸载它,然后安装@vue/cli,并且@vue/cli安装好后,如果不安装yarn,那么在vue create创建的时候报错ERROR Error: spawn yarn ENOENT

  • npm init vite-app hello-vue

    • Vue3刚发布不久,官方文档中推荐的创建方法之一就是通过脚手架Vite来创建一个vue3项目

      需要安装create-vite-app

      • 1
        2
        3
        4
        5
        6
        $ npm init vite-app vue3-vite
        Scaffolding project in F:\Documents\HBuilderProjects\vue3-vite...
        Done. Now run:
        $ cd vue3-vite
        $ npm install (or `yarn`)
        $ npm run dev (or `yarn dev`)

        可以看到,这个时需要进入项目目录后,里面没有package-lock或者yarn.loca,而是需要自己主动选择包管理工具进行安装依赖的;vue createvue init的不需要可以直接运行

  • @vitejs/app

    • npm init @vitejs/app然后选择project的框架

注:2既可以创建Vue2,也可以创建vue3;3只能创建vue3;在创建速度上,法3比法2快上很多

2.编写vue组件

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
<-- HelloWord.vue -->
<template>
<div>
你好
</div>
</template>

<script>
export default ({
name: "Content"
})
</script>

<-- Content.vue -->
<template>
<div>
你好
</div>
</template>


<script>
export default ({
name: "Content"
})
</script>

3. 编写router

在根目录下创建router文件夹,并创建index.js文件,需要路由的组件全在这里面规定好。

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
/*
* @Author: Mrli
* @Date: 2021-07-29 16:19:37
* @LastEditTime: 2021-07-29 16:45:56
* @Description:
*/
import Vue from "vue"
import VueRouter from "vue-router"
import Content from "../components/Content.vue"
import HelloWorld from "../components/HelloWorld.vue"

Vue.use(VueRouter)

export default new VueRouter({
mode: "history",
routes:[
{
path: "/hello",
name: "Hello",
component: HelloWorld
},
{
path: "/content",
name: "Content",
component: Content,
// 路由嵌套
children: [
{
path:'/user/profile',
component: Profile
},
{
path:'/user/userlist',
component: Userlist
}
]
}
]
})

4. 挂载router-view

app.vue中使用路由

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
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<router-link to="/hello">hello</router-link>
<router-link to="/content">content</router-link>
<router-view></router-view>
</div>
</template>

<script>
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>

5.挂载router

将路由信息暴露给Vue实例使用, main.js

1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue';
import App from './App.vue';
// 导入定义好的router模块
import router from './router';

Vue.config.productionTip = false

new Vue({
el: "#app",
router,
render: h => h(App),
}).$mount('#app')

注:按钮点击后跳转,在method的function中可以通过this.$router.push("/hello")跳转到指定路由。

路由嵌套

在父组件A.vue中进行路由跳转

A.vue

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
<template>
<div>
<el-container>
<el-aside width="200px">
<el-menu :default-openeds="['1', '3']">
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>会员管理</template>
<el-menu-item-group>
<el-menu-item index="1-1">
<!-- 路径匹配传参:两种方式 -->
<!-- <router-link to="/member/level/2">会员等级</router-link> -->
<router-link :to="{name: 'MemberLevel', params: {id:3}}">会员等级</router-link>
</el-menu-item>
<el-menu-item index="1-2">会员积分</el-menu-item>
<el-menu-item index="1-3">
<router-link to="/member/list">会员列表</router-link>
</el-menu-item>
</el-menu-item-group>
</el-submenu>


<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>商品管理</template>
<el-menu-item-group>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2">选项2</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>

<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>用户中心</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>王小虎</span>
</el-header>


<!-- 重点是这行, 展示子组件 -->
<el-main>
<router-view></router-view>
</el-main>

</el-container>

</el-container>


</div>
</template>

<script>

export default ({
name: "Main"
})
</script>

index.js

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
import Vue from "vue"
import VueRouter from "vue-router"
import Main from "../views/Main"
import MemberLevel from "../views/Member/MemberLevel"
import MemberList from "../views/Member/MemberList"

Vue.use(VueRouter)

export default new VueRouter({
mode: "history",
routes: [
{
name: "main",
path: "/main",
component: Main,
children: [
{
name: "MemberList",
path: "/member/list",
component: MemberList
},
{
name: "MemberLevel",
path: "/member/level/:id",
component: MemberLevel
},
]
}
]
})

MemberLevel.vue

1
2
3
4
5
<template>
<div>
会员等级 ID: {{ $route.params.id }}
</div>
</template>

如果index.js和MemberLevel.vue采用props, 则可以直接写

注: 两种方式跳转:

  • <route-link to="/member/level/3"></route-link>
  • <route-link :to="{name: 'MemberLevel', params: {id: 'xxx'}}"></route-link> -->to加了冒号后,后面可以跟对象

组件重定向

重定向的意思大家都明白,但Vue中的重定向是作用在路径不同但组件相同的情况下

1
2
3
4
5
6
7
8
9
10
11
//index.js    
{
name: "Main",
path: "/main/:name",
component: Main,
...
},
{
path: "/goMain/:name",
redirect: "/main/:name"
}

说明:这里定义了两个路径,一个是/main,一个是/goMain,其中/goMain重定向到了/main路径,由此可以看出重定向不需要定义组件;

Main.vue

1
2
3
4
5
<el-menu-item index="1-3">
<router-link to="/goMain/admin123">回到首页</router-link>
</el-menu-item>

<span>{{ $route.params.name }}</span>

整合ElementUI

Vue只关注视图层,但没有UI界面, 类似Bootstrap: Bootstrap 是个 css框架,类似jqueryMoblie一样,只需要给标签加class就行了。Bootstrap 需要 .css + .js ,由于依赖jqery,所以需要将jquery.js也导入

ElementUI教程

安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建好工程
vue create <projectName>
# 安装elementUI
npm i element-ui -S
#
npm install vue-router --save-dev

# 以下的在上述无法渲染的情况下添加
# 安装Sass加载器(webpack加载器)和node-sass(scss-->css)
npm install sass-loader node-sass --save-dev
# 注windows下安装sass如果失败,则因为原因缺少缺少编译环境python2,需要做以下设置
npm install -g node-gyp
npm install --global --production windows-build-tools
npm install node-sass --registry=http://registry.npm.taobao.org

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import ElementUI from "element-ui";
import 'element-ui/lib/theme-chalk/index.css'; // 这行import很重要, 不然渲染不出来
import Vue from 'vue';
import App from './App.vue';
import router from "./router";

Vue.use(ElementUI)
Vue.config.productionTip = false

new Vue({
el: "#app",
router,
render: h => h(App),
}).$mount('#app')

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<router-link to="hello">hello</router-link>
<router-link to="login">login</router-link>
<router-view/>
</div>
</template>

<script>
export default {
name: 'App',
}
</script>

Login.vue

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
<template>
<div>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="活动名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="活动区域">
</el-form-item>

</el-form>
</div>
</template>

<script>
export default {
name: "Login",
data(){
return {
form: {
name: ""
}
}
}
}
</script>

router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Vue from "vue"
import VueRouter from "vue-router"
import HelloWorld from "../components/HelloWorld"
import Login from "../views/Login"
Vue.use(VueRouter)

export default new VueRouter({
mode: "history",
routes: [
{
name: "login",
path: "/login",
component: Login
},
{
name: "Hello",
path: "/hello",
component: HelloWorld
}
]
})

Vuex

状态管理库, 可看作是管理会话的数据库

通过在根实例App中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到(如果在函数中自带this指针,则可以直接写成store.state.xxx)。

1.创建store文件夹, 定义js文件

在根目录创建store文件夹, 然后创建index.js

关于js定义有两种:

  • 普通:
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
// index.js
import Vue from "vue"
import Vuex from "vuex"

Vue.use(Vuex)

// 全局state对象, 用于保存所有组件的公共数据
const state = {
user : {
name: ""
}
}
// 监听state对象的值的最新状态(计算属性)
const getters = {
getUser(state){
return state.user
}
}
// 唯一可以修改state对象的值的最新状态(同步阻塞方法)
const mutations = {
updateUser(state, user){
state.user = user
}
}
// 异步执行mutations方法
const actions = {
asyncUpdateUser(context, user){
context.commit("updateUser", user)
}
}
export default new Vuex.Store({
state,
getters,
mutations,
actions
})
  • 模块化

将对某个对象数据的管理视为一个模块,因此在store文件夹下还需要创建modules文件夹,并创建相应的模块js

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
// index.js
import Vue from "vue"
import Vuex from "vuex"
import user from "./modules/user"

Vue.use(Vuex)

export default new Vuex.Store({
modules:{
user
}
})
// modules/user.js
const user = {
// 全局state对象, 用于保存所有组件的公共数据
state :{
user : {
name: ""
}
},
// 监听state对象的值的最新状态(计算属性)
getters :{
getUser(state){
return state.user
}
},
// 唯一可以修改state对象的值的最新状态(同步阻塞方法)
mutations :{
updateUser(state, user){
state.user = user
}
},
// 异步执行mutations方法
actions :{
asyncUpdateUser(context, user){
context.commit("updateUser", user)
}
},
}
export default user;

2. 导入main.js中

3. 使用vuex

工具

附录:

什么是Vue.use()?

Vue实例通过Vue.use() 来装载插件,从而用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

  1. 添加全局方法或者 property。如:vue-custom-element
  2. 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  3. 通过全局混入来添加一些组件选项。如 vue-router
  4. 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

注:通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:

开发插件

Vue.js 的插件应该暴露一个 install 方法,即XxxxPlugin.install = function (Vue, options) {}。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

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
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}

// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})

// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})

// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}

Q: 为什么axios不用?

Q:相信很多人在用Vue使用别人的组件时,会用到 Vue.use() 。例如:Vue.use(VueRouter)Vue.use(MintUI)。但是用 axios时,就不需要用 Vue.use(axios),就能直接使用。那这是为什么呐?

A:因为 axios 没有 实现install方法,即axios并不是Vue插件,他并不是特地为Vue服务的网络通信库,而是通用的库。

所以为了Vue实例能够全局使用axios,采取的方法是将axios加到Vue实例的原型中,只不过这样在组件中使用时需要通过this.$axios.func来调用(最合理的调用),Q: this.$axios和this.axios和axios有什么区别?

参考:https://www.jianshu.com/p/89a05706917a——如何定义一个自己的Vue插件

vue中以this.$xx的属性详解

Vue实例里this的使用

这是vue文档里的原话:All lifecycle hooks are called with their ‘this’ context pointing to the Vue instance invoking it.

意思是:在Vue所有的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例

  • 在Vue所有的生命周期钩子方法
    • methods(普通方法)中使用的this指向为创建的vue组件实例。
  • vue组件生命周期钩子方法里面还有方法使用this: 在函数内部使用this时,this会指向window,而非vue实例
  • vue箭头方法中使用this: 箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window;
  • data中的this指向为windows。

参考:vue中this的指向——比较全

template的组件中ref作用

可以获得相应的html元素,效果跟document.getElementById相同,获得以后就可以调用其元素的一些功能,如focus啥的

好应用案例

https://juejin.cn/post/6888604279679451143)

JS函数

普通函数

1
2
3
4
5
var x = myFunction(7, 8);        // 调用函数,返回值被赋值给 x

function myFunction(a, b) {
return a * b; // 函数返回 a 和 b 的乘积
}

箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 单参数
x => x * x
function (x) {
return x * x;
}

// 多参数, 需要用括号括起来
(x, y) => x * x + y * y

// 无参数, 需要用括号声明
() => 3.14

// 如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错
// SyntaxError:
x => { foo: x }
// 因为和函数体的{ ... }有语法冲突,所以要改为
// ok:
x => ({ foo: x })

箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};
// ---
// 现在,箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25

匿名函数

匿名函数的使用场景比较多,是简化了代码编写的一种手段。

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
// 对象方法
var obj={
name:"张培跃",
age:18,
fn:function(){
return "我叫"+this.name+"今年"+this.age+"岁了!";
}
};
// 函数表达式
var fn=function(){
return "我是一只小小小小留下,怎么飞也飞不高!"
}

// 回调函数
setInterval(function(){
console.log("我其实是一个回调函数,每次1秒钟会被执行一次");
},1000);

// 返回值
function fn(){
//返回匿名函数
return function(){
return "张培跃";
}
}

匿名函数的作用:

1、通过匿名函数可以实现闭包,关于闭包在后面的文章中会重点讲解。在这里简单介绍一下:闭包是可以访问在函数作用域内定义的变量的函数。若要创建一个闭包,往往都需要用到匿名函数。

2、模拟块级作用域,减少全局变量。执行完匿名函数,存储在内存中相对应的变量会被销毁,从而节省内存。再者,在大型多人开发的项目中,使用块级作用域,会大大降低命名冲突的问题,从而避免产生灾难性的后果。自此开发者再也不必担心搞乱全局作用域了。

JS成员方法

写Vue的时候总是不知道什么时候用xxx: {}, 什么时候用xxx(){}。本身这个问题并不难,问题就在于一知半解,所以本小节就是专门进行说明的

1
2
3
4
5
6
7
8
9
10
// say和data都是v对象的成员方法, 而name是v的成员属性
const v = {
name: "cl",
data(){
console.log("data")
},
say: function(){
console.log("say")
}
}
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
	/*
方式一、Object形式的自定义对象
var 对象名 = new Object(); //new一个空的对象
对象名.属性名 = 值; // 给对象实例添加一个属性
对象名.函数名 = function(){} //给对象实例添加一个方法
js对象的属性访问:
对象.属性名
*/
var Student1=new Object();
Student1.name="方式一 爱尔得华";
Student1.age=23;
//创建对象方法 如果是toString 则调用对象相对于调用此方法
Student1.toString1=function(){
alert("方式一 hello my name is toString1()");
}

Student1.toString=function(){
var result="方式一 在toString方法内输出信息:年龄:" + this.age + ",姓名:" + this.name;
alert(result);

return result="方式一 谁调用toString方法就返回给谁输出这句话 重写方法内容";
}
alert(Student1);
Student1.toString1();
/*
方式二、{}花括号形式的自定义对象
花括号中,多组属性,我们必须使用,逗号进行分隔。
var 对象名 = {
属性名: 值, // 给对象添加属性
属性名(函数名): function(){} // 给对象添加方法
}
对象的访问:
对象.属性名
*/
var Student2={
name: "方式二 爱尔得华2",
age: 29,
toString1: function(){
alert("方式二 my name is Student2 toString1()");
}
},

编写注意点:

如果是在函数体内写的语句是不需要分号分隔的,如

1
2
3
4
5
6
7
8
9
10
setup(){
onMounted(()=>{

})

functipon xxx(){

}
return {}
}

与之对应的是在对象体内,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var app = {
data() { // 等价于 data: ()=>{}

},
methods:{

},
props:{

},
mounted(){
console.log("xxx")
}
}

里面会出现键值对必须要逗号分隔开

【Vue】详解 SFC 与 vue-loader

vue-loader 是一个Webpack的 loader,使用 vue-loader 就可以用 Vue Single-File Component (SFC) 即单文件组件的形式编写一个组件,vue-loader 会将.vue文件转换为 JS模块。

.vue 单文件组件 (SFC) 规范

  1. <template>模板块

一个SFC中最多一个< template >块;
其内容将被提取为字符串传递给 vue-template-compiler ,然后webpack将其编译为js渲染函数,并最终注入到从 <script>导出的组件中;

  1. <script>脚本块

一个SFC最多一个<script>块;
它的默认导出应该是一个 Vue.js 的组件选项对象,也可以导出由 Vue.extend() 创建的扩展对象。
思考:Vue.extend() 中 data 必须是函数,所以在.vue SFC的script中,export中的data是函数

  1. <style>样式块

一个 .vue 文件可以包含多个 <style> 标签;
可以使用scope和module进行封装;
具有不同封装模式的多个<style> 标签可以在同一个组件中混合使用;

.env配置文件

Vue-cli创建的

使用vue-cli3构建的项目就简单多了,因为vue-cli3使用上述的DefinePlugin方式帮你把process.env.NODE_ENV配置好了,我们不需要再自己去配置。它自带了三种模式:

  • development:在vue-cli-service serve下,即开发环境使用
  • production:在vue-cli-service build和vue-cli-service test:e2e下,即正式环境使用
  • test: 在vue-cli-service test:unit下使用
1
2
3
4
5
6
7
8
9
10
11
{
"name": "",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve", //本地开发运行,会把process.env.NODE_ENV设置为'development'
"build": "vue-cli-service build", //默认打包模式,会把process.env.NODE_ENV设置为'production'
},
"dependencies": {
}
}

对应的.env如下:

  • .env.development
  • .env.test
  • .env.production

如果自己写package.json中的scripts命令的话应该如下写

1
2
3
4
5
6
7
"scripts": {
// --mode 后面不能是dev必须为development才能识别成生产模式
"dev": "vue-cli-service serve --mode development",
"test": "vue-cli-service build --mode test",
// --mode 后面不能是prod必须为production才能识别成生产模式
"prod": "vue-cli-service build --mode production",
},

程序中通过process.env来获取

1
2
3
4
5
6
7
// 如console.log(process.env)

const option = {
// baseURL: "http://devops4.sucsoft.com:30383/api/v1", // api的base_url
baseURL: process.env.NODE_ENV == "development" ? "http://192.168.31.172:5501/api/v1" : "http://devops4.sucsoft.com:30383/api/v1",
timeout: 5000 // 请求超时时间
}

注: .env中的文件需要以VUE_APP_为前缀才能被检测到, 如.env.production中填写VUE_APP_BASE_URL="hhh",之后可以在项目中通过process.env.VUE_APP_BASE_URL来获取

此外,还可以通过添加--mode参数自定义指定要加载的.env文件

1
2
3
"scripts": {
"vvv": "vue-cli-service serve --mode vvv"
},

搭配.env.vvv的配置文件

Vite

.env文件

1
2
3
4
.env                # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载,mode可为production,development或其他自定义值。
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略
  • .env.prod中定义
1
VITE_APP_WEB_URL=http://baidu.com

注:前缀必须为VITE_APP_,并且通过console.log(import.meta.env)来获得

  • 在页面中使用console.log(import.meta.env.VITE_APP_WEB_URL)
  • 在 package.json中定义
1
2
3
4
5
"scripts": {
"dev": "vite",
"build": "vite build",
"prod": "vite --mode prod"
},

说明:执行npm run prod时, 既先会加载.env的内容,然后加载.env.prod的内容

src/viewssrc/components`区别

两者的区分更多的是一种惯例,实际上两者定义的都是视图组件;如果要说区别的话,最大的区别在于用途和重用程度上:

  • 可重用的组件内容可以保存在src / components文件夹中(诸如广告,网格或任何自定义控件之类的示例,如样式化的文本框或按钮。再比如页眉,页脚等页面结构性质的可重用组件,还可以创建src/layouts/文件夹)
  • 与路由器绑定的内容,可以保存在src / views中(作页面的组件,路由到类似页面进行导航)

即views的组件多一层充当路由组件的功能:

在Vue中(通常是Vue Router)处理路由时,将定义路由以切换组件中使用的当前视图<router-view>。这些路线通常位于src/router/routes.js,我们可以看到以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
// 可以看到router.js中导入的组件全部都是views下的

export default [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/about',
name: 'about',
component: About,
},
]

位于src/components下里的组件不太可能在一条路线中使用,而位于src/views将被至少一条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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

const routes = [{
path: '/',
name: 'Index',
key: 'Index',
redirect: '/dashboard/index',
hidden: true,
label: "首页",
icon: renderIcon(BookIcon),
},

// 首页, 这种形式可能不太一样, 因为这边是采用了一个可重用的组件充当首页, 之后的路由展示只是将其下面的<router-view>进行切换
{
path: '/dashboard',
name: 'Dashboard',
key: 'Dashboard',
redirect: '/dashboard/index',
component: Home,
label: "首页",
icon: renderIcon(BookIcon),
meta: {
label: "首页",
},
children: [{
path: "/dashboard/index",
name: 'DashboardIndex',
key: 'DashboardIndex',
component: () => import('../views/HelloWorld.vue'),
label: "Hello",
icon: renderIcon(BookIcon),
meta: {
label: "Hello",
}
}, {
path: "/dashboard/page1",
name: 'DashboardPage1',
key: 'DashboardPage1',
component: () => import('../views/Page1.vue'),
label: "第一页",
icon: renderIcon(PersonIcon),
meta: {
label: "第一页",
}
}]
},
// 第二页
{
path: "/users",
name: 'Users',
key: 'Users',
redirect: '/users/index',
component: Home,
label: "账户",
icon: renderIcon(WineIcon),
meta: {
label: "账户",
},
children: [{
path: '/users/index',
name: 'UsersIndex',
key: 'UsersIndex',
component: () => import('../views/Page2.vue'),
label: "第二页",
icon: renderIcon(WineIcon),
meta: {
label: "第二页",
},
}],
},
]

Vue部署

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM node:10 AS build-env
WORKDIR /app
COPY ./package.json .
RUN npm install --registry=http://registry.npm.taobao.org
COPY . .
RUN npm run build

FROM nginx
WORKDIR /usr/share/nginx/html
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY --from=build-env /app/dist /usr/share/nginx/html/
EXPOSE 80

from : https://github.com/liguobao/58HouseSearch/blob/master/House-Map.UI/Dockerfile

Author: Mrli

Link: https://nymrli.top/2019/01/17/前端Vue框架学习/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
阿里云nginx+wsgi部署flask
NextPost >
git基础知识
CATALOG
  1. 1. 前端Vue框架学习(未完)
    1. 1.1. MVVM模型
  2. 2. vueify介绍
    1. 2.1. Vue生命周期
    2. 2.2. 使用Vue的初次操作
      1. 2.2.1. 创建一个Vue实例
      2. 2.2.2. 完整的html页面
    3. 2.3. 常用指令
      1. 2.3.1. v-model一般是放在表单中,实现了双向绑定
      2. 2.3.2. v-repeat===>v-for="变量名 in 数组"
      3. 2.3.3. v-text
      4. 2.3.4. v-html
      5. 2.3.5. v-on侦听Dom事件
      6. 2.3.6. v-bind
      7. 2.3.7. v-for
      8. 2.3.8. v-if
      9. 2.3.9. v-slot
      10. 2.3.10. template标签
      11. 2.3.11. 父子组件间通信数据
    4. 2.4. Vue实例属性
      1. 2.4.1. 一个较为完整的栗子
    5. 2.5. 网络通信axios
    6. 2.6. vue中的h函数总结
  3. 3. Vue-router
    1. 3.1. 1.新建vue工程
    2. 3.2. 2.编写vue组件
    3. 3.3. 3. 编写router
    4. 3.4. 4. 挂载router-view
    5. 3.5. 5.挂载router
    6. 3.6. 路由嵌套
    7. 3.7. 组件重定向
  4. 4. 整合ElementUI
  5. 5. Vuex
    1. 5.1. 1.创建store文件夹, 定义js文件
    2. 5.2. 2. 导入main.js中
    3. 5.3. 3. 使用vuex
    4. 5.4. 工具
  6. 6. 附录:
    1. 6.1. 什么是Vue.use()?
      1. 6.1.1. 开发插件
      2. 6.1.2. Q: 为什么axios不用?
    2. 6.2. vue中以this.$xx的属性详解
    3. 6.3. Vue实例里this的使用
    4. 6.4. template的组件中ref作用
    5. 6.5. 好应用案例
    6. 6.6. JS函数
    7. 6.7. JS成员方法
    8. 6.8. 编写注意点:
    9. 6.9. 【Vue】详解 SFC 与 vue-loader
      1. 6.9.1. .vue 单文件组件 (SFC) 规范
    10. 6.10. .env配置文件
      1. 6.10.1. Vue-cli创建的
      2. 6.10.2. Vite
    11. 6.11. src/views和src/components`区别
    12. 6.12. Vue部署