vue
Vue入门
- Vue入门
- basic
- Vue2模块化
- 的作用,#后的都只是hash值,并不是地址,也就是说,你在给服务端发请求的时候,#后的不会带过去
- Vue3
basic
1.概述
- Vue (读音/vju/, 类似于view)是一套用于构建用户界面的渐进式框架,发布于2014年2月。
- 与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。
- Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库(如: vue-router: 跳转,vue-resource: 通信,vuex:管理)或既有项目整合。
- 官网:https://cn.vuejs.org/
- Soc原则:关注点分离原则
- Vue 的核心库只关注视图层,方便与第三方库或既有项目整合。
- HTML + CSS + JS : 视图 : 给用户看,刷新后台给的数据
- 网络通信 : axios
- 页面跳转 : vue-router
- 状态管理:vuex
- Vue-UI : ICE , Element UI
声明式渲染:不需要改变Dom,Vue可以通过绑定Dom修改数据,而不会修改Dom
2.第一个vue
2.1丶什么是MVVM
MVVM(Model-View-ViewModel)是一种软件设计模式, 源自于经典的MVC(Model-View-Controller)模式 , MVVM的核心是ViewModel层 ,作用如下
-
该层向上与视图层进行双向数据绑定
-
向下与Model层通过接口请求进行数据交互
MVVM已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流行的MVVM框架有
Vue.js
,
2.2丶为什么使用MVVM
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大好处
-
低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
-
可复用:你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
-
独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewMode),设计人员可以专注于页面设计。
-
可测试:界面素来是比较难以测试的,而现在测试可以针对ViewModel来写。
2.3丶 MVVM模式的实现者
- Model:模型层, 在这里表示JavaScript对象
- View:视图层, 在这里表示DOM(HTML操作的元素)
- ViewModel:连接视图和数据的中间件, Vue.js就是MVVM中的View Model层的实现者
在MVVM架构中, 是不允许数据和视图直接通信的, 只能通过ViewModel来通信, 而View Model就是定义了一个Observer观察者
- ViewModel能够观察到数据的变化, 并对视图对应的内容进行更新
- ViewModel能够监听到视图的变化, 并能够通知数据发生改变
至此, 我们就明白了, Vue.js就是一个MV VM的实现者, 他的核心就是实现了DOM监听与数据绑定
2.4.丶第一个vue
- 先安装插件vue.js
- 新建html文件,导入Vue.js
- 创建vue实例
- 将数据绑定导页面元素
- 有了viewmodel的区别:
- 当我们需要修改前端的数据,使用js就会修改整个dom
- 而我们使用viewmodel后,可以动态修改数据,而不需要修改dom
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层,模板-->
//3.绑定
<div id="a">
{{A}}
</div>
</body>
//2.引入
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
//3,创建实例
var vm=new Vue({
el:"#a",
//model:数据
data:{
A:"helloVue"
}
})
</script>
</html>
3.基础语法
3.1丶v-bind: 绑定元素特性
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层,模板-->
//绑定 v-bind:title
<div id="a" v-bind:title="A">
你好
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
var vm=new Vue({
el:"#a",
//model:数据
data:{
A:"nihao"
}
})
</script>
</html>
3.2丶v-if, v-else-if,v-else
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="a">
<h1 v-if="A==='nihao'">你好</h1>
<h1 v-else-if="A==='nihao2'">你好2</h1>
<h1 v-else>你好3</h1>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
var vm=new Vue({
el:"#a",
//model:数据
data:{
A:"nihao"
}
})
</script>
</html>
3.3丶for
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="a">
//H是数组,hh是元素别名
<li v-for="H in hh">
{{H}}
</li>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
var vm=new Vue({
el:"#a",
//model:数据
data:{
hh:[
{A:"你好",B:"nihao"},1,true
]
}
})
</script>
</html>
3.4丶v-on监听事件
事件有Vue的事件、和前端页面本身的一些事件!我们这里的click
是vue的事件, 可以绑定到Vue中的methods
中的方法事件!
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="a">
<button v-on:click="A">点击</button>
{{msg}}
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
var vm=new Vue({
el:"#a",
//model:数据
data:{
msg: "nihao"
},
methods:{
A:function (){
this.msg="你好啊"
}
}
})
</script>
</html>
4.双向绑定
4.1丶概念
-
什么是双向数据绑定?
Vue.js是一个MV VM框架, 即数据双向绑定, 即当数据发生变化的时候, 视图也就发生变化, 当视图发生变化的时候,数据也会跟着同步变化。这也算是Vue.js的精髓之处了
值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的非UI控件不会涉及到数据双向绑定
-
为什么要实现双向绑定
在
Vue.js
中,如果使用vuex
, 实际上数据还是单向的, 之所以说是数据双向绑定,这是用的UI控件来说, 对于我们处理表单,Vue.js
的双向数据绑定用起来就特别舒服了。
4.2丶input输入框
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="a">
<input type="text" v-model="msg">
{{msg}}
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
var vm=new Vue({
el:"#a",
data:{
msg:"123"
}
})
</script>
</html>
4.2丶单选框
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="a">
性别:
<input type="radio" name="sex" value="男" v-model="msg"> 男
<input type="radio" name="sex" value="女" v-model="msg"> 女 <br>
{{msg}}
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
var vm=new Vue({
el:"#a",
data:{
msg:""
}
})
</script>
</html>
4.下拉列表
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="a">
性别:
<select v-model="msg1">
<option>男</option>
<option>女</option>
</select>
<br>
性别选中:{{msg1}}
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
var vm=new Vue({
el:"#a",
data:{
msg1:"男"
}
})
</script>
</html>
5.watch监听和computed计算属性
watch的使用
new Vue({
el: "#Root",
data: {
id: 0
},
methods: {
add() {
this.id += 1;
}
},
watch: {
immediate:true, //初始化时让handler调用一下
id(a,b){
alert(a)
alert(b)
}
}
简写:
watch:{
id(a,b){
a:旧,b;新
} 如果id(a){
此时的a为新
}
}
computed的使用
computed的使用:定义返回的值不能是data里定义过的
计算属性:
1.定义:要用的属性不存在,要通过已有属性计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
<body>
<div id="Root">
<p>{{id}}</p>
{{id2}}
<button @click="add">计算属性</button>
</div>
</body>
<script>
var vm = new Vue({
el: "#Root",
data: { id: 123 },
methods: { add() {
// 此时我们是可以拿到id2的,在vm里,不要觉得data里没有就是没有这个数据 this.id2 += 1; } },
computed: {
// 简写 // id2(){ // return this.id+=10; // }
// 完整版 id2: { get() { return this.id; },
// set在id2被修改时执行 set(value) { alert(value) this.id += 10 } } } })
</script>
6.vue组件
非单文件组件
步骤:
- 首先注册组件
- 创建组件模板
- 定义一个属性来传递参数
- 在view层,调用组件,v-bind绑定属性传递参数
- 作用:提高复用
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>基本使用</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
Vue中使用组件的三大步骤:
一、定义组件(创建组件)
二、注册组件
三、使用组件(写组件标签)
一、如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。
二、如何注册组件?
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)
三、编写组件标签:
<school></school>
-->
<!-- 准备好一个容器-->
<div id="root">
<hello></hello>
<hr>
<h1>{{msg}}</h1>
<hr>
<!-- 第三步:编写组件标签 -->
<school></school>
<hr>
<!-- 第三步:编写组件标签 -->
<student></student>
</div>
<div id="root2">
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
//第一步:创建school组件
const school = Vue.extend({
template:`
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.schoolName)
}
},
})
//第一步:创建student组件
const student = Vue.extend({
template:`
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
studentName:'张三',
age:18
}
}
})
//第一步:创建hello组件
const hello = Vue.extend({
template:`
<div>
<h2>你好啊!{{name}}</h2>
</div>
`,
data(){
return {
name:'Tom'
}
}
})
//第二步:全局注册组件
Vue.component('hello',hello)
//创建vm
new Vue({
el:'#root',
data:{
msg:'你好啊!'
},
//第二步:注册组件(局部注册)
components:{
school,
student
}
})
new Vue({
el:'#root2',
})
</script>
</html>
单文件组件
一个组件必须要有的三部分:
结构:html
交互:js,vue
样式:css
注意:浏览器在不会识别vue文件的,需要脚手架
-
次组件
<template> <div class="yangshi"> <h1 v-text="a"></h1> <button @click="dianji"></button> </div> </template> <script> #一定要暴露出去,不然外面不能引入 export default { name: "SChool", data () { return { a: "大班" } }, methods: { dianji () { alert("你好") } } } </script> <style scoped> .yangshi { background-color: aqua; } </style>
-
主组件APP
<template> <div> <School></School> </div> </template> <script> 引入 import School from "../html./vUE/单组件.vue" export default { name: "App", components: { School } } </script> <style> </style>
-
main.js里的new Vue()
import App from "App" new Vue({ el: "#Root", components: { App } })
-
index.html的实现
<!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="Root">
<App></App>
</div>
</body>
<script src="../js/vue.js"></script>
<script src="./main.js"></script>
</html>
Vue2模块化
1.Vue自带的脚手架
(1).文件结构分析
-
node_moudles:是放一些我们需要的文件
- 里面vue文件里:有完整版和runtime版本的vue
- 注意:
- vue=核心功能+模板解析器
- runtime版本的vue,没有模板解析器,不能使用template,绑定模板
- 当你引用的是runtime版本的vue文件时,就要用render
-
public:
- index.html就是放主文件的容器
- favico.ico:网页图标
-
src:来源
-
assets:资源
-
components:我们自己定义的组件
-
App.vue:主组件
-
main.js:入口
-
-
babel.config.js:es6转换为es5
-
jsconfig.json:可以自己做一些配置
-
pakeage-lock: 文件是锁定安装时的包的版本号,以保证其他人在npm install时大家的依赖能保证一致。第一次npm install时自动生成。
-
package: 文件是对项目或者模块包的描述,也是配置项,安装node_modules的凭据。
-
readme:可以笔记
-
vue.config.js:修改默认配置
- 例如main.js和index.html的位置
2.属性ref(标识属性)
干什么用的?
类似于获取dom,js获取dom是拿不到组件标签的,但使用这个我们可以拿到组件标签的vc对象
怎么用?
注意:组件的对象和Vue对象是不一样的
<template>
<div>
<h1 ref="hi">{{name}}</h1>
<School id="school" ref="school"></School>
<button @click="dianji">点击</button>
</div>
</template>
<script>
import School from "./components/单组件.vue"
export default {
// name: "App",
data () {
return {
name:"liuchao"
}
},
components: {
School
},
methods: {
dianji(){
// 1.拿到html标签
// console.log(this.$refs)
// 此时dom是拿不到我们的组件标签的
console.log(document.getElementById(school))
// 通过cv对象拿到
console.log(this.$refs.school)
}
}
}
</script>
3.props属性
干啥用的?
父组件给子组件传数据
传的数据类型有?
String
Number
Boolean
Array
Object
(甚至可以传一个函数,函数也是对象,在下面7的案例里应用到)
注意点:
- 注意当age前面v-bind绑定时,此时就是一个动态属性了
- 1.如果我们:age="11",那么子组件拿到的数据是字符串里的表达式'11',在这里也就是11
- 2.如果你写成:age='age',拿的是下面data里的数据,这里传到子组件里的就是一个字符串11
- 3.可以传所有的类型
- 4.props传下来的数据不能改(只读),不然会报错,如果你传了一个对象下来,修改了对象里的属性值,此时是不会报错的,因为地址没改,但是不建议
具体的使用:
-
父组件传值
-
子组件接收
-
接收方式三种
- 直接传
- 限制属性
- 类型限制+默认值的指定+必要性的限制
-
父组件传值
<template>
<div>
<!-- 注意当age前面v-bind绑定时,此时就是一个动态属性了
(注意点)1.如果我们:age="11",那么子组件拿到的数据是字符串里的表达式'11',在这里也就是11
2.如果你写成:age='age',拿的是下面data里的数据,这里传到子组件里的就是一个字符串11
3.可以传所有的类型 -->
<Student :name='name'
:sex="sex"
:age="11"
:shuzu='["father", "mother", "borther", "sister"]'
:duixiang='{a:"小花",b:"小红",c:"小兰"}' />
<button @click="dijian2">点击2</button>
</div>
</template>
<script>
import Student from './components/student.vue'
export default {
name: 'App',
data () {
return {
name: "lc",
sex: "男",
age: "20"
}
},
components: { Student },
methods: {
dijian2 () {
console.log(this)
}
}
}
</script>
子组件
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{myage+1}}</h2>
<ul>
<li v-for="(s,index) in shuzu"
:key="index">{{s}}</li>
<li v-for="(d,index) in duixiang"
:key="index">{{d}}</li>
</ul>
<!-- 当我们想操作父组件传进来的数据时 -->
<button @click="updateAge">点击1</button>
</div>
</template>
<script>
export default {
name: 'Student',
data () {
return {
msg: "我的信息",
myage: this.age
}
},
// 第一种:简单声明接收
// props: ['name', 'sex', 'age', 'shuzu', 'duixiang'],
// 第二种:限制类型
// props: {
// name: String,
// sex: String,
// age: Number,
// shuzu: Array,
// duixiang: Object
// },
// 第三种:接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
props: {
name: {
type: String,
require: false, //设置为false就是可以不传
default: "lc" //默认值,一般不跟require一起用
},
sex: String,
age: Number,
shuzu: Array,
duixiang: Object
},
methods: {
updateAge () {
// 1.直接操作数据会有效果,但是控制台会有报错
// this.age+=1;
// 2.所以我们创建一个myage,转换一下
this.myage += 1
}
}
}
</script>
4.mixin混合
干啥用的?
提高代码复用,就是提取配置属性的
注意点
- 当提取的和组件data里的数据重复时,优先组件里的
- 挂载是两边都会执行
- 有全局使用和局部使用
使用步骤
- 提取mixin混合(定义)
- 使用mixins: [hunhe],必须用数组配置(局部)
- 直接在主入口,main.js中使用Vue.mixin()调用(主动)
- 全局使用时,所有的vm和vc上都有这个混合属性
局部使用
teacher.vue
<template>
<div>
<p v-text="text"></p>
<button @click="dianji">
老师点
</button>
</div>
</template>
<script>
import hunhe from '../mixin'
export default {
name: "Teacher",
data () {
return {
text: "1",
}
},
mixins: [hunhe],
mounted () {
console.log("挂载谁先1")
}
}
</script>
student.vue
<template>
<div>
<p v-text="text"></p>
<button @click="dianji">
学生点
</button>
</div>
</template>
<script>
import hunhe from '../mixin'
export default {
name: 'Student',
data () {
return {
text: "1"
}
},
mixins: [hunhe],
mounted () {
console.log("挂载谁先1")
}
}
</script>
mixin.js(提取)
- 要export导出
export default {
methods: {
dianji () {
this.text = "你好"
}
},
// 当这里的data和组件里的重复时,优先组件里的
data () {
return {
text:"2"
}
},
// 挂载是两边都会执行
mounted () {
console.log("挂载谁先2")
}
}
全局使用
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
// 全局mixin
import hunhe from './mixin'
Vue.mixin(hunhe)
//创建vm
new Vue({
el: '#app',
render: h => h(App)
})
5.插件
干啥用?
提高复用, 通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者 property。如:vue-custom-element
- 添加全局资源:指令/过滤器/过渡等。如 vue-touch
- 通过全局混入来添加一些组件选项。如 vue-router
- 添加 Vue 实例方法,通过把它们添加到
Vue.prototype
上实现。 - 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
怎么用?
创建插件
plug.js(名字随意)
export default {
//必须有install方法,参数为Vue的构造函数
install (Vue,a,b) {
alter(a+b)
Vue.mixin({
data () {
return {
x:100
}
}
})
}
}
使用插件
main.js主入口里
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
// 引入创建的插件
import plug from './plug'
//创建vm
Vue.use(plug,1,2)
new Vue({
el: '#app',
render: h => h(App)
})
注意点
-
必须有install方法,参数为Vue的构造函数
-
使用use后,Vue运行就自己调用
-
install()函数有多个对象,第二个之后都是自己定义的需要的参数
-
一般使用在全局配置上
6.scope样式
干啥用?
解决不同组件汇总到App后样式名字相同的冲突
怎么用?
丢到style标签里就行
<style scoped>
.text {
background-color: red;
}
是怎么实现的
使用scoped后会使用随机数字标记
7.ToDoList案例
结构
App
- header
- list
- item
- footer
代码
APP.vue
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :Addlist="Addlist" />
<MyList :list="list"
:qufan="qufan"
:shanchu="shanchu" />
<MyFooter :List="list"
:AllPick="AllPick"
:clean="clean" />
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import MyList from './components/MyList.vue'
export default {
name: 'App',
components: {
MyHeader,
MyFooter,
MyList,
},
data () {
return {
list: [
{ id: "01", title: "run", ok: false },
{ id: "02", title: "breakfast", ok: false },
{ id: "03", title: "water", ok: false }
],
complete: 0
}
},
methods: {
Addlist (item) {
this.list.unshift(item)
},
qufan (id) {
this.list.forEach((element) => {
if (element.id == id) {
element.ok = !element.ok
}
});
},
shanchu (id) {
this.list = this.list.filter(L => {
return L.id != id
})
},
AllPick (ok) {
this.list.forEach((element) => {
element.ok = ok
});
},
clean () {
this.list = this.list.filter(L => {
return !L.ok
})
}
}
}
</script>
<style>
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
Header.vue
<template>
<div>
<div class="todo-header">
<input type="text"
placeholder="请输入你的任务名称,按回车键确认"
v-model="title"
@keyup.enter="add" />
</div>
</div>
</template>
<script>
export default {
name: "Header",
props: ["Addlist"],
data () {
return {
title: ""
}
},
methods: {
add () {
if (this.title.trim() == '') {
return alert("输入不能为空")
} else {
const item = { id: Math.random(), title: this.title, ok: false }
this.Addlist(item)
this.title = ''
}
}
}
}
</script>
<style>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
</style>
List.vue
<template>
<ul class="todo-main">
<MyItem v-for="l in list "
:key='l.id'
:list="l"
:qufan="qufan"
:shanchu="shanchu" />
</ul>
</template>
<script>
import MyItem from './MyItem.vue';
export default {
name: "list",
components: { MyItem },
props: ["list", "qufan", "shanchu"]
}
</script>
<style>
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
</style>
Item.vue
<template>
<li>
<label>
<input type="checkbox"
:checked="list.ok"
@click="gouxuan(list.id)" />
<span>{{list.title}}</span>
</label>
<button class="btn btn-danger"
@click="remove(list.id)">删除</button>
</li>
</template>
<script>
export default {
name: "item",
props: ["list", "qufan", "shanchu"],
data () {
return {
show: true
}
},
methods: {
remove (id) {
// 通知APp删除
this.shanchu(id)
},
gouxuan (id) {
// 通知App取反
this.qufan(id)
}
}
}
</script>
<style scoped>
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
li:hover {
background-color: #ddd;
}
li:hover button {
display: block;
}
</style>>
Footer.vue
<template>
<!-- 拿到一个数,在js里如果一个整数大于0就会返回一个处理成一个true,小于等于0为false -->
<div class="todo-footer"
v-if="List.length">
<label>
<!-- 这里有一个优化,使用v-model , :checked="isAll"初始化 @click="quanxuan" 修改-->
<!-- <input type="checkbox" :checked="isAll" @click="quanxuan" /> -->
<input type="checkbox"
v-model="isAll" />
</label>
<span>
<span>已完成{{gouxuan}}</span> / 全部{{List.length}}
</span>
<button class="btn btn-danger" @click="cleanAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "Header",
props: ["List", "AllPick","clean"],
computed: {
// 勾选后,修改下面的数据
gouxuan () {
let i = 0;
this.List.forEach(element => {
if (element.ok) {
i++
}
}
)
return i
},
// 当上面全部选中,下面全选
isAll: {
get () {
return this.gouxuan == this.List.length
},
// set在isAll被修改时执行
set (value) {
this.AllPick(value)
}
}
},
methods: {
cleanAll(){
this.clean()
}
}
}
</script>
<style>
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
</style>
遇到的问题和学到的东西
-
怎么完整使用组件来写代码
- 拆分静态组件,按功能拆分
- 实现动态组件,当数据多个组件使用时,需要状态提升,这个案例里我就都在APP.vue写的数据
-
兄弟组件间的通信如何通信
- 让两个组件的共同上级组件
-
子组件间怎么访问父组件的数据
- 可以传函数,父组件创建函数并使用父组件的数据实现一个功能,传给子组件,子组件调用函数
-
过滤器 return 后是过滤的条件,会返回一个新的你要过滤的对象,需要重新接收
-
当你调用方法时,都会有一个参数 e 可以调用到 当前的dom
-
当你要对一个输入标签要做初始化展示,又需要后续修改,试试v-model
注意点
- props传的数据,不要修改,只使用
8.浏览器本地存储
localStorage(本地,只有清除缓存才能删除数据)和sessionStorage(会话,关闭浏览器就没了)
都有4个函数
localStorage.setItem("mei", "123") 添加(只能添加两个字符串)
localStorage.remove("mei") 删除一个
localStorage.getItem("mei") 获取这个
localStorage.clear() 删除所有
sessionStorage.一样的函数
优化Todolist案例,将数据放在本地
data () {
return {
// list是拿本地储存的,当取到的数据为null时,就会使用[]
list: JSON.parse(localStorage.getItem("list")) || [],
complete: 0
}
},
//监听list
watch: {
// 这里要用深度监视,不然,检测不到对象里的数据的变化
list: {
deep: true,
handler (value) {
localStorage.setItem("list", JSON.stringify(value))
}
}
},
9.自定义组件事件
干啥用的?
- 给组件做事件,自己定义一些处理,自定义事件其实,基本还是需要用到原本的js事件做一些自己想要的处理,就是事件包装
- 可以做到子传父的效果
- 我们上面用到的父给子传函数,达到子给传父数据,都可以用自定义事件代替
怎么用?
两种方式
- 第一种 v-on 其实就是在这个zhi组件实例里挂上一个事件,也就是要拿到这个组件实例,所以就有了第二种情况
- 第二种 用ref 的优势 灵活可以在mounth里做处理
步骤
-
子组件的实例对象使用$emit挂载上事件
- 父组件实现的事件,就是子组件里定义的事件
- 比如你子组件里定义了点击,移开,回车,那么这三个事件父组件都能触发
-
父组件使用事件,触发一个方法,方法的参数就是子组件传回来的数据
子组件
<template>
<div>
姓名:{{name}} <br>
大小:{{size}}
<button @mouseenter="dianji">自定义事件</button>
</div>
</template>
<script>
export default {
name: "zhi",
data () {
return {
name: "lc",
size: 28
}
},
methods: {
dianji(){
//$emit()触发绑定事件
this.$emit("shu",{name:this.name,size:this.size})
}
}
}
</script>
父组件
<template>
<div id="root">
<!-- 第一种 v-on 其实就是在这个zhi组件实例里挂上一个事件,也就是要拿到这个组件实例,所以就有了第二种情况-->
<!-- <zhi @shu="shuchu"/> -->
<!-- 第二种 用ref 的优势 灵活可以在mounth里做处理 -->
<zhi ref="zidingyi" />
<button @click="see">看看</button>
</div>
</template>
<script>
import zhi from './components/自定义组件事件Test.vue'
export default {
name: 'App',
components: {
zhi
},
methods: {
shuchu (shuju) {
console.log(shuju)
},
see () {
console.log(this)
}
},
// 第二种
mounted () {
// 第一个参数 自定义事件名 对应组件那边绑定的事件名, 第二个 调用的函数
//$on绑定事件
this.$refs.zidingyi.$on("shu", this.shuchu)
}
}
</script>
如何解绑事件
如何实现,使用vc实例的$off()方法
jiebang () {
// 解绑一个
this.$off("shu")
// 解绑多个
// this.$off(["shu","second"])
}
10.全局事件总线(通信)
干啥用?
还是用来通信,像我们前面的案例,实现兄弟通信是把数据存到了更高一级的组件里,然后分别传下来,麻烦
适用于任何组件通信
怎么用?
首先这个玩意,不是官方的api,而是一种写法,利用了所有组件都能拿到原型对象
-
创建事件总线,并绑定一个属性
-
$on绑定一个事件,接收数据并发送一个消息
-
$emit调用这个事件,并传入数据
-
$off销毁这个事件
main.js
new Vue({
el: '#app',
//为什么用beforeCreate钩子函数,因为必须在Vue实例创建之前执行,不然$bus没有创建成功,而组件里又调用了它,就会报错
beforeCreate () {
//创建全局事件总线,当然不仅仅可以用vm对象也可以用vc对象
Vue.prototype.$bus = this
},
render: h => h(App)
})
student.vue
<template>
<div>
<button @click="sendName">发送给学校</button>
</div>
</template>
<script>
export default {
name: "Student",
data () {
return {
name: "刘超"
}
},
methods: {
sendName () {
//发送数据
this.$bus.$emit("send", this.name)
}
}
}
</script>
<style>
</style>
School.vue
<template>
<div>{{name}}</div>
</template>
<script>
export default {
name: "School",
data () {
return {
name: ""
}
},
mounted () {
//接收数据
this.$bus.$on("send", (data) => {
this.name = data
})
},
//销毁
beforeDestroy () {
console.log("销毁事件send")
this.off("send")
}
}
</script>
注意
- $on绑定事件的地方,是接收数据的一方,$emit是你想发送数据的一方
- 由于都是在一个属性上创建事件,所以可能会事件名冲突,所以需要用完就销毁事件
11.消息订阅与发布(通信)
特点:有很多第三方库可以实现,不是vue里的,很多框架都可以使用
下载一个库,用法和全局事件大同小异,百度即可
12动画&过渡
干什么用
在插入,更新和移除Dom元素的时候,给元素添加样式类名
动画效果
- 使用transition标签包裹住需要动画的标签
- 定义动画
- 使用v-enter-active和.v-leave-active进来和出去调用动画
<template>
<div>
<button @click="IsShow=!IsShow">切换</button>
<transition appear>
<p v-show="IsShow">你好</p>
</transition>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
IsShow: true
}
}
}
</script>
<style scoped>
p {
height: 50px;
width: 400px;
background-color: aqua;
}
//进来和出去的的过程执行的
.v-enter-active {
animation: zz 0.1s;
}
.v-leave-active {
animation: zz 0.1s linear reverse;
}
/* 定义一个动画关键帧 */
@keyframes zz {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
</style>>
过度效果
-
使用transition标签
-
使用.v-enter,.v-enter-to,.v-leave,.v-leave-to
-
定义开始到结束的时间v-enter-active,.v-leave-active
/* 定义进入时开始的位置和出去结束的位置 */ .v-enter, .v-leave-to { transform: translateX(-100%); } /* 定义出去时结束的位置和出去时开始的位置 */ .v-enter-to, .v-leave { transform: translateX(0); } /* 还需要定义这个过渡的时间等 */ .v-enter-active, .v-leave-active { transitio
多个元素过渡(列表过渡)
-
使用标签transition-group标签
-
必须要有key
<transition-group appear> <p v-show="!IsShow" :key="1">你好</p> <p v-show="IsShow" :key="2">刘超</p> </transition-group> </div> </template>
第三方库
这里以animate.css(官网: 动画.css |CSS 动画的跨浏览器库。 (animate.style) )为例子
步骤
-
修改name:为第三方库提供的name
-
使用属性 enter-active-class和leave-active-class进入和出去,调用某一个动画
<transition-group name="animate__animated animate__bounce" enter-active-class="animate__shakeY" leave-active-class="animate__bounceOut" appear> <p v-show="!IsShow" :key="1">你好</p> <p v-show="IsShow" :key="2">刘超</p> </transition-group>
注意点
- transition这个标签不会再浏览器元素中显示,当你实现动画时,会出现下面一瞬间添加属性的效果
- appear是出场动画,为true实现
- 当有多个不同的标签要实现过渡动画,就需要定义name进行区分
13.插槽
干啥用?
- 解决了有相同布局,但是展示标签内容不同的组件,提高复用
- 也可以说是父组件在子组件指定位置插入某个html结构,也是组件间的通信方式
怎么用?
-
在定义的子组件里想展示不一样内容的位置(挖坑) 写入
标签 -
在父组件运用子组件时,使用双标签,在双标签里,填入想展示的内容,填坑
分类
默认插槽
子组件One.vue
<template>
<div class="cathgory">
<p>{{title}}</p>
//挖坑
<slot>我是默认值,如果组件没有定义,就显示这句话</slot>
</div>
</template>
<script>
export default {
name: "One",
props: ["title"]
}
</script>
<style>
.cathgory {
height: 300px;
width: 200px;
background-color: aqua;
text-align: center;
}
</style>
父组件APP.vue
<template>
<div class="whole">
<One title="music">
//填坑
<ul>
<li v-for="(i,index) in 5"
:key="index">{{i}}</li>
</ul>
</One>
<One title="games">
<img src="./assets/logo.png"></img>
</One>
<One title="films">
<video src="https://www.bilibili.com/video/BV1Zy4y1K7SH?p=102"></video>
</One>
</div>
</template>
<script>
import One from './components/One.vue';
export default {
name: 'App',
components: {
One,
}
}
</script>
<style scoped>
.whole {
display: flex;
justify-content: space-around;
}
</style>
具名插槽
挖坑的地方起名
<div class="cathgory">
<p>{{title}}</p>
<slot name="first">我是默认值1,如果组件没有定义,就显示这句话</slot>
<slot name="second">我是默认值2,如果组件没有定义,就显示这句话</slot>
</div>
填坑的地方使用名
- 当多个标签要用到一个插槽时,都补上一个很麻烦,怎么解决
- 用div? 会多了一个div,不好看,那用什么
- 用template,template是不会显示在元素上的
<One title="music">
<ul slot="first">
<li v-for="(i,index) in 5"
:key="index">{{i}}</li>
</ul>
<p slot="second">插槽2</p>
</One>
<One title="games">
<img slot="first"
src="./assets/logo.png"></img>
</One>
<One title="films">
<template slot="first"> //v-slot:first
<video src="https://www.bilibili.com/video/BV1Zy4y1K7SH?p=102"></video>
<p>欢迎收看电影</p>
</template>
</One>
作用域插槽
为什么需要?
能够做到将挖坑的子组件的数据传给填坑的父组件中的插槽处
怎么用?
挖坑处传数据
One.vue
//可以传多组数据
<slot name="first"
:musics="music">我是默认值1,如果组件没有定义,就显示这句话</slot>
data () {
return {
music: [
"稻香", "龙卷风", "青花瓷", '晴天'
]
}
APP.vue
- 一定要使用template接收,收到的会是一个对象(为啥是对象,因为可能拿到多组数据)
- 其实这些可以用子传父完成,官方给我们一种精简版本
<One title="music">
<template slot="first"
scope="myMusic">
<ul>
<li v-for="(i,index) in myMusic.musics"
:key="index">{{i}}</li>
</ul>
<p>插槽2</p>
</template>
</One>
填坑处拿数据
14.vuex
官网链接: 安装 | Vuex (vuejs.org)
干啥用?
先看官网:
- Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 (状态就是数据)
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
总结:
也就是解决多个组件需要相同的数据,适用于多组件数据的共享通信
结构分析
看图可知结论
- vuex:是由Actions,Mutations,State三部分(都是对象)组成
- 我们的组件是调用一个方法dispatch(派遣)给Action,参数有需要执行的方法和State里的数据
- Action又通过commit(提交)方法给Mtations,commit的参数也是执行的方法和State的数据
- Mutate方法不需要我们调用,会自动修改State的数据,如何会Render渲染我们的组件
思考:
上面的结论点有个冲突,感觉Actions是多余的,因为只是把数据传进去又传出来,那么Actions有什么用呢
-
Actions上面有个Backend Api(后端接口),也就是说,当需要从另外一台服务器拿到数据,就需要Action执行,
-
那么如果数据可以直接拿到,Actions是不是就多余了,所以我们的组件是可以直接调用commit的,跳过Actions方法
注意点:
- vuex三部分由store对象管理
- dispatch和commit都是store的方法
- Mutations才是正在做数据修改的地方
怎么用?
环境的搭建
- 安装vuex,记住vue2对应vuex3,vue3对应vuex4
- vue.use(vuex),全局使用插件
- 为什么要这样,因为我们想所有的组件上都有store对象,我们必须要用到store对象的方法
- 创建store商店
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
// 引入vuex
// 使用插件,得放到store里
// 引入我们创建的store,后面不写文件默认index
import store from './store'
//关闭Vue的生产提示
Vue.config.productionTip = false
var vm=new Vue({
el: '#app',
store,
render: h => h(App)
})
console.log(vm)
store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
// 这里得使用插件,必须放在这里,因为脚手架的约定
Vue.use(Vuex)
// 响应组件的动作(可以做异步)
const Actions = {}
// 操作数据
const Mumations = {}
// 储存数据
const State = {}
var store=new Vuex.Store({
Actions,
Mumations,
State
})
export default store
遇到的问题
在创建store对象前,必须要先使用vuex插件,而你引入store就会执行完,如果你想把引用vuex和执行vuex插件放在前面,但是由于脚手架,默认把所有import优先放在前面,所以你还是不能解决
那该怎么解决?
把引用vuex和执行vuex插件在store里执行就行
基础的使用
index.js
import Vuex from 'vuex'
import Vue from 'vue'
// 这里得使用插件,必须放在这里,因为脚手架的约定
Vue.use(Vuex)
// 响应组件的动作(可以做异步),和一些业务处理
const actions = {
ji (context, value) {
if (context.state.sum % 2 != 0) {
context.commit('JI', value)
}
}
}
// 操作数据
const mutations = {
add (state, value) {
state.sum += value
},
jian (state, value) {
state.sum -= value
},
JI (state, value) {
state.sum += value
}
}
// 储存数据
const state = {
sum: 0
}
// 类似于计算属性,可以对数据进行处理
const getters = {
sumLast (state) {
return state.sum * 10;
}
}
var store = new Vuex.Store({
actions,
mutations,
state,
getters
})
export default store
count.vue
<template>
<div>
<p>当前总和{{this.$store.state.sum}}</p>
<p>当前总和*10{{this.$store.getters.sumLast}}</p>
<select v-model.number="n">
<option v-for="x in xuan"
:key="x">{{x}}</option>
</select>
<button @click="add">+</button>
<button @click="jian">-</button>
<button @click="ji">奇数才能+</button>
</div>
</template>
<script>
export default {
name: "count",
data () {
return {
n: 1,
zong: 0,
xuan: [1, 2, 3]
}
},
methods: {
add () {
this.$store.commit("add", this.n)
console.log(this.$store)
},
jian () {
this.$store.commit("jian", this.n)
},
ji(){
this.$store.dispatch("ji",this.n)
}
}
}
</script>
注意:
-
state储存数据,actions做一些异步或者业务处理,mutations做数据处理
-
action对象中,方法的参数,分别是context(mini版store),和value(我们自己传入的数据)
- 为什么说是mini版store,里面的方法是store的一部分,包括我们常用的commit和state以及dispatch
-
mutations,方法的参数,分别是state(储存数据的对象)和value(我们自己传入的数据)
-
getters对象类似于计算属性(计算属性只能在单组件里使用),通过$store.getters获取值,getter对象里方法的参数是state,跟计算属性一样方法名就是返回的值名,需要返回
-
开发者工具是记录mutations的,也就是如果你在actions做完了数据处理,开发者工具检测不到
思考:
这个vuex的三个组成部分,有点像MVC架构,在count.vue(Controller层)里调用,actions(Service层)里调用,mutations(Dao层)里创建这个真正操作数据的方法
扩展mapState和mapGetters
为啥要用?
我们在插值语法里一直写$store.state.某值,然后我们想到去计算属性里自己定义,但是我们发现还是有大量的重复,所以vuex给我们提供了对象mapState和mapGetters
<p>当前总和{{sum}}</p>
computed: {
sum () {
return this.$store.state.sum
},
sumLast(){
return this.$store.getters.sumLast
}
},
使用
-
引入
import { mapState } from 'vuex' import { mapGetters } from 'vuex'
-
参数 为一个对象,且键值对都是字符串,第一个是方法计算方法sum,第二个是state的数据sum(这里的...是es6的能把对象的key:value帮我们展开)
computed: {
...mapState({ 'sum': 'sum' }),
...mapGetters({ "sumLast": "sumLast" })
//如果key,vue重名可以写成mapGetters({["sumLast"]})
},
扩展mapActions和mapMutations
作用:跟上面扩展一样
methods: {
add () {
this.$store.commit("add", this.n)
},
jian () {
this.$store.commit("jian", this.n)
},
ji () {
this.$store.dispatch("ji", this.n)
}
}
使用
引用
import { mapActions, mapMutations } from 'vuex'
使用
参数:方法名,actions下的方法和mutations下面的方法
methods: {
//默认会帮我们创建方法ji(),add(),jian()
...mapActions({ 'ji': 'ji' }),
...mapMutations({ 'add': 'ADD', 'jian': 'JIAN' })
}
注意:
- 我们发现,这里的话我们需要的参数没有传过去,肯定有问题,可以通过下面这种方式传数据
<button @click="add(n)">+</button>
<button @click="jian(n)">-</button>
<button @click="ji(n)">奇数才能+</button>
- 上面的4个扩展,当两个参数名相同时,简写用数组接收,比如mapGetters({["sumLast"]})
模块化的使用
为什么需要?
你想啊,我们所有的组件共用一个store里的东西,当组件使用的东西多了,那不是很难管理
怎么使用?
在store文件夹中再分一层为moudle层,放各个组件的需要操作数据,最后引入到index里
list2.vue
<template>
<div>
前:
<ul>
<li v-for="(L,index) in list2"
:key="index">{{L}}</li>
</ul>
<input type="text"
v-model="shu">
<button @click="add(shu)">添加</button>
<button @click="addxian(shu)">限制添加</button>
后
<ul>
<li v-for="(L,index) in addlist"
:key="index">{{L}}</li>
</ul>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { mapMutations } from 'vuex'
export default {
name: "list1",
data () {
return {
value: 1,
shu: ""
}
},
computed: {
//简写
...mapState("a", { "list2": "list" }),
addNumber () {
console.log(this.$store.getters["a/addNumber"])
return this.$store.getters["a/addNumber"]
},
sum () {
return this.$store.state.a.sum
},
addlist () {
return this.$store.getters['a/addlist']
}
},
methods: {
...mapMutations("a", { "add": "ADD" }),
addxian () {
this.$store.dispatch("a/addxian", this.shu)
}
}
}
</script>
<style>
</style>
One.js
<template>
<div>
前:
<ul>
<li v-for="(L,index) in list2"
:key="index">{{L}}</li>
</ul>
<input type="text"
v-model="shu">
<button @click="add(shu)">添加</button>
<button @click="addxian(shu)">限制添加</button>
后
<ul>
<li v-for="(L,index) in addlist"
:key="index">{{L}}</li>
</ul>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
name: "list1",
data () {
return {
value: 1,
shu: ""
}
},
computed: {
// ...mapState("a", { "list2": "list" }),
list2 () {
return this.$store.state.a.list
},
addlist () {
return this.$store.getters["a/addlist"]
},
// ...mapGetters("a", { "addlist": "addlist" }),
},
methods: {
// ...mapMutations("a", { "add": "ADD" }),
add () {
return this.$store.commit("a/ADD", this.shu)
},
// ...mapActions("a", { "addxian": "addxian" }),
addxian(){
return this.$store.dispatch("a/addxian", this.shu)
}
}
}
</script>
<style>
</style>
Two.js
const Two = {
state: {
sum: 10
},
mutations: {
ADD (state, value) {
state.sum += value
}
}
}
export default Two
index.js
在store里定义对象modules声明
import Vuex from 'vuex'
import Vue from 'vue'
import One from './Moudle/One'
import Two from './Moudle/Two'
Vue.use(Vuex)
var store = new Vuex.Store({
modules: {
a: One,
b: Two
}
})
export default store
此时我们输出这个时候的store只有我们定义的a,b对象
像上面说的,只有state里只有a和b了,那么数据的调用就会发生改变
数据调用方式
第一种:
-
拿state数据时:调用state里的对象a再去调用里面的数据
-
调用方法(actions和mutations)时(不同组件可能有相同方法):需要 moudle里定义的名字/方法
-
拿getters里的数据要用getters["moudle/getter下的属性"],自己打印store看
methods: {
add () {
return this.$store.commit( 'a/ADD', this.shu)
},
addxian () {
this.$store.dispatch("a/addxian", this.shu)
}
},
computed: {
list2 () {
return this.$store.state.a.list
},
addlist(){
return this.$store.getters['a/addlist']
},
},
第二种:
使用前面的扩展来实现简写
computed: {
//...展开对象的键值对填充到当前对象
//'a':表示store里的a对象
//{key:value}:都是字符串,是a对象下的,在计算属性下,key是最终的计算值,value是state对象下的值
...mapState("a", { "list2": "list" }),
...mapGetters("a", { "addlist": "addlist" }),
},
methods: {
...mapMutations("a", { "add": "ADD" }),
...mapActions("a", { "addxian": "addxian" }),
}
}
注意:
const One = {
namespaced: true,
}
如果在One.js下的对象里没有加入namespaced: true就会出现下面的报错
15.通信总结
父传子:用pros
子传父:props传函数,自定义事件
兄弟组件:全局事件总线(上面都能实现)
vuex:都能实现,但是麻烦,一般用在大型项目上
16.路由
什么是路由?
-
生活中的路由器:一个网络接口对应一台设备(key:value)
-
程序中的路由:
- 前端路由:
- 组件间的跳转
- 后端路由:
- 通过一个请求会找到一个function,并返回数据(比如java的Controller)
- 前端路由:
为什么需要路由?
-
多页面网站:
- 一个网站跳到另一个网站,需要刷新页面,耍起来很不舒服
-
单页面网站(SPA):
- 不刷新网页的情况下更新数据(ajax),舒服
路由的基础使用?
环境步骤:
- 下载vue-router
- use使用插件(跟vuex一样,不使用这个插件,vm实例对象里没有这个router对象)
- 通过router创建路由
- 使用路由
使用步骤:
-
创建路由(此时就算没有下面这个步骤,你也可以通过浏览器链接跳转)
export default new Router({ routes: [ { path: "/shopcast", component: Shop }, { path: "/", component: Lie } ] })
-
使用路由
- to就是路由里配置的path
- link默认为a标签,style样式里想写就必须写a标签的
- router-view就是对应组件呈现的样子,你想呈现在哪,就放哪
<router-view></router-view> <router-link to="/">列表</router-link> <router-link to="/shopcast">购物车</router-link>
-
将router挂到vm上
import Router from './router/index' Vue.use(vueRouter) Vue.config.productionTip = false var vm = new Vue({ el: '#app', router: Router, render: h => h(App) })
项目目录:
.
|-- App.vue
|-- components
| |-- lie.vue
| `-- shop.vue
|-- main.js
`-- router
`-- index.js
lie.vue
<template>
<p>我是列表菜单</p>
</template>
<script>
export default {
name: "Lie"
}
</script>
<style>
p {
font-size: 30px;
}
</style>
shop.vue
<template>
<p>我是购物车</p>
</template>
<script>
export default {
name: "Shop"
}
</script>
index.js
import Router from 'vue-router'
import Shop from '../components/shop.vue'
import Lie from '../components/lie.vue'
export default new Router({
routes: [
{ path: "/shopcast", component: Shop },
{ path: "/", component: Lie }
]
})
main.js
import Vue from 'vue'
import App from './App.vue'
import vueRouter from 'vue-router'
import Router from './router/index'
Vue.use(vueRouter)
Vue.config.productionTip = false
var vm = new Vue({
el: '#app',
router: Router,
render: h => h(App)
})
App.vue
<template>
<div>
<router-view></router-view>
<router-link to="/">列表</router-link>
<router-link to="/shopcast">购物车</router-link>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style scoped>
a {
width: 500px;
margin-right: 20px;
border: 1px solid;
text-decoration: none;
display: block;
height: 40px;
float: left;
font-size: 20px;
height: 40px;
text-align: center;
margin-top: 500px;
}
div{
margin-left: 160px;
}
</style>
注意点
路由组件和一般组件
一般组件:我们会去直接使用 比如:
路由组件:需要我们link 和view标签跳转和呈现
在切换路由组件的过程中,其他路由组件的状态
结论:销毁
验证:
lie.vue
export default {
name: "Lie",
mounted () {
console.log("列表组件被挂载了")
},
beforeDestroy () {
console.log("列表组件被销毁了")
}
}
shop.vue
export default {
name: "Shop",
mounted () {
console.log("商品组件被挂载了")
},
beforeDestroy () {
console.log("商品组件被销毁了")
}
}
点击切换就会发现
$route及 router
结论:
$route:每一个路由组件都是不同的
$router:所有组件共有一个
当你在路由里给组件注册了路由路径时,输出这个组件实例对象,就可以发现
嵌套路由
使用
一个路由里套另一个就行children
路由规则
export default new Router({
routes: [
{ path: "/shopcast", component: Shop },
{
path: "/lie1", component: Lie, children: [
{ path: "lie2", component: Lie2 }
]
}
]
})
组件使用
<template>
<div>
<p>我是列表菜单</p>
<router-link to="/lie1/lie2">lie2啊</router-link>
<router-view></router-view>
</div>
</template>
我的想法
原本我以为需要在父路由组件实例里挂载上当前的路由,再一想好像确实没有必要,因为当定义一个路由配置的时候,path绑定不仅仅绑定了一个component,还有children,所以挂载到vm实例时,一个组件和他所有的子路由组件都挂载上去了
注意点:
- children里的path路径不能带 /
- 子路由组件使用时,to=父路径/子路径
- 父路由会在子路由后销毁
query参数
干啥用?
当一个组件需要复用的时候,只是数据不同就需要传递数据
怎么用?
-
在link上传递数据
-
在对应的路由组件接收数据,每一个路由组件实例都会有各自的$route,里面有数据
项目结构
lie2.vue
<template>
<div>
<ul>
<li v-for="i in mes"
:key="i.y">
<!-- 字符串写法 -->
<!-- <router-link :to="`/lie1/lie2/lie3?id=${i.y}&messega=${i.context}`">{{i.x}}</router-link> -->
<!-- 对象式写法 -->
<router-link :to="{path:'/lie1/lie2/lie3',query:{id:i.y,messega:i.context}}">{{i.x}}</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Lie2",
data () {
return {
mes: [
{ x: "消息1", y: "1", context: "你好1" },
{ x: "消息2", y: "2", context: "你好2" },
{ x: "消息3", y: "3", context: "你好3" },
]
}
},
mounted () {
console.log("lie2挂载上了")
console.log(this)
}
}
</script>
show.vue
<template>
<div>
<p>id:{{$route.query.id}}</p>
<p>messega:{{$route.query.messega}}</p>
</div>
</template>
<script>
export default {
name: "Show",
mounted () {
console.log(this.$route)
}
}
</script>
<style>
</style>
index.js
import Router from 'vue-router'
import Shop from '../pages/shop.vue'
import Lie from '../pages/lie.vue'
import Lie2 from '../pages/lie2.vue'
import Show from '../pages/show.vue'
export default new Router({
routes: [
{ path: "/shopcast", component: Shop },
{
path: "/lie1", component: Lie, children: [
{
path: "lie2", component: Lie2, children: [
{ path: "lie3", component: Show }
]
}
]
}
]
})
命名路由
干啥用?
解决路由路径过长的问题
怎么用?
定义路由配置的时候,加个name
export default new Router({
routes: [
{ name: "shop", path: "/shopcast", component: Shop },
{
name: "lie1",
path: "/lie1", component: Lie, children: [
{
name: "lie2",
path: "lie2", component: Lie2, children: [
{ name: "lie3", path: "lie3", component: Show }
]
}
]
}
]
})
使用时,必须使用对象类型,调用name
<router-link :to="{name:'lie3',query:{id:i.y,messega:i.context}}">{{i.x}}</router-link>
params参数
用法:
- 组件上只传值,不传值的名
- 在配置文件那里定义值名
<!-- params参数 -->
<!-- 字符串写法 -->
<!-- <router-link :to="`/lie1/lie2/lie3/${i.y}/${i.context}`">{{i.x}}</router-link> -->
<!-- 对象写法,并且params只能使用name,不能使用path -->
<router-link :to="{name:'lie3',params:{id:i.y,messega:i.context}}">{{i.x}}</router-link>
{ name: "lie3", path: "lie3/:id/:messega", component: Show }
路由的props配置
为什么使用?
-
在vuex里模板里大量使用$store.state.数据后来我们使用mapState等对象处理简化了它
-
那么在vue-router里该怎么简化呢?
- props属性配置
怎么用?
- 在路由配置里配置对象属性props
- 在对应的组件里接收参数
import Router from 'vue-router'
import Shop from '../pages/shop.vue'
import Lie from '../pages/lie.vue'
import Lie2 from '../pages/lie2.vue'
import Show from '../pages/show.vue'
export default new Router({
routes: [
{ name: "shop", path: "/shopcast", component: Shop },
{
name: "lie1",
path: "/lie1", component: Lie, children: [
{
name: "lie2",
path: "lie2", component: Lie2, children: [
{
name: "lie3", path: "lie3/:id/:messega", component: Show,
// 第一种对象形式,只能赋固定值
// props:{id:"1",messega:"你好"}
//第二种(只适用于params),通过布尔值,如果为真就把所有params的值,传给props
// props: true
// 第三种,函数形式,我们无非就是想拿到$route,函数默认参数就是route
props ($route) {
return {
id: $route.query.id,
messega: $route.query.messega
}
}
}
]
}
]
}
]
})
<template>
<div>
<p>id:{{id}}</p>
<p>messega:{{messega}}</p>
</div>
</template>
<script>
export default {
name: "Show",
mounted () {
console.log(this.$route)
},
//像组件传值一样接收
props: ["id","messega"]
}
</script>
<style>
</style>
push和replace模式
应用场景:
-
push:router-link默认模式,就像栈一样,有一个从上往下压,当前有一个指针,可以返回到之前的历史位置
-
replace:需要在router-link添加replace,每跳转到一个组件,之前的组件记录就会被删掉,无法跳回
<router-link replace to="/lie1">列表</router-link>
<router-link replace to="/shopcast">购物车</router-link>
编程式路由导航
干啥用?
不借助
怎么用?
注意点
记得我们上面说过$route(每一个路由的规则)和$router(路由器)
route:是路由的规则,可以有多个
router:是路由器,是VueRouter的实例对象,全局唯一一个
使用
-
路由规则不变
-
编程路由
- 有两种定义方式,上面说的push和replace
- 对象里的内容跟link 绑定的:to对象内容格式一样
<button @click="p(i)">push显示</button>
<button @click="r(i)">replace显示</button>
methods: {
r (i) {
this.$router.replace({
path: '/lie1/lie2/lie3',
query: { id: i.y, messega: i.context }
})
},
p (i) {
this.$router.push({
name: 'lie3',
query: { id: i.y, messega: i.context }
})
}
}
拓展
前进和后退,同样在$router下的原型对象下
methods: {
forward () {
this.$router.forward();
},
back () {
this.$router.back();
},
go () {
this.$router.to(2);
}
}
路由组件缓存
干啥用?
我们前面(注意点)说过,一个组件跳到另外的组件时,这个组件会被销毁
解决,当我们跳到另一个路由组件仍然需要保存当前组件上的数据
怎么用?
- 使用
标签包裹展示组件 ,此时包裹的所有组件都被缓存 - 使用属性(include,exclude,值为组件名),限制是哪一个组件缓存
<keep-alive include="Lie2">
<router-view></router-view>
</keep-alive>
特别的组件生命周期
干啥用:组件独有的钩子函数,用于捕获路由组件的激活状态
activated和deactivated
当展示的是当前组件时,调用activated
当不是时调用deactivated
全局路由守卫
干什么用?
做权限判断,每次切换路由,都会经过路由守卫
怎么用?
前置守卫(切换前调用)
每次切换的时候就会执行,有三个参数
to:
from:
next():不执行这个方法,组件就不会切换过去
const router = new Router({
routes: [ //meta里存储数据
{ name: "shop", path: "/shopcast", component: Shop, meta: { isOk: true, n: "商店" }
//省略
}
]
})
// 前置守卫
router.beforeEach((to, from, next) => {
// to:去哪,能拿到组件name,path,meta,query,params等
// from:来自哪,同上
if (to.meta.isOk) {
if (localStorage.getItem("guard") == 'lie1') {
next() //放行
}
} else {
alert("权限不足")
}
// console.log(to, from)
})
meta元数据
干啥用的?
router配置里有一个专门供我们存储数据的位置
后置守卫(切换后调用)
每次执行完前置的next方法后才会调用这个
这里有个应用案例:每次切换路由更改对应网页title
// 后置守卫,前置守卫next()后,切换路由后执行
router.afterEach((to, from) => {
//只有两个参数,路由都切换了当然不需要next()
console.log(to, from)
document.title = to.meta.n
})
独享和组件路由守卫
独享守卫:很明显,
- 是某一个路由里单独使用的,
- 只有前置beforeEnter,参数与全局的一致
- 无后置
routes: [
{
name: "shop", path: "/shopcast", component: Shop, meta: { isOk: true, n: "商店" }, beforeEnter (to,from,next) {
console.log(to,from,next)
}
}
]
组件路由守卫
- 放在组件里
- 参数一样3个
- 这里的before和after和全局路由守卫前后置的区别
- 全局守卫前置:那你从一个路由切换到另外一个执行,此时不执行next()方法,将无法执行后置,并不会路由跳转
- 全局后置:就是跳转成功后执行
- 组件路由beforeRouteEnter().进入到当前组件(必须通过路由规则进入)的路由执行
- 组件路由beforeRouteLeave(),离开当前组件(必须通过路由规则离开)到另外一个组件执行
- 区别:
- 除了上面使用的区别
- 组件路由跳转成功,执行一个方法
- 而全局跳转成功则是执行两个
export default {
name: "Lie",
mounted () {
// console.log(this)
// console.log("列表组件被挂载了")
},
beforeDestroy () {
// console.log("列表组件被销毁了")
},
methods: {
lie4 () {
this.$router.push({
name: "lie4",
})
}
},
// 当进入当前路由组件时,执行
beforeRouteEnter (to, from, next) {
console.log("进入")
console.log(to, from, next)
next()
},
// 当退出当前路由组件,执行
beforeRouteLeave (to, from, next) {
console.log("出去")
console.log(to, from, next)
// next()
}
}
hash和history模式及打包
hash
- 带#,不美观
- 默认就是这个工作模式
- 兼容性好
-
的作用,#后的都只是hash值,并不是地址,也就是说,你在给服务端发请求的时候,#后的不会带过去
history
- 美观
- 兼容性没有hash好
- 在应用部署时上线时,需要后端人员的配合,解决刷新404问题
打包
我们把我们的文件给别人,不可能直接把vue文件给别人
- 所以就需要打包 npm run build
- 生成dist文件夹,下面就是html,css,js文件了
17.element ui
移动端常用ui
Vant
Cube UI
Mint UI
pc端常用ui
Element UI
IView UI
基础使用
-
下载
-
引入,并使用全局插件
-
官网每个组件下面都会有对应属性看着修改就行了
main.js
//引入全部的组件
import Element from 'element-ui'
// 引入全部的样式
import 'element-ui/lib/theme-chalk/index.css';
//注册全局组件
Vue.use(Element)
按需使用
如果直接下载全部引用的话,会导致消耗很大的资源
使用
-
官网下载里的按需引入,下载
-
根据官网配置babel文件
-
用哪个,引入哪个
-
全局组件注册
import { Button, Link, InputNumber, Rate } from 'element-ui'; //此时不需要引入css,引入组件就会帮我们引入css了 // 注册全局组件 第一个参数就是组件的名字,你甚至都可以改成自己的 Vue.component(InputNumber.name, InputNumber) Vue.component("el-link", Link) Vue.component("LC-rate", Rate) Vue.component(Button.name, Button);
报错
解决:下载没找到的东西就行
修改配置文件
官网的: ["es2015", { "modules": false }],
修改成:["@babel/preset-env", { "modules": false }]