发布时间:2023-04-03 12:30
在初步学习前端课程的时候,相信大家都知道Vue这个名字,至于Vue到底是什么却不得而知,又或者知道Vue是前端的一大流行框架,那么它到底是用来干啥的呢,对于其他框架而言Vue又有什么优势和特点,学习前端的人为什么都要去学习Vue呢?
对于这些答案,相信大家都想去亲自揭开它的谜底,但是如果你未学过JavaScript和jQuery那么,请先不要学习Vue,因为没有根基是非常晦涩难学的。反之学习的时候会特别轻松,所以希望大家学习Vue的时候,尽量都有javascript和jQuery库类的基础。
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
什么是渐进式框架?
说白了,就是框架分层。那是如何分层的呢?就像《功夫》里面黄圣依手里拿的棒棒糖一样:最核心的是视图层渲染,然后往外是组件机制,在此基础上再加入路由机制,再加入状态管理,最外层是构建工具,vue和react都是如此。
Vue分层:声明式渲染>组件系统>客户端路由>集中式状态管理>项目构建
创始人:尤雨溪
官 网:https://cn.vuejs.org/
安 装:https://cn.vuejs.org/v2/guide/installation.html =》点击开发版本
Vue是框架而jQuery顶多算个库类
在Vue还未诞生之前,jQuey可以说是一个前端的流行框架,操作DOM元素非常方便,缺点是要大量的获取和操作DOM元素。在 React、Vue、Angular前端三大主流框架出现后,jQuey就被降级了,所以顶多算个库类。而在使用Vue等其他主流框架的时候,我们不需要一直关注DOM元素,因为在Vue中DOM是虚拟的,我们不需要去获取,这样就减轻了前端开发人员的工作量。
前端渲染方式
Vue程序结构框架:
Vue.js是典型的MVVM框架,什么是MVVM框架,介绍之前我们先介绍下什么是MVC框架
MVC是后端分层开发的思想 即 Model-View-Controller 的缩写,就是 模型-视图-控制器 , 也就是说一个标准的Web 应用程序是由这三部分组成的:
View 用来把数据以某种方式呈现给用户。
Model 其实就是数据。
Controller 接收并处理来自用户的请求,并将 Model 返回给用户。
MVC框架对于简单的应用处理是可以的,也符合软件架构的分层思想。但随着H5 的不断发展,人们更希望使用H5 开发的应用能和Native 媲美,或者接近于原生App 的体验效果,于是前端应用的复杂程度已不同往日,今非昔比。这时前端开发就暴露出了三个痛点问题:
其实,早期jquery 的出现就是为了前端能更简洁的操作DOM 而设计的,但它只解决了第一个问题,另外两个问题始终伴随着前端一直存在。随着智能手机,平板电脑的流行,多终端开始流行。
MVVM框架开始流行,应用场景:
框架数据分层情况:
M-Model:数据模型层,可以在Model中定义数据修改和操作的业务逻辑, Vue中数据层都放在data里面
V-View:视图层,它负责将数据模型转化成UI 展现出来
VM-ViewModel:是控制器,将视图和数据之间建立联系,或者说是一个同步视图和数据的对象,即Vue对象
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue差值表达式title>
head>
<body>
<div id="app">div>
// 导入vue
<script src="./js/vue.js">script>
<script>
// 创建Vue环境
let vm = new Vue({
// 将该DOM元素内部搭建为Vue环境
el: "#app",
// 模型数据
data: {
name: "张三",
sex: "男"
}
})
script>
body>
html>
注意❗:
与template-web.js模板引擎绑定数据的语法类似,都是使用双大括号的文本插值{{ msg }}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue差值表达式title>
head>
<body>
<div id="app">
// 从data模型数据中获取
<div>姓名:{{name}}div>
<div>性别:{{sex}}div>
div>
<script src="./js/vue.js">script>
<script>
let vm = new Vue({
el: "#app",
// 模型数据
data: {
name: "张三",
sex: "男"
}
})
script>
body>
html>
注意❗:
v-
前缀的特殊属性,那么指令的本质就是自定义属性v-
开头v-cloak
指令就属于无参数的指令不需要表达式
用法:
这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
功能:解决插值表达式存在的闪动问题
原理:先隐藏,替换好值之后,再显示最终的值
示例:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue差值表达式title>
<style>
/* 1.通过属性选择器,选择到带有v-cloak的元素,让其隐藏 */
[v-cloak] {
display: none;
}
style>
head>
<body>
<div id="msg">
<div v-cloak>姓名:{{name}}div>
<div v-cloak>性别:{{sex}}div>
div>
<script src="./js/vue.js">script>
<script>
let vm = new Vue({
el: "#msg",
data: {
name: "张三",
sex: "男"
}
})
script>
body>
html>
预期:string
详细:
更新元素的 文本内容。如果要更新部分的 文本内容,则需要使用 {{ Mustache }}
插值表达式。
示例:
<span v-text="msg">span>
<span>{{msg}}span>
注意:
预期:string
详细:
更新元素的 innerHTML
。注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译,并且可识别HTML标签
注意:
示例:
<div v-html="html">div>
不需要表达式
用法:
跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
注意:
示例:
<span v-pre>{{ 这将不会被编译 }}span>
不需要表达式
详细:
只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
注意:
示例:
<span v-once>这永远不会改变: {{msg}}span>
<div v-once>
<h1>commenth1>
<p>{{msg}}p>
div>
<my-component v-once :comment="msg">my-component>
<ul>
<li v-for="i in list" v-once>{{i}}li>
ul>
参考:
v-once
预期:随表单控件类型不同而不同。
限制:
修饰符:
.lazy
- 取代 input
监听 change
事件.number
- 输入字符串转为有效的数字.trim
- 输入首尾空格过滤用法:
示例:
<div id="app">
<div v-model>{{msg}}div>
<input type="text" v-model="msg">
div>
<script src="./js/vue.js">script>
<script>
let vm = new Vue({
el: "#app",
data: {
msg: "你好世界"
}
})
script>
预期:Function | 内联声明 | Object
参数:event
修饰符:
.stop
- 调用 event.stopPropagation()
。.prevent
- 调用 event.preventDefault()
。.capture
- 添加事件侦听器时使用 capture 模式。.self
- 只当事件是从侦听器绑定的元素本身触发时才触发回调。.{keyCode | keyAlias}
- 只当事件是从特定键触发时才触发回调。.native
- 监听组件根元素的原生事件。.once
- 只触发一次回调。.left
- (2.2.0) 只当点击鼠标左键时触发。.right
- (2.2.0) 只当点击鼠标右键时触发。.middle
- (2.2.0) 只当点击鼠标中键时触发。.passive
- (2.3.0) 以 { passive: true }
模式添加侦听器.enter
- 按下回车键.esc
- 按下退出键自定义事件修饰符
全局 config.keyCodes 对象,112等是按键对应的ASCII码值
// shoot实际按修饰父的名称
Vue.config.keyCodes.shoot = 112
用法:
$event
属性:v-on:click="handle('ok', $event)"
。2.4.0
开始,v-on
同样支持不带参数绑定一个事件/监听器键值对的对象。注意当使用对象语法时,是不支持任何修饰器的。事件对象:
$event
,固定语法。那么函数形参就有两个参数,一个是自定义参数,另一个是事件对象示例:
<button v-on:click="doThis">button>
<button v-on:[event]="doThis">button>
<button v-on:click="doThat('hello', $event)">button>
<button @click="doThis">button>
<button @[event]="doThis">button>
<button @click.stop="doThis">button>
<button @click.prevent="doThis">button>
<form @submit.prevent>form>
<button @click.stop.prevent="doThis">button>
<input @keyup.enter="onEnter">
<input @keyup.13="onEnter">
<button v-on:click.once="doThis">button>
<button v-on="{ mousedown: doThis, mouseup: doThat }">button>
缩写::
预期:any (with argument) | Object (without argument)
参数:attrOrProp (optional)
用法:
class
或 style
属性时,支持其它类型的值,如数组或对象。可以通过下面的教程链接查看详情。写法:
<img v-bind:src="imageSrc">
<button v-bind:[key]="value">button>
<img :src="imageSrc">
<button :[key]="value">button>
<img :src="'/path/to/images/' + fileName">
<div :class="{ red: isRed }">div>
<div :class="[classA, classB]">div>
<div :class="[classA, { classB: isB, classC: isC }]">
<div :style="{ fontSize: size + 'px' }">div>
<div :style="[styleObjectA, styleObjectB]">div>
示例:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式的绑定title>
<style>
.big {
width: 100px;
height: 100px;
background-color: pink;
}
.small {
border: 1px solid black;
}
style>
head>
<body>
<div id="app">
<div v-bind:class="{big:isActive,small:isActive}">div>
<div :class="[big,small]">div>
<div v-bind:style="{width:size,height:size,background:activeColor}">div>
<div :style="myselfStyle">div>
<div :style="[style01,style02]">div>
div>
<script src="./js/vue.js">script>
<script>
let vm = new Vue({
el: "#app",
data: {
// 对象绑定的数据
isActive: true,
// 数组绑定的数据
big: "big",
small: "small",
// style方式绑定的数据
size: "100px",
activeColor: "green",
myselfStyle: {
width: "150px",
height: "150px",
background: "blue"
},
style01: {
width: "100px",
height: "100px"
},
style02: {
background: "red"
}
}
})
script>
body>
html>
any
,将提出它的内容作为条件块。v-if
一起使用时,v-for
的优先级比 v-if
更高。预期:不需要表达式
限制:前一兄弟元素必须有 v-if 或 v-else-if。
用法:
示例:
<div v-if="Math.random() > 0.5">
Now you see me
div>
<div v-else>
Now you don't
div>
类型:any
限制:前一兄弟元素必须有 v-if
或 v-else-if
。
用法:
表示 v-if
的“else if 块”。可以链式调用。
示例:
<div v-if="type === 'A'">
A
div>
<div v-else-if="type === 'B'">
B
div>
<div v-else-if="type === 'C'">
C
div>
<div v-else>
Not A/B/C
div>
预期:any
用法:
display
属性。v-show
与v-if
的区别?
示例:
<li class="li">
<p @click="display" v-show="flag">{{value}}p>
<input type="text" v-model="val" v-show="!flag">
li>
预期:Array | Object | number | string | Iterable (2.6 新增)
用法:
基于源数据多次渲染元素或模板块。此指令之值,必须使用特定语法 变量名 in 表达式
,为当前遍历的元素提供别名:
<div v-for="item in items">
{{ item.text }}
div>
另外也可以为数组索引指定别名 (或者用于对象的键):
<div v-for="(item, index) in items">div>
<div v-for="(val, key) in object">div>
<div v-for="(val, name, index) in object">div>
v-for
的默认行为会尝试原地修改元素而不是移动它们。要强制其重新排序元素,你需要用特殊 属性 key
来提供一个排序提示,目的是帮助 Vue 区分不同的元素,从而提高性能
<div v-for="item in items" :key="item.id">
{{ item.text }}
div>
当和 v-if
一起使用时,v-for
的优先级比 v-if
更高。
除了核心功能默认内置的指令 (v-model
和 v-show
),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。例如在表单中获取焦点:
当页面加载时,该元素将获得焦点 (注意:autofocus
在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:
全局指令
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
局部指令
// 在实例化的Vue对象中选项中写,组件也可以
directives: {
// 创建一个局部的自定义组件focus
focus: {
// 指令的定义,并获取挂载的元素
inserted: function (el) {
// 对元素进行获取焦点
el.focus()
}
}
}
然后你可以在模板中任何元素上使用新的 v-focus
属性,如下:
<input v-focus>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>加法计算器案例title>
head>
<body>
<div id="app">
<form action="#">
<h2>简单计算器h2>
<div>
<label for="num1">数值A:label>
<input type="text" v-model.trim.number="one" id="num1">
div>
<div>
<label for="num2">数值B:label>
<input type="text" v-model.trim.number="two" id="num2">
div>
<div>
<input type="button" @click="value" value="计算">
div>
form>
<div>
计算结果:<span v-text="result">span>
div>
div>
<script src="./js/vue.js">script>
<script>
let vm = new Vue({
el: "#app",
data: {
one: 0,
two: 0,
result: 0
},
methods: {
value: function() {
this.result = this.one + this.two;
}
}
})
script>
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>淘宝选项卡title>
<style>
* {
padding: 0;
margin: 0;
}
#app>#img {
width: 590px;
height: 470px;
background-color: #ccc;
margin: 20px auto;
display: none;
}
#app>div img {
width: 100%;
}
#app {
position: relative;
}
#left,
#right {
position: absolute;
top: 50%;
transform: translateY(-30px);
cursor: pointer;
}
li {
width: 100px;
height: 30px;
line-height: 30px;
text-align: center;
color: #fff;
background-color: pink;
list-style: none;
margin-top: 10px;
}
#left {
left: 150px;
}
#right {
right: 150px;
}
#app ul li.active,
#app ol li.active {
background-color: skyblue;
border-color: #f1f1f1;
}
#app div.current {
display: block;
}
style>
head>
<body>
<div id="app">
<div id="left">
<ul>
<li @click="change(index)" :class="currentIndex===index?'active':''" :key="item.id" v-if="item.id<3" v-for="(item,index) in list">{{item.title}}li>
ul>
div>
<div id="img" :style="currentIndex===index?{display:show}:''" v-for="(item,index) in list">
<img :src="item.src" alt="">
div>
<div id="right">
<ol>
<li @click="change(index)" :class="currentIndex===index?'active':''" :key="item.id" v-if="item.id>2" v-for="(item,index) in list">{{item.title}}li>
ol>
div>
div>
<script src="./js/vue.js">script>
<script>
let vm = new Vue({
el: "#app",
data: {
// 样式需要属性
show: "block",
// 记录索引号
currentIndex: 0,
// 数据
list: [{
id: 1,
title: "笔记本",
src: "./img/1.jpg"
}, {
id: 2,
title: "酒水",
src: "./img/d.jpg"
}, {
id: 3,
title: "手机",
src: "./img/q.jpg"
}, {
id: 4,
title: "床垫",
src: "./img/c.jpg"
}],
},
methods: {
// 记录索引
change: function(index) {
this.currentIndex = index;
}
}
})
script>
body>
html>
你可以用 v-model
指令在表单 、
及
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但
v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的 value
、checked
、selected
属性 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data
选项中声明初始值。
基于Vue的表单操作:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单元素title>
head>
<body>
<div id="app">
<form action="" @submit.prevent="submit">
<div>
昵称:<input type="text" name="" id="" v-model="nick">
div>
<div>
性别:<input type="radio" name="" id="man" v-model="sex" value="0"><label for="man">男label>
<input type="radio" name="" id="woman" v-model="sex" value="1"><label for="woman">女label>
div>
<div>
爱好:<input type="checkbox" name="football" id="football" v-model="hobby" value="0"><label for="football">足球label>
<input type="checkbox" name="football" id="basketball" v-model="hobby" value="1"><label for="basketball">篮球label>
<input type="checkbox" name="football" id="volleyball" v-model="hobby" value="2"><label for="volleyball">排球label>
div>
<div>
职业:
<select name="" id="" v-model="job">
<option value="0">教师option>
<option value="1">护士option>
<option value="2">程序员option>
<option value="0">医生option>
select>
div>
<div>
评价:<textarea name="" id="" cols="30" rows="10" v-model="evaluate">textarea>
div>
<div>
<input type="submit" value="提交">
div>
form>
div>
<script src="./js/vue.js">script>
<script>
let vm = new Vue({
el: "#app",
data: {
nick: "小明",
// 性别
sex: "0",
// 爱好
hobby: ["0", "1"],
// 职业
job: "2",
evaluate: "请书写评价"
},
methods: {
submit: function() {
console.log(this.nick);
console.log(this.sex);
console.log(this.hobby);
console.log(this.job);
console.log(this.evaluate);
}
}
});
script>
body>
html>
表达式的计算逻辑可能会比较复杂,使用计算属性可以使模板内容更加简洁
// 定义计算属性的选项
computed: {
// reversedMessage计算属性的名称,那接下来这个属性就可以像data中的数据一样直接在模板中使用了
reversedMessage: function ()
// 对Vue内部数据进行一系列的操作
return this.msg.split ('').reverse().join('')
}
}
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
// 注意:firstName和lastName都是已经存在的属性
watch: {
firstName: function(val){
// val表示变化之后的值
this.fullName = val + this.lastName;
},
lastName: function(val) {
this.fullName = this.firstName + val;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>验证用户名是否存在</title>
<style>
.green {
color: green;
}
.red {
color: red
}
</style>
</head>
<body>
<div id="app">
<!-- input双向数据绑定 -->
用户名:<input v-model="name" v-focus v-on="{focus:getFocus,blur:lostFocus}" type="text" placeholder="请输入昵称">
// 如果不存在则使用这个
<span v-text="prompt" v-if="!flag" :class="green"></span>
// 如果存在则使用这个
<span v-text="prompt" v-else :class="red"></span>
</div>
<script src="./js/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
// 判断是否失去焦点
isFocus: true,
// 获取用户输入的数据
name: "",
green: "green",
red: "red",
flag: false,
// 提示信息
prompt: "",
// 已经存在的昵称
listNick: ["御帝哥謌", "孙悟空", "猪八戒", "沙悟净"]
},
// 事件函数
methods: {
// 失去焦点的事件
lostFocus: function() {
// 修改focus属性
this.isFocus = false;
},
// 获取焦点的事件
getFocus: function() {
// 修改focus属性
this.isFocus = true;
},
// 检查的函数
checkName: function(val) {
// 使用防抖来拿到最后一次输入
setTimeout(() => {
// 判断失去焦点
if (!this.isFocus) {
// 判断是否存在
this.flag = this.listNick.some((item, index) => {
// 如果存在
if (val === item) {
this.prompt = '用户名已经存在,请更换一个';
return true;
}
})
// 如果不存在
if (!this.flag) {
this.prompt = '该用户名可用';
}
}
}, 1000);
}
},
// 局部指令
directives: {
// 获取焦点的指令
focus: {
inserted: function(el) {
el.focus();
}
}
},
// 侦听器
watch: {
// val为获取到的值
name: function(val) {
// 当name属性发生变化后,调用该函数
return this.checkName(val);
}
}
})
</script>
</body>
</html>
</html>
用户输入的一些数据它不符合我们数据的规则,影响视觉,那么就要格式化数据,比如:
全局过滤器
// 定义一个全局的过滤器,val就是模板中默认传来的参数
Vue.filter(('过滤器名称', function(val){
//过滤器业务逻辑
})
局部过滤器
// 定义局部的过滤器,val就是模板中默认传来的参数
filters: {
过滤器名称: function(val) {
//过滤器业务逻辑
}
}
带参数的过滤器
一般情况下,过滤器都会默认自动带一个模板中传来的参数,但有些情况下我们需要再传递一个参数,比如传递一个转换格式字符串,这里以全局过滤器为例
// 定义一个全局的过滤器,val就是模板中默认传来的参数,而format是人为传递的参数
Vue.filter(('过滤器名称', function(val,format){
//过滤器业务逻辑
})
过滤器的使用
单个过滤器
<div>{{msg | 过滤器名称}}div>
多个过滤器
<div>{{msg | 过滤器名称 | 过滤器名称(参数)}}div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>格式化日期</title>
</head>
<body>
<div id="app">
<!-- 对当前日期以"yyyy-MM-dd hh:mm:ss"格式进行过滤 -->
当前日期:<span>{{time | formatDate("yyyy-MM-dd hh:mm:ss")}}</span>
</div>
<script src="./js/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
// 获取当前日期
time: new Date()
},
filters: {
formatDate: function(date, format) {
if (typeof date === "string") {
var mts = date.match(/(\/Date\((\d+)\)\/)/);
if (mts && mts.length >= 3) {
date = parseInt(mts[2]);
}
}
date = new Date(date);
if (!date || date.toUTCString() == "Invalid Date") {
return "";
}
var map = {
"M": date.getMonth() + 1, //月份
"d": date.getDate(), //日
"h": date.getHours(), //小时
"m": date.getMinutes(), //分
"s": date.getSeconds(), //秒
"q": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
var v = map[t];
if (v !== undefined) {
if (all.length > 1) {
v = '0' + v;
v = v.substr(v.length - 2);
}
return v;
} else if (t === 'y') {
return (date.getFullYear() + '').substr(4 - all.length);
}
return all;
});
return format;
}
}
})
</script>
</body>
</html>
所有的生命周期钩子自动绑定 this
上下文到实例中,因此你可以访问数据,对 property 和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos()
)。这是因为箭头函数绑定了父上下文(由于this的指向),因此 this
与你期待的 Vue 实例不同,this.fetchTodos
的行为未定义。
生命周期图示:
el
被新创建的 vm.$el
替换了。如果根实例挂载到了一个文档内的元素上,当 mounted
被调用时 vm.$el
也在文档内。注意❗:
在vue对象中直接修改data对象属性内的对象或数组的值无法触发响应式(只有数据发生了改变,而页面内容不会发生更改,也就是没有及时的渲染)。而变异方法,可以保持数组方法的原有功能不变的前提并且对其功能进行拓展或删减,或者使用Vue内置的数据响应式方法set。
全局数据响应
即在没有创建或使用Vue对象,但是导入了Vue.js时使用,所以称之为全局的响应式数据
Vue.set(target,propertyName/index, value);
局部数据响应
即在创建并使用了Vue对象时,在Vue方法中就可以使用该方法,我们称之为局部的响应式数据
vm.$set(target,propertyName/index, value);
//等价于
this.$set(target,propertyName/index, value);
参数 | 说明 |
---|---|
target | 要处理的数组或对象名称 |
propertyName/index | 要处理的对象属性或数组索引 |
value | 要处理的对象或数组的值 |
Vue响应式数据方法可以实现数据的修改,并且还可以实现添加数据的功能。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数组编译方法</title>
</head>
<body>
<div id="app">
<input type="text" v-model="item" @keydown="keydown">
<button @click="add">提交</button>
<button @click="amend">修改</button>
<ul>
<li v-for="item in list ">{{item}}</li>
</ul>
</div>
<script src="./js/vue.js "></script>
<script>
let vm = new Vue({
el: "#app ",
data: {
item: "css",
list: ["C# ", "HTML ", "Java "]
},
methods: {
// 键盘按下的事件
keydown: function(e) {
// 打印事件对象
console.log(e);
},
// 添加元素的事件
add: function() {
// 局部数据响应,添加一个数据
this.$set(this.list, this.list.length, this.item);
},
// 修改元素的事件
amend: function() {
// 全局数据响应,修改一个数据
Vue.set(vm.list, 4, "css");
}
}
})
</script>
</body>
</html>
首先,让我们一起来看一个Vue组件的实例:
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
// 组件数据
data: function () {
return {
count: 0
}
},
// 模板,点击后count++
template: ''
})
组件是可复用的Vue实例,且带有一个名字:在这个例子中是
,我们可以在一个通过new Vue
创建的Vue根实例中,把这个组件作为自定义元素来使用:
// vue环境
<div id="components-demo">
// 自定义组件
<button-counter>button-counter>
div>
因为组件是可复用的Vue实例,所以它们与new Vue
接收相同的选项,例如:data
、computed
、watch
、methods
,以及生命周期钩子函数……等。仅有的例外是el
这样跟实例特有的选项。
你可以将组件进行任意多次的复用,因为它们之间的参数是相互独立的,但是内部参数相同。
<div id="components-demo">
<button-counter>button-counter>
<button-counter>button-counter>
<button-counter>button-counter>
div>
注意:当点击按钮时,每个组件都会各自独立维护它的 count
。因为你每用一次组件,就会有一个它的新实例被创建。
当我们定义这个
组件时,你可能会发现它的 data
并不是像new Vue
中的data这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data
选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
如果 Vue 没有这条规则,那么点击一个按钮就可能会出现组件中的数据变化是同步的,因为它们用的是一个数据,而不是相互独立的存在。
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
全局组件:全局注册的组件可以用在其被注册之后的任何 (通过 new Vue
) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
Vue.component(
组件名称,
{
data:组件数据(以对象为返回值的函数),
template:组件模板内容
}
)
//例如 注册一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {count: 0}
},
template: '<button v-on:click="count++"> 点击了 {{ count 次 .</button>
})
局部组件:相反,而局部组件只能在当前Vue实例中使用,所以大多数情况下我们注册的都是全局组件
// 因为组件内部本身就是以对象形式存在的,那么我们就可以这样定义一个局部的组件,但是必须在Vue环境中使用
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
// 当前Vue环境
new Vue({
el: '#app'
// 局部组件
components: {
// 使用局部组件
'component-a': ComponentA,
'component-b': ComponentB,
'component-c': ComponentC,
}
})
用法
<div id="app">
// 使用组件
<button-counter>button-counter>
div>
组件注册注意事项
data必须是一个函数
分析函数与普通对象的对比
组件模板内容必须是单个根元素
分析演示实际的效果
组件模板内容可以是模板字符串
模板字符串需要浏览器提供支持( ES6 语法)
组件命名方式
短横线方式
Vue.component('my-component', { /* ... */ })
驼峰方式
Vue.component('MyComponent ', { /* ... */})
注意:不论使用哪种命名法则,在使用的时候只能使用my-component标签名称,所以我建议大家在使用的时候尽量以短横线的方式进行命名。
由于组件间存在数据相互依赖的关系,所以学习Vue组件通信是必须的。比如子组件要使用父组件从服务器获取的数据,并对这个值进行处理在自己的模板中使用。又或者子组件修改了父组件从服务器获取的数据,那么为了实现数据响应,就必须将数据传输给父组件,让父组件进行处理。又或者两个子组件之间相互依赖对方的数据,那又该怎么办?
分析:
首先父组件向子组件传递数据是通过子组件的props属性来实现的,那么也就是说子组件必须要有这个props属性。
// 定义一个menu-item组件
Vue.component('menu-item', {
// props属性用于接收父组件传递的数据,该属性的值为一个数字
props: [],
// 模板要使用父组件传递的数据title
template: '{{ title }}'
})
那么此时父组件通过自定义属性的方式将数据传递给子组件的props属性,那子组件的props属性中当然要有一个变量来接收
<menu-item title=" 来自父组件的数据 ">menu-item>
<menu-item :title="title">menu-item>
那么此时就可以在子组件的props属性中定义一个包含变量的数组,数组中的值必须为字符串类型。那么数组的元素就是父组件传递过来的值。
Vue.component('menu-item', {
// props数组属性中title变量就是父组件通过自定义属性传来的值title
props: ['title'],
// 模板中使用props中父组件传递的值title
template: '{{ title }}'
})
注意:
this.属性名
的方式来获取和使用,而在template
模板中与data的使用方式一样,直接书写名称。分析:
父组件并没有像子组件一样的props属性。而Vue规定可以使用自定义事件,那么我们就可以让父组件自定义一个事件,并监听它的触发。
<menu-item v-on:enlarge-text='$event'>menu-item>
<menu-item @enlarge-text='$event'>menu-item>
那么此时子组件就要触发父组件创建并监听的事件,并将要传递的数据加上。
// 创建全局的组件
Vue.component('title-get', {
// 组件数据
data:function(){
return {
size: 0.1
}
},
// 字符串模板
template: `
`
});
当子组件触发了父组件监听的事件,那么父组件就可以获取到子组件传递的参数
<menu-item v-on:enlarge-text='fontSize+=$event'>menu-item>
注意:
this.$emit("自定义事件名称", 参数)
的形式,即用this点出来$event
作为函数的参数传递,那么就可以在父组件的函数中对子组件传递的参数进行一系列的复杂操作分析:
要想实现兄弟组件间的通信,则必须要有事件中心,这个事件中心所起到的作用就是触发事件后通过事件中心传值
// 创建一个事件中心
let eventHub = new Vue();
在子组件创建后,但数据和模板还未进行渲染的情况下,使用事件中心的$on
方法创建并监听自定义事件
// 2.在组件内部使用mounted钩子函数,因为mounted钩子函数就是在组件创建后,但数据和模板未进行渲染的情况下带调用的
mounted: function() {
// A组件监听自己的事件,val为触发组件传递的数据
eventHub.$on("a-event", (val) => {
})
},
当在某种情况下需要兄弟组件之间通讯时,使用事件中心的$emit()
方法触发对方的事件,并将参数传递
// 事件函数
methods: {
// 定义handle函数
handle: function() {
// B组件触发兄弟组件的A的"a-event"事件,并将自己的参数传递
eventHub.$emit("b-event", this.title)
}
}
那么此时兄弟组件监听的事件就会得到数据,并对数据进行操作,如果下次不需要传值了,那么可以在此销毁事件
// 2.在组件内部使用mounted钩子函数,因为mounted钩子函数就是在组件创建后,但数据和模板未进行渲染的情况下带调用的
mounted: function() {
// A组件监听自己的事件,val为触发组件传递的数据
eventHub.$on("a-event", (val) => {
// 将接收的参数赋值给当前组件的属性
this.content = val;
// 销毁自定义事件
eventHub.$off("a-event");
})
},
案例:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>兄弟组件之间传递参数</title>
</head>
<body>
<div id="app">
<item-a></item-a>
<item-b></item-b>
</div>
<script src="./js/vue.js"></script>
<script>
// 创建一个事件中心
let eventHub = new Vue();
// 组件A
Vue.component(
"item-a", {
data: function() {
return {
title: "春天来了",
content: ""
}
},
template: `
内容:{{content}}
`,
// 2.在子组件准备就绪之后,使用事件中心的$on函数创建并监听自定义事件,并接收一个参数
mounted: function() {
// A组件监听自己的事件,如果自己的事件触发了则
eventHub.$on("a-event", (val) => {
// 将接收的参数赋值给当前组件的属性
this.content = val;
eventHub.$off("a-event");
})
},
// 3.如果当前子组件要传递给其他子组件数据,调用该函数
methods: {
handle: function() {
// A组件触发兄弟组件的B的"b-event"事件,并将自己的参数传递
eventHub.$emit("b-event", this.title)
}
}
}
)
// 组件B
Vue.component("item-b", {
data: function() {
return {
title: "",
content: "明年春暖花开之日,就是我们见面之时。。。"
}
},
template: `
标题:{{title}}
`,
// 在子组件准备就绪之后,使用事件中心的$on函数创建并监听自定义事件,并接收一个参数
mounted: function() {
// B组件监听自己的事件,如果自己的事件触发了则
eventHub.$on("b-event", (val) => {
// 将接收的参数赋值给当前组件的属性
this.title = val;
// 销毁事件
eventHub.$off("b-event");
})
},
// 3.如果当前子组件要传递给其他子组件数据,调用该函数
methods: {
// B组件触发兄弟组件的A的"b-event"事件,并将自己的参数传递
handle: function() {
eventHub.$emit("a-event", this.content)
}
}
})
let vm = new Vue({
el: "#app",
data: {
}
})
</script>
</body>
</html>
插槽就是子组件中的提供给父组件使用的一个占位符,用
表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的
标签。
有些时候,我们需要父组件向子组件中填充一些数据,那么假如子组件没有使用插槽,父组件如果需要往子组件中填充模板或者html代码块, 是没办法做到的。
子组件插槽占位符
// 全局的子组件alert-box
Vue.component('alert-box', {
template: `
Error!
`
})
父组件向插槽中填充内容
<div id="app">
<alert-box>Something bad happened.alert-box>
div>
描述
具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。一个不带 name
的
出口会带有隐含的名字“default”。
使用
子组件的模板
给slot标签一个name属性,那么它就是具名插槽,而不带name属性的插槽则为默认插槽
<div class="container">
<header>
<slot name="header">slot>
header>
<main>
<slot>slot>
main>
<footer>
<slot name="footer">slot>
footer>
div>
父组件传值
在给具名插槽传值的时候必须使用template标签,并使用v-slot
指令冒号后为插槽名称。而默认插槽则以默认的方式传递,或者以defaulte
为name值传递。
<base-layout>
<template v-slot:header>
<h1>Here might be a page titleh1>
template>
<p>A paragraph for the main content.p>
<p>And another one.p>
<template v-slot:footer>
<p>Here's some contact infop>
template>
base-layout>
注意
父级的填充内容如果指定到子组件的没有对应名字插槽,那么该内容不会被填充到默认插槽中。
如果子组件没有默认插槽,而父级的填充内容指定到默认插槽中,那么该内容就“不会”填充到子组件的任何一个插槽中。
如果子组件有多个默认插槽,而父组件所有指定到默认插槽的填充内容,将“会” “全都”填充到子组件的每个默认插槽中。
描述
作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。
使用
子组件模板
给插槽上绑定一个属性(包含数据),使得父组件可以获取到。绑定在
元素上的 属性 被称为插槽 prop。
<ul>
-
<slot v-bind:item="item">
{{item.name}}
slot>
li>
ul>
父组件传值
现在在父级作用域中,我们可以使用带值的 v-slot
来定义我们提供的插槽 prop 的名字:
<lug-list :list="list">
<template slot-scope="slotProps">
<strong v-if='slotProps.info.id==1' class="current">
{{slotProps.info.name}}
strong>
<span v-else>
{{slotProps.info.name}}
span>
template>
lug-list>
使用场景
如果子组件中的某一部分的数据,每个父组件都会有自己的一套对该数据的不同的呈现方式,这时就需要用到作用域插槽。
也就是说,要从外部控制模板插槽对应数据的样式的话,就用作用域插槽。
路由是一个比较广义和抽象的概念,路由的本质就是对应关系。而在开发中,路由分为:后端路由 和 前端路由。
SPA(Single Page Application)单页面应用程序:整个网站只有一个页面,内容的变化通过Ajax局部更新实现、同时支持浏览器地址栏的前进和后退操作,并且地址栏会更新url,通过这个url就可以来到这个页面,还会刷新局部的内容。
人话:意思就是说前端渲染实现的菜单栏点击后不会局部更新url地址,同时也不会拥有前进和后退的功能,那么前端路由就是为了解决这个问题来诞生的。
需求:基于UR的hash实现菜单栏(点击菜单的时候改变URL的hash,根据hash的变化控制组件的切换)
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生js实现简易前端路由title>
head>
<body>
<div id="app">
<div>
<ul>
<li><a href="#/my">我的页面a>li>
<li><a href="#/you">你的页面a>li>
<li><a href="#/your">你们的页面a>li>
ul>
div>
<div>
<component :is="comName">component>
div>
div>
<script src="./js/vue.js">script>
<script>
//定义需要切换的四个组件
//主页组件
const my = {
template: "我的页面
"
};
const you = {
template: `你的页面
`
};
const your = {
template: `你们的页面
`
};
// 实例化Vue对象
var vm = new Vue({
el: "#app",
// 默认值为my
data: {
// 设置comName属性的值为路由名称
comName: "my"
},
//注册局部组件
components: {
my,
you,
your
}
});
// 重点是通过window的onhashchange事件来监听浏览器hash值的变化
window.onhashchange = function() {
// 截取哈希值,从字符串索引为1的地方
switch (location.hash.slice(1)) {
//根据获取到的最新的hash值切换到显示的组件名称
case "/my":
vm.comName = "my"
break;
case "/you":
vm.comName = "you"
break;
case "/your":
vm.comName = "your"
break;
}
}
script>
body>
html>
Vue Router(官网:https://router.vuejs.org/zh/)是 Vue.js 官方的路由管理器。 它和 Vue.js 的核心深度集成,可以非常方便的用于SPA应用程序的开发,可以说Vue Router让构建单页面应用变得易如反掌。
Vue Router 包含的功能有:
引入相关的库文件
<script src="./vue.js">script>
<script src="./vue-router.js">script>
添加路由链接
<router-link to="/user">Userrouter-link>
<router-link to="/register">Registerrouter-link>
添加路由填充位
<router-view>router-view>
定义路由组件
var User = { template: 'User' }
var Register = { template: 'Register' }
配置路由规则并创建路由实例
// 创建路由实例对象
var router = new VueRouter({
// routes 是路由规则数组
routes: [
// 每个路由规则都是一个配置对象,其中至少包含path和component 两个属性:
// path 表示当前路由规则匹配的 hash 地址
// component 表示当前路由规则对应要展示的组件
{path:'/user',component: User},
{path:'/register',component: Register}
]
})
把路由挂载到 Vue 根实例中
new Vue({
el: '#app',
// 为了能够让路由规则生效,必须把路由对象挂载到 vue 实例对象上
router
});
使用 Vue Router 实现简易的菜单:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue路由的基本使用title>
head>
<body>
<div id="app">
<div>
<ul>
<li>
<router-link to="/my">我的router-link>
li>
<li>
<router-link to="/you">你的router-link>
li>
<li>
<router-link to="/your">你们的router-link>
li>
ul>
div>
<div>
<router-view>router-view>
div>
div>
<script src="./js/vue.js">script>
<script src="./js/vue-router.js">script>
<script>
// 创建并定义路由组件
let my = {
template: `我的主页
`
};
let you = {
template: `你的page
`
};
let your = {
template: `你们的page
`
};
// 创建路由实例对象
let router = new VueRouter({
// 通过routes配置路由规则
routes: [
// 每个路由规则都是一个配置对象,其中至少包含 path 和component 两个属性:
// path 表示当前路由规则匹配的 hash 地址
// component 表示当前路由规则对应要展示的组件
{
path: "/my",
component: my
}, {
path: "/you",
component: you
}, {
path: "/your",
component: your
}
]
});
// 实例化Vue对象
let vm = new Vue({
el: "#app",
// 为了能够让路由规则生效,必须把路由对象挂载到vue实例对象上
router
});
script>
body>
html>
路由重定向指:
使用:
// 路由对象
var router = new VueRouter({
routes: [
// 其中,path 表示需要被重定向的原地址,redirect 表示将要被重定向到的新地址
// 也就是说访问/地址的时候,让其强制跳转到/user地址所对应的路由上
{path:'/', redirect: '/user'},
{path:'/user',component: User},
{path:'/register',component: Register}
]
})
嵌套路由是指:
父级路由组件模板
父级路由链接
父组件路由填充位
<p>
<router-link to="/user">Userrouter-link>
<router-link to="/register">Registerrouter-link>
p>
<div>
<router-view>router-view>
div>
子级路由组件模板
子级路由链接:在父级路由组件的模板中定义
子级路由填充位:在父级路由组件的模板中定义
const Register = {
template: `
Register 组件
Tab1
Tab2
`
}
嵌套路由配置
const router = new VueRouter({
routes: [
{ path: '/user', component: User },
{ path: '/register', component: Register,
// 通过 children 属性,为 /register 添加子路由规则
children: [ {
path: '/register/tab1', component: Tab1 },
{ path: '/register/tab2', component: Tab2 }
]}
]
})
通过动态路由参数的模式进行路由匹配,与后端路由类似,就是给路由传递参数,可以是查询参数,也可以是params参数
var router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
路由组件模板中通过$route.params
获取路由参数
const User = {
// 路由组件中通过$route.params获取路由参数
template: 'User {{ $route.params.id }}'
}
$route
与对应路由形成高度耦合,不够灵活,所以可以使用props将组件和路由解耦
props的值为布尔类型
const router = new VueRouter({
routes: [
// 如果props被设置为true,那么route.params 将会被设置为组件属性
{ path: '/user/:id', component: User, props: true }
]
})
const User = {
props: ['id'],
// 使用 props 接收路由参数
template: '用户ID:{{ id }}' // 使用路由参数
}
props的值为对象类型
const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
// 如果 props 是一个对象,它会被按原样设置为组件属性
props: { uname: 'lisi', age: 12 }
}]
})
const User = {
props: ['uname', 'age'],
template: `<div>用户信息:{{ uname + '---' + age}}</div>'
}
props的值为函数类型
const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
// 如果 props 是一个函数,则这个函数接收 route 对象为自己的形参
props: route => ({
uname: 'zs',
age: 20,
id: route.params.id
})
}]
});
const User = {
props: ['uname', 'age', 'id'],
template: `<div>用户信息:{{ uname + '---' + age + '---' +'---'id}}</div>'
}
简介:为了更加方便的表示路由的路径,可以给路由规则起一个别名,即为“命名路由”。
在路由对象配置中可以使用name
属性给所在的路由起一个名字
const router = new VueRouter({
routes: [{
path: '/user/:id',
// 给所在路由起名字,即为命名式路由
name: 'user',
component: User
}]
})
在路由链接中就可以使用直接使用对象的形式来获取name所代表路由,并传递参数
<router-link :to="{ name: 'user', params: { id: 123 }}">Userrouter-link>
声明式导航:通过点击链接实现导航的方式,叫做声明式导航
例如:普通网页中的 < a>
链接 或 vue 中的 < router-link>< /router-link>
编程式导航:通过调用JavaScript形式的API实现导航的方式,叫做编程式导航
例如:普通网页中的 location.href
this.$router.push('hash地址')
this.$router.go(n)
// 字符串(路径名称)
router.push('/home');
// 对象:命名路由
router.push({ path: '/home' })
// 命名的路由(传递参数)
router.push({ name: '/user', params: { userId: 123 }})
// 带查询参数,变成 /register?uname=lisi
router.push({ path: '/register', query: { uname: 'lisi' }})
vue脚手架指的是vue-cli,它是一个专门为单页面应用快速搭建繁杂的脚手架,它可以轻松的创建新的应用程序而且可用于自动生成vue和webpack的项目模板。
安装 3.x 版本的 Vue 脚手架:
npm install -g @vue/cli
基于交互式命令行的方式,创建 新版 vue 项目
vue create my-project
基于 图形化界面 的方式,创建 新版 vue 项目
vue ui
基于 2.x 的旧模板,创建 旧版 vue 项目
npm install -g @vue/cli-init
vue init webpack my-project
通过 package.json 配置项目
// 必须是符合规范的json语法
"vue": {
"devServer": {
"port": "8888",
"open" : true
}
},
注意:不推荐使用这种配置方式。因为 package.json 主要用来管理包的配置信息;为了方便维护,推荐将 vue 脚手架相关的配置,单独定义到 vue.config.js 配置文件中。
通过单独的配置文件配置项目
在项目的跟目录创建文件 vue.config.js
在该文件中进行相关配置,从而覆盖默认配置
// vue.config.js
module.exports = {
devServer: {
port: 8888
}
}
v-bind
属性绑定v-on
自定义事件绑定$on
接收数据的那个组件$emit
发送数据的那个组件分析:试想一下,如果我们在开发中使用这种方法来共享数据未免有点太麻烦了,是不是影响我们的开发效率,而且代码不容易看懂
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
人话:Vuex 是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享。图示如下:
分析:
安装 vuex 依赖包
npm install vuex --save
导入 vuex 包
import Vuex from 'vuex'
Vue.use(Vuex)
创建 store 对象
const store = new Vuex.Store({
// state 中存放的就是全局共享的数据
state: { count: 0 }
})
将 store 对象挂载到 vue 实例中
new Vue({
el: '#app',
render: h => h(app),
router,
// 将创建的共享数据对象,挂载到 Vue 实例中
// 所有的组件,就可以直接从 store 中获取全局的数据了
store
})
注意:当然,你也可以使用脚手架使用图形化界面来创建项目,并选择安装Vuex,那么你就不需要配置这些东西,直接使用即可
State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 的 State 中进行存储。
// 创建store数据源,提供唯一公共数据
const store = new Vuex.Store({
state: { count: 0 }
})
组件访问 State 中的数据
方法1:this.$store.state.全局数据名称;
方法2:
按需导入
// 1. 从 vuex 中按需导入 mapState 函数
import { mapState } from 'vuex'
通过刚才导入的 mapState 函数,将当前组件需要的全局数据,映射为当前组件的 computed 计算属性,那么就可以直接使用
// 2. 将全局数据,映射为当前组件的计算属性
computed: {
...mapState(['count'])
}
在 mutations 属性中 定义修改状态的方法
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
// 更改state状态的方法,当然也可以传递其他的参数
add(state,...) {
// 变更状态
state.count++
}
}
})
方法1:this.$store.commit('addN', 参数1...)
// 触发mutation
methods: {
handle2() {
// 在调用 commit 函数,
// 触发 mutations 时携带参数
this.$store.commit('addN', 3)
}
}
方法2:按需导入
从 vuex 中按需导入 mapMutations 函数
import { mapMutations } from 'vuex'
通过刚才导入的 mapMutations 函数,将需要的 mutations 函数,映射为当前组件的 methods 方法
methods: {
...mapMutations(['add', 'addN'])
}
this.方法名
的方式去调用它了Action 用于处理异步任务,比如计时器……等。如果通过异步操作变更数据,必须通过 Action,而不能使用 Mutation,但是在 Action 中还是要通过触发Mutation 的方式间接变更数据。
人话:意思就是说 Action 用于处理 Mutation 中带有异步事件的方法,或者是如果你想在 Mutation 中定义带有异步事件的方法,那么你就要在Mutation中定义同步方法,然后在Action中使用异步事件调用即可。
在 mutations 属性中 定义修改状态的同步方法
// 定义 Action
const store = new Vuex.Store({
// ...省略其他代码
mutations: {
add(state) {
state.count++
}
}
})
在 Action 中定义一个方法,使用异步事件调用 mutations 中的同步方法
// 定义 Action
const store = new Vuex.Store({
// ...省略其他代码
mutations: {
add(state) {
state.count++
}
},
actions: {
addAsync(context) {
setTimeout(() => {
context.commit('add')
}, 1000)
}
}
})
方法1:this.$store.dispatch('addAsync')
方法2:按需导入
从 vuex 中按需导入 mapActions 函数
import { mapActions } from 'vuex';
通过刚才导入的 mapActions 函数,将需要的 actions 函数,映射为当前组件的 methods 方法
methods: {
...mapActions(['addASync', 'addNASync'])
}
Getter 用于对 Store 中的数据进行加工处理形成新的数据。
注意:
注意:因为是要对state中的数据进行加工,那么第一个参数必须是state
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
// 创建一个方法,对state中的数据进行加工,并返回一个加工后的数据
showNum: state => {
return '当前最新的数量是【'+ state.count +'】'
}
}
})
在插值表达式中使用:this.$store.getters.名称
的方式直接调用
按需导入
从 vuex 中按需导入 mapGetters 函数
import { mapGetters } from 'vuex'
通过刚才导入的 mapGetters 函数,将当前组件需要的全局数据,映射为当前组件的 computed 计算属性
computed: {
...mapGetters(['showNum'])
}
接下来直接使用计算属性的方式调用即可
Element-UI:一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。
官网地址:https://element.eleme.cn/#/zh-CN
安装:
基于命令行方式手动安装
安装依赖包
npm i element-ui –S
②导入 Element-UI 相关资源
// 导入组件相关样式
import 'element-ui/lib/theme-chalk/index.css';
// 配置 Vue 插件
Vue.use(ElementUI);
基于图形化界面自动安装
vue ui
命令,打开图形化界面需求:使用Vue指令、计算属性、侦听器、过滤器……等实现简单的图书管理(增删改)功能,并使用文件操作(读写)来存储数据和获取数据。
步骤:
效果展示:
源码奉上:
前台代码
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图书列表title>
<link rel="stylesheet" href="./css/bootstrap.css">
<style>
* {
padding: 0;
margin: 0;
}
#app {
width: 60%;
margin: 50px auto;
}
[v-cloak] {
display: none;
}
style>
head>
<body>
<div id="app" class="panel panel-default">
<div class="panel-heading">图书管理列表div>
<div class="panel-body form-inline">
<div class="input-group">
<span class="input-group-addon" id="basic-addon1">编号span>
<input type="text" class="form-control" placeholder="请输入编号" :disabled="disable" aria-describedby="basic-addon1" v-model="id" v-focus>
div>
<div class="input-group">
<span class="input-group-addon" id="basic-addon1">书名span>
<input type="text" class="form-control" placeholder="请输入书名" aria-describedby="basic-addon1" v-model="name">
div>
<button type="button" class="btn btn-primary" @click="submit">提交button>
<span>图书总数:span><span v-cloak>{{total}}span>
div>
<table class="table">
<tr>
<th>编号th>
<th>书名th>
<th>时间th>
<th>操作th>
tr>
<tr :key="item.id" v-for="(item,index) in books">
<td v-cloak>{{item.id}}td>
<td v-cloak>{{item.name}}td>
<td v-cloak>{{item.date | format("yyyy-mm-dd hh:mm:ss")}}td>
<td>
<a href="" @click.prevent="deleted(item.id)">删除a>
<span>|span>
<a href="" @click.prevent="updata(item.id)">修改a>
td>
tr>
table>
div>
<script src="./js/vue.js">script>
<script src="./js/time.js">script>
<script src="./js/axios.min.js">script>
<script>
const beforURL = "http://127.0.0.1:8024/api";
// 自定义全局指令:获取表单焦点
Vue.directive("focus", {
inserted: function(el) {
el.focus();
}
});
// 全局过滤器
Vue.filter("format", function(val, arg) {
return dateFormat(val, arg)
})
let vm = new Vue({
el: "#app",
data: {
disable: false,
sign: true,
// 编号
id: "",
// 书名
name: "",
// 书的数据(暂时为空)
books: []
},
mounted: function() {
// 调用ajax,获取数据
axios.get(beforURL + "/getData").then(val => {
// 获取到的数据
let data = val.data;
// 判断获取到的状态码
if (data.status !== 0) return alert("数据获取失败,请联系管理员!");
// 将数据填充到vue数据中
this.books = data.message;
})
},
methods: {
// 提交事件
submit: function() {
// 修改图书
if (this.disable) {
// 判断该书名是否存在的结果
this.books.some((item) => {
// 编号不同,但是书名相同的是否存在
if (item.id != this.id && item.name == this.name) {
// 提示并清空数据
alert("该图书已被占用!");
this.id = "";
this.name = "";
return true
}
});
// 修改的方式其实就是通过id去更新id对应的数据
// some方法也能够响应数据
this.books.some((item, index) => {
// 如果遍历到的id与当前表单中的id相同,那么就是我们要修改的数据
if (item.id == this.id) {
axios.put(beforURL + "/upData", {
// 索引
index: index,
// 数据
data: {
id: this.id,
name: this.name,
date: item.date
}
}).then(val => {
// 获取服务器响应的数据
let data = val.data;
if (data.status != 0) return alert("数据修改失败,请联系管理员!");
// 将返回的数据在次替换到原始数据中
this.books = data.data;
return true;
});
}
});
this.disable = false;
} else {
// 判断该编号或书名是否存在的结果
this.books.some((item, index) => {
// 如果编号或者书名存在
if (item.id == this.id || item.name == this.name) {
// 提示并清空数据
alert("该编号或图书已被占用!");
this.id = "";
this.name = "";
return true
}
});
// 调用ajax并传递数据
axios.post(beforURL + "/addData", {
ele: {
id: this.id,
name: this.name,
date: new Date()
}
}).then(val => {
// 获取服务器响应的数据
let data = val.data;
if (data.status != 0) return alert("数据修改失败,请联系管理员!");
// 将返回的数据在次替换到原始数据中
this.books = data.data;
});
}
// 清空表单
this.id = "";
this.name = "";
},
// 点击修改后,将数据显示到表单中,并修改标志
updata: function(id) {
this.sign = false;
// filter方法是根据内部的回调函数进行筛选符合项,并返回数组中符合项的元素
var book = this.books.filter((item) => {
// 返回books中id属性于id属性相同的元素:对象数组
return item.id == id;
});
// 修改vm双向绑定数据中的对应数据,即input中的值
this.id = book[0].id;
this.name = book[0].name;
// 修改属性
this.disable = true;
},
// 删除数据
deleted: function(id) {
// 遍历原始数据中每一个元素的id不等于当前id的元素,将其都赋值给原始数组,其实是改变元素数组
// this.books = this.books.filter((item) => {
// return item.id != id
// });
// 在元素数据中遍历查找与当前id值相同的item元素,并返回索引值
var index = this.books.findIndex((item) => {
return item.id == id
});
// 调用ajax,删除数据
axios.delete(beforURL + "/delData", {
params: {
index: index
}
}).then(val => {
// 获取服务器响应的数据
let data = val.data;
// 如果状态不为0
if (data.status != 0) return alert("数据修改失败,请联系管理员!");
// 将返回的数据在次替换到原始数据中
this.books = data.data;
})
}
},
// 计算属性
computed: {
// 返回书的个数
total: function() {
return this.books.length;
}
},
// 侦听器:监听输入框中的数值是否存在
watch: {
id: function(val) {
// 遍历原始数组,判断遍历的元素的id属性是否和当前的name相同
let flag = this.books.some((item) => {
if (item.id == this.id) {
return true
}
});
// 如果相同则。。。
if (flag && this.sign) {
alert("该编号已被占用!");
this.id = "";
}
},
name: function(val) {
// 遍历原始数组,判断遍历的元素的id属性是否和当前的name相同
let flag = this.books.some((item) => {
if (item.name == this.name) {
return true
}
});
// 如果相同则。。。
if (flag && this.sign) {
alert("该图书已被占用!");
this.name = "";
}
}
}
})
script>
body>
html>
服务器代码
// 导入express模块
const express = require("express");
// 导入cors模块
const cors = require("cors");
// 导入路由模块
const router = require("./route/actionRoute");
// 创建服务器
const app = express();
// 使用一个自定义中间件
app.use(function(req, res, next) {
// 对send函数进行封装
res.dispatch = function(error, status = 1) {
// 返回数据
res.send({
status: status,
message: (error instanceof Error) ? error.message : error
});
};
next();
});
// 使用跨域请求
app.use(cors());
// 可解析json数据
app.use(express.json());
// 配置可解析表单数据的参数
app.use(express.urlencoded({ extended: false }));
// 使用路由模块
app.use("/api", router);
oldApi();
// 监听并开启服务器
app.listen(8024, "127.0.0.1", function() {
console.log("127.0.0.1:8024 服务器开启成功!");
});
路由接口代码
// 导入express模块
const express = require("express");
// 导入fs模块
const fs = require("fs");
// 导入path模块
const path = require("path");
// 创建路由对象
const router = express.Router();
// 文件路径
let filePath = path.join(__dirname, "../db/data.txt");
// 获取数据模块
router.get("/getData", function(req, res) {
// 调用读取文件的函数,并以响应数据作为参数进行传递
fs.readFile(filePath, { encoding: "utf8" }, (err, val) => {
// 如果err不为空,则文件读取异常
if (err) return res.dispatch(err);
res.send({
status: 0,
message: JSON.parse(val)
});
});
});
// 添加数据
router.post("/addData", function(req, res) {
// 获取传进来的数据
let ele = req.body.ele;
// 读数据
fs.readFile(filePath, { encoding: "utf8" }, (err, val) => {
// 如果err不为空,则文件读取异常
if (err) return res.dispatch(err);
// 将获取的数据转化为数组
let oldData = JSON.parse(val);
// 向数组的末尾添加数据(对象)
oldData.push(ele);
// 对数组中的元素根据id进行排序
sortByArray(oldData);
// 将添加后的数组转化为json字符串
let newData = JSON.stringify(oldData);
// 开始文件写入
write(res, newData, "添加成功!");
});
});
// 修改数据模块
router.put("/upData", function(req, res) {
// 通过解构,获取值
let {
index,
data
} = req.body;
// 文件读取
fs.readFile(filePath, { encoding: "utf8" }, (err, val) => {
// 如果err不为空,则文件读取异常
if (err) return res.dispatch(err);
// 将获取的数据转化为数组
let oldData = JSON.parse(val);
// 向数组中添加数据
oldData.splice(index, 1, data);
// 将删除后的数组转化为json字符串
let newData = JSON.stringify(oldData);
// 开始文件写入
write(res, newData, "修改成功!");
});
});
// 删除数据的路由接口
router.delete("/delData", function(req, res) {
//获取要删除的索引
let index = req.query.index;
// 首先进行文件读取
fs.readFile(filePath, { encoding: "utf8" }, (err, val) => {
// 如果err不为空,则文件读取异常
if (err) return res.dispatch(err);
// 将获取的数据转化为数组
let oldData = JSON.parse(val);
// 删除数组中指定下标的元素
oldData.splice(index, 1);
// 对数组中的元素根据id进行排序
sortByArray(oldData);
// 将删除后的数组转化为json字符串
let newData = JSON.stringify(oldData);
// 开始文件写入
write(res, newData, "删除成功!");
});
});
// 对数组排序,默认为升序:小=》大,第一个参数为数组
function sortByArray(array, model = 0) {
// 小=》大
if (model === 0) {
array.sort((a, b) => {
return a.id - b.id;
});
// 大=》小
} else {
array.sort((a, b) => {
return b.id - a.id;
});
}
}
// 写文件的函数:参数(res:响应对象,data:写入的数据,message:提示信息)
function write(res, data, message) {
fs.writeFile(filePath, data, { encoding: "utf8" }, err => {
// 如果err不为空,则文件读取异常
if (err) return res.dispatch(err);
// 成功后,响应数据
return res.send({
status: 0,
message: message,
data: JSON.parse(data)
});
});
}
// 共享路由对象
module.exports = router;
需求:使用Vue组件化的开发思想实现简单的购物车案例,包含获取商品列表、增加/减少商品的数量,并计算总价、删除商品后总价扣除……等功能
分析:
效果展示:
源码奉上:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>购物车案例title>
<link rel="stylesheet" href="./css/bootstrap.css">
<style>
* {
padding: 0;
margin: 0;
}
#app {
width: 60%;
margin: 30px auto;
}
.panel-heading {
text-align: center;
}
#app ul li {
display: flex;
justify-content: space-between;
}
#app ul li div {
/* width: 80px; */
height: 33px;
line-height: 33px;
}
#app ul li div:nth-child(2) {
width: 90px;
}
#app .panel-footer {
display: flex;
justify-content: flex-end;
}
#app .panel-footer div {
padding-right: 30px;
}
#app .btn-group button:nth-child(2) {
width: 50px;
}
#app .panel-footer div span {
display: inline-block;
width: 50px;
text-align: center;
}
style>
head>
<body>
<div id="app" class="panel panel-default panel-primary">
<page-top :name="name">page-top>
<page-content v-on:updata-val="list=$event" :key="item.id" :list="list" :id="item.id" :name="item.name" :price="item.price" v-for="item in list">page-content>
<page-footer :count="count" :price="price">page-footer>
div>
<script src="./js/vue.js">script>
<script>
// 创建事件中心,用户兄弟组件之间的数据传输
let eventHub = new Vue();
// 创建一个顶部的组件
Vue.component("page-top", {
props: ["name"],
template: ` {{name}}的购物车`
});
// 创建一个内容列表组件
Vue.component("page-content", {
data: function() {
return {
count: 0,
};
},
props: ["id", "name", "price", "list"],
template: `
-
编号:{{id<10?"0"+id:id}}
名称:{{name}}
价格:{{price}}元
`,
methods: {
// 删除商品的事件
updata: function(val) {
// 寻找数组中是否有相同id的元素
let index = this.list.findIndex((item) => {
// 判断id是否相同
return item.id == val[0];
});
// 那么就删除
this.list.splice(index, 1);
// 触发父组件的事件:更新数据
this.$emit("updata-val", this.list);
//触发兄弟组件底部的事件
eventHub.$emit("get-data", [2, this.count, this.price])
},
// 添加/删除商品的事件
setValue: function(isAdd, val, event) {
// 获取触发事件的自定义属性
let length = event.target.getAttribute("count");
// 如果为添加
if (isAdd) {
// 自身属性++
this.count++;
// 触发兄弟组件底部的事件
eventHub.$emit("get-data", [0, 1, this.price]);
// 否则就是减少
} else {
// 如果触发事件自定义属性大于0的时候才能减少
if (length > 0) {
this.count <= 0 ? 0 : this.count--;
// 触发兄弟组件底部的事件
eventHub.$emit("get-data", [1, 1, this.price]);
}
}
}
}
});
// 创建底部组件
Vue.component("page-footer", {
data: function() {
return {
length: 0,
money: 0
}
},
props: ["count", "price"],
template: `
`,
// 该组件准备就绪后,使用事件中心的$on函数创建监听自己的自定义事件,并接受一个参数
mounted: function() {
// 该组件监听自己的事件,如果自己的事件触发了,则
eventHub.$on("get-data", (val) => {
// 如果增加
if (val[0] === 0) {
// 修改数量和总价
this.length += val[1];
this.money += val[2];
// 如果减少
} else if (val[0] === 1) {
// 修改数量和总价
this.length <= 0 ? 0 : this.length -= val[1];
this.money <= 0 ? 0 : this.money -= val[2];
// 否则就是删除
} else {
// 修改数量和总价
this.length -= val[1];
this.money -= val[1] * val[2];
}
});
}
})
// vue环境
let vm = new Vue({
el: "#app",
data: {
name: "张三",
count: 0,
price: 0,
list: [{
id: 1,
name: "方便面",
price: 20
}, {
id: 2,
name: "辣条",
price: 12
}, {
id: 3,
name: "生菜",
price: 10
}, {
id: 4,
name: "螺狮粉",
price: 28
}]
}
})
script>
body>
html>
需求:使用Vue组件化的开发思想、指令……等知识,实现ToDoList案例,包含增加待办、删除待办、修改待办、获取待办以及更改待办状态……等功能。
分析:
注意:
ref的使用:
ref="名称"
的属性this.$refs.名称
的方式将带有ref="名称"
属性并且名称一致的的子组件中的实例数据拿到,拿到的是一个对象this.$refs.名称
,那么名称必须不能相同,所以是唯一的this.$refs.名称
和v-for
指令一起使用,那么从父组件拿到的子组件数据将是一个对象数组,因为你的这个组件使用了for遍历,那么也就有多个ref名称相同,所以返回的就为数组效果展示:
源码奉上:
html和Vue操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,
maximum-scale=1.0,minimum-scale=1.0">
<title>ToDoList</title>
<!-- 移动端布局样式初始化的插件 -->
<link rel="stylesheet" href="./css/normalize.css ">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="app" @click.self="hide">
<!-- 顶部导航 -->
<page-header @add-value="list=$event" :list="list"></page-header>
<!-- 内容区域 -->
<section>
<!-- 正在进行 -->
<div>
<!-- 数量标题组件 -->
<titele-proceed :list="list"></titele-proceed>
<!-- 列表 -->
<ol>
<list-proceed ref="child" @alter-val="[id,isFinish]=$event" @del-value="list=$event" v-if="!item.checked" :list="list" :value="item.value" :id="item.id" :key="item.id" v-for="(item,index) in list"></list-proceed>
</ol>
</div>
<!-- 已经完成 -->
<div>
<!-- 数量标题组件 -->
<titele-finish :list="list"></titele-finish>
<!-- 列表 -->
<ul>
<list-finish ref="childtwo" @alter-val="[id,isFinish]=$event" @del-value="list=$event" v-if="item.checked" :list="list" :value="item.value" :id="item.id" :key="item.id" v-for="(item,index) in list"></list-finish>
</ul>
</div>
</section>
<!-- 页脚 -->
<page-footer @clear-val="list=$event"></page-footer>
</div>
<script src="./js/vue.js"></script>
<script>
// 全局获取焦点的指令
Vue.directive(
"focus", {
inserted: function(el) {
el.focus();
}
}
);
// 顶部组件
Vue.component("page-header", {
data: function() {
return {
value: ""
}
},
props: ["list"],
template: `
`,
methods: {
enter: function() {
// 如果为空
if (this.value == "") {
alert("不能为空");
}
let flag = false;
// 判断是否和列表中的重复
flag = this.list.some((item) => {
// 如果有重复
if (this.value.trim() == item.value) {
// 创建一个新对象,并添加到数组中
return true;
}
});
if (!flag) {
this.list.push({
checked: false,
id: this.list.length + 1,
value: this.value.trim()
});
// 触发父组件的事件
this.$emit("add-value", this.list);
} else {
alert("不能重复");
}
this.value = "";
}
}
});
// 正在进行标题组件
Vue.component("titele-proceed", {
props: ["list"],
template: `
正在进行
{{count()}}
`,
methods: {
// 数量
count: function() {
let num = 0;
for (let i = 0; i < this.list.length; i++) {
if (!this.list[i].checked) {
num++;
}
}
return num
}
}
});
// 正在进行列表组件
Vue.component("list-proceed", {
data: function() {
return {
isFinish: false,
flag: true,
index: 0,
val: this.value
}
},
props: ["value", "id", "list"],
template: `
-
-
{{value}}
`,
methods: {
// 更新数据
updata: function() {
// 拿到要删除的元素的索引
let index = this.list.findIndex((item) => {
return item.id == this.id
});
// 删除元素的索引
this.list.splice(index, 1);
// 触发父组件的事件
this.$emit("del-value", this.list);
},
// 修改为已完成
checked: function(e) {
// 获取事件对象的checked属性,是否被选中
if (e.currentTarget.checked) {
let index = this.list.findIndex((item) => {
return item.id == this.id;
});
// 修改数据
this.list[index].checked = true;
// 将数据返回给父组件
this.$emit("del-value", this.list);
}
},
// 修改数据
display: function() {
// 修改显示与隐藏的值
this.flag = false;
// 拿到id
this.index = this.id;
// 给父组件传递id和标志
this.$emit("alter-val", [this.index, this.isFinish]);
}
}
});
// 已完成标题组件
Vue.component("titele-finish", {
props: ["list"],
template: `
已完成
{{count()}}
`,
methods: {
// 数量
count: function() {
let num = 0;
for (let i = 0; i < this.list.length; i++) {
if (this.list[i].checked) {
num++;
}
}
return num
}
}
});
// 已完成列表组件
Vue.component("list-finish", {
data: function() {
return {
isFinish: true,
flag: true,
index: 0,
val: this.value
}
},
props: ["value", "id", "list"],
template: `
-
-
{{value}}
`,
methods: {
// 更新数据
updata: function() {
// 拿到要删除的元素的索引
let index = this.list.findIndex((item) => {
return item.id == this.id
});
// 删除元素的索引
this.list.splice(index, 1);
// 触发父组件的事件
this.$emit("del-value", this.list);
},
// 修改为进行中
checked: function(e) {
// 获取事件对象的checked属性,是否被取消选中
if (!e.currentTarget.checked) {
let index = this.list.findIndex((item) => {
return item.id == this.id;
});
// 修改数据
this.list[index].checked = false;
// 将数据返回给父组件
this.$emit("del-value", this.list);
}
},
// 修改数据
display: function() {
// 修改显示与隐藏的值
this.flag = false;
// 拿到id
this.index = this.id;
// 给父组件传递id和标志
this.$emit("alter-val", [this.index, this.isFinish]);
}
}
});
// 底部组件
Vue.component("page-footer", {
template: `
`
});
// Vue环境
let vm = new Vue({
el: "#app",
data: {
// 获取修改列表的id
id: 0,
isFinish: null,
list: [{
checked: true,
id: 1,
value: "6:00 早晨起床"
}, {
checked: false,
id: 2,
value: "8:00 吃早饭"
}, {
checked: false,
id: 3,
value: "9:00 去上班"
}, {
checked: false,
id: 4,
value: "12:00 去吃午饭"
}, {
checked: false,
id: 5,
value: "2:00 去上班"
}, {
checked: false,
id: 6,
value: "6:00 下班回家"
}]
},
methods: {
// 点击该组件后,input消失
hide: function() {
let n = false;
// 如果不是完成发来的数据
if (!this.isFinish) {
// 注意:this.$refs.child拿过来是一个全新的数组,所以我们不知道是要修改哪个元素,但是它们各自身上都有id,那么我们可以根据id来判断是这个数组中的第几个元素
let index = this.$refs.child.findIndex((item) => {
return item.id == this.id;
});
// 修改flag隐藏与展示的值
this.$refs.child[index].flag = true;
// 判断和自己编号不同,但是值一样的是否存在
let n = this.list.some((item) => {
if (item.id != this.id && item.value == this.$refs.child[index].val) {
return true
}
});
// 存在
if (n) {
alert("查找到有相同项,请修改!");
// 将本身的值再重新赋过去
this.$refs.child[index].val = this.list[this.id - 1].value;
// 不存在
} else {
// 修改文本值,id总比原始数组中的元素多1
this.list[this.id - 1].value = this.$refs.child[index].val;
}
// 如果是完成发来的数据
} else {
let index = this.$refs.childtwo.findIndex((item) => {
return item.id == this.id;
});
this.$refs.childtwo[index].flag = true;
// 判断和自己编号不同,但是值一样的是否存在
let n = this.list.some((item) => {
if (item.id != this.id && item.value == this.$refs.childtwo[index].val) {
return true
}
});
// 存在
if (n) {
alert("查找到有相同项,请修改!");
// 将本身的值再重新赋过去
this.$refs.childtwo[index].val = this.list[this.id - 1].value;
// 不存在
} else {
// 修改文本值,id总比原始数组中的元素多1
this.list[this.id - 1].value = this.$refs.childtwo[index].val;
}
}
},
}
});
</script>
</body>
</html>
css样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 当设备宽度大于750px时 */
@media screen and (min-width:750px) {
/* html采用75px的字体大小 */
html {
font-size: 75px !important;
}
}
body {
background: #CDCDCD;
}
header {
height: .666667rem;
background: rgba(47, 47, 47, 0.98);
}
header form {
width: 10rem;
margin: 0 auto;
display: flex;
align-items: center;
}
header label {
flex: 1;
width: 1.333333rem;
height: .666667rem;
line-height: .666667rem;
color: #ffffff;
font-size: .32rem;
cursor: pointer;
}
header input {
flex: 1;
width: 4.826667rem;
height: .346667rem;
padding-left: .2rem;
border: none;
border-radius: .066667rem;
box-shadow: 0 1px rgba(255, 255, 255, 0.24), 0 1px 6px rgba(0, 0, 0, 0.45) inset;
font-size: .16rem;
margin-right: 10px;
/* 点击表单后,表单边框消失 */
outline: none;
}
section,
footer {
min-width: 320px;
max-width: 750px;
width: 10rem;
margin: 0 auto;
font-family: Arial, Helvetica;
background: #CDCDCD;
}
section h2 {
height: .413333rem;
margin: .266667rem 0;
font-size: .333333rem;
}
section span {
float: right;
width: .266667rem;
height: .266667rem;
line-height: .266667rem;
text-align: center;
border-radius: 50%;
background: #E6E6FA;
font-size: .186667rem;
color: #666666;
margin-right: .133333rem;
}
ul,
ol {
list-style: none;
}
footer {
color: #666666;
font-size: .186667rem;
text-align: center;
}
footer a {
text-decoration: none;
color: #999999;
}
.li {
height: .426667rem;
background: #ffffff;
margin-bottom: .133333rem;
border-radius: .04rem;
border-left: .066667rem solid #629A9C;
box-shadow: 0 .013333rem .026667rem rgba(0, 0, 0, 0.07);
/* 弹性布局 */
display: flex;
/* 侧轴居中 */
align-items: center;
/* 两边贴边,再平分剩余空间 */
justify-content: space-around;
/* 相对定位 */
position: relative;
cursor: pointer;
}
.li input {
position: absolute;
width: .293333rem;
height: .293333rem;
left: .2rem;
}
.li p {
display: inline-block;
width: 85%;
height: 100%;
line-height: .426667rem;
font-size: .186667rem;
}
.li input[type=text] {
display: inline-block;
width: 80%;
height: 75%;
margin-left: 38px;
padding-left: 5px;
line-height: .426667rem;
font-size: .186667rem;
}
.li a {
font-size: 0.2rem;
position: absolute;
width: .346667rem;
height: .32rem;
line-height: .14rem;
text-align: center;
right: .106667rem;
border-radius: 50%;
border: .08rem double #ffffff;
background: #cccccc;
color: #ffffff;
}
.finish {
opacity: 0.5;
border-left: .066667rem solid #999999;
}
1、需求:使用Vue单文件组件、Vuex、Vue脚手架……等知识完成一个可以二目运算的简单计算器
2、需求大体角度分析:
3、组件和Vuex角度分析:
4、效果展示
5、源代码
Vuex中的数据
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: { one: 0, two: 0, flag: 0, result: 0 },
mutations: {
// 获取文本框中的值,并将其赋值给公共数据
getValue(state, arr) {
// isFirstNum用来判断是否是第一个数字
if (arr[0]) {
state.one = arr[1];
} else {
state.two = arr[1];
}
},
// 获取下拉菜单中的运算符号,并将其赋值给公共数据
getFlag(state, symbol) {
state.flag = symbol;
},
// 计算运算结果,并将其赋值给公共数据
computation(state) {
// 判断操作符的数字
switch (Number(state.flag)) {
// +
case 0:
state.result = state.one + state.two;
break;
// -
case 1:
state.result = state.one - state.two;
break;
// *
case 2:
state.result = state.one * state.two;
break;
// ÷
case 3:
state.result = state.one / state.two;
break;
// %
case 4:
state.result = state.one % state.two;
}
}
},
actions: {},
modules: {}
});
输入框组件中的数据
<template>
<input type="text" id="num1" v-model="num" value="num" >
</template>
<script>
import {mapMutations} from "vuex";
let timer = null;
export default {
//父组件给子组件传值,来判断是否是第一个输入框组件
props:['isFirstNum'],
data(){
return {num:0}
},
methods:{
...mapMutations(['getValue']),
// 获取数据的函数
getNum(flag){
let arr=[flag,Number(this.num)];
// 将标志和数字传入
this.getValue(arr);
}
},
watch:{
num:function(){
clearTimeout(timer);
timer=setTimeout(()=>{
this.getNum(this.isFirstNum);
},500)
}
}
}
</script>
<style scoped>
</style>
运算符组件中的数据
<template>
<select name="" id="" v-model="flag">
<option value="0">+</option>
<option value="1">-</option>
<option value="2">×</option>
<option value="3">÷</option>
<option value="4">%</option>
</select>
</template>
<script>
import {mapMutations} from 'vuex';
export default {
data(){
return {flag:0}
},
methods:{
...mapMutations(['getFlag']),
getValue(flag){
this.getFlag(flag);
}
},
watch:{
flag:function(){
this.getValue(this.flag);
}
}
}
</script>
<style scoped>
</style>
按钮以及结果组件中的数据
<template>
<div>
<button @click="getResult">=</button>
<span>{{this.result}}</span>
</div>
</template>
<script>
import {mapState,mapMutations} from 'vuex';
export default {
data(){
return {};
},
methods:{
...mapMutations(['computation']),
// 获取结果的函数
getResult(){
// 调用vuex中的函数,并返回结果,赋值给num
this.computation();
// console.log(this.result);
}
},
computed:{
...mapState(['result'])
}
}
</script>
<style scoped>
div{
display: inline-block;
}
span{
display: inline-block;
padding:0 20px;
border: 1px solid #333;
}
</style>
以上就是Vue框架的全部内容,当然还有很多内容没有延申拓展,因为本篇文章只是说让大家对Vue有一个基本的认识,方便初学者打好基础,那么在接下来的前端生涯中少走弯路。
当然如果本篇文章哪里讲的有不妥的地方,还请各位踊跃发言。那么下次博主将给大家带有React框架的全部内容,希望大家多多支持。