10-父传子与todolist案例
hs 6/3/2022
# 1, 组件的通信
在一个Vue项目中,组件之间的通信是非常重要的环节
# 1.1, 父子传递数据给子组件
父子组件之间通信
- 父组件传递给子组件:通过props属性
- 子组件传递给父组件:通过$emit触发事件
父组件传递给子组件
- 父组件有一些数据,需要子组件来进行展示,可以通过props来完成组件之间的通信
- Props是你可以在组件上注册一些自定义的attribute
- 父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值
Props有两种常见的用法
- 方式一:字符串数组,数组中的字符串就是attribute的名称
- 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等
// ======== main.js
// 引入vue核心库
import Vue from 'vue'
// 引入App组件
import App from './App.vue'
Vue.config.productionTip = false
// 初始化项目唯一的vm实例
// 把App组件渲染到容器中
new Vue({
render: h => h(App),
}).$mount('#app')
// ======== App.vue
<template>
<div id="app">
<h1>父组件 --- 王健林</h1>
<hr>
<!-- car="二八大杠" 叫自定义属性,父传子就是靠自定义属性 -->
<!-- :money="money" 也是自定义属性,值不是字符串,值是number -->
<!-- :changeAppMoney="changeAppMoney" 也是自定义属性 值是一个方法 -->
<MyComponent1
:car="car"
:money="money"
:changeAppMoney="changeAppMoney"
:changeAppCar="changeAppCar"
></MyComponent1>
<hr>
<MyComponent2 :msg="msg"></MyComponent2>
</div>
</template>
<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
name: 'App',
components: {
MyComponent1,
MyComponent2
},
data(){
return{
money:10000000,
car:"二八大杠",
msg:"hello vue"
}
},
methods:{
// 定义修改状态的方法
changeAppMoney(){
this.money = "1个小目标"
},
changeAppCar(){
this.car = "鬼火"
}
}
}
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
// ======== MyComponent1.vue
<template>
<div>
<h1>王思聪组件</h1>
<p>收到父的money ---- {{money}}</p>
<p>收到父的car ---- {{car}}</p>
<!-- <button @click="changeAppMoney">修改钱</button> -->
<button @click="updateMoney">修改钱</button>
<button @click="changeAppCar">修改车</button>
</div>
</template>
<script>
export default{
name:"MyComponent1",
// props代表子组件接收父组件的数据
// props有多种写法,第一种写法,props后面写一个数组
// props中写自定义属性的名字
props:["car","money","changeAppMoney","changeAppCar"],
mounted(){
// console.log(typeof this.car);
// console.log(typeof this.money);
// console.log(this.changeAppMoney);
},
data(){ return {
} },
methods:{
updateMoney(){
// 调用父传递过来的方法
this.changeAppMoney();
}
}
}
</script>
<style lang="less" scoped>
</style>
type的类型都可以是哪些
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
// ======== MyComponent2.vue
<template>
<!-- 在vue2中,需要有一个唯一的根标签 -->
<div>
<h1>我是子组件MyComponent2</h1>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
name: "MyComponent2",
// props:["msg"],
// props的第二种写法:对象的写法
props: {
// msg后面也可以再配置一个对象
// msg:{
// type:String, // 期望父传递的是字符串类型,如果不是,会发出警告
// default:"haha", // 如果父没有传递msg,默认值是haha
// }
// String Number Boolean Array Object Date Function Symbol
msg: {
type: String,
required:true, // msg数据必须要传,如果不传就发出警告
},
},
data() {
return {};
},
methods: {},
};
</script>
<style lang="less" scoped>
</style>
Prop 的大小写命名
- HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符
- 这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名;
非Prop的Attribute
- 当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的Attribute
- 常见的包括class、style、id属性等
- 当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中
禁用Attribute继承
- 不希望组件的根元素继承attribute,可以在组件中设置 inheritAttrs: false
- 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素
- 可以通过 $attrs来访问所有的 非props的attribute
# 2, TodoList
/*index.css*/
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;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.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;
}
/*item*/
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;
}
/*footer*/
.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;
}
<!-- todo.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
<ul class="todo-main">
<li>
<label>
<input type="checkbox" />
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
<li>
<label>
<input type="checkbox" />
<span>yyyy</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</ul>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>
</body>
</html>
// App.vue
<template>
<div class="todo-container">
<h4>{{todos}}</h4>
<h1>{{isAllDone}}</h1>
<div class="todo-wrap">
<TodoHeader :addTodo="addTodo"></TodoHeader>
<TodoMain :todos="todos" :updateDone="updateDone" :deleteTodo="deleteTodo"></TodoMain>
<TodoFooter
:todos="todos"
:todoIsDone="todoIsDone"
:isAllDone="isAllDone"
:updateAllDone="updateAllDone"
:deleteDoneTodo="deleteDoneTodo"
></TodoFooter>
</div>
</div>
</template>
<script>
import TodoHeader from "./components/TodoHeader.vue";
import TodoMain from "./components/TodoMain.vue";
import TodoFooter from "./components/TodoFooter.vue";
export default {
name: "App",
components: {
TodoHeader,
TodoMain,
TodoFooter,
},
data() {
return {
// todos:[
// {id:"01",title:"学习vue",done:true},
// {id:"02",title:"学习react",done:false},
// {id:"03",title:"学习小程序",done:false},
// ]
todos:JSON.parse(localStorage.getItem("TODO")) || []
};
},
methods: {
// 更新todo的Done
updateDone(id,done){
// 01 false
// 03 true
// console.log(id,done);
// for循环遍历
// for(let i=0; i<this.todos.length; i++){
// if(this.todos[i].id == id){
// this.todos[i].done = done
// }
// }
// forEach
// this.todos.forEach(todo=>{
// if(todo.id == id){
// todo.done = done;
// }
// })
// 使用map
// this.todos = this.todos.map(item=>{
// if(item.id == id){
// return { ...item, done}
// }
// return item;
// })
// 简化后的
this.todos = this.todos.map(item=>item.id==id?{...item,done}:item)
},
// 删除单个todo
deleteTodo(id){
// this.todos = this.todos.filter(item=>{
// if(item.id==id){
// return false;
// }else{
// return true
// }
// })
this.todos = this.todos.filter(item=>item.id==id?false:true)
},
// 添加todo
addTodo(todo){
// find:如果找到了,返回对应的todo 如果没有找到,返回und
let repeat = this.todos.find(item=>item.title === todo.title);
console.log(repeat);
// if(!repeat){
// this.todos.push(todo)
// }else{
// alert(`【${todo.title}】任务已存在`)
// }
// !repeat && this.todos.push(todo);
!repeat ? this.todos.push(todo) : alert(`【${todo.title}】任务已存在`)
},
// 全选和反选
updateAllDone(done){
this.todos = this.todos.map(item=>({...item,done}))
},
// 清除已完成
deleteDoneTodo(){
this.todos = this.todos.filter(item=>!item.done)
}
},
computed:{
// 统计已完成的数据
todoIsDone(){
// 过滤出done为true的todo
return this.todos.filter(item=>item.done)
},
// 统计是否全部已完成
isAllDone(){
return this.todos.every(item=>item.done)
}
},
watch:{
todos(){
// 侦听todos,todos数据一旦变化,就会被侦听到
// 需要把数据持久化存储 localStorage
localStorage.setItem("TODO",JSON.stringify(this.todos))
}
}
};
</script>
<style lang="less">
</style>
// TodoHeader.vue
<template>
<div class="todo-header">
<input
type="text"
placeholder="请输入你的任务名称,按回车键确认"
@keyup.enter="enterHandler"
/>
</div>
</template>
<script>
export default {
name: "TodoHeader",
props: ["addTodo"],
data() {
return {};
},
methods: {
enterHandler(e){
if(e.target.value.trim() === ""){
alert("输入内容不能为空~")
return;
}
// 拼装一个todo
let newTodo = {id:Date.now(), title:e.target.value, done:false};
// console.log(newTodo);
this.addTodo(newTodo)
e.target.value = ""; // 清空输入框
}
},
};
</script>
<style lang="less" scoped>
</style>
// TodoMain.vue
<template>
<ul class="todo-main">
<li
v-for="(todo,index) in todos"
:key="todo.id"
@mouseenter="enterHander(index)"
:class="{active:currentIndex === index}"
>
<label>
<input type="checkbox" :checked="todo.done" @change="handler(todo,$event)" />
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" v-show="todo.done" @click="clickHandler(todo.id)">删除</button>
</li>
</ul>
</template>
<script>
export default {
name: "TodoMain",
props: ["todos","updateDone","deleteTodo"],
data() {
return {
currentIndex:-1
};
},
methods: {
// 鼠标移入li
enterHander(index){
// console.log(index);
this.currentIndex = index;
},
// 点击单选框
handler({id},e){
// console.log(id);
// console.log(e.target.checked);
this.updateDone(id,e.target.checked)
},
// 点击删除
clickHandler(id){
// 子调用父传递的方法
this.deleteTodo(id)
}
},
};
</script>
<style lang="less" scoped>
.active{
color: yellowgreen;
background-color: #eee;
}
</style>
// TodoFooter.vue
<template>
<div class="todo-footer">
<label>
<!-- <input type="checkbox" :checked="isAllDone" @change="changeHandler" /> -->
<input type="checkbox" :checked="todoIsDone.length === todos.length && todos.length>0" @change="changeHandler" />
</label>
<span> <span>已完成{{todoIsDone.length}}</span> / 全部{{todos.length}} </span>
<button class="btn btn-danger" @click="clickHandler">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "TodoFooter",
props: ["todos","todoIsDone","isAllDone","updateAllDone","deleteDoneTodo"],
data() {
return {};
},
methods: {
changeHandler(e){
this.updateAllDone(e.target.checked)
},
// 点击清除已完成
clickHandler(){
this.deleteDoneTodo();
}
},
};
</script>
<style lang="less" scoped>
</style>
11-自定义指令
hs 6/3/2022
# 1, 自定义指令
# 1.1, 自定义指令
在Vue的模板语法中我们学习过各种各样的指令:v-show、v-for、v-model等等,除了使用这些指令之外,Vue也允许我们来自定义自己的指令
- 在Vue中,代码的复用和抽象主要还是通过组件
- 通常在某些情况下,你需要对DOM元素进行底层操作,这个时候就会用到自定义指令
自定义指令分为两种
- 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用
- 自定义全局指令:app的 directive 方法,可以在任意组件中被使用
自定义局部指令
// MyComponent1.vue
<template>
<!--
前面学习过各种各样的指令:
v-html v-text v-model v-if v-elseif v-else v-show v-for v-bind v-on v-cloak v-pre v-once
组件:
代码复用
指令:
对DOM元素进行底层操作时,可以使用自定义指令
自定义指令分两类:
1)自定义局部指令:只能在当前组件中使用
2)自定义全局指令:在任意组件中使用
自定义局部指令:
也是一个配置项,叫directives
-->
<div>
<h1>{{msg}}</h1>
<hr>
<h1 v-upper="msg"></h1>
<hr>
<h2 v-upper="msg2"></h2>
</div>
</template>
<script>
export default {
name: "MyComponent1",
props: [],
data() {
return {
msg:"hello vue",
msg2:"abcdef"
};
},
methods: {},
// 自定义指令是配置在directives选项中的
directives: {
// 自定义指令,也是写成函数的形式
// 使用时,需要以v-打头 v-upper
// 当使用v-upper时,下面的函数就会执行一次
// 把字符串中的小写字母变大写
// upper(element,options) {
// // element 表示当前绑定指令的真实DOM节点
// // console.log("upper....");
// // console.log(element);
// // options表示当前指令一些配置项参数(对象)
// // console.log(options);
// element.innerHTML = options.value.toUpperCase();
// },
},
};
</script>
<style lang="less" scoped>
</style>
// MyComponent2.vue
<template>
<div>
<input v-focus type="text">
</div>
</template>
<script>
export default {
name: "MyComponent2",
props: [],
data() {
return {};
},
methods: {},
directives: {
// 自定义局部指令
// focus() {},
},
};
</script>
<style lang="less" scoped>
</style>
// App.vue
<template>
<div id="app">
App
<!-- 在MyCommont1组件中定义的自定义指令,在其它组件中是不能使用的 -->
<p v-upper="msg3"></p>
<hr>
<MyComponent2></MyComponent2>
<hr>
<MyComponent1></MyComponent1>
</div>
</template>
<script>
import MyComponent1 from "./components/MyComonent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
name: 'App',
data(){
return{
msg3:"abc"
}
},
components: {
MyComponent1,
MyComponent2
}
}
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
自定义全局指令
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 自定义全局指令
// 第一个参数,表示自定义指令的名字,定义时,不要加v-
// 第二个参数,是一个回调函数
Vue.directive("upper",(element,options)=>{
element.innerHTML = options.value.toUpperCase();
})
Vue.directive("focus",{
// 配置钩函数 会在合适的时机,自动调用
// 当被绑定的元素插入到 DOM 中时……
inserted(element){
// element是对应的DOM元素
console.log("element:",element);
console.log("inserted........");
element.focus(); // 自动获取焦点
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
12-过滤器
hs 6/3/2022
# 1, 过滤器
# 1.1, 过滤器的使用
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)
- 过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示
局部过滤器
// MyComponent1.vue
<template>
<div>
<!-- 使用过滤器,需要对time时间进行格式化,进行过滤 -->
<h1>{{ time | timeFormat }}</h1>
</div>
</template>
<script>
// 引入moment moment是专门用来处理时间
import moment from "moment";
export default {
name: "MyComponent1",
props: [],
data() {
return {
time: Date.now(),
};
},
methods: {},
// 在filters选项中,可以配置局部过滤器
// 在这里配置的局部过滤器,只能在当前组件中使用
// filters: {
// // 定义了一个过滤器,叫timeFormat
// // 在模板中,就可以使用过滤器了
// timeFormat(params){
// // console.log(params);
// // 利用moment对时间进行格式化
// // return 666;
// // 需要return一个格式化后的时间
// return moment(params).format("YYYY-MM-DD")
// }
// },
};
</script>
<style lang="less" scoped>
</style>
// MyComponent2.vue
<template>
<div>
<h1>{{ time | timeFormat }}</h1>
</div>
</template>
<script>
export default {
name: "MyComponent1",
props: [],
data() {
return {
time:Date.now(),
};
},
methods: {},
};
</script>
<style lang="less" scoped>
</style>
// App.vue
<template>
<div id="app">
App
<hr>
<MyComponent1></MyComponent1>
<hr>
<MyComponent2></MyComponent2>
</div>
</template>
<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
name: 'App',
data(){
return{
msg3:"abc"
}
},
components: {
MyComponent1,
MyComponent2
}
}
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
全局过滤器
// main.js
import Vue from 'vue'
import App from './App.vue'
import moment from "moment"
Vue.config.productionTip = false
// 定义全局过滤器
// 第一个参数是过滤器的名字
// 第二个参数是回调函数
Vue.filter("timeFormat",(val)=>{
return moment(val).format("YYYY-MM-DD")
})
new Vue({
render: h => h(App),
}).$mount('#app')
13-自定义插件
hs 6/3/2022
# 1, 自定义插件
# 1.1, 自定义插件
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者 property。如:vue-custom-element
- 添加全局资源:指令/过滤器/过渡等。如 vue-touch
- 通过全局混入来添加一些组件选项。如 vue-router
- 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
// plugins/myplugins.js
// 开发自定义插件,目的是给项目提供一些全局的功能
import moment from "moment"
// plugins对象,叫插件对象
let plugins = {
// vue规定,一个插件对象身上,必须要有install方法
// 当Vue.use时,内部会自动执行indtall方法
install(Vue, options) {
// 第一个参数是Vue构造函数
// 第二个参数是Vue.use时,传递的第二个参数
// console.log(Vue);
// console.log(options);
// 把自定义指令封装到一个插件中
Vue.directive("upper", (element, options) => {
element.innerHTML = options.value.toUpperCase();
})
// 把过滤器封装到插件中
Vue.filter("timeFormat", (val) => {
return moment(val).format("MM-DD")
})
// 还可以在Vue的原型对象上,添加公共的方法
Vue.prototype.$wc = function () {
alert("这是一只小狗,叫wc")
}
// 注册全局组件
Vue.component("Count", {
data() {
return {
count: 0
}
},
methods: {
add() { this.count++ },
minus() { this.count-- }
},
// render函数后面说
render: function (createElement) {
return createElement(
'h1',
{},
"我是Count组件"
)
},
})
// 后面会讲两个非常重要的插件,vue-router vuex
}
}
// 对外暴露插件 为了让别的模块去使用插件
export default plugins;
// MMyComponent1.vue
<template>
<div>
<h1 v-upper="msg"></h1>
<Count></Count>
</div>
</template>
<script>
export default {
name: "MyComponent1",
props:[],
data() {
return {
msg:"hello vue"
};
},
methods: {},
};
</script>
<style lang="less" scoped>
</style>
// MMyComponent2.vue
<template>
<div>
<h1>{{ time | timeFormat }}</h1>
<Count></Count>
</div>
</template>
<script>
export default {
name: "MyComponent2",
props:[],
data() {
return {
time:Date.now()
};
},
methods: {},
};
</script>
<style lang="less" scoped>
</style>
// MMyComponent3.vue
<template>
<div>
<button @click="fn">点我</button>
<Count></Count>
</div>
</template>
<script>
export default {
name: "MyComponent3",
props: [],
data() {
return {};
},
methods: {
fn() {
// this 表示VC
this.$wc();
},
},
};
</script>
<style lang="less" scoped>
</style>
// App.vue
<template>
<div id="app">
App
<hr>
<MyComponent1></MyComponent1>
<hr>
<MyComponent2></MyComponent2>
<hr>
<MyComponent3></MyComponent3>
</div>
</template>
<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
import MyComponent3 from "./components/MyComponent3.vue"
export default {
name: 'App',
data(){
return{
msg3:"abc"
}
},
components: {
MyComponent1,MyComponent2,MyComponent3
}
}
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 使用插件
import myplugins from "./plugins/myplugins"
Vue.use(myplugins,{name:"upper"})
new Vue({
render: h => h(App),
}).$mount('#app')
14-自定义事件与todolist案例
hs 6/3/2022
# 1, 自定义事件
# 1.1, 子组件传递给父组件
什么情况下子组件需要传递内容到父组件呢
- 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容
- 子组件有一些内容想要传递给父组件的时候
自定义事件的操作步骤
- 首先,我们需要在子组件中定义好在某些情况下触发的事件名称
- 其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中
- 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
// App.vue
<template>
<div class="app">
<h1>我是父组件 王健林</h1>
<hr>
<!--
在使用子组件的位置绑定自定义事件
事件源:MySon
事件类型:wc
监听器:handler
如何触发MySon事件?
答:在事件源(组件)内部通过$emit手动写代码触发!!!
$emit("wc",500)
-->
<MySon @wc="handler" ref="cur"></MySon>
</div>
</template>
<script>
import MySon from "./components/MySon.vue"
export default {
name: 'App',
components: {
MySon
},
// 当组件挂载完毕执行一次mounted
mounted(){
// 当代码走到这里,说明,组件都挂载,渲染完毕
// 问:能不能获取组件实例? 答:可以
// console.log(this.$refs.cur); // 得到VC实例
// 可以通过$on绑定自定义事件
// 当xq事件发生了,处罚监听器
this.$refs.cur.$on("xq",(val)=>{
// 谁触发了xq事件,就可以传递数据
console.log("val:",val);
})
},
methods:{
handler(val){
// val是子传递给父的数据
console.log("val:",val);
}
}
}
</script>
<style lang="less">
*{
margin: 0;
padding: 0;
}
html,body{
width: 100%;
height: 100%;
}
.app{
width: 100%;
height: 100%;
background-color: pink;
}
</style>
// MySon.vue
<template>
<div class="son">
<h1>我是子组件 王思聪</h1>
<!-- @click="clickHandler" 不叫自定义事件 -->
<button @click="clickHandler">触发自定义事件</button>
</div>
</template>
<script>
export default {
name: "MySon",
props:[],
data() {
return {
money:500,
car:"凤凰牌自行车"
};
},
methods: {
clickHandler(){
// console.log("clickHandler...");
// 在这里,触发自定义事件
// 在vc身上没有是$emit,沿着原型链可以去Vue的原型对象上找到$emit
// console.log(this);
// this.$emit("wc",this.money)
this.$emit("xq",this.car)
}
},
};
</script>
<style lang="less" scoped>
div.son{
width: 300px;
height: 200px;
background-color: gold;
margin: 50px;
}
</style>
# 2, 自定义事件应用于TodoList
// App.vue
<TodoHeader @addTodo="addTodo"></TodoHeader>
// TodoHeader.vue
<template>
<div class="todo-header">
<input
type="text"
placeholder="请输入你的任务名称,按回车键确认"
@keyup.enter="enterHandler"
/>
</div>
</template>
<script>
export default {
name: "TodoHeader",
data() {
return {};
},
methods: {
enterHandler(e){
if(e.target.value.trim() === ""){
alert("输入内容不能为空~")
return;
}
// 拼装一个todo
let newTodo = {id:Date.now(), title:e.target.value, done:false};
// 触发自定义事件
this.$emit("addTodo",newTodo)
e.target.value = ""; // 清空输入框
}
},
};
</script>
<style lang="less" scoped>
</style>
15-事件总线
hs 6/3/2022
# 1, 事件总线
# 1.1, 事件总线
在开发中,我们构建了组件树之后,除了父子组件之间的通信之外,还会有非父子组件之间的通信
- 全局事件总线
- Provide/Inject
// ErZi1.vue
<template>
<div class="erzi1">
<h1>我是儿子1组件</h1>
<button @click="$bus.$emit('maotai','一车茅台')">给老二一车茅台</button>
</div>
</template>
<script>
export default {
name: "ErZi1",
props:[],
data() {
return {
};
},
methods: {},
};
</script>
<style lang="less" scoped>
.erzi1{
width: 400px;
height: 200px;
background-color: gold;
margin: 50px 0px;
}
</style>
// ErZi2.vue
<template>
<div class="erzi2">
<h1>我是儿子2组件</h1>
<SunZi></SunZi>
</div>
</template>
<script>
import SunZi from "./SunZi.vue"
export default {
name: "ErZi2",
props: [],
data() {
return {};
},
mounted(){
this.$bus.$on("maotai",val=>{
console.log("val:",val);
})
},
methods: {},
components:{
SunZi
}
};
</script>
<style lang="less" scoped>
.erzi2 {
width: 400px;
height: 200px;
background-color: pink;
margin: 50px 0px;
}
</style>
// SunZi.vue
<template>
<div class="sunzi">
<span>我是孙子组件</span>
<button @click="clickHandler">点击给爷爷100万</button>
</div>
</template>
<script>
export default {
name: "SunZi",
props:[],
data() {
return {
money:"100万"
};
},
methods: {
clickHandler(){
// 发布
this.$bus.$emit("money",this.money)
}
},
};
</script>
<style lang="less" scoped>
.sunzi{
width: 300px;
height: 100px;
background-color: skyblue;
margin: 20px 0px;
}
</style>
// App.vue
<template>
<div id="app">
<h1>我是App组件----{{money}}</h1>
<ErZi1></ErZi1>
<ErZi2></ErZi2>
</div>
</template>
<script>
import ErZi1 from "./components/ErZi1.vue"
import ErZi2 from "./components/ErZi2.vue"
export default {
name: 'App',
data(){
return{
money:0
}
},
mounted(){
// 在mounted中进行订阅
// 绑定一个自定义事件
this.$bus.$on("money",(val)=>{
console.log("爷爷接收money:",val);
this.money = val
})
},
components: {
ErZi1,
ErZi2
}
}
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// let $bus = new Vue(); // $buts就相当于是vm实例 通过$bus可以得到$on $emit
// Vue.prototype.$bus = new Vue();
new Vue({
beforeCreate(){
// this表示vm
// 配置全局事件总线,说白了,就是在Vue原型对象上添加$bus属性,值是vm
// this是vm,不是vc
// 在$bus身上有,$on和$emit $on可以用来接收数据 $emit可以用来发送数据
Vue.prototype.$bus = this;
},
render: h => h(App),
}).$mount('#app')
// 目前为止,vue中数据通信的方案?
// 1)props 自定义属性 一般是父传子 如果传递的是方法,也可以实现子传父
// 2)emit 自定义事件 子传子 子可以把数据传递给父
// 3)事件总线 $bus 值是一个vm $on订阅 $emit发布
16-github案例(数据请求)
hs 6/3/2022
# 1, 数据请求实战案例
// MyHeader.vue
<template>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input
type="text"
placeholder="enter the name you search"
v-model="keyword"
/> <button @click="clickHandler">Search</button>
</div>
</section>
</template>
<script>
export default {
name: "MyHeader",
props:[],
data() {
return {
keyword:""
};
},
methods: {
clickHandler(){
if(this.keyword.trim() === ""){
alert("用户名不能为空!")
return;
}
// 目的:发送数据级兄弟
this.$bus.$emit("sendKeyword",this.keyword)
this.keyword = ""; // 清除输入框中的内容
}
},
};
</script>
<style lang="less" scoped>
</style>
// MyMain.vue
<template>
<div>
<!-- 欢迎界面 -->
<div v-show="show==0">欢迎....</div>
<!-- 正在加载中 -->
<div v-show="show==1">正在加载中....</div>
<!-- 正常显示用户信息 -->
<div class="row" v-show="show==2">
<div class="card" v-for="(card) in items" :key="card.id">
<a href="https://github.com/xxxxxx" target="_blank">
<img :src="card.avatar_url" style="width: 100px" />
</a>
<p class="card-text">{{ card.login }}</p>
</div>
</div>
<!-- 失败 -->
<div v-show="show==3">失败....</div>
</div>
</template>
<script>
// import axios from "axios";
import request from "../api/request"
export default {
name: "MyMain",
props: [],
// mounted() {
// this.$bus.$on("sendKeyword", async (val) => {
// // 发送ajax请求
// this.show = 1;
// // console.log("val:",val);
// try {
// // let result = await axios.get(
// // `https://api.github.com/search/users?q=${val}`
// // );
// // 现在的api接口是 /search/users?q=${val}
// // 前面没有写域名,没有写端口
// // 默认就向当前服务器发请求
// // 当前服务器是谁?
// // 答:http://localhost:8081/
// let result = await axios.get(`/api/search/users?q=${val}`);
// // console.log(result);
// this.items = result.data.items;
// this.show = 2;
// } catch (error) {
// console.log("error:", error);
// this.show = 3;
// }
// });
// },
mounted() {
this.$bus.$on("sendKeyword", async (val) => {
this.show = 1;
try {
// request是我们二次封装的axios
// 后面写项目,一般都是使用封装后的axios
let result = await request.get(`/api/search/users?q=${val}`);
this.items = result.items;
this.show = 2;
} catch (error) {
console.log("error:", error);
this.show = 3;
}
});
},
data() {
return {
items: [],
show: 0, // 0表示欢迎界面 1表示正在加载 2表示用户信息 3失败
};
},
methods: {},
};
</script>
<style lang="less" scoped>
</style>
// App.vue
<template>
<div id="app">
<div class="container">
<MyHeader></MyHeader>
<MyMain></MyMain>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyMain from "./components/MyMain.vue";
export default {
name: "App",
data() {
return {
money: 0,
};
},
mounted() {},
components: {
MyHeader,
MyMain,
},
};
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
beforeCreate(){
Vue.prototype.$bus = this;
},
render: h => h(App),
}).$mount('#app')
// api/request.js
// 对axios的二次封装
import axios from "axios";
// 引入进度条
import nprogress from "nprogress";
// 引入对应的样式
import "nprogress/nprogress.css"
// 配置请求拦截器
axios.interceptors.request.use(config=>{
// 在请求拦截器中,还可以做很多的事情
// 开启进度条
nprogress.start();
return config
})
// 配置响应拦截器
axios.interceptors.response.use(res=>{
nprogress.done();
return res.data;
},()=>{
// 响应失败
nprogress.done();
// 终止Promise链
return new Promise();
})
// 导出我们封装后的axios
export default axios;
17-vuex状态管理
hs 6/3/2022
# 1, vuex状态管理
# 1.1, 为什么要使用Vuex
Vue应用程序的状态(state)管理器。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- 把“状态”换成“数据”。
- 如果你的项目中,需要用到在各个子组件中共享数据,则你就需要用到vuex。
# 1.2,示例
设置如下组件:
- 一个vue实例,充当根组件
- 子组件AddNumber
- 子组件SubNumber
希望在两个子组件AddNumber和subNumer之间共同维护和使用数据项:counter
- 在vue实例中:显示 counter的值
- 在子组件AddNumber中:显示 counter的值;添加counter的值
- 在子组件subNumber中:显示 counter的值;减少counter的值
- 由于上面的需要,我们需要在三个地方共同使用同一数据项counter,所以我们需要用到 vuex
第一步:先引入vue.js,再引入vuex.js
<!-- 引入Vue.js -->
<script src="./libs/vue.js"></script>
<!-- 引入Vuex.js -->
<script src="./libs/vuex.js"></script>
第二步:实例化vuex中的store对象
- 实例化一个对象,就是通过new的方式去创建一个对象
// 创建一个仓库 需要传入一个配置对象
let store = new Vuex.Store({
// state是状态 是集中管理的状态
// 状态是响应式的
// 组件中的data中的状态也是状态式
state: {
counter: 0
},
// 修改状态的唯一途径
mutations: {
// 每一个mutations就是一个函数
// 第1个参数是仓库中的state
// payload 是commit mutation时,传递的参数
add(state, payload) {
// state.counter++;
state.counter += payload
},
sub(state) {
state.counter--;
}
},
// 放异步代码,如果有异步操作,代码需要写在actions中
actions: {
},
// 类似于组件中的计算属性
// 根据已有状态计算出一个新的状态
getters: {
}
});
第三步:注入到Vue实例中
let vm = new Vue({
el: "#app",
store, // vuex也是一个插件,需要挂载到根组件上,就意味着,它的子子孙孙,都可以使用这个仓库了
components: {
AddCounter,
SubCounter
},
data() {
return {
}
}
});
第四步:定义两个组件并在App中使用之
let AddCounter = Vue.extend({
template: `
<div>
<p>我是addcounter组件</p>
<p>在addcounter组件中使用仓库中的数据:{{$store.state.counter}}</p>
<button @click="add">加1</button>
</div>
`,
methods: {
add() {
// 这样是粗暴地修改状态 极力不推荐
// 如果其它组件也这样修改状态,状态不好追踪了
// this.$store.state.counter++;
// 需要commit一个mutation,通过mutation去修改状态
this.$store.commit("add", 100)
}
}
})
let SubCounter = Vue.extend({
template: `
<div>
<p>我是subcounter组件</p>
<p>在subcounter组件中使用仓库中的数据:{{$store.state.counter}}</p>
<button @click="sub">减1</button>
</div>
`,
methods: {
sub() {
// this.$store.state.counter--;
this.$store.commit("sub")
}
}
})
第五步:使用store中的数据
- 一旦你在vue的实例中注入了store,则在所有的子组件及 vue的实例中,你都可以通过:this.$store.state. 数据名 去获取数据
- 类似于我们把router注入到vue实例中,我们就可以通过this.route操作路由。
<p>在addcounter组件中使用仓库中的数据:{{$store.state.counter}}</p>
<p>在subcounter组件中使用仓库中的数据:{{$store.state.counter}}</p>
- 可以通过:this.$store.state. 数据名 = 值 去修改数据,但是,vuex反对这么做
你可以有两种方法去使用数据
- 获取:this.$store.state. 数据名
- 修改:在组件内部通过this.$commit方法触发事件,执行mutations当中的对应的方法
# 2, 在vue脚手架中使用vuex
设置如下组件:
- 一个vue实例,充当根组件
- 子组件AddNumber。
- 子组件subNumber。
希望在两个子组件AddNumber和subNumer之间共同维护和使用数据项:counter
- 在vue实例中:显示 counter的值
- 在子组件AddNumber中:显示 counter的值;添加counter的值
- 在子组件subNumber中:显示 counter的值;减少counter的值
# 3, vuex的四个概念
- state: 放数据。在组件之间共享
- mutations: 修改数据 修改数据的唯一途径
- getters: 从state中的数据中,取出一部分来,依据数据项产生新的结果。类似于vue实例中的computed(计算属性)。
- actions: 在对数据实现异步操作时,要用的
# 3.1, store
- 每一个 Vuex 应用的核心就是 store(仓库)
- store是一个容器,包含着vue应用的状态(state)
两大特点
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新
- 你不能直接更改 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交(commit) mutations。这样使得我们可以方便地跟踪每一个状态的变化
在组件中应该如何去获取保存在vuex. 中的数据
- 方法一:this.$store.state.xxx
- 方法二:变通一下,在组件内部用一个计算属性来获取数据
- 方法三:mapState
mapState
- this.$store.state.count写起来比较麻烦。在vuex中提供一个便捷的写法:mapState。
- 它的作用:是把写在vuex.store.state中的数据直接映射到组件中计算属性中。
# 3.2, Getters
有时希望在state中的数据基础上,派生出一些其它的数据,此时可以使用getters
在组件中使用getters
- 方法一:this.$store.getters.failedNumber
- 方法二 :mapGetters
- 与mapState类似,它的作用也是用来帮助我们去简化代码。我们如果直接定义一个计算属性也是可以的。
# 3.3, Mutations
作用是:它是唯一的用来修改数据的工具
在组件中使用的方式
- 方法一:this.$store.commit
- 方法二:mapMutations
# 3.4, Actions
在mutations中,如果操作是异步的,在控制台中并不能追踪到
- 可以把异步操作写在actions中
- 虽然把异步操作写在actions中,但是改变状态的唯一方式是不变,还是通过mutations
action与mutations的区别
- actions 提交的是 mutations,而不是直接变更状态。
- actions 可以包含任意异步操作。
- action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,可以通过context.commit提交,也可以通过context.state获取state。
在组件中使用actions
- 方法一: this.$store.dispatch(“actions名”)
- 方法二:mapActions
# 4, Vuex版本的TodoMVC
目标
# 4.1, 分析
数据项
- 准备两个数据项。放在vuex中,供组件去使用
- todos:[{text:"学习vuex, So easy~",done:false},{text:"学习React, So easy~",done:false}]
- visibilty:,筛选的条件 :all completed active
功能
- 显示todos数据
- 添加一条todos数据
- 删除todos数据
- 修改某一项的状态:完成,没有完成之间切换
- 批量修改状态:全部完成,全部未完成
- 统计没有完成的数量
- 批量删除已经完成的
- 三种状态的筛选
- 编辑todos的内容
- 本地存储
组件
- 在脚手架工具的基础上,额外设置两个组件
# 4.2, 处理静态资源
- 把app.vue中的template的内容用todo.html的内容来代替
- 在main.js中引入对应的css
# 4.3, 创建并引入vuex
在main.js中去引入
import Vue from 'vue'
import App from './App.vue'
import store from "./store/index"
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App),
}).$mount('#app')
# 4.4, 拆分组件(显示todo数据)
listTodo基本的使用
在app.vue组件中,使用listtodo组件
在app.vue组件中给listTodo传递数据,显示todos
现在对list-todo进行循环
效果如下:
通app.vue这个父组件给list-todo这个子组件进行数据传递
- 因为我们需要把一个一个todos具体内容传递list-todo,让它来显示具体的信息
在使用子组件时,设置自定义属性
在子组件中,设置props
# 4.5, 添加一条todos数据(AddTodo组件)
在app.vue中使用
下面就要具体去实现添加一条todo的功能
- 本质上需去vuex.store.state.todos中加一条记录,这里就是涉及修改vuex中的数据,必须要用mutations
- 所以,我们应该先去vuex中去创建好一个mutations。
把这个方法直接映射到addTodo组件的methods中
# 4.6, 删除一条todos数据
删除操作也是要去修改数据,则对应的也要建立一个mutations。
这个删除操作是在listTodo组件中完成的。在 listTodo组件中给删除的按钮绑定事件
# 4.7, 修改某一项的状态:完成,没有完成之间切换
这个操作,本质就是要去修改某一个todo项的done属性。所以也需要在mutations中去写好对应的方法。
这个工作是在listTodo组件完成的,我们去给按钮加点击事件。
在app.vue组件,我们根据todo是否已经完成,设置一个特殊的类:
# 4.8, 批量修改状态:全部完成,全部未完成
- 我们通过input的check属性来设置css样式。如果它是处于check,则说明全部的任务已经完成,则它的样式应该是“高亮”的。
- 首先,我们要去判断当前情况下,是否所有的任务已经全部完成。如果全部完成,则它应该要处于check状态。
- 现在,我们需要在原始数据的基础,加工得到一个新数据:是否全部完成。
- 所以,应该要在vuex.store中的加一个getters
在app.vue中使用:
现在只是实现了:根据是否全部完成来设置input的checked。下面,我们还要给它加点击事件:
- 如果当前是处于checked ,则点击事件发生,要全部改成done:false
- 如果当前是没有选中,则点击事件发生,要全部改成done:true
上面的操作,也是要修改数据,则对应地要去建立mutations
在app.vue中:
使用toggleAllTodo
# 4.9, 统计没有完成的数量
不需要对数据进行修改,只是在原始数据的基础上,加工得到新数据。
- 去vuex.store中设置一个 Getters
把它map到app.vue组件中,成为一个计算属性。
使用:
# 4.10, 批量删除已经完成的
这个按钮,只有在“完成了数量>0”的情况下才可见。
- todos.length是所有任务的数量
- unDoneNumber:还没有完成的数量
- 当Todos.length >unDoneNumber 说明,有一部分是已经完成了的。此时应该要显示这个按钮
下面,接给它添加点击事件,点击之后,要把已经完成的任务从整个的todos当中删除掉。由于这里涉及修改数据 ,我们需要去建立 一个mutations。
下面,把这个功能映射到app.vue组件的methods 中。
给按钮加点击事件:
# 4.11, 三种状态的筛选
这个操作不会更改数据,只是在原数据的基础上进行筛选,得到新数据。
- 所以,我们去创建getters
每次点击不同的按钮,就相当于是要去修改visibility,所以,我们要去设置一个mutations
把上面的changeVisibility映射到app.vue组件的方法中去。
使用:
最重要的一点:现在的数据的来源不是整个的todos,而是根据当前条件过滤的数据:
- 把原来的todos改成了filterTodo。
- 由于对于数据的来源产生的变化,所以我们重新去写一个getter来处理是否显示“清除已完成”
# 4.12, 编辑todos的内容
当我们在todos的内容上双击时,则会处于编辑状态:
开启编辑状态:加一个editing这个类.
- 去listtodo中,设置双击加一个类的效果
- 给listTodo这个组件加一个isEdit数据项,用它来表示当前是否处于编辑状态
对文件框和整个的li,它们在编辑状态会有不同的样式:
对文本框来说:
- 初始值就是当前的todo的text.
- 它失去焦点,或者是回车时,都应该结束编辑
上面的finishEdit是一个方法,如下:
但,结束编辑时,应该去修改数据。我们应该定义 一个mutations去处理这种情况。
下面,我们把这个mutations映射到listTodo组件的methods中去。
修改finishEdit方法:
# 4.13, 本地存储
新建一个模块:
在store/index.js中引入这个模块
18-过渡动画
hs 6/3/2022
# 1, Vue中的动画
# 1.1, Vue中的动画介绍
Vue中为我们提供一些内置组件和对应的API来完成动画,利用它们我们可以方便的实现过渡动画效果
- 没有动画的情况下,整个内容的显示和隐藏会非常的生硬
- 如果我们希望给单元素或者组件实现过渡动画,可以使用 transition 内置组件来完成动画
# 1.2, Vue的transition动画
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用 v-if)条件展示 (使用 v-show)
- 动态组件
- 组件根节点
当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理
- 自动嗅探目标元素是否应用了CSS过渡或者动画,如果有,那么在恰当的时机添加/删除 CSS类名
- 如果 transition 组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用
- 如果没有找到JavaScript钩子并且也没有检测到CSS过渡/动画,DOM插入、删除操作将会立即执行
过渡动画class(Vue就是帮助我们在这些class之间来回切换完成的动画)
-
v-enter-from:定义进入过渡的开始状态
- 在元素被插入之前生效,在元素被插入之后的下一帧移除。
-
v-enter-active:定义进入过渡生效时的状态,
- 在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线 函数
-
v-enter-to:定义进入过渡的结束状态
- 在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除
-
v-leave-from:定义离开过渡的开始状态
- 在离开过渡被触发时立刻生效,下一帧被移除
-
v-leave-active:定义离开过渡生效时的状态
- 在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟 和曲线函数
-
v-leave-to:离开过渡的结束状态
- 在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被删除),在过渡/动画完成之后移除
class的name命名规则如下
- 如果我们使用的是一个没有name的transition,那么所有的class是以 v- 作为默认前缀
- 如果我们添加了一个name属性,比如 ,那么所有的class会以 wc- 开头
// App.vue
<template>
<div>
<button @click="show = !show">点击我切换显示与隐藏</button>
<!-- transiton:组件,全局组件。可以在任意组件内直接使用-->
<!-- name属性值:如果页面中有多个多度动画需求,区分样式类名。 如果没有书写name,默认类名v-xxx开头,
如果有name属性值,类名就以name属性值开头使用。这样可以去多个过渡动画效果!!!
-->
<transition name="jch">
<div class="box" v-show="show">
<h3>我是H3标题</h3>
</div>
</transition>
</div>
</template>
<script>
export default {
name: "",
data() {
return {
show: false,
};
},
};
</script>
<!-- 在style标签身上加上一个lang=less属性,代表style里面书写的less样式 -->
<!-- 进入阶段的过渡动画 -->
<style scoped lang="less">
@color: cyan;
@f: red;
.box {
width: 400px;
height: 200px;
background: @color;
h3 {
color: @f;
}
}
/* 进入阶段的过渡动画*/
/*进入开始阶段*/
.jch-enter {
opacity: 0;
transform: rotate(0deg);
}
/* 定义进入阶段过渡动画时间、速率类名地方*/
.jch-enter-active {
/*定义过渡动画时间、速率等等*/
transition: all 5s;
}
/*进入结束阶段*/
.jch-enter-to {
opacity: 1;
transform: rotate(360deg);
}
/*定义离开阶段的过渡动画*/
/*
离开阶段的开始状态
*/
.jch-leave {
opacity: 1;
transform: scale(1);
}
/* 定义离开阶段过渡动画时间、速率类名地方*/
.jch-leave-active {
transition: all 3s;
}
/*离开阶段的结束状态*/
.jch-leave-to {
opacity: 0;
transform: scale(0);
}
</style>
过渡动画遮罩层
// components/ElDialog.vue
<template>
<div>
<transition>
<div class="mask" v-show="visible"></div>
</transition>
</div>
</template>
<script>
export default {
name: "",
props: ["visible"],
};
</script>
<style scoped>
.mask {
position: fixed;
left: 20%;
top: 20%;
right: 20%;
bottom: 20%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.v-enter {
transform: scale(0);
}
.v-enter-active {
transition: all 2s;
}
.v-enter-to {
transform: scale(1);
}
.v-leave{
transform: scale(1);
border-radius: 0px;
}
.v-leave-active{
transition: all 2s;
}
.v-leave-to{
transform: scale(0);
border-radius:50%;
}
</style>
// App.vue
<template>
<div>
<button @click="visible=!visible">我是按钮需要遮罩层组件</button>
<ElDialog :visible="visible"></ElDialog>
</div>
</template>
<script>
import ElDialog from '@/components/ElDialog'
export default {
name: '',
data() {
return {
visible:false
}
},
components:{
ElDialog
}
}
</script>
<style scoped>
</style>
19-路由
hs 6/3/2022
# 1, 认识前端路由
# 1.1, 前端路由介绍
路由其实是网络工程中的一个术语
- 在架构一个网络时,非常重要的两个设备就是路由器和交换机
- 目前在我们生活中路由器也是越来越被大家所熟知,因为我们生活中都会用到路由器
- 路由器主要维护的是一个映射表,映射表会决定数据的流向
前端路由的实现方案(前端路由是如何做到URL和内容进行映射呢?监听URL的改变)
- 前端路由的核心是什么呢?改变URL,但是页面不进行整体的刷新
- URL的hash
- HTML5的History
URL的hash
- URL的hash也就是锚点(#), 本质上是改变window.location的href属性
- 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
- hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#,显得不像一个真实的路径
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<a href="#/home">home</a>
<a href="#/about">about</a>
<div class="router-view"></div>
</div>
<script>
// 1,获取router-view
const routerViewEl = document.querySelector(".router-view");
// 2, 监听hashchange
window.addEventListener("hashchange", () => {
switch (location.hash) {
case "#/home":
routerViewEl.innerHTML = "home";
break;
case "#/about":
routerViewEl.innerHTML = "about";
break;
default:
routerViewEl.innerHTML = "default";
}
})
</script>
</body>
</html>
HTML5的History(history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面)
- replaceState:替换原来的路径
- pushState:使用新的路径
- popState:路径的回退
- go:向前或向后改变路径
- forward:向前改变路径
- back:向后改变路径
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<style>
* {
/* global font */
font-family: Verdana;
font-size: 18px;
}
#root {
display: flex;
flex-direction: row;
}
#content {
display: flex;
display: block;
width: 800px;
height: 250px;
/* vertically centered text */
line-height: 250px;
border: 2px solid #555;
margin: 32px;
text-align: center;
}
.route {
cursor: pointer;
justify-content: center;
width: 150px;
height: 50px;
/* vertically centered text */
line-height: 50px;
position: relative;
border: 2px solid #555;
background: white;
text-align: center;
margin: 16px;
}
.route.selected {
background: yellow;
}
</style>
<body>
<section id="root">
<section class="route" id="home">/home</section>
<section class="route" id="about">/about</section>
<section class="route" id="gallery">/gallery</section>
<section class="route" id="contact">/contact</section>
<section class="route" id="help">/help</section>
</section>
<main id="content">Content loading...</main>
</body>
<script type="text/javascript">
window.onload = event => {
//监听 路由的点击事件 执行push方法
window["home"].addEventListener("click", event => push(event))
window["about"].addEventListener("click", event => push(event))
window["gallery"].addEventListener("click", event => push(event))
window["contact"].addEventListener("click", event => push(event))
window["help"].addEventListener("click", event => push(event))
}
function select_tab(id) {
// 改变 路由按钮的样式
document.querySelectorAll(".route").forEach(item => item.classList.remove('selected'));
document.querySelectorAll("#" + id).forEach(item => item.classList.add('selected'));
}
function load_content(id) {
//更新内容
document.querySelector("#content").innerHTML = 'Content loading for /' + id + '...';
}
function push(event) {
let id = event.target.id;
select_tab(id);
document.title = id;
load_content(id);
window.history.pushState({
id
}, `${id}`, `/page/${id}`);
}
window.addEventListener("popstate", event => {
let stateId = event.state.id;
select_tab(stateId);
load_content(stateId);
});
</script>
</html>
# 2, 认识vue-router
# 2.1, vue-router介绍
目前前端流行的三大框架, 都有自己的路由实现
- Angular的ngRouter
- React的ReactRouter
- Vue的vue-router
Vue Router 是 Vue.js 的官方路由
- 它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用(SPA)变得非常容易
- 路由用于设定访问路径, 将路径和组件映射起来
- 在vue-router的单页面应用中, 页面的路径的改变就是组件的切换
- 安装Vue Router:npm install vue-router@3.5.3
路由的使用步骤
-
第一步:创建路由需要映射的组件(打算显示的页面)
-
第二步:通过new VueRouter创建路由对象,并且传入routes和history模式
- 安装插件 Vue.use(VueRouter);
- 配置路由映射: 组件和路径映射关系的routes数组
- 创建基于hash或者history的模式
-
第三步:通过 router 配置参数注入路由,从而让整个应用都有路由功能
-
第四步:路由使用: 通过router-link和router-view
// router/index.js
//配置路由
//引入vue-router插件:经过打印查看,引入进来的VueRouter构造函数
//vue基础学习构造函数:Vue、VueComponent、Vuex.Store、VueRouter
import VueRouter from 'vue-router';
import Vue from 'vue';
//安装插件
Vue.use(VueRouter);
//引入路由组件
import Home from '../pages/Home';
import About from '../pages/About';
//配置项目的路由
//通过VueRouter【路由器】类,创建了一个VueRouter类的一个实例!!!
//对外暴露
export default new VueRouter({
mode: 'history',
routes: [{
// path配置的是根路径: /
path: "/",
// redirect是重定向, 也就是我们将根路径重定向到/home的路径下, 这样就可以得到我们想要的结果了
redirect: "/home"
}, {
//path设置路由的K,path有的属性值务必都是小写的
path: "/home",
//component设置路由的V,一个K对应一个V
component: Home,
}, {
path: '/about',
component: About
},
]
});
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from "./routes/router"
// 把路由对象挂载到根实例上
// 那么它的子子孙孙都可以使用这个路由对象
new Vue({
router,
render: h => h(App),
}).$mount('#app')
// Home.vue
<template>
<div>
<h1>Home组件</h1>
</div>
</template>
<script>
export default {
name: "Home",
};
</script>
<style>
</style>
// About.vue
<template>
<div>
<h1>About组件</h1>
</div>
</template>
<script>
export default {
name:"About"
}
</script>
<style>
</style>
// App.vue
<template>
<div id="app">
<!-- router-link 当成a标签 -->
<router-link to="/home" active-class="active">Home</router-link>
<router-link to="/about" active-class="active">About</router-link>
<hr>
<!-- 路由的出口:路由匹配到的组件,需要放到路由出口 -->
<router-view />
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style lang="less">
.active{
color: red;
}
</style>
# 3, router-link
# 3.1, router-link属性
router-link属性
- to属性: 是一个字符串,或者是一个对象
- replace属性:设置 replace 属性的话,当点击时,会调用 router.replace(),而不是 router.push();
- active-class属性:设置激活a元素后应用的class,默认是router-link-active
- exact属性:精确匹配模式
# 4, 路由懒加载
# 4.1, 路由懒加载
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载:
- 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效
- 也可以提高首屏的渲染效率
- Vue Router默认就支持动态来导入组件
- 因为component可以传入一个组件,也可以接收一个函数,该函数需要放回一个Promise,而import函数就是返回一个Promise
- 分包是没有一个很明确的名称的,其实webpack从3.x开始支持对分包进行命名(chunk name)
{
path: "/",
redirect: "/home"
}, {
path: "/home",
component: ()=>import(/* webpackChunkName: "home" */"../pages/Home.vue"),
}, {
path: '/about',
component: ()=>import(/* webpackChunkName: "about" */"../pages/About.vue"),
},
打包后的代码如下:
# 5, 路由其他属性
# 5.1, 路由其他属性
- name属性:路由记录独一无二的名称;
- meta属性:自定义的数据
{
path: "/",
redirect: "/home"
}, {
path: "/home",
component: ()=>import(/* webpackChunkName: "home" */"../pages/Home.vue"),
name:"home"
}, {
path: '/about',
component: ()=>import(/* webpackChunkName: "about" */"../pages/About.vue"),
name:"about",
meta:{
name:"wc",
age:18
}
},
# 6, 嵌套路由和404组件
# 6.1, 嵌套路由
什么是路由的嵌套呢?
-
我们匹配的Home、About等都属于第一级路由,我们在它们之间可以来回进行切换
-
Home页面本身,也可能会在多个组件之间来回切换
- 比如Home中包括Cart、Mine,它们可以在Home内部来回切换
- 这个时候我们就需要使用嵌套路由,在Home中也使用 router-view 来占位之后需要渲染的组件
404组件
- 对于哪些没有匹配到的路由,我们通常会匹配到固定的某个页面
- 在路由规则最后面配置:{ path:"*", component:NotFount },
import Vue from "vue";
import VueRouter from "vue-router"
// 自定义的组件 直接引入 下面的组件,一上来,就全部引入
import Home from "../components/Home"
// import About from "../components/About"
import NotFount from "../components/NotFount"
import Cart from "../components/Cart"
// import Mine from "../components/Mine"
// 能不能点击某个连接时,用到了这个组件,再去引入呢?
// 答:可以,使用路由的懒加载
Vue.use(VueRouter);
// 配置路由规则
let routes = [
{ path:"/", redirect:"/home" },
{
path:"/home",
component:Home,
children:[
{ path:"/home", redirect:"/home/cart" },
{ path:"/home/cart", component:Cart },
{ path:"/home/mine", component:()=>import(/* webpackChunkName: "Mine" */"../components/Mine") },
]
},
// { path:"/about", component:About },
{ path:"/about", component:()=>import(/* webpackChunkName: "About" */"../components/About") },
{ path:"*", component:NotFount },
];
let router = new VueRouter({
// hash路由有一个特点,就是#
mode:"hash", // hash路由
routes
})
export default router;
// Home.vue
<template>
<div>
<h1>Home组件</h1>
<router-link to="/home/cart">购物车</router-link>
<router-link to="/home/mine">我的</router-link>
<hr>
<router-view />
</div>
</template>
<script>
export default {
name: "Home",
};
</script>
<style>
</style>
// About.vue
<template>
<div>
<h1>About组件</h1>
</div>
</template>
<script>
export default {
name:"About"
}
</script>
<style>
</style>
// Cart.vue
<template>
<div>
<h3>购物车</h3>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
// Mine.vue
<template>
<div>
<h3>我的</h3>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
// NotFount.vue
<template>
<div>
<h3>你的页面飞了</h3>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
// App.vue
<template>
<div id="app">
<!-- router-link 当成a标签 -->
<router-link to="/home" active-class="active">Home</router-link>
<router-link to="/about" active-class="active">About</router-link>
<hr>
<!-- 路由的出口:路由匹配到的组件,需要放到路由出口 -->
<router-view />
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
}
},
methods:{
},
components: {
},
};
</script>
<style lang="less">
.active{
color: red;
}
</style>
# 7, 动态路由
# 7.1, 动态路由基本匹配
很多时候我们需要将给定匹配模式的路由映射到同一个组件
- 例如,我们可能有一个 User 组件,它应该对所有用户进行渲染,但是用户的ID是不同的;
- 在Vue Router中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数;
获取动态路由的值
- 在template中,直接通过 $route.params获取值
- 在created中,通过 this.$route.params获取值
import VueRouter from "vue-router";
import Vue from "vue";
import Home from "../components/Home"
import About from "../components/About"
import Mine from "../components/Mine"
import NotFount from "../components/NotFount"
Vue.use(VueRouter);
let routes = [
{ path:"/", redirect:"/home" },
{ path:"/home", component:Home },
{ path:"/about", component:About },
{ path:"/mine/:name/:age/:address", component:Mine },
{ path:"*", component:NotFount },
];
let router = new VueRouter({
// hash带#
// history不带#
mode:"history",
routes
})
export default router;
// Home.vue
<template>
<div>
<h2>Home</h2>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
// About.vue
<template>
<div>
<h2>About</h2>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
// Mine.vue
<template>
<div>
<h2>Mine -- {{ name }}</h2>
<h2>Mine -- {{ age }}</h2>
<h2>Mine -- {{ address }}</h2>
</div>
</template>
<script>
export default {
// 需要获取动态路由参数
// 使用路由,需要知道4个知识点
// 1)<router-view /> 路由出口
// 2)<router-link /> a标签 点击跳转
// 3)$router 对象 有一堆的方法 挂载到当前组件实例上 this
// 3)$route 对象 有一堆的属性 挂载到当前组件实例上 this
data(){
return{
name:"",
age:"",
address:"",
}
},
// 生命周期函数(钩子函数)
created(){
// 获取动态路由参数
console.log(this.$route);
console.log(this.$route.params.name);
this.name = this.$route.params.name
this.age = this.$route.params.age
this.address = this.$route.params.address
}
}
</script>
<style>
</style>
// NotFount.vue
<template>
<div>
<h2>404</h2>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
// App.vue
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
}
},
methods:{
},
components: {
},
};
</script>
<style lang="less">
.active{
color: red;
}
</style>
在router-link中进行如下跳转:
<router-link to="/main/wc/18/bj">我的</router-link>
# 8, 编程式路由(代码的页面跳转)
# 8.1, 代码的页面跳转
有时候我们希望通过代码来完成页面的跳转,比如点击的是一个按钮
-
此时,我们可以使用编程式路由
-
this.$router.push("/about")
-
当然,我们也可以传入一个对象
- this.$router.push({path:"/about"})
- this.$router.push({name:"about"})
query方式的参数
-
this.$router.push({path:"/about",query:{name:"wc",age:18}})
-
在界面中通过 $route.query 来获取参数
-
query: -
-
params方式的参数
- this.$router.push({path:"/about",params:{ address:"bj" }})
- 在界面中通过 $route.params 来获取参数
页面的前进后退
-
router的go方法:
- router.go(1) 向前移动一条记录,与router.forword()相同
- router.go(-1) 向后移动一条记录,与router.back()相同
- router.go(3) 前进3条记录
- router.go(100) 如果没有那么多记录,静默失败
- router.go(-100) 如果没有那么多记录,静默失败
-
router也有back:
- 通过调用 history.back() 回溯历史。相当于 router.go(-1)
-
router也有forward:
- 通过调用 history.forward() 在历史中前进。相当于 router.go(1)
import VueRouter from "vue-router";
import Vue from "vue";
import Home from "../components/Home"
import About from "../components/About"
import Mine from "../components/Mine"
import NotFount from "../components/NotFount"
Vue.use(VueRouter);
let routes = [
{ path:"/", redirect:"/home" },
{ path:"/home", name:"home", component:Home },
{ path:"/about", name:"about", component:About },
{ path:"/mine", name:"mine", component:Mine },
{ path:"*", component:NotFount },
];
let router = new VueRouter({
// hash带#
// history不带#
mode:"history",
routes
})
export default router;
// Home.vue
<template>
<div>
<h2>Home</h2>
<button @click="toAbout">去about</button>
<button @click="forward">forward</button>
</div>
</template>
<script>
export default {
methods: {
toAbout() {
// 实现跳转到about
// this上有route 还有router
// route上有一堆的属性
// router上有一堆方法
// 跳转方式一:
// this.$router.push("/about")
// 跳转方式二:
// this.$router.push({ path:"/about" })
// 跳转方式三:
// this.$router.push({ name:"about" })
// 跳转传参方式一:
// this.$router.push({ name:"about", params:{ address:"bj" } })
// 跳转传参方式二:
this.$router.push({ name:"about", query:{ score:"100分" } })
// replace
// this.$router.replace({ name:"about", query:{ score:"100分" } })
},
forward(){
// this.$router.forward()
this.$router.go(1) // go(1) 等价于 forward
}
},
};
</script>
<style>
</style>
// About.vue
<template>
<div>
<h2>About</h2>
<!-- <p>{{ $route.params.address }}</p> -->
<p>{{ $route.query.score }}</p>
<button @click="back">back</button>
</div>
</template>
<script>
export default {
// 4个东西:两个组件 两个对象route rotuer
created(){
console.log(this.$route);
// console.log(this.$route.params.address);
console.log(this.$route.query.score);
},
methods:{
back(){
// this.$router.back();
this.$router.go(-1); // go(-1) 等价于 back
}
}
}
</script>
<style>
</style>
// Mine.vue
<template>
<div>
<h2>Mine</h2>
</div>
</template>
<script>
export default {
data(){
return{
}
},
}
</script>
<style>
</style>
// NotFount.vue
<template>
<div>
<h2>404</h2>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
// App.vue
<template>
<div id="app">
<router-link to="/home" active-class="active">home</router-link>
<router-link to="/about" active-class="active">about</router-link>
<router-link to="/mine" active-class="active">mine</router-link>
<hr>
<router-view />
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
}
},
methods:{
},
components: {
},
};
</script>
<style lang="less">
.active{
color: red;
}
</style>
# 9, 路由导航守卫
# 9.1, 路由导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航
全局的前置守卫beforeEach是在导航触发时会被回调的:
- to:即将进入的路由Route对象
- from:即将离开的路由Route对象
- next: 在Vue2中我们是通过next函数来决定如何进行跳转的,是在Vue3中我们是通过返回值来控制的,不再推荐使用next函数,这是因为开发中很容易调用多次next;
// /router/index.js
import VueRouter from "vue-router";
import Vue from "vue";
import Home from "../components/Home"
import About from "../components/About"
import Mine from "../components/Mine"
import Login from "../components/Login"
import NotFount from "../components/NotFount"
Vue.use(VueRouter);
let routes = [
{ path:"/", redirect:"/home" },
{ path:"/home", name:"home", component:Home },
{ path:"/about", name:"about", component:About },
{ path:"/mine", name:"mine", component:Mine },
{ path:"/login", name:"login", component:Login },
{ path:"*", component:NotFount },
];
let router = new VueRouter({
// hash带#
// history不带#
mode:"history",
routes
})
// 配置全局路由守卫
// to表示去哪
// from表示从哪里来
// next表示是否放行 放行到哪里
router.beforeEach((to,from,next)=>{
// console.log("from:",from);
// console.log("to:",to);
if(to.path !== "/login"){
// 去的地方不是/login
// 只有登录了,才能去/home /about /mine
if(window.isLogin){
// 表示已登录
next();
}else{
return next("/login");
}
}
next();
});
export default router;
// Home.vue
<template>
<div>
<h2>Home</h2>
</div>
</template>
<script>
export default {
methods: {
toAbout() {
},
},
};
</script>
<style>
</style>
// About.vue
<template>
<div>
<h2>About</h2>
</div>
</template>
<script>
export default {
methods:{
}
}
</script>
<style>
</style>
// Mine.vue
<template>
<div>
<h2>Mine</h2>
</div>
</template>
<script>
export default {
data(){
return{
}
},
}
</script>
<style>
</style>
// Login.vue
<template>
<div>
<button @click="login">登录</button>
</div>
</template>
<script>
export default {
methods:{
login(){
// 给GO上放一个isLogin是true
window.isLogin = true;
}
}
}
</script>
<style>
</style>
// NotFount.vue
<template>
<div>
<h2>404</h2>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
// App.vue
<template>
<div id="app">
<router-link to="/home" active-class="active">home</router-link>
<router-link to="/about" active-class="active">about</router-link>
<router-link to="/mine" active-class="active">mine</router-link>
<hr>
<router-view />
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
}
},
methods:{
},
components: {
},
};
</script>
<style lang="less">
.active{
color: red;
}
</style>
其他导航守卫:
- Vue还提供了很多的其他守卫函数,目的都是在某一个时刻给予我们回调,让我们可以更好的控制程序的流程或者功能
- next.router.vuejs.org/zh/guide/ad…
完整的导航解析流程:
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
21-vant组件库
hs 8/17/2022
# 1. vant组件库
# 1.0 vant组件库-介绍
目标: vant是一个轻量、可靠的移动端 Vue 组件库, 开箱即用
特点:
- 提供 60 多个高质量组件,覆盖移动端各类场景
- 性能极佳,组件平均体积不到 1kb
- 完善的中英文文档和示例
- 支持 Vue 2 & Vue 3
- 支持按需引入和主题定制
# 1.1 全部引入
目标: 看官网文档, 下载, 引入vant组件库
全部引入, 快速开始:vant-contrib.gitee.io/vant/#/zh-C…
- 全部引入, 快速开始: vant-contrib.gitee.io/vant/#/zh-C…
- (opens new window)
- 下载Vant组件库到当前项目中
- 在main.js中全局导入所有组件,
- 使用按钮组件 – 作为示范的例子
- 下载vant组件库到当前项目中
yarn add vant -D
- 导入所有组件, 在main.js中
import Vue from 'vue';
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>
# 1.2 手动按需引入
目标: 只引入使用的组件
- 手动单独引入, 快速开始: vant-contrib.gitee.io/vant/#/zh-C…
// 方式2: 手动 按需引入
// import Button from 'vant/lib/button'; // button组件
// import 'vant/lib/button/style'; // button样式
- 注册
// components: { // 手动注册组件名
// // VanButton: Button
// // 等价的
// [Button.name]: Button
// }
- 使用
<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>
# 1.3 自动按需引入
目标: 按需加载组件
(opens new window) 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。
- 安装插件
yarn add babel-plugin-import -D
- 在babel配置文件里 (babel.config.js)
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
- 全局注册 - 会自动按需引入
// 方式1: 全局 - 自动按需引入vant组件
// (1): 下载 babel-plugin-import
// (2): babel.config.js - 添加官网说的配置 (一定要重启服务器)
// (3): main.js 按需引入某个组件, Vue.use全局注册 - 某个.vue文件中直接使用vant组件
import {
Button
} from 'vant';
Vue.use(Button) // Button组件全局注册, 真正注册的组件名VanButton
# 1.4 弹出框使用
目标: 使用弹出框组件
vant-contrib.gitee.io/vant/#/zh-C…
<template>
<div>
<van-button type="primary" @click="btn">主要按钮</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>
// 方式2: 手动 按需引入
// import Button from 'vant/lib/button'; // button组件
// import 'vant/lib/button/style'; // button样式
// 目标: 使用弹出框
// 1. 找到vant文档
// 2. 引入
// 3. 在恰当时机, 调用此函数 (还可以用组件的用法)
import { Dialog } from "vant";
export default {
// components: { // 手动注册组件名
// // VanButton: Button
// // 等价的
// [Button.name]: Button
// }
methods: {
btn() {
Dialog({ message: "提示", showCancelButton: true }); // 调用执行时, 页面就会出弹出框
},
},
};
</script>
# 1.5 表单使用
目标: 使用vant组件里的表单组件
vant-contrib.gitee.io/vant/#/zh-C…
表单验证规则: