Vue概念:
为什么学习Vue:
更少的时间,干更多的活,开发网站速度快
原生js ---------------jQuery------------Vue
案例:把数组数据–循环渲染到页面上
原生js:
<body>
<ul id="myul"></ul>
</body>
<script>
let arr = ['aa', 'bb', 'cc', 'dd', 'ee']
let myUl = document.getElementById('myul')
for (let i = 0; i < arr.length; i++) {
let thenLi = document.createElement('li')
thenLi.innerHTML = arr[i]
console.log(thenLi)
myUl.appendChild(thenLi)
}
</script>
vue:
<body>
<div id="app">
<ul>
<li v-for="item in arr">{{item}}</li>
</ul>
</div>
</body>
<script>
new Vue({
el: '#app',
data() {
return {
arr: ['aa', 'bb', 'cc', 'dd', 'ee'],
}
},
})
</script>
注意:vue写起来很爽,vue的底层还是原生js
vue开发更加的效率和简洁,易于维护,快快快,现在很多的项目都是用vue开发的
vue是什么:
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架
什么是渐进式:
渐进式:逐渐进步,想用什么就用什么,不用全部都用
vue从基础开始学习,循序渐进向前学习
声明式渲染—组件系统—路由—vuex
什么是库和框架:
库: 封装的属性和方法(jquery)
框架:拥有自己的规则和元素,比库强大的多(vue.js)
vue如何学:
- 每天的知识点做到了如指掌,花时间去记住结论和公式(语法)
- 记住vue指令的作用,基础语法,整理笔记
- 课上的例子,练习,案例,作业,项目反复的练习
- 学会查找问题和解决问题(弄个报错总结文档.md)
vue是渐进式的框架,有自己的规则,我们要记住语法,特点和作用,反复练习,多总结
vue的学习:
传统的开发模式:
基于html/css/js文件开发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>Document</title>
<!-- 第一步:引入vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 第二步:创建vue容器
vue的语法代码只能写在下面的div中 -->
<div id="app"></div>
</body>
<!-- 第三步:创建vue实例 -->
<script>
// console.log(Vue)
var vm = new Vue({
el: '#app', //提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标
data: {
//Vue 实例的数据对象
msg: 'hello word',
flag: false,
arr: [11, 2, 3, 4, 56, 6],
num: 2,
},
})
</script>
</html>
工程化的开发模式:
webpack环境中开发vue,最推荐,企业常用的方式
插值表达式:
目的:在dom标签中,直接插入内容
又叫:声明式渲染/文本插值
语法: {{表达式}}
<div id="app">
<!-- 插值表达式中不能写循环语法,不能写条件语句 -->
<p>{{num}}</p>
<p>{{num+1}}</p>
<p>{{34}}</p>
<p>{{flag}}</p>
<p>{{flag?'hello':"nihao"}}</p>
<p>{{str}}</p>
<p>{{str +"vue是必须要学会的"}}</p>
<p>{{'hello world'}}</p>
<p>{{arr}}</p>
<p>{{arr.reverse()}}</p>
<p>{{obj}}</p>
<p>{{obj.name}}</p>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
num: 3,
flag: false,
str: '大家一定要好好学习vue',
arr: [11, 22, 333, 44],
obj: {
name: 'zs',
age: 12,
},
},
})
</script>
总结:dom中插值表达式赋值,vue的变量必须在data中声明
vue基础——MVVM设计模式:
转变思想,用数据驱动视图改变; 操作dom的事,vue源码干了
设计模式:一套被反复使用,多数人知晓的,经过分类编目的,代码设计经验的总结
MVVM:
一种软件架构模式,决定了写代码的思想和层次
M : model 数据模型 (data中定义)
V :view视图 (html页面)
VM:ViewModel 视图模型(vue.js源码)
MVVM通过数据双向绑定让数据自动双向同步,不再需要操作DOM
V (修改视图)-----> M(数据自动同步)
M(修改数据)------>V(视图自动同步)
总结:vue源码采用MVVM设计模式思想,大大减少了DOM操作,提高开发效率
Vue指令:
指令 (Directives) 是带有
v-
前缀的特殊 attribute每个指令,都有独立的作用
v-bind:
给标签属性设置变量的值
语法: v-bind:属性名=“vue变量”
简写: :属性名=“vue变量”
<body>
<div id="app">
<!-- v-bind 属性绑定: 把vue变量的值,赋予给dom属性上,影响标签的显示效果 -->
<a v-bind:href="url">我是a标记</a>
<a :href="url">我是a标记</a>
<hr />
<img v-bind:src="imgurl" alt="" />
<img :src="imgurl" alt="" />
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
url: 'http://www.baidu.com',
imgurl: 'https://img01.yzcdn.cn/vant/ipad.jpeg',
},
})
</script>
v-on:
给标签定义事件
语法:
- v-on:事件名=“要执行的少量代码”
- v-on:事件名=“methods中的函数”
- v-on:事件名=“methods中的函数(实参)”
- 简写:@事件名=“methods中的函数”
<body>
<div id="app">
<!-- v-on 事件绑定 -->
<p>要购买的商品的数量{{count}}</p>
<button v-on:click="count=count+1">增加1</button>
<button v-on:click="addFn">增加1</button>
<button v-on:click="addCount(5)">一次增5</button>
<button @click="subFn">减1</button>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
count: 2,
},
methods: {
//专门用来定义函数(方法)的
addFn() {
//this代表的是当前的vue实例对象
console.log(this)
this.count++
},
addCount(num) {
this.count += num //
},
subFn() {
this.count--
},
},
})
</script>
v-on事件对象:
vue处理函数中,拿到事件对象
语法:
- 无传参: 通过形参直接接收
- 传参: 通过$event指定事件对象传给事件处理函数
<body>
<div id="app">
<a @click="fn" href="http://www.baidu.com">阻止百度</a>
<hr />
<a @click="eveFn(10,$event)" href="http://www.baidu.com"
>阻止跳转到百度</a
>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {},
methods: {
fn(e) {
// 阻止默认行为
e.preventDefault()
},
eveFn(num, e) {
e.preventDefault()
},
},
})
</script>
v-on修饰符:
在事件后面.修饰符名,给事件带来更强大的功能
修饰符是由点开头的指令后缀来表示的
语法:
- @事件名.修饰符 =“methods函数”
- .stop 阻止事件冒泡
- .prevent 阻止默认行为
- .once 程序运行期间,只触发一次事件处理函数
- .self 只当在 event.target 是当前元素自身时触发处理函数
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
.main {
width: 400px;
height: 200px;
background-color: yellow;
}
</style>
</head>
<body>
<div id="app">
<div @click="fn1">
<button @click.stop="btnFn">stop阻止事件冒泡</button>
<a @click.prevent.stop="aFn" href="http://www.baidu.com"
>prevent阻止默认行为</a
>
<button @click.once="onceFn">once触发一次</button>
</div>
<!--.self 只当在 event.target 是当前元素自身时触发处理函数 -->
<div @click.self="fn1" class="main">
<button @click="btnFn">stop阻止事件冒泡</button>
<a @click.prevent="aFn" href="http://www.baidu.com"
>prevent阻止默认行为</a
>
<button @click.once="onceFn">once触发一次</button>
</div>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {},
methods: {
fn1() {
alert('fn1')
},
btnFn() {
alert('btnFn')
},
aFn() {
alert('aFn')
},
onceFn() {
alert('onceFn')
},
},
})
</script>
v-on按键修饰符:
给按键事件添加修饰符
语法:
- @keyup.enter 监听回车按键
- @keyup.esc 监听返回按键
<body>
<div id="app">
<input type="text" @keyup.enter="enterFn" />
<br />
<input type="text" @keyup.esc="escFn" />
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {},
methods: {
enterFn() {
alert('enterFn')
},
escFn() {
alert('escFn')
},
},
})
</script>
v-text/v-html:
更新DOM对象的einnerText和innerHTML
语法:
- v-text=“vue数据变量”
- v-html=“vue数据变量"
<body>
<div id="app">
<p>{{str1}}</p>
<p>{{str2}}</p>
<hr />
<p v-text="str1"></p>
<p v-text="str2"></p>
<hr />
<p v-html="str1"></p>
<p v-html="str2"></p>
<!-- v-text把值当做普通字符串显示
v-html 把值当做html解析
-->
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
str1: '<span>我是一个span标签</span>',
str2: 'hello',
},
methods: {},
})
</script>
v-model:
主要用于表单元素,把value属性和vue数据变量,双向绑定到一起
语法:
v-model=‘vue数据变量’
- 双向数据绑定(vue内部采用MVVM设计模式)
- 数据变化–>视图自动同步
- 视图变化–>数据自动同步
<body>
<div id="app">
<!-- v-model:实现vue变量和表单标签value属性,双向绑定的指令 -->
<div>
<span>用户名</span>
<input type="text" v-model="username" />
</div>
<div>
<span>密码</span>
<input type="password" v-model="pass" />
</div>
<div>
<span> 来自于</span>
<!-- 下拉菜单要绑定在select上 -->
<select v-model="city">
<option value="北京市">北京</option>
<option value="南京市">南京</option>
<option value="天津市">天津</option>
</select>
</div>
<div>
<span>性别</span>
<input type="radio" value="男" v-model="gender" />男
<input type="radio" value="女" v-model="gender" />女
</div>
<!--
遇到复选框 v-model的变量值
数组 关联的是复选框的value值
非数组 关联的是复选框的checked属性
-->
<div>
<span>爱好</span>
<input type="checkbox" v-model="hobby" value="抽烟" />抽烟
<input type="checkbox" v-model="hobby" value="喝酒" />喝酒
<input type="checkbox" v-model="hobby" value="打游戏" />打游戏
</div>
<div>
<span>全选</span>
<input type="checkbox" v-model="checkall" />
</div>
<div>
<span>自我评价</span>
<textarea v-model="content"></textarea>
</div>
<hr />
<div>{{username}}</div>
<div>{{pass}}</div>
<div>{{city}}</div>
<div>{{gender}}</div>
<div>{{hobby}}</div>
<div>{{checkall}}</div>
<div>{{content}}</div>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
username: '用户名',
pass: '',
city: '南京市',
gender: '男',
hobby: ['打游戏'],
checkall: true,
content: '',
},
methods: {},
})
</script>
v-model修饰符:
让v-model拥有更强大的功能
语法:
-
v-model.修饰符=“vue数据变量”
- .number 以parseFloat转成数字类型
- .trim 去除首尾空白字符
- .lazy change时触发而非input 不会每次输入都执行事件
<body>
<div id="app">
<div>
<span>年龄</span>
<input type="text" v-model.number="age" />
</div>
<div>
<span>人生格言</span>
<input type="text" v-model.trim="motto" />
</div>
<div>
<span>自我介绍</span>
<textarea v-model.lazy="content"></textarea>
</div>
{{content}}
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
age: 0,
motto: '',
content: '',
},
methods: {},
})
</script>
v-show和v-if:
控制标签的显示隐藏
语法:
- v-show=“vue变量”
- v-if=“vue变量”
原理:
- v-show用的是display:none控制标签的显示隐藏 (频繁切换)
- v-if 用的是插入和删除节点来控制标签的显示隐藏
- v-else
<body>
<div id="app">
<h1 v-show="isOk">v-show的盒子</h1>
<h1 v-if="isOk">v-if的盒子</h1>
<div>
<!-- v-if和v-else的两个标签必须紧挨着 -->
<p v-if="age>15">我成年了</p>
<p v-else>未成年</p>
</div>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
isOk: true,
age: 15,
},
methods: {},
})
</script>
</html>
v-for:
列表渲染,所在标签结构,按照数据数量 循环生成
语法:
- v-for=“(值,索引) in 目标结构”
- v-for=“值 in 目标结构”
目标结构
- 可以遍历数组/对象/数字/字符串
<body>
<div id="app">
<ul>
<li v-for="(item,index) in arr">{{index}} ---{{item}}</li>
</ul>
<hr />
<ul>
<li v-for="obj in stuArr">
{{obj.name}}---{{obj.sex}}---{{obj.hobby}}
</li>
</ul>
<hr />
<div v-for="(value,key) in tObj">{{key}}---{{value}}</div>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
arr: ['aaa', 'bbb', 'ccc', 'ddd'],
stuArr: [
{
id: 1001,
name: '孙悟空',
sex: '男',
hobby: '唱歌',
},
{
id: 1002,
name: '猪八戒',
sex: '男',
hobby: '背媳妇',
},
],
tObj: {
name: '小妞',
age: 12,
class: '1班',
},
},
methods: {},
})
</script>
</html>
vue数据监听-key的作用:
1、数组翻转 2、数组截取 3、更新值
-
数组变更方法,就会导致v-for更新,页面更新
(push,pop,shift,unshift,splice,sort,reverse)
-
数组非变更方法,返回新数组,不会导致v-for更新,可采用覆盖数组 或this.$set
(filter,concat,slice)
<body>
<div id="app">
<ul>
<li v-for="(val,index) in arr" :key="index">{{val}}</li>
</ul>
<button @click="revBtn">数组翻转</button>
<button @click="sliceFn">截取前3个</button>
<button @click="updateFn">更新第一个元素值</button>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
arr: [4, 3, 2, 1, 4, 5, 7],
},
methods: {
revBtn() {
// 数组翻转 可以让v-for更新
this.arr.reverse()
// console.log(this.arr)
},
sliceFn() {
// 数组slice 不会造成v-for更新
// slice不会改变原数组
// this.arr.slice(0, 3)
// console.log(this.arr)
// 解决v-for更新,覆盖原始数组
let newArr = this.arr.slice(0, 3)
this.arr = newArr
},
updateFn() {
// 更新某个值的时候 v-for是监测不到的
this.arr[0] = 1999
// 解决 this.$set
// 参数1 更新目标结构(对象/数组)
// 参数2 更新位置
// 参数3 更新值
this.$set(this.arr, 0, 1999)
},
},
})
</script>
v-for如何更新DOM呢:
真实DOM:
document对象上,渲染到浏览器上显示的标签
虚拟DOM:
本质是保存节点信息 ,属性和内容的一个js对象
内存中虚拟DOM比较:
内存中比较变化的部分,然后给真实DOM打补丁(更新)
虚拟DOM好处:
提高DOM更新的性能,不频繁操作真实的DOM,在内存中找到变化的部分,更新真实的DOM(打补丁)
问题:
- 新的虚拟DOM根元素,或者属性变化,如何更新
- 具体如何比较新旧虚拟DOM
diff算法:
diff算法是通过同级比较 来比较新旧虚拟dom
根元素变化 删除重新建立整个dom树
根元素未变 属性改变 DOM复用 只更新属性
同级比较,根元素变化-整个dom树删除重建:
同级比较,根元素不变-属性改变 更新属性:
标签内子元素/内容改变:
无key:
从第二个往后更新内容–性能不高
有key,值为索引:
有key属性,基于key的来比较新旧虚拟dom。移除key不存在元素
有key,key值唯一:
vue基础-动态class:
使用v-bind给标签class设置动态的值
语法:
- :class=“{类名:布尔值}”
<style>
.red_str {
color: red;
}
</style>
</head>
<body>
<div id="app">
<!--
:class="{类名:布尔值}"
场景: vue 变量控制标签是否应该有类名
-->
<p :class="{'red_str':bool}">动态class</p>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
bool: true,
},
})
</script>
vue基础-动态style:
给标签动态设置style的值
语法:
- :style=“{css属性:值}”
<body>
<div id="app">
<!--
语法:
:style="{css属性:值}"
-->
<p :style="{backgroundColor:colorStr,color:str,border:borStr}">
动态style
</p>
<p :style="{'background-color':colorStr,color:str,border:borStr}">
动态style
</p>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
colorStr: 'red',
str: '#fff',
borStr: '5px solid blue',
},
})
</script>
vue过滤器:
定义使用:
转换格式,过滤器就是一个函数,传入值返回处理后的值
过滤器只能用在插值表达式和v-bind表达式
场景:
- 字母转大写:输入 hello 输出 ‘HELLO’
- 字符串翻转 输入’hello world’ 输出 ’dlrow olleh‘
- 时间戳转换为时间日期格式
语法:
- 全局: Vue.filter(‘过滤器名’,(值)=>{return 返回处理后的值})
- 局部: filters:(过滤器名字,(值)=>{return 返回处理后的值})
<body>
<div id="app">
<p>原来的样子:{{msg}}</p>
<!--
2:过滤器使用
语法: {{值|过滤器名字}}
-->
<p>{{msg | toUp}}</p>
<p>{{msg | reverse}}</p>
</div>
<hr />
<div id="main">
{{str }}
<p>{{str | reverse}}</p>
</div>
</body>
<script>
// 全局过滤器 任何一个vue文件中都可以使用
/*
Vue.filter("过滤器的名字",(val)=>{return 处理后的值})
*/
Vue.filter('reverse', (val) => {
return val.split('').reverse().join('')
})
var vm = new Vue({
el: '#app',
data: {
msg: 'hello vue',
},
// 局部--局部--过滤器
// 只能在当前vue文件(vue实例)中使用
/*
filters:{
过滤器1(val){
return 处理后的值
},
过滤器2(val){
return 处理后的值
}
}
*/
filters: {
toUp(val) {
return val.toUpperCase()
},
},
})
// ---------------------------
var aa = new Vue({
el: '#main',
data: {
str: 'good bye',
},
})
</script>
处理日期的工具模块moment:
调用:
<script src="http://cdn.staticfile.org/moment.js/2.24.0/moment.min.js"></script>
<body>
<div id="app">
<!--
定义初始数据,渲染到购物车页面
点击对应的删除按钮,删除对应的数据
当数据没有了,显示一条提示消息
-->
<table class="tb">
<tr>
<th>编号</th>
<th>名称</th>
<th>创立时间</th>
<th>操作</th>
</tr>
<tr v-for="(item,index) in arr" :key="item.id">
<td>{{index+1}}</td>
<td>{{item.name}}</td>
<td>{{item.time |formatData}}</td>
<td>
<button @click="del(index)">删除</button>
</td>
</tr>
<tr v-if="arr.length===0">
<td colspan="4">没有数据</td>
</tr>
</table>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
arr: [
{
id: 1,
name: '奔驰',
time: new Date(),
},
{
id: 2,
name: '宝马',
time: new Date(),
},
{
id: 3,
name: '奥迪',
time: new Date(),
},
],
},
methods: {
del(index) {
this.arr.splice(index, 1)
},
},
filters: {
formatData(val) {
return moment(val).format('YYYY-MM-DD, h:mm:ss')
},
},
})
</script>
传参和多过滤器:
可同时使用多个过滤器,或给过滤器传参
语法:
- 过滤器传参 vue变量 | 过滤器(实参)
- 多个过滤器 vue变量|过滤器1|过滤器2
<body>
<div id="app">
<p>原来的样子{{msg}}</p>
<!--
给过滤器传参
语法 vue变量 | 过滤器名(值)
-->
<p>翻转过滤器{{msg | reverse("-")}}</p>
<!--
多个过滤器使用
语法 vue变量 | 过滤器1 | 过滤器2
-->
<p>{{msg|toUp|reverse("|")}}</p>
<p :title="msg|toUp|reverse('|')">鼠标停留</p>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'hello world',
},
filters: {
toUp(val) {
return val.toUpperCase()
},
reverse(val, s) {
return val.split('').reverse().join(s)
},
},
})
</script>
vue计算属性:
computed:
一个数据依赖另外一些数据计算而来的结果
(一个变量,值要通过计算得到,变量要在computed中定义)
语法:
computed:{
'计算属性名'(){
return 值
}
}
代码:
<body>
<div id="app">
<p>{{num}}</p>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
a: 10,
b: 20,
},
/*
一个变量的值 ,需要用另外的变量计算得来
computed:{
计算属性名(){
return 值
}
}
注意:计算属性和data都是变量---不能重名
2函数内依赖的变量变化,会自动重新计算结果返回
*/
computed: {
// 1页面加载会默认执行一次 给变量num赋初始值
num() {
console.log(1)
return this.a + this.b
},
},
})
</script>
vue计算属性–缓存:
计算属性 基于他们的依赖项的值结果进行缓存的,只要依赖的变量不变,都直接从缓存取结果
<body>
<div id="app">
<p>{{reverseMessage}}</p>
<p>{{reverseMessage}}</p>
<p>{{reverseMessage}}</p>
<p>{{getMessage()}}</p>
<p>{{getMessage()}}</p>
<p>{{getMessage()}}</p>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'hello world',
},
computed: {
/*
带缓存
计算属性对应的函数执行后,会把return的值缓存起来
依赖项不变,多次调用都是从缓存取值
依赖项变化 函数会自动 重新执行--并缓存新的值
*/
reverseMessage() {
console.log('计算属性执行了')
return this.msg.split('').reverse().join('')
},
},
methods: {
getMessage() {
console.log('函数执行了')
return this.msg.split('').reverse().join('')
},
},
})
</script>
计算属性-完整写法:
计算属性也是变量 ,如果想要直接赋值,需要使用完整写法
语法:
computed:{
‘属性名’:{
set(值){
},
get(){
}
}
}
需求:
计算属性给v-model使用
<body>
<div id="app">
<div>
<span>姓名</span>
<input type="text" v-model="full" />
</div>
</div>
</body>
<script>
// 给计算属性赋值 需要setter
/*
computed:{
‘属性名’:{
set(值){
},
get(){
}
}
}
*/
var vm = new Vue({
el: '#app',
data: {},
computed: {
full: {
set(val) {
// 给full赋值触发set方法
console.log(val, 'set')
},
get() {
console.log('get')
//使用full的值 触发get方法
return '无名氏'
},
},
},
})
</script>
vue侦听器:
可以侦听data/computed属性值改变
语法:
watch:{
'被侦听的属性名'(newValue,oldValue){
}
}
代码:
<body>
<div id="app">
<input type="text" v-model="name" />
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
name: '',
},
/*
目标:侦听name值的改变
watch:{ '被侦听的属性名'(newValue,oldValue){ }}
*/
watch: {
// newValue 当前最新值
// oldValue 旧值 上一刻值
name(newValue, oldValue) {
console.log(newValue, oldValue)
},
},
})
</script>
想要侦听一个属性变化,使用侦听属性watch
深度侦听和立即执行:
侦听复杂类型,或者立即执行侦听函数
语法:
watch:{
'被侦听的属性名':{
immediate:true,//立即执行
deep:true, //深度侦听复杂类型变化
handler(newValue,oldValue){
}
}
}
@vue/cli脚手架:
webpack自己配置环境很麻烦,下载@vue/cli包,用vue命令创建脚手架项目
@vue/cli是vue官方提供的一个全局模块包(vue命令),此包用于创建脚手架项目
好处:
-
0配置webpack
-
babel支持、
-
css,less支持
-
开发服务器支持
安装:
把@vue/cli模块包安装到全局,电脑拥有vue命令,才能创建脚手架工程
全局安装:
npm install -g @vue/cli
# OR
yarn global add @vue/cli
查看版本:
vue -V
如果出现版本号,安装成功,否则失败
@vue/cli创建项目启动服务:
使用vue命令创建脚手架项目(项目名不能带大写字母,中文和特殊符号)
创建项目:
//vue create 是命令 vuecli-demo是文件夹名
vue create vuecli-demo
选择模板:
可以上下箭头选择,弄错了ctrl+c重来
回车,等待生成项目文件夹+文件+下载必须的第三方包
进入脚手架项目,启动内置的热更新本地服务器
cd vuecli-demo
npm run serve
or
yarn serve
启动成功了(底层node+webpack热更新服务)
打开浏览器输入上述地址
@vue/cli目录和代码分析:
vuecli-demo #项目目录
node_modules 项目依赖的第三方包
public 静态文件目录
index.html 但页面的html文件(网页浏览的就是它)
src 业务文件夹 &****
assets 静态资源
components 组件目录
HelloWorld.vue 欢迎页面的vue代码文件
APP.vue 整个应用的根组件
main.js 主入口文件
.gitignore git提交忽略的配置
babel.config.js babel配置
package.json 依赖包列表
README.md 项目说明
vue.config.js vue的配置文件
主要文件及含义
node_modules 下都是下载的第三方包
public/index.html ---浏览器运行的网页
src/main.js webpack打包的入口文件
src/App.vue vue项目入口页面
package.json 依赖包列表文件
vue.config.js vue的配置文件
@vue/cli项目架构了解:
知道项目入口,以及代码执行顺序和引入关系
@vue/cli自定义配置:
项目中没有webpack.config.js文件,应为@vue/cli用的vue.config.js
module.exports = defineConfig({
// 覆盖webpack的配置
devServer: {
//自定义服务配置
open: true, //自动打开浏览器
port: 3000,
},
})
eslint了解:
- main.js随便声明一个变量,但是使用,终端和页面都报错了(在main里面写入let)
- 代码步严谨
解决:
1:手动解决掉错误,后面项目中会讲如何解决
2:暂时关闭eslint检查,在vue.config.js中配置后重启服务
module.exports = defineConfig({
...
lintOnSave: false, // 关闭eslint检查
transpileDependencies: true,
})
@vue/cli单文件:
单vue文件好处,独立作用域互不影响
- vue推荐采用.vue文件来开发项目
- template里只能有一个根标签
- vue文件-独立模块 --作用域互不影响
- style配合scoped属性,保证样式只针对当前template内标签生效
- vue配合webpack,把他们打包起来插入到index.html
// template 只能有一个根标签
<template>
<div>
</div>
</template>
<script>
export default {
}
</script>
// 当前组件的样式 设置scoped 可以保证样式只对当前页面有效
<style scoped>
</style>
@vue/cli欢迎界面清理:
- src/App.vue 默认有很多内容,可以全部删除留下框
- assets和components文件夹下一切都删除掉(不要默认的欢迎页面)
vscolde插件补充:
vue文件代码高亮插件:
代码提示插件:
快速生成:
脚手架项目中定义全局过滤器:
过滤时间
- 项目中下载moment模块
yarn add moment
- main.js引入moment并创建全局过滤器
import moment from 'moment'
// 全局过滤器
Vue.filter('formData', (val) => {
return moment(val).format('YYYY-MM-DD h:mm:ss')
})
- 项目中的任何一个vue文件都可以使用
{{ obj.time | formData }}
vue组件:
为什么用组件:
需求:想要多个收起 展开的部分
方法一:复制代码
- 代码重复 冗余
- 不利于维护
<template>
<div id="app">
<!-- 目标:点击展开或收起时,把内容区域显示或者隐藏 -->
<h3>折叠面板</h3>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span>
</div>
<div class="container" v-show="isShow">
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
</div>
</div>
<!-- 复制 -->
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span @click="isShow1 = !isShow1">{{ isShow1 ? '收起' : '展开' }}</span>
</div>
<div class="container" v-show="isShow1">
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
</div>
</div>
<!-- 复制 -->
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span @click="isShow2 = !isShow2">{{ isShow2 ? '收起' : '展开' }}</span>
</div>
<div class="container" v-show="isShow2">
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
isShow1: false,
isShow2: false,
}
},
}
</script>
<style scoped>
#app {
width: 400px;
margin: 20px auto;
background-color: yellow;
border: 4px solid burlywood;
border-radius: 2px;
padding: 10px;
}
h3 {
text-align: center;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 5px;
}
</style>
vue组件-概念:
可复用的vue实例,封装标签,样式和代码
组件化: 封装的思想,把页面上 可复用的部分,封装为组件,从而方便项目的开发和维护
一个页面,可以拆分成一个个组件(一个vue文件),一个组件就是一个整体,每个组件可以有自己独立的结构,样式和行为(html,css,js)
vue组件–基础使用:
每个组件都是一个独立的个体,代码里体现为一个独立的vue文件
哪部分标签复用,就把哪部分封装到组件内
- 组件内template只能有一个标签
- 组件内的data必须是一个函数,独立作用域
步骤:
1.创建组件
- 创建组件 components/Pannel.vue
封装结构+样式+js 组件都是独立的,为了复用
<template>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span>
</div>
<div class="container" v-show="isShow">
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
<p>一片冰心在玉壶</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
}
},
}
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 5px;
}
</style>
2.注册组件
注册组件,创建后需要注册后再使用
全局注册使用
全局入口main.js 在new Vue之上注册
语法:
// 全局注册组件
// 1 引入组件
import Pannel from './components/Pannel'
// 2 全局注册组件
/*
Vue.component('组件名',组件对象)
*/
Vue.component('PannelG', Pannel)
全局注册组件PannelG,就可以当做标签在任意vue文件中的template中使用
单双标签都可以或者小写加 - 形式,运行后,会把这个自定义标签当做组件解析,使用 组件里封装的标签替换到这个位置
<PannelG></PannelG>
<PannelG />
<pannel-g></pannel-g>
局部注册使用:
语法
import 组件对象 from 'vue文件路径'
export default {
components:{
'组件名':组件对象
}
}
在vue文件中引入、注册、使用
<template>
<div id="app">
<hr />
<MyPannel></MyPannel>
<my-pannel></my-pannel>
</div>
</template>
<script>
// 1引入组件
import MyPannel from './components/MyPannel.vue'
export default {
//2 局部注册组件
components: {
MyPannel: MyPannel,
},
}
</script>
组件使用总结:
- (创建)封装html+css+js到独立的.vue文件中
- (引入注册)组件文件==>得到组件配置对象
- (使用)当前页面当做标签使用
vue组件-scoped作用:
解决多个组件样式名相同,冲突问题
需求:div标签名选择器,设置背景色
问题: 组件里的div和外面的div都生效了
解决:给Pannel.vue组件里style标签上加scoped属性
<style scoped>
在style上假如scoped属性,就会在此组件的标签上加上一个随机生成的data-v开头的属性
而且必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到
style上加scoped,组件内的样式只在当前vue组件生效
组件通信:
每个组件的变量和值都是独立的
父:使用其他组件的vue文件(App.vue)
子:被引入的组件(MyPannel.vue)
从外面给组件内传值,学会语法,练习,项目看使用场景
父向子:
- 创建组件componets/MyProduct.vue
- 组件内在props定义变量,用于接收外部传入的值
- App.vue中引入注册组件,使用时,传入具体数据给组件显示
口诀:
1:子组件--props--变量(准备接收)
2:父组件--自定义属性-传值进去
父组件–App.vue
<template>
<div>
<!--
每次组件显示不同的数据信息
2-自定义属性:给子组件传值
口诀:
1:子组件--props--变量(准备接收)
2:父组件--自定义属性-传值进去
-->
<my-product title="超好吃的口水鸡" price="199"></my-product>
<my-product title="超级难吃的浏览" price="299"></my-product>
<my-product :title="tit" :price="p"></my-product>
</div>
</template>
<script>
import MyProduct from './components/MyProduct.vue'
export default {
data() {
return {
tit: '超级大的西瓜',
p: 50,
}
},
components: {
// MyProduct: MyProduct,
MyProduct,
},
}
</script>
子组件 MyProduct.vue
<template>
<div class="list">
<h3>标题:{{ title }}</h3>
<p>价格:{{ price }}</p>
<p>开业大酬宾,全场8折</p>
</div>
</template>
<script>
export default {
// 1-组件内props定义变量 用来接收外部传入的值
props: ['title', 'price'],
}
</script>
<style scoped>
.list {
width: 300px;
height: 140px;
border: 1px solid;
border-radius: 10px;
}
</style>
父传子–配合循环:
把数据循环分别传入给组件内显示
<template>
<div>
<!--
每次组件显示不同的数据信息
2-自定义属性:给子组件传值
口诀:
1:子组件--props--变量(准备接收)
2:父组件--自定义属性-传值进去
-->
<my-product
v-for="item in arr"
:key="item.id"
:title="item.tit"
:price="item.price"
></my-product>
</div>
</template>
<script>
import MyProduct from './components/MyProduct.vue'
export default {
data() {
return {
arr: [
{
id: 1,
tit: '超好吃的口水鸡',
price: 199,
},
{
id: 2,
tit: '超好吃的老鸭',
price: 49,
},
{
id: 3,
tit: '超级无敌的冰激凌',
price: 1999,
},
],
}
},
components: {
// MyProduct: MyProduct,
MyProduct,
},
}
</script>
单向数据流:
在vue中需要遵循单向数据流原则
1:父组件的数据发生了改变,子组件会自动跟着变
2:子组件不能直接修改父组件传递过来的props,props是只读的
父组件传给子组件是一个对象,子组件修改对象的属性, 是不会报错的,对象是引用类型,互相更新
总结:props的值不能重新赋值,对象引用关系属性值改变,互相影响
子传父:
从子组件把值传出来给外面使用
语法:
- 父 : @自定义事件名=“父methods函数”
- 子 : this.$emit(‘自定义事件名’,传值)—执行父methods里函数
父组件
<template>
<div>
<!--
11-父组件:
@自定义事件名="父methods函数"
-->
<my-product @priceevent="priceFn" :title="tit" :price="price"></my-product>
</div>
</template>
<script>
import MyProduct from './components/MyProduct.vue'
export default {
data() {
return {
tit: '超级好吃的口水鸡',
price: 199,
}
},
methods: {
priceFn(val) {
// val就是从子组件传递过来的值
console.log(val)
this.price = val
},
},
components: {
// MyProduct: MyProduct,
MyProduct,
},
}
</script>
子组件
<template>
<div class="list">
<h3>标题:{{ title }}</h3>
<p>价格:{{ price }}</p>
<p>开业大酬宾,全场8折</p>
<button @click="changPrice">更改</button>
</div>
</template>
<script>
export default {
// 1-组件内props定义变量 用来接收外部传入的值
props: ['title', 'price'],
methods: {
changPrice() {
// 从子组件中把值传递出去
// this.$emit触发自定义事件
// this.$emit('自定义事件名',传递参数)
this.$emit('priceevent', 599)
},
},
}
</script>
非父子:
两个组件的关系非常复杂,通过父子组件通信是非常麻烦的,使用通用的组件通讯方式:事件总线(event-bus)
第一步:定义事件总线对象EventBus:
src/EventBus/index.js
import Vue from 'vue'
// 导出空白的vue对象
export default new Vue()
MyProduct.vue触发事件:
<script>
import EventBus from '../EventBus'
export default {
methods: {
transmitFn() {
// 传递数据方
// 触发自定义事件
EventBus.$emit('transmitEvent', 'hello')
},
},
}
</script>
MyChild.vue注册事件:
import EventBus from '../EventBus'
export default {
data() {
return {
msg: '',
}
},
mounted() {
// 组件创建完毕 监听transmitEvent事件
// 接收方
// 自定义事件 transmitEvent
EventBus.$on('transmitEvent', (v) => {
console.log(v, '接收方')
this.msg = v
})
},
}
总结:空的vue对象,只负责on注册事件emit触发事件 一定要确保$on先执行
vue生命周期:
组件从创建到销毁的整个过程就是生命周期
钩子函数:
vue框架内置函数,随着组件的生命周期阶段,自动执行
作用:
特定的时间点,执行特定的操作
场景:
组件创建完毕后,可以在created生命周期函数中发起ajax请求,初始化data数据;mounted函数中,操作Dom元素; beforeDestory生命周期函数中 ,解绑事件,清除定时器…
4大阶段 8个方法:
创建:
- beforeCreate
- created
挂载:
- beforeMount
- mounted
更新:
- beforeUpdate
- updated
代码:
<template>
<div>
<p>学习vue生命周期</p>
<p id="myP">{{ msg }}</p>
<ul id="myul">
<li v-for="(val, index) in arr">
{{ val }}
</li>
</ul>
<button @click="arr.push(122)">点击添加</button>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'hello world',
arr: [5, 8, 2, 1],
timer: null, //保存定时器
}
},
/*
一:创建阶段
new Vue()以后,vue内部给实例添加了一些属性和方法 data和methods
*/
beforeCreate() {
//创建前 没有数据 没有方法
console.log('beforeCreate')
console.log(this.msg) //undefined
},
created() {
// 创建后
// data和methods初始化之后
// 场景:网络请求 注册全局事件 开启定时器
console.log('created')
console.log(this.msg) // "hello world"
this.timer = setInterval(() => {
console.log('娃哈哈')
}, 1000)
},
/*
挂载
真实dom挂载之前
场景:预处理data,不会触发updated钩子函数
*/
beforeMount() {
console.log('beforeMount')
console.log(document.getElementById('myP')) //null
},
/*
真实dom挂载以后
场景: ajax请求 dom操作 echarts
*/
mounted() {
console.log('mounted')
console.log(document.getElementById('myP')) //<p data-v-7ba5bd90="" id="myP">hello world</p>
},
/*
以上四个生命周期钩子函数在整个生命周期过程中只执行一次
*/
/*
更新阶段:data数据改变才会执行
*/
beforeUpdate() {
// 更新之前
console.log('beforeUpdate')
console.log(document.querySelectorAll('#myul>li')[4]) //undefined
},
updated() {
// 更新之后
// 获取更新后的真实dom
console.log('updated')
console.log(document.querySelectorAll('#myul>li')[4]) //<li data-v-7ba5bd90=""> 122 </li>
},
}
</script>
<style scoped></style>
销毁:
- beforeDestory
- destoryed
$refs:
$refs获取DOM:
利用ref和$refs获取DOM元素
<template>
<div>
<p>获取原生Dom元素</p>
<h1 id="myH" ref="myH">我是一名学员</h1>
</div>
</template>
<script>
export default {
mounted() {
// console.log(document.getElementById('myH'))
/*
1:给标签定义ref属性
2:通过this.$refs.属性名 获取元素
*/
console.log(this.$refs.myH)
},
}
</script>
$refs获取组件对象:
- 创建组件MyProduct.vue
<template>
<div>MyProduct组件</div>
</template>
<script>
export default {
data() {
return {
msg: 'hello',
}
},
methods: {
fn() {
console.log('组件被调用了')
},
},
}
</script>
- 获取组件对象,调用组件方法
<template>
<div>
<h2>获取组件对象--可以调用组件内的一切</h2>
<my-product ref="myP"></my-product>
</div>
</template>
<script>
/*
1:创建组件/引入组件/注册组件/使用组件
2: 给组件起别名 ref
3: 恰当时机,获取组件对象
*/
import MyProduct from './components/MyProduct'
export default {
components: {
MyProduct,
},
mounted() {
let myPobj = this.$refs.myP
console.log(myPobj.msg)
myPobj.fn()
},
}
</script>
$nextTick:
vue更新DOM–异步的:
需求:点击count++,通过原生DOM拿标签内容,无法拿到新值
<template>
<div>
<h2>$nextTick</h2>
<p ref="myP">
{{ count }}
</p>
<button @click="addCount">count++</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
}
},
methods: {
addCount() {
this.count++ //vue监听数据更新,开启一个DOM更新队列(异步任务)
// console.log(this.$refs.myP.innerHTML) //0
/*
vue更新DOM 异步任务
解决:this.$nextTick
过程: DOM更新完会挨个触发$nextTick里的函数体
*/
this.$nextTick(() => {
console.log(this.$refs.myP.innerHTML)
})
},
},
}
</script>
场景:
点击搜索按钮,弹出文本框并获取焦点,按钮消失
$nextTick和async、await
<template>
<div>
<button @click="btnFn" v-if="isShow">搜索</button>
<input v-else type="text" placeholder="这是一个文本框" ref="myInp" />
</div>
</template>
<script>
export default {
data() {
return {
isShow: true,
}
},
methods: {
async btnFn() {
this.isShow = false
// this.$refs.myInp.focus()
// 原因:DOM更新是异步的 文本框还没有挂载到真实DOM上
// this.$nextTick(() => {
// this.$refs.myInp.focus()
// })
// 等同于
// await取代回调函数
await this.$nextTick()
this.$refs.myInp.focus()
},
},
}
</script>
组件name使用:
可以用组件的name属性值,来注册组件名字
我们封装的组件,可以自己定义name属性组件名 让使用者有个统一的前缀风格
components/MyCom.vue
<template>
<div>我是一个Com组件</div>
</template>
<script>
export default {
name: 'ComNameHaHa', //注册时可以定义自己的名字
}
</script>
<style scoped></style>
App.vue中注册和使用:
<template>
<div>
<ComNameHaHa></ComNameHaHa>
<MyProducts></MyProducts>
</div>
</template>
<script>
import Com from './components/MyCom.vue'
import Pro from './components/MyProduct.vue'
export default {
components: {
[Pro.name]: Pro,
[Com.name]: Com, //对象里的key是变量的话 [] 属性名表达式
// 相当于
// ComNameHaHa: Com,
},
}
</script>
动态组件:
多个组件使用一个挂载点,并动态切换,就是动态组件
vue内置component组件,配合is属性,设置要显示的组件
需求:完成一个注册功能页面,2个按钮切换,一个填写注册信息,一个填写用户简介信息
- 定义两个组件 UserName.vue , UserInfo.vue 2个组件
- 引入到App.vue组件中
- data中定义变量来存放要显示的组件名
- 要设置挂载点
<component>
使用is属性来设置要显示那个组件 - 点击按钮 修改变量里的组件名
<template>
<div>
<button @click="comName = 'UserName'">账号密码</button>
<button @click="comName = 'UserInfo'">个人信息</button>
<p>下面显示动态切换组件</p>
<div style="border: 1px solid red">
<component :is="comName"></component>
</div>
</div>
</template>
<script>
/*
同一个挂载点要切换不同组件 显示
1:创建要切换的组件 标签+样式
2:引入到要展示的vue文件内,注册
3:变量 承载要显示的组件名
4:设置挂载点 <component :is="变量"></component>
5:点击按钮 切换comName值为要显示的组件名
*/
import UserName from './components/UserName.vue'
import UserInfo from './components/UserInfo.vue'
export default {
data() {
return {
comName: 'UserName',
}
},
components: {
UserName,
UserInfo,
},
}
</script>
组件缓存:
组件切换会导致组件被频繁的销毁和重新创建,性能不高
使用vue内置的keep-alive组件,可以让包裹的组件保存在内存中不被销毁
演示:给UserName.vue 和 UserInfo.vue 注册created 和destroyed生命周期事件,观察创建和销毁的过程
使用keep-alive内置的vue组件,让动态组件缓存
语法:
Vue内置的keep-alive组件,包裹要频繁切换的组件
App.vue:
<div style="border: 1px solid red">
<!-- vue内置的keep-alive组件,把包起来的组件缓存起来 -->
<keep-alive>
<component :is="comName"></component>
</keep-alive>
</div>
补充生命周期:(组件缓存下新增的两个钩子)
-
activated 激活时触发
-
deactivated 失去激活状态触发
<template>
<div>
<p>用户名:<input type="text" /></p>
<p>密码: <input type="text" /></p>
</div>
</template>
<script>
export default {
created() {
console.log('UserName创建')
},
destroyed() {
console.log('UserName销毁')
},
// 组件缓存下 多了两个钩子函数
activated() {
console.log('activated') //激活状态
},
deactivated() {
console.log('deactivated') // 失去激活状态
},
}
</script>
组件插槽slot:
用来实现组件内容的分发,通过slot标签,可以接收到写在组件标签内的内容
vue提供组件插槽的能力,允许开发者在封装组件时,把不确定的部分定义为插槽
基础使用–匿名插槽:
- 组件内使用slot 占位
- 使用组件时
<pannel></pannel>
中间传入标签替换slot
Pannel.vue:
<template>
<div>
<div class="title">
<h4>鹅鹅鹅</h4>
<span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span>
</div>
<div class="container" v-show="isShow">
<!--1: 组件内使用slot 占位 -->
<slot></slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
}
},
}
</script>
<style scoped>
h4 {
text-align: center;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 10px;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 10px;
}
</style>
App.vue:
<template>
<div>
<!-- 2 使用组件时 pannel
在组件中间 传入标签替换slot -->
<pannel>
<p>曲项向天歌</p>
<p>曲项向天歌</p>
<p>曲项向天歌</p>
<p>曲项向天歌</p>
<p>曲项向天歌</p>
</pannel>
<pannel>
<img src="./assets/logo.png" alt="" />
</pannel>
</div>
</template>
<script>
import Pannel from './components/Pannel.vue'
export default {
components: { Pannel },
}
</script>
默认内容:
如果外面不给传,想给个默认显示内容
<slot>
夹着内容默认显示内容,如果不给插槽slot传东西,就使用slot夹着的内容在原地显示
<slot>默认内容</slot>
具名插槽v-slot:
当一个组件内有2处以上需要外部传入标签的地方
传入的标签可以分别派发给不同的slot位置
v-slot一般跟template标签使用(template是HTML5新出标签内容模板元素,不会渲染在页面上,一般被vue解析内部标签)
Pannel.vue:
<template>
<div>
<div class="title">
<slot name="title"></slot>
<span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span>
</div>
<div class="container" v-show="isShow">
<!--1: 组件内使用slot 占位 -->
<slot name="content"></slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
}
},
}
</script>
App.vue:
<template>
<div>
<!-- 2 使用组件时 pannel
在组件中间 传入标签替换slot -->
<pannel>
<template v-slot:title>
<h4>鹅鹅鹅</h4>
</template>
<template v-slot:content>
<span>我是内容</span>
</template>
</pannel>
<!-- v-slot: 简写为 # -->
<pannel>
<template #title>
<h2>hello</h2>
</template>
<template #content>
<p>123</p>
</template>
</div>
</template>
总结:
v-slot: 可以简写为 #
slot的name属性起插槽名,使用组件时,template配合#插槽名传入具体的标签
作用域插槽(可以将主组件的属性获取到):
子组件里的值,在给插槽赋值时在父组件环境下使用
步骤:
- 子组件:在slot上绑定属性和子组件内的值
- 使用组件时,传入自定义标签,用template和v-slot=“自定义变量名scope”
- scope变量名自动绑定slot上所有的属性和值
Pannel.vue:
<template>
<div>
<div class="title">
<!-- 1:slot上绑定属性和子组件内的值 -->
<slot name="title" :msg="msg" tit="nihao"></slot>
<span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span>
</div>
<div class="container" v-show="isShow">
<!--1: 组件内使用slot 占位 -->
<slot name="content"> </slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
msg: 'hello world',
}
},
}
</script>
App.vue:
<template>
<div>
<pannel>
<!--2 传入自定义标签 template和v-slot:插槽名="自定义变量名" -->
<template v-slot:title="scope">
<!--3 scope上自动绑定slot上所有属性和值 -->
<h4>{{ scope.tit }}</h4>
</template>
<template v-slot:content>
<span>我是内容</span>
</template>
</pannel>
<!-- v-slot: 简写为 # -->
<pannel>
<template #title>
<h2>hello</h2>
</template>
<template #content>
<p>123</p>
</template>
</pannel>
</div>
</template>
总结:
组件内变量绑定到slot上,使用组件v-slot:插槽名=“自定义变量”,变量上会自动绑定属性和值
使用场景:
需求:封装一个表格组件,在表格组件内循环产生单元格
- 准备MyTable.vue组件—内置表格,传入数组循环铺设页面,把对象每个内容显示在单元格里
- App.vue组件里,准备数据传入给MyTable.vue使用
- 分析
- 想要给td内显示图片,传入自定义的img标签(td中准备slot占位符,但是外面需要把图片地址赋予给src属性,在slot上把item数据进行绑定)
MyTable.vue:
需求:封装一个表格组件,在表格组件内循环产生单元格
- 准备MyTable.vue组件—内置表格,传入数组循环铺设页面,把对象每个内容显示在单元格里
- App.vue组件里,准备数据传入给MyTable.vue使用
- 分析
- 想要给td内显示图片,传入自定义的img标签(td中准备slot占位符,但是外面需要把图片地址赋予给src属性,在slot上把item数据进行绑定)
MyTable.vue:
<template>
<div>
<table>
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>头像</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list" :key="index">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>
<slot :row="item">
<!-- 默认值,使用组件不自定义标签显示默认文字 -->
{{ item.headImgUrl }}
</slot>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: ['list'],
}
</script>
App.vue:
<template>
<div>
<my-table :list="list">
<template v-slot="scope">
<img :src="scope.row.headImgUrl" width="50" alt="" />
</template>
</my-table>
<my-table :list="list">
<template v-slot="{ row }"> // 解构
<a :href="row.headImgUrl">
{{ row.headImgUrl }}
</a>
</template>
</my-table>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
export default {
components: { MyTable },
data() {
return {
list: [
{
name: '张三',
age: 18,
headImgUrl: 'https://img01.yzcdn.cn/vant/ipad.jpeg',
},
{
name: '李四',
age: 23,
headImgUrl: 'https://img01.yzcdn.cn/vant/ipad.jpeg',
},
{
name: '王五',
age: 45,
headImgUrl: 'https://img01.yzcdn.cn/vant/ipad.jpeg',
},
],
}
},
}
</script>
自定义指令:
除了核心功能默认内置的指令(v-model和v-show),vue允许我们注册自定义指令。 v-xxxx
html+css的复用的主要形式是组件
你需要对普通DOM元素进行底层操作,这时候就会用到自定义指令
目标:获取标签 扩展额外的功能
局部注册和使用
只能在当前组件中使用
<template>
<div>
<div class="main">
<input type="text" v-focus />
</div>
</div>
</template>
<script>
/*
局部指令
directives:{
'指令名':{
bind(el,binding,vnode){},
inserted(el,binding,vnode){}
}
}
在标签上使用自定义指令 v-指令名
inserted 指令所在标签 被插入到页面上触发(一次)
update 指令对应的数据/标签更新时 触发
*/
export default {
directives: {
// 页面加载时获取焦点
focus: {
inserted(el, binding, vnode) {
// console.log(el)
el.focus()
},
},
},
}
</script>
全局注册和使用
在任何的.vue文件中使用
main.js中用Vue.directive 全局注册指令
// 全局注册
Vue.directive('gfocus', {
inserted(el) {
el.focus()
},
})
自定义指令-传值:
使用自定义指令,传入一个值
需求:定义color指令,传入一个颜色,给标签设置文字颜色
<template>
<div>
<div class="main">
<input type="text" v-focus />
<input type="text" v-gfocus />
</div>
<p v-color="theColor" @click="changeColor">修改文字颜色</p>
</div>
</template>
<script>
/*
局部指令
directives:{
'指令名':{
bind(el,binding,vnode){},
inserted(el,binding,vnode){}
}
}
在标签上使用自定义指令 v-指令名
inserted 指令所在标签 被插入到页面上触发(一次)
update 指令对应的数据/标签更新时 触发
*/
export default {
data() {
return {
theColor: 'blue',
}
},
methods: {
changeColor() {
this.theColor = 'yellow'
},
},
directives: {
// 页面加载时获取焦点
focus: {
inserted(el, binding, vnode) {
// console.log(el)
el.focus()
},
},
// 给标签设置文字颜色
color: {
inserted(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
},
},
},
}
</script>
vue路由:
生活中的路由:
设备和ip的映射关系
后端路由:
接口和服务的映射关系
前端路由:
路径和组件的映射关系
为什么使用路由:
在一个页面里,切换业务场景
vue单页面应用(SPA)所有的功能在一个html页面上实现
优点:
- 整体不刷新页面,用户体验好
- 数据传递容易,开发效率高
缺点:
- 开发成本高(需要学习专门知识)
- 首次加载比较慢,不利于SEO
vue-router介绍:
vue集成路由
vue-router模块包,它和vue.js深度集 成
定义映射规则-----模块化—提供2个内置全局组件
路由-组件分类:
.vue文件分2类,一个页面组件,一个是复用组件
.vue文件本质无区别
src/views(pages)文件夹 —页面组件—配合路由使用
src/components文件夹 —复用组件(展示数据–复用)
总结:views下的页面组件,配合路由切换;components下的一般引入到views下的vue中复用展示数据
vue-router使用:
安装:
npm i [email protected]
导入路由:
src下创建router/index.js
import Vue from 'vue'
// 1导入路由
import VueRouter from 'vue-router'
// 引入组件
// import Home from '../views/Home'
// import Login from '../views/Login'
// import Register from '../views/Register'
// 2 使用路由插件
// 在vue中 使用vue的插件 需要调用Vue.use()
Vue.use(VueRouter)
// 3 创建vue路由规则
const routes = [
// 路径和组件的映射关系
{
path: '/',
// component: Home,
// 路由懒加载
component: () => import('../views/Home'),
},
{
path: '/login',
component: () => import('../views/Login'),
},
{
path: '/register',
component: () => import('../views/Register'),
},
]
// 4创建路由对象 传入规则
const router = new VueRouter({
// routes: routes,
routes,
})
// 导出路由对象
export default router
main.js:
import router from './router'
new Vue({
router,
render: (h) => h(App),
}).$mount('#app')
路由出口:
App.vue
<router-view></router-view>
声明式导航:
使用全局组件 router-link来替代a标签
- vue-router提供了一个全局组件 router-link
- router-link 实质上最终会渲染成a链接,to属性等价于提供href属性(to无需#)
- router-link提供了声明式导航高亮的功能(自带类名)
<template>
<div>
<div class="nav">
<router-link to="/">首页</router-link>
<router-link to="/login">登陆</router-link>
<router-link to="/register">注册</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
.nav {
display: flex;
width: 400px;
justify-content: space-around;
}
.router-link-exact-active { // 高亮显示,点击谁谁变红
color: red;
}
</style>
重定向:
强制切换到目标path上
- 网页打开url默认hash值是 / 路径
- redirect 是设置要重定向到那个路由路径
需求:网页默认打开,匹配路由 ‘/’ 强制切换到 ‘/home’上
const routes = [
{
path: '/',
redirect: '/home', //重定向到 /home
},
}
总结: 强制重定向后,还会重新来数组中匹配一次规则
路由-404页面:
如果路由hash值没有和数组里规则匹配
默认给一个404页面
路由最后,path匹配 *(任意路径) --前面都不匹配,就匹配最后这个,显示对应的组件
- 创建NotFound页面
<template>
<div>404</div>
</template>
<script>
export default {}
</script>
<style scoped></style>
- 修改路由配置
const routes = [
// 写在最后
// {
// path: '*',
// redirect: '/home',
// },
{
path: '*',
component: () => import('../views/NotFound'),
},
]
**总结:**如果路由未命中任何规则,给出一个兜底的404页面
路由模式设置:
修改路由在地址栏的模式
hash路由 : 地址栏URL中的#符号 http://localhost:3000/#/abc ,不会被包括在HTTP请求中,对后端完全没有影响,改变hash不会重新加载页面
history路由:http://localhost:3000/abc(需要服务器支持,否则找的是文件夹)
利用了HTML5 新增的pushState() 和replaceState()方法
// 4创建路由对象 传入规则
const router = new VueRouter({
// routes: routes,
routes,
mode: 'history', //默认是hash
})
vue路由-编程式导航:
编程式导航:用js代码跳转
声明式导航:router-link实现跳转
语法:
this.$router.push({
path:"路由路径",
name:"路由名"
})
router/index.js 路由规则里,给路由起名字
const routes = [
// 路径和组件的映射关系
{
path: '/home',
name: 'home',
component: () => import('../views/Home'),
},
{
path: '/login',
name:"login",
component: () => import('../views/Login'),
},
{
path: '/register',
name:"register",
component: () => import('../views/Register'),
},
]
App.vue中router-link换成span,配合js的编程式导航跳转
<template>
<div>
<div class="nav">
<!-- <router-link to="/">首页</router-link>
<router-link to="/login">登陆</router-link>
<router-link to="/register">注册</router-link> -->
<span @click="goTo('/home', 'home')">首页</span>
<span @click="goTo('/login', 'login')">登录</span>
<span @click="goTo('/register', 'register')">注册</span@click=>
</div>
<router-view></router-view>
</div>
</template>
<script>
/*
编程式导航: js方式跳转路由
语法:
this.$router.push({path:"路由路径"})
this.$router.push({name:"路由名"})
注意:
虽然用name跳转,但是url的hash值还是切换path路径值
场景
方便修改,name路由名(在页面上看不见,随便修改)
path可以在url的hash值看到(尽量符合组件内规范)
*/
export default {
methods: {
goTo(targetPath,targetName) {
this.$router.push({
// path:targetPath
name:targetName
})
}
},
}
</script>
嵌套路由:
在现有的一级路由下,再嵌套二级路由
- 创建需要用的所有组件
- src/views/Find.vue ----发现音乐
- src/views/My.vue—我的音乐
- src/views/Second/Recommend.vue —发现音乐/推荐页面
- src/views/Second/Ranking.vue —发现音乐/排行榜
- src/views/Second/SongList.vue —发现音乐/歌单页面
- main.js 配置2级路由
- 一级路由由path从/开始定义
- 二级路由往后path 直接写名字 ,无需 / 开头
- 二级路由在上级路由的children数组里编写路由信息对象
- 说明
- App.vue 的router-view 负责发现音乐 和我的音乐页面 切换
- Find.vue的router-view负责发现音乐下的三个页面切换
配置路由规则:
const routes = [
{
path: '/',
redirect: '/find', //重定向到 /home
},
{
path: '/find',
redirect: '/find/recommend',
component: () => import('../views/Find'),
children: [
{
path: 'recommend',
component: () => import('../views/Second/Recommend'),
},
{
path: 'ranking',
component: () => import('../views/Second/Ranking'),
},
{
path: 'songlist',
component: () => import('../views/Second/SongList'),
},
],
},
{
path: '/my',
component: () => import('../views/My'),
},
{
path: '*',
component: () => import('../views/NotFound'),
},
]
App.vue写路由出口:
App.vue 的router-view 负责发现音乐 和我的音乐页面 切换
<template>
<div>
<div class="nav">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
.nav {
display: flex;
width: 400px;
justify-content: space-around;
}
</style>
Find.vue写二级路由出口:
Find.vue的router-view负责发现音乐下的三个页面切换
<template>
<div>
<div class="nav_main">
<router-link to="/find/recommend">推荐</router-link>
<router-link to="/find/ranking">排行榜</router-link>
<router-link to="/find/songlist">歌单</router-link>
</div>
<div style="1px solid red">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
.nav_main {
background-color: red;
color: white;
padding: 10px 0;
}
.nav_main a {
text-align: center;
text-decoration: none;
color: white;
margin: 7px 20px;
padding: 0px 15px;
height: 20px;
display: inline-block;
line-height: 20px;
}
.nav_main a:hover {
background-color: brown;
}
.nav_main .router-link-exact-active {
background-color: brown;
}
</style>
**总结:**嵌套路由,找准在那个页面里写router-view和对应的规则里写children
声明式导航-类名区别:
router-link 自带的2个类名的区别是什么
router-link-active(模糊匹配) url中hash值,包含href属性值这个路径
router-link-exact-active(精确匹配) url中hash值路径,与href属性值完全相同,设置此类名
路由传参:
跳转路由时 可以给路由对应的组件内传参
声明式导航:
router-link 上的to属性,语法格式
/path?参数名=值
/path/值 ------需要路由对象提前配置 path:'/path/:参数名'
对应的页面组件接收传递过来的值
$route.query.参数名
$route.params.参数名
router/index.js
// 3 创建vue路由规则
const routes = [
{
path: '/',
redirect: '/list', //重定向到 /home
},
{
path: '/list',
component: () => import('../views/List'),
},
{
path: '/part',
component: () => import('../views/Part'),
},
{
path: '/detail/:name',
component: () => import('../views/Detail'),
},
{
path: '*',
component: () => import('../views/NotFound'),
},
]
List.vue:
<template>
<div>
<router-link to="/part?name=小川">朋友--小川</router-link>
<router-link to="/detail/小妞">朋友--小妞</router-link>
<router-link :to="'/part?name=' + n1">朋友--{{ n1 }}</router-link>
<router-link :to="'/detail/' + n1">朋友--{{ n1 }}</router-link>
</div>
</template>
<script>
export default {
data() {
return {
n1: '花姐',
n2: '露露',
}
},
}
</script>
Part.vue:
<template>
<div>
<p>关注明星</p>
{{ $route.query.name }}
<hr />
{{ name }}
</div>
</template>
<script>
export default {
data() {
return {
name: '',
}
},
created() {
// 创建完成 第一次操作data中数据 执行一次
this.name = this.$route.query.name
console.log(this.name)
},
}
</script>
Detail.vue:
<template>
<div>
detail
{{ $route.params.name }}
<hr />
{{ name }}
</div>
</template>
<script>
export default {
data() {
return {
name: '',
}
},
created() {
this.name = this.$route.params.name
},
}
</script>
编程式导航:
语法:
query/params 任选一个
this.$router.push({
path:"路由路径",
name:"路由名",
query:{
"参数名":"值"
},
params:{
"参数名":"值"
}
})
List.vue:
<template>
<div>
<router-link to="/part?name=小川">朋友--小川</router-link>
<router-link to="/detail/小妞">朋友--小妞</router-link>
<router-link :to="'/part?name=' + n1">朋友--{{ n1 }}</router-link>
<router-link :to="'/detail/' + n1">朋友--{{ n1 }}</router-link>
<hr />
<span @click="oneFn">朋友--小川</span>
<span @click="twoFn">朋友--小妞</span>
<span>朋友--{{ n1 }}</span>
<span>朋友--{{ n1 }}</span>
</div>
</template>
<script>
export default {
data() {
return {
n1: '花姐',
n2: '露露',
}
},
methods: {
oneFn() {
this.$router.push({
path: '/part',
query: {
// name: '小川',
name: this.n2,
},
})
},
twoFn() {
// path会自动的忽略params
// this.$router.push({
// name: 'detail',
// params: {
// name: '小妞',
// },
// })
this.$router.push('/detail/' + this.n1)
},
},
}
</script>
路由守卫:
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。
全局前置守卫:
需求:在跳转路由前,判断用户是否登录,登录了才能跳转到“我的音乐“页面,未登录弹窗提示
在路由对象上使用固定的方法 beforeEach
// 1 在路由对象上使用固定的方法 beforeEach
/*
路由跳转"之前" 先执行这里,决定是否跳转
router.beforeEach((to,from ,next)=>{
to 要跳转到的路由 (路由对象信息) 目标
from 从哪里跳转的路由(路由对象信息) 来源
next 函数体, next() 才会让路由正常的跳转切换, next(false)在原地停留 next("路由路径") 强制修改到另一个路由路径上
不调用next 页面留在原地
})
*/
// 在跳转路由前,判断用户是否登录,登录了才能跳转到“part“页面,未登录弹窗提示
const isLogin = false //登陆状态 (未登陆)
router.beforeEach((to, from, next) => {
// console.log(to)
// console.log(from)
if (to.path === '/part' && isLogin === true) {
alert('请登陆')
next(false)
} else {
next() //正常放行
}
})
Vant组件库:
vant 轻量、可靠的移动端 Vue 组件库
安装:
yarn add vant@latest-v2 -S
导入所有的组件:
main.js中导入:
// 导入所有的组件
import Vant from 'vant'
import 'vant/lib/index.css'
Vue.use(Vant)
使用组件:
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
手动按需引入:
只引入使用的组件
在不使用插件的情况下,可以手动引入需要的组件。—每一个组件中引入
import Button from 'vant/lib/button';
import 'vant/lib/button/style';
注册–使用:
<template>
<div>
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
</div>
</template>
<script>
import Button from 'vant/lib/button'
import 'vant/lib/button/style'
export default {
components: {
// VanButton: Button,
[Button.name]: Button,
},
}
</script>
自动按需引入组件:
babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。
安装插件:
yarn add babel-plugin-import -D
babel.config.js配置,重新启动项目:
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
引入:
main.js
import { Button, Icon, Tabbar, TabbarItem, Tag } from 'vant'
Vue.use(Tabbar)
Vue.use(TabbarItem)
Vue.use(Button)
Vue.use(Icon)
Vue.use(Tag)
组件中使用:
<template>
<div>
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
<van-icon name="chat-o" />
<van-icon name="https://b.yzcdn.cn/vant/icon-demo-1126.png" />
</div>
</template>
<script>
export default {}
</script>
<style scoped></style>
案例:
路由配置:
二级路由
组件:
- Layout.vue —总的框架
- List.vue ----商品列表
- Search.vue -----商品搜索
- My.vue-----我的信息
配置路由:
const routes = [
{
path: '/',
redirect: '/list',
component: () => import('../views/Layout'),
children: [
{
path: 'list',
component: () => import('../views/List'),
},
{
path: 'search',
component: () => import('../views/Search'),
},
{
path: 'my',
component: () => import('../views/My'),
},
],
},
{
path: '*',
component: () => import('../views/NotFound'),
},
]
底部封装:
- 创建MyTabBar.vue组件
<template>
<van-tabbar v-model="active" route>
<van-tabbar-item icon="home-o" to="/list">商品列表</van-tabbar-item>
<van-tabbar-item icon="search" to="/search">商品搜索</van-tabbar-item>
<van-tabbar-item icon="friends-o" to="/my">我的信息</van-tabbar-item>
</van-tabbar>
</template>
<script>
export default {
data() {
return {
active: 0,
}
},
}
</script>
顶部封装:
- 创建MyHead.vue组件
<template>
<div class="head">TabBar案例</div>
</template>
<script>
export default {}
</script>
<style scoped>
.head {
height: 50px;
line-height: 50px;
background-color: blue;
text-align: center;
color: white;
}
</style>
商品列表:
- 封装MyTable.vue ===标签和样式
- axios 在MyGoodList.vue请求数据回来
- 请求地址https://www.escook.cn/api/goods
- 传入MyTable.vue 中循环数据显示
axios 在MyGoodList.vue请求数据回来
- 下载axios
yarn add axios
- MyGoodList.vue
<template>
<div>
<my-table :list="list"></my-table>
</div>
</template>
<script>
import axios from 'axios'
import MyTable from './MyTable.vue'
export default {
components: {
MyTable,
},
data() {
return {
list: [],
}
},
created() {
// axios({
// url: 'https://www.escook.cn/api/goods',
// }).then((res) => {
// let {
// data: { data: result },
// } = res
// console.log(result)
// })
this.getDate()
},
methods: {
async getDate() {
let {
data: { data: result },
} = await axios({ url: 'https://www.escook.cn/api/goods' })
console.log(result)
this.list = result
},
},
}
</script>
- MyTable.vue
<template>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list">
<td>{{ item.id }}</td>
<td>{{ item.goods_name }}</td>
<td>{{ item.goods_price }}</td>
<td>{{ item.tags }}</td>
<td>
<van-button type="primary">删除</van-button>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
list: Array,
},
}
</script>
<style scoped>
.table {
width: 100%;
margin: 20px auto;
border: 1px solid #333;
border-collapse: collapse;
}
td,
th {
border: 1px solid #333;
height: 30px;
}
</style>
商品表格–插槽:
使用插槽技术,和作用域插槽技术,给MyTable.vue组件,自定义列标题,自定义表格内容
需求:允许用户自定义表个头和表格单元格内容
- 把MyTable.vue里准备slot
- 使用MyTable组件时传入具体标签
步骤:
- 提高组件==复用性和灵活性 ==,把表格列标题thead部分预留slot ,设置name属性
- 使用MyTable.vue时,传入列标题标签
- 表格内容td部分也可以让组件使用者自定义,也给tbody预留slot 和name属性
- 使用插槽需要用到插槽内的item对象上的数据,作用域插槽
MyTable.vue:
<template>
<table class="table">
<thead>
<tr>
<!-- <th>#</th>
<th>名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th> -->
<slot name="header"></slot>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list">
<!-- <td>{{ item.id }}</td>
<td>{{ item.goods_name }}</td>
<td>{{ item.goods_price }}</td>
<td>{{ item.tags }}</td>
<td>
<van-button type="primary">删除</van-button>
</td> -->
<slot name="body" :row="item" :index="index"></slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
list: Array,
},
}
</script>
MyGoodList.vue:
<template>
<div>
<my-table :list="list">
<template #header>
<th>#</th>
<th>名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</template>
<template #body="{ row, index }">
<td>{{ row.id }}</td>
<td>{{ row.goods_name }}</td>
<td>{{ row.goods_price }}</td>
<td>{{ row.tags }}</td>
<td>
<van-button type="primary">删除</van-button>
</td>
</template>
</my-table>
</div>
</template>
商品表格tags:
<td>
<van-tag v-for="(str, ind) in row.tags" :key="ind" type="primary">{{
str
}}</van-tag>
</td>
商品表格–删除功能:
点击删除按钮删除数据
分析:
- 删除按钮绑定点击事件
- 作用域插槽绑定id出来
- 传给删除方法,删除MyGoodList.vue里数组的数据
MyGoodList.vue—注册点击事件:
<van-button type="primary" @click="removeBtn(row.id)"
>删除</van-button>
MyGoodList.vue 根据id删除:
methods:{
removeBtn(id) {
// 1 根据id查找下标
let index = this.list.findIndex((obj) => obj.id === id)
// 2 实现删除
this.list.splice(index, 1)
},
}
商品表格–添加tab:
需求:点击tab按钮,出现输入框自动获取焦点,失去焦点关闭输入框,会出新增tag,esc清空内容
- 点击tab,按钮消失,输入框出现
- 输入框自动获焦
- 失去焦点,输入框消失,按钮出现
- 检测输入框回车,无数据拦截
- 输入框取消,清空数据
- 检测输入框回车,有数据添加
点击tab,按钮消失,输入框出现:
<div class="top">
<van-field
v-model="value"
v-if="row.inputVisible"
placeholder="请输入tab内容"
/>
<van-button v-else @click="row.inputVisible = true" type="info"
>tag+</van-button
>
</div>
输入框自动获焦:
<van-field
v-model="value"
v-if="row.inputVisible"
placeholder="请输入tab内容"
:autofocus="true"
/>
失去焦点,输入框消失,按钮出现:
<van-field
v-model="value"
v-if="row.inputVisible"
placeholder="请输入tab内容"
:autofocus="true"
@blur="row.inputVisible = false"
/>
输入框回车新增tag:
1:监听input的回车事件
<van-field
v-model="row.inputValue"
v-if="row.inputVisible"
placeholder="请输入tab内容"
:autofocus="true"
@blur="row.inputVisible = false"
@keydown.enter="enterFn(row)"
/>
2.事件处理函数
// 新增tag
enterFn(row) {
// console.log(row, 0)
// 非空判断
if (row.inputValue.trim().length === 0) {
return alert('请输入数据')
}
// 添加
row.tags.push(row.inputValue)
row.inputValue = ''
},
input框 esc清空内容:
@keydown.esc="row.inputValue = ''"
Vuex基础–介绍:
为什么会有vuex?
Vuex 是一个专为 Vue.js 应用程序开发的状态 管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
Vuex是采用集中式管理组件依赖的共享数据的一个工具,可以解决不同组件数据共享问题
结论:
- 修改state状态必须通过mutations
- mutations只能执行同步代码,类似ajax,定时器之类的代码不能在mutations中执行
- 执行异步代码,通过actions,然后将数据提交给mutations才可以完成
- state的状态即共享数据可以在组件中引用
- 组件中可以调用aciton
Vuex的使用:
下载:
yarn add [email protected]
src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({})
export default store
main.js引入:
import store from './store/index'
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app')
Vuex–state:
state 放置所有公共状态的属性,如果你有一个公共状态数据,你只需要定义在state对象中
定义state:
// 初始化vuex对象
const store = new Vuex.Store({
state: {
// 管理数据
count: 0,
},
})
如何在组件中获取count?
原始形式–插值表达式:
Good.vue:
组件中可以使用this.$store 获取到vuex中的store对象实例,可通过state属性获取count
<div>state的数据{{ $store.state.count }}</div>
<script>
export default {
created() {
console.log(this.$store.state.count)
},
}
</script>
辅助函数–mapState:
mapState是辅助函数,帮助我们把store中的数据映射到组件的计算属性中,属于一种方便用法
List.vue:
第一步:导入mapState
第二步:采用数组形式引入state属性
第三部:利用展开运算法将导出的状态映射给计算属性
<template>
<div>
list
{{ count }}
</div>
</template>
<script>
//导入mapState
import { mapState } from 'vuex'
export default {
//采用数组形式引入state属性
//利用展开运算法将导出的状态映射给计算属性
computed: {
...mapState(['count']),
// 类似于
// count() {
// return this.$store.state.count
// },
},
}
</script>
vuex–mutations:
state数据的修改只能通过mutatons,并且mutations必须是同步更新,目的是形成数据快照
数据快照:一次mutation的执行,立刻得到一种视图状态,因为是立刻,所以必须是同步
定义mutations:
const store = new Vuex.Store({
state: {
// 管理数据
count: 70,
},
// d定义mutations
mutations: {
}
})
mutations是一个对象,对象中存放修改state的方法
mutations: {
// 方法里的参数 第一个参数是当前store的state属性
// 第二个参数payload 运输参数 调用mutations的时候 可以传递参数
addCount(state) {
state.count += 1
},
addCountN(state, n) {
state.count += n
},
},
如何在组件中调用mutations?
原始形式 $store:
Good.vue:
<template>
<div>
state的数据{{ $store.state.count }}
<hr />
<button @click="addCount">+1</button>
</div>
</template>
<script>
export default {
created() {
console.log(this.$store.state.count)
},
methods: {
addCount() {
// 调用store中的mutations 提交给mutations
// commit('mutations方法名',参数)
this.$store.commit('addCount')
},
},
}
</script>
带参数的传递:
<button @click="addCountN(9)">+n</button>
methods: {
addCountN(n) {
this.$store.commit('addCountN', n)
},
},
辅助参数–mapMutations:
mapMutations和mapState很像,把位于mutations中的方法提取出来,可以将它导入到methods中
<template>
<div>
list
{{ count }}
<button @click="addCount">+1</button>
<button @click="addCountN(8)">+n</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState(['count']),
// 类似于
// count() {
// return this.$store.state.count
// },
},
methods: {
// 把位于mutations中的方法提取出来,可以将它导入到methods中
...mapMutations(['addCount', 'addCountN']),
},
}
</script>
vuex-actions:
state是存放数据的,mutations是同步更新数据,actions是负责进行异步操作
定义actions:
actions: {
// 获取异步的数据 context 表示当前的store实例
// 可以通过context.state 获取状态
// 也可以通过context.commit 来提交mutations
// 也可以context.dispatch调用其它的action
getAsyncCount(context) {
setTimeout(() => {
// 1秒后,要去修改state
context.commit('addCount')
}, 1000)
},
},
原始调用–$store.dispatch:
addAsyncCount() {
this.$store.dispatch('getAsyncCount')
},
传参函数:
- actions
actions: {
getAsyncCountN(context, n) {
setTimeout(() => {
// 1秒后,要去修改state
context.commit('addCountN', n)
}, 1000)
},
},
- 调用
<button @click="addAsyncCountN(6)">+nAsync</button>
addAsyncCountN(m) {
this.$store.dispatch('getAsyncCountN', m)
},
辅助函数–mapActions
actions也有辅助函数,可以将action导入到组件中
<template>
<div>
list
{{ count }}
<button @click="getAsyncCount">+1Async</button>
<button @click="getAsyncCountN(6)">+nAsync</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState(['count']),
// 类似于
// count() {
// return this.$store.state.count
// },
},
methods: {
...mapActions(['getAsyncCount', 'getAsyncCountN']),
},
}
</script>
vuex-getters:
除了state之外,有时还需要从state中派生出一些状态,这些状态是依赖state的,会用到getters
state中定义了list,是1~10的数组
state: {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
},
组件中需要显示大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它
定义getters:
getters: {
// getters函数第一个参数是state
// 必须要有返回值
filterList: (state) => state.list.fiter((item) => item > 5),
},
原始方式–getters:
<ul>
<li v-for="(item, index) in $store.getters.filterList">
{{ item }}
</li>
</ul>
辅助函数–mapGetters:
<template>
<div>
list
{{ count }}
<ul>
<li v-for="(item, index) in filterList" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['filterList']),
}
}
</script>
vuex-Module:
为什么会有模块化:
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂的时候,store对象就会变得相当臃肿。
我们把所有的状态都放在state中,项目变得越来越大的时候,vuex会变得越来越难以维护------Vuex模块化
模块化简单应用:
- 定义两个模块 countModule 和 arrModule
- countModule 管理状态count
- arrModule 管理状态 arr
const store = new Vuex.Store({
modules: {
countModule: {
state: {
count: 0,
},
},
arrModule: {
state: {
arr: [2, 4, 67, 23, 12, 34, 68, 76],
},
},
},
})
Good.vue组件中,分别显示count 和arr:
<div>count:{{ $store.state.countModule.count }}</div>
<div>
<ul>
<li v-for="item in $store.state.arrModule.arr">
{{ item }}
</li>
</ul>
</div>
注意:获取子模块的状态,通过$store.state.模块名.属性名来获取
上面的获取有点麻烦,可以getters 来改变一下
//getters是根级别的getters
getters: {
count: (state) => state.countModule.count,
arr: (state) => state.arrModule.arr,
},
组件中通过mapGetters获取数据
<template>
<div>
<div>count:{{ count }}</div>
<ul>
<li v-for="(item, index) in arr" :key="index">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['count', 'arr']),
},
}
</script>
模块化中的命名空间:
namespaced 命名空间
默认情况下,模块内部的action,mutation,getter 是注册在全局命名空间的,—这样使得多个模块对同一mutation或者action作出响应
我们想要保证内部模块的高封闭性,可以采用namespaced来进行设置
const store = new Vuex.Store({
modules: {
countModule: {
namespaced: true, //命名空间
state: {
count: 0,
},
action: {},
mutations: {
addCount (state) {
// 这里的state表示的是countModule的state
state.count++
},
},
getters: {},
}
})
调用:
方法一: 直接调用-带上模块的属性名路径
this.$store.commit('countModule/addCount')
方法二:辅助函数–带上模块的属性名路径
<button @click="add">+1</button>
methods: {
...mapMutations(['countModule/addCount', 'arrModule/delArr']),
add() {
this['countModule/addCount']()
}
},
v-model语法糖:
父子组件通信,单项的,很多时候需要双向通信
父组件使用 msg.sync=“aa”,子组件使用 $emit(“update:msg”,参数)
父组件
<Good :msg.sync="test"></Good>
子组件
<template>
<div>
{{ msg }}
<button @click="fn">更改msg</button>
</div>
</template>
<script>
export default {
props: {
msg: {
default: '',
},
},
methods: {
fn() {
this.$emit('update:msg', 'world')
},
},
}
</script>
父组件传值传对象:
父组件使用:v-model
- 第一种
vue2.x中,v-model语法糖的简写
<List :msg="msg" @ccEvent="fn"/>
父组件App.vue
<template>
<div>
<List v-model="test"></List>
</div>
</template>
<script>
import List from './views/List.vue'
export default {
components: {
List,
},
data() {
return {
test: 'hello',
}
},
}
</script>
子组件 List.vue
<template>
<div>
{{ '值为' + msg }}
<button @click="fn">更改父组件的值</button>
</div>
</template>
<script>
export default {
// model 有2个属性,
// prop属性将msg作为该组件被使用时,v-model能取到的值
// event 就是自定义事件 是emit('ccEvent')的时候 参数的值就是父组件v-model收到的值
model: {
prop: 'msg',
event: 'ccEvent',
},
props: {
msg: '',
},
methods: {
fn() {
this.$emit('ccEvent', this.msg + 2)
},
},
}
</script>
- 第二种
<List :value="msg" @input="fn"/>
父组件 App.vue
<List v-model="test"></List>
子组件 List.vue
<template>
<div>
{{ '值为' + value }}
<button @click="fn">更改父组件的值</button>
</div>
</template>
<script>
export default {
props: {
value: {
//必须使用value
default: '',
},
},
methods: {
fn() {
// 这里必须是input发送数据,
// 发送数据会被父级v-model="test" 接收到,
// 再被value=test传回来
this.$emit('input', this.value + 2)
},
},
}
</script>
st: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
},
组件中需要显示大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它
##### 定义getters:
getters: {
// getters函数第一个参数是state
// 必须要有返回值
filterList: (state) => state.list.fiter((item) => item > 5),
},
##### 原始方式--getters:
- {{ item }}
辅助函数–mapGetters:
<template>
<div>
list
{{ count }}
<ul>
<li v-for="(item, index) in filterList" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['filterList']),
}
}
</script>
vuex-Module:
为什么会有模块化:
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂的时候,store对象就会变得相当臃肿。
我们把所有的状态都放在state中,项目变得越来越大的时候,vuex会变得越来越难以维护------Vuex模块化
[外链图片转存中…(img-UdJZ2hDM-1728799233543)]
模块化简单应用:
- 定义两个模块 countModule 和 arrModule
- countModule 管理状态count
- arrModule 管理状态 arr
const store = new Vuex.Store({
modules: {
countModule: {
state: {
count: 0,
},
},
arrModule: {
state: {
arr: [2, 4, 67, 23, 12, 34, 68, 76],
},
},
},
})
Good.vue组件中,分别显示count 和arr:
<div>count:{{ $store.state.countModule.count }}</div>
<div>
<ul>
<li v-for="item in $store.state.arrModule.arr">
{{ item }}
</li>
</ul>
</div>
注意:获取子模块的状态,通过$store.state.模块名.属性名来获取
上面的获取有点麻烦,可以getters 来改变一下
//getters是根级别的getters
getters: {
count: (state) => state.countModule.count,
arr: (state) => state.arrModule.arr,
},
组件中通过mapGetters获取数据
<template>
<div>
<div>count:{{ count }}</div>
<ul>
<li v-for="(item, index) in arr" :key="index">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['count', 'arr']),
},
}
</script>
模块化中的命名空间:
namespaced 命名空间
默认情况下,模块内部的action,mutation,getter 是注册在全局命名空间的,—这样使得多个模块对同一mutation或者action作出响应
[外链图片转存中…(img-xaBI50xX-1728799233543)]
我们想要保证内部模块的高封闭性,可以采用namespaced来进行设置
const store = new Vuex.Store({
modules: {
countModule: {
namespaced: true, //命名空间
state: {
count: 0,
},
action: {},
mutations: {
addCount (state) {
// 这里的state表示的是countModule的state
state.count++
},
},
getters: {},
}
})
调用:
方法一: 直接调用-带上模块的属性名路径
this.$store.commit('countModule/addCount')
方法二:辅助函数–带上模块的属性名路径
<button @click="add">+1</button>
methods: {
...mapMutations(['countModule/addCount', 'arrModule/delArr']),
add() {
this['countModule/addCount']()
}
},
v-model语法糖:
父子组件通信,单项的,很多时候需要双向通信
父组件使用 msg.sync=“aa”,子组件使用 $emit(“update:msg”,参数)
父组件
<Good :msg.sync="test"></Good>
子组件
<template>
<div>
{{ msg }}
<button @click="fn">更改msg</button>
</div>
</template>
<script>
export default {
props: {
msg: {
default: '',
},
},
methods: {
fn() {
this.$emit('update:msg', 'world')
},
},
}
</script>
父组件传值传对象:
父组件使用:v-model
- 第一种
vue2.x中,v-model语法糖的简写
<List :msg="msg" @ccEvent="fn"/>
父组件App.vue
<template>
<div>
<List v-model="test"></List>
</div>
</template>
<script>
import List from './views/List.vue'
export default {
components: {
List,
},
data() {
return {
test: 'hello',
}
},
}
</script>
子组件 List.vue
<template>
<div>
{{ '值为' + msg }}
<button @click="fn">更改父组件的值</button>
</div>
</template>
<script>
export default {
// model 有2个属性,
// prop属性将msg作为该组件被使用时,v-model能取到的值
// event 就是自定义事件 是emit('ccEvent')的时候 参数的值就是父组件v-model收到的值
model: {
prop: 'msg',
event: 'ccEvent',
},
props: {
msg: '',
},
methods: {
fn() {
this.$emit('ccEvent', this.msg + 2)
},
},
}
</script>
- 第二种
<List :value="msg" @input="fn"/>
父组件 App.vue
<List v-model="test"></List>
子组件 List.vue
<template>
<div>
{{ '值为' + value }}
<button @click="fn">更改父组件的值</button>
</div>
</template>
<script>
export default {
props: {
value: {
//必须使用value
default: '',
},
},
methods: {
fn() {
// 这里必须是input发送数据,
// 发送数据会被父级v-model="test" 接收到,
// 再被value=test传回来
this.$emit('input', this.value + 2)
},
},
}
</script>
标签:Vue,default,笔记,学习,state,vue,组件,import,路由
From: https://blog.csdn.net/Python_Ghost/article/details/142897831