介绍 | Vue CLI (vuejs.org)
command line interface 命令行接口工具
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:
-
通过 @vue/cli 实现的交互式的项目脚手架。 -
通过 @vue/cli + @vue/cli-service-global 实现的零配置原型开发。 -
一个运行时依赖 ( @vue/cli-service
),该依赖:
- 可升级;
- 基于 webpack 构建,并带有合理的默认配置;
- 可以通过项目内的配置文件进行配置;
- 可以通过插件进行扩展。
-
一个丰富的官方插件集合,集成了前端生态中最好的工具。 -
一套完全图形化的创建和管理 Vue.js 项目的用户界面。
Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。
3.1,安装脚手架
1,配置 npm 淘宝镜像
npm config set registry https://registry.npm.taobao.org
2,(仅第一次执行):全局安装@vue/cli
npm install -g @vue/cli
3,切换到代码目录,创建项目
vue create vue_test01
这里选Vue2
4,按照提示启动
cd vue_test01
npm run serve
5,访问测试
localhost:8080
4,分析脚手架
4.1,脚手架目录
node_modules 依赖包
public
-
index.html 主页面
-
<!DOCTYPE html>
<html lang="">
<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">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>
src
-
assets 放静态资源 -
components 放组件 -
App.vue -
main.js(入口)
根目录
4.2,将上面的单文件导入vue_test01中
运行报错error Mixed spaces and tabs no-mixed-spaces-and-tabs
解决办法 package.json中添加rules
"rules": {
"no-mixed-spaces-and-tabs": "off"
}
重新运行npm run serve
4.3,render函数
默认导入的是精简版的vue,不包含模板解析器, 需要解析使用render来创建
render: h => h(App),
render: h => h('h1','你好啊'),
render(createElement){
return createElement(App)
}
关于不同版本的Vue:
4.4,修改Vue配置文件vue.config.js
- 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
- 使用vue.config.js可以对脚手架进行个性化定制,
- 官网:配置参考 | Vue CLI (vuejs.org)
- pages, 可修改入口
- lintOnSave: 语法检查
5,ref与props
ref属性
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch"/>
</div>
methods: {
showDOM(){
console.log(this.$refs.title) //真实DOM元素
console.log(this.$refs.btn) //真实DOM元素
console.log(this.$refs.sch) //School组件的实例对象(vc)
}
}
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- 打标识:
<h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School> - 获取:
this.$refs.xxx
props配置项
//传递参数<Student name="李四" sex="女" :age="18"/>
//简单声明接收
props:['name','age','sex']
//接收的同时对数据进行类型限制
props:{
name:String,
age:Number,
sex:String
}
//接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
props:{
name:{
type:String, //name的类型是字符串
required:true, //name是必要的
},
age:{
type:Number,
default:99 //默认值
},
sex:{
type:String,
required:true
}
}
-
功能:让组件接收外部传过来的数据 -
传递数据:<Demo name="xxx"/> -
接收数据:
-
第一种方式(只接收):props:['name'] -
第二种方式(限制类型):props:{name:String} -
第三种方式(限制类型、限制必要性、指定默认值): props:{
name:{
type:String,
required:true,
default:'老王'
}
}
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
6,mixin(混入)
-
功能:可以把多个组件共用的配置提取成一个混入对象 -
使用方式: 第一步定义混合: {
data(){....},
methods:{....}
....
}
export const hunhe = {
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
},
}
export const hunhe2 = {
data() {
return {
x:100,
y:200
}
},
}
第二步使用混入: 全局混入:Vue.mixin(xxx) 局部混入:mixins:['xxx']
7,插件
-
功能:用于增强Vue -
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。 -
定义插件: 对象.install = function (Vue, options) {
Vue.filter(....)
Vue.directive(....)
Vue.mixin(....)
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
-
使用插件:Vue.use()
例子
export default {
install(Vue,x,y,z){
console.log(x,y,z)
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
Vue.directive('fbind',{
bind(element,binding){
element.value = binding.value
},
inserted(element,binding){
element.focus()
},
update(element,binding){
element.value = binding.value
}
})
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
8,scoped样式
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
安装less-loader
npm i less-loader
npm i less-loader@7
例子
<template>
<div class="demo">
<h2 class="title">学生姓名:{{name}}</h2>
<h2 class="atguigu">学生性别:{{sex}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男'
}
}
}
</script>
<style lang="less" scoped>
.demo{
background-color: pink;
.atguigu{
font-size: 40px;
}
}
</style>
9,Todo-案例
9.1,组件化通用流程
1.实现静态组件:抽取组件,使用组件实现静态页面效果
2.展示动态数据:
- 2.1. 数据的类型、名称是什么?
- 2.2. 数据保存在哪个组件?
3.交互——从绑定事件监听开始
1,拆分
2,静态组件
TodoFooter.vue
<template>
<div class="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: "TodoFooter"
}
</script>
<style scoped>
/*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;
}
</style>
TodoHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
</template>
<script>
export default {
name: "TodoHeader"
}
</script>
<style scoped>
/*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);
}
</style>
TodoItem.vue
<template>
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</template>
<script>
export default {
name: "TodoItem"
}
</script>
<style scoped>
/*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;
}
</style>
TodoList.vue
<template>
<div>
<ul class="todo-main">
<TodoItem></TodoItem>
</ul>
</div>
</template>
<script>
import TodoItem from "@/components/TodoItem";
export default {
name: "TodoList",
components:{
TodoItem
}
}
</script>
<style scoped>
/*list*/
.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>
App.vue
<template>
<div>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<TdHeader></TdHeader>
<TodoList></TodoList>
<TodoFooter></TodoFooter>
</div>
</div>
</div>
</div>
</template>
<script>
//引入组件
import TodoFooter from "@/components/TodoFooter";
import TdHeader from "@/components/TodoHeader";
import TodoList from "@/components/TodoList";
export default {
name:'App',
components:{
TdHeader,TodoList,TodoFooter
}
}
</script>
<style>
/*base*/
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>
9.2,展示动态数据
TodoList.vue, 传递数据到item
<template>
<div>
<ul class="todo-main">
<TodoItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"></TodoItem>
</ul>
</div>
</template>
<script>
import TodoItem from "@/components/TodoItem";
export default {
name: "TodoList",
components:{
TodoItem
},
data(){
return {
todos:[
{id:'001',title:'吃饭',done:true},
{id:'002',title:'睡觉',done:true},
{id:'003',title:'开车',done:true}
]
}
}
}
</script>
TodoItem.vue,接收展示
<template>
<li>
<label>
<input type="checkbox" :checked="false"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</template>
<script>
export default {
name: "TodoItem",
//接收todo对象
props:['todo'],
}
</script>
9.3,添加todo
- 目前header的数据无法传入到list(目前知识点办不到兄弟之间传值)
- 可以先放到APP中
- App(父)给Header(儿)传一个函数
- 儿子获取到了函数, 调用函数给里面传值
App.vue
<template>
<div>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<TdHeader :addTodo="addTodo"></TdHeader>
<TodoList :todos="todos"></TodoList>
<TodoFooter></TodoFooter>
</div>
</div>
</div>
</div>
</template>
<script>
//引入组件
import TodoFooter from "@/components/TodoFooter";
import TdHeader from "@/components/TodoHeader";
import TodoList from "@/components/TodoList";
export default {
name:'App',
components:{
TdHeader,TodoList,TodoFooter
},
data() {
return {
todos: [
{id: '001', title: '吃饭', done: true},
{id: '002', title: '睡觉', done: true},
{id: '003', title: '开车', done: true}
]
}
},
methods:{
addTodo(todoObj){ //接收儿子传递的参数
this.todos.unshift(todoObj);
}
}
}
</script>
TodoList.vue
<template>
<div>
<ul class="todo-main">
<TodoItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"></TodoItem>
</ul>
</div>
</template>
<script>
import TodoItem from "@/components/TodoItem";
export default {
props:['todos'],
name: "TodoList",
components:{
TodoItem
},
}
</script>
TodoHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid' //需安装 npm i nanoid
export default {
props:['addTodo'], //父亲给儿子传一个函数
name: "TodoHeader",
data(){
return {title: ''}
},
methods:{
add(){
if (this.title.trim() ===''){ return alert("不能输入空值"); }
// const text = event.target.value; //通过传入事件对象获取
const todoObj = {id:nanoid(),title:this.title,done:false};
this.addTodo(todoObj); //儿子调用传递参数
this.title='';//输入完之后清空
// event.target.value=''; //输入完之后清空
}
}
}
</script>
9.4,勾选已完成
- v-model也能实现:checked+@change功能, 由于修改了props,违反原则,不建议使用
<input type="checkbox" v-model="todo.done"/> - 由于props传入的todo是一个对象, 修改了todo.done, vue也没发现, 一般是不能修改props的
App.vue
<TodoList :todos="todos" :updateTodo="updateTodo"></TodoList> <!-- 传递给item需要通过list传 -->
methods:{
updateTodo(id){
this.todos.forEach((todo)=>{
if (todo.id === id){
todo.done = !todo.done;
}
});
}
}
TodoList.vue
<TodoItem v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:updateTodo="updateTodo"></TodoItem><!-- 传递给item -->
//接收App传的函数
props:['todo','updateTodo'],
TodoItem.vue
<!-- 这里用@click 或 @change都可以 -->
<input type="checkbox" :checked="todo.done" @change="handleChek(todo.id)"/>
<!-- v-model也能实现:checked+@change功能, 由于修改了props,违反原则,不建议使用 -->
<!-- <input type="checkbox" v-model="todo.done"/>-->
<script>
export default {
//接收todo对象
props:['todo','updateTodo'],
name: "TodoItem",
methods:{
handleChek(id){
console.log(id)
this.updateTodo(id)
},
}
}
</script>
测试
9.5,删除
显示删除按钮
<!--去掉display-->
<button class="btn btn-danger" >删除</button>
<style scoped>
li:hover{
background-color: #cccccc;
}
li:hover button{
display: block;
}
</style>
App.vue
同样的套路, 给list传, 再给item传
<TodoList :todos="todos" :updateTodo="updateTodo" :deleteTodo="deleteTodo"></TodoList>
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !==id
})
// this.todos = this.todos.filter( todo=> todo.id !==id) //简写
}
TodoList.vue
<TodoItem v-for="todoObj in todos"
:key="todoObj.id" :todo="todoObj"
:updateTodo="updateTodo"
:deleteTodo="deleteTodo"></TodoItem>
props:['todos','updateTodo','deleteTodo'],
TodoItem.vue
<button class="btn btn-danger" @click="deleteItem(todo.id)">删除</button>
props:['todo','updateTodo','deleteTodo'],
methods:{
deleteItem(id){
if (confirm('确定删除?')){ //点确定为真, 取消为假
this.deleteTodo(id);
}
}
}
测试
9.6,底部统计
App.vue, App传todos到footer
<TodoFooter :todos="todos"></TodoFooter>
TodoFooter.vue
<template>
<div class="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成{{todoCount}}</span> / 全部{{todos.length}}
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</template>
<script>
export default {
props:['todos'],
name: "TodoFooter",
computed:{
todoCount(){
return this.todos.reduce((pre,todo)=>{
return pre + (todo.done ? 1 : 0); //如果勾选了就+1
},0)
// this.todos.reduce((pre,todo) => pre + (todo.done ? 1 : 0),0) //简写
/* forEach方式
let i = 0;
this.todos.forEach((todo)=>{
if (todo.done){
i++
}
});
return i;*/
/* filter方法
return this.todos.filter((todo)=>{
return todo.done !== false;
}).length*/
}
}
}
</script>
reduce方法
this.todos.reduce((pre,todo)=>{
return pre + (todo.done ? 1 : 0);
},0)
测试
9.7,底部交互
App.vue
<template>
<div>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<TdHeader :addTodo="addTodo"></TdHeader>
<!-- 传递给item需要通过list传 -->
<TodoList :todos="todos" :updateTodo="updateTodo" :deleteTodo="deleteTodo"></TodoList>
<TodoFooter :todos="todos" :isAllTotal="isAllTotal" :clearAllTodo="clearAllTodo"></TodoFooter>
</div>
</div>
</div>
</div>
</template>
<script>
//引入组件
import TodoFooter from "@/components/TodoFooter";
import TdHeader from "@/components/TodoHeader";
import TodoList from "@/components/TodoList";
export default {
name:'App',
components:{
TdHeader,TodoList,TodoFooter
},
data() {
return {
todos: [
{id: '001', title: '吃饭', done: false},
{id: '002', title: '睡觉', done: true},
{id: '003', title: '开车', done: true}
]
}
},
methods:{
//添加待办项
addTodo(todoObj){ //接收儿子传递的参数
this.todos.unshift(todoObj);
},
//修改待办项状态, done
updateTodo(id){
this.todos.forEach((todo)=>{
if (todo.id === id){
todo.done = !todo.done;
}
});
},
//删除待办项
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !==id
})
// this.todos = this.todos.filter( todo=> todo.id !==id) //简写
},
//修改待办项状态
isAllTotal(done){
this.todos.forEach((todo)=>{
todo.done = done
})
},
//清除已完成的事项
clearAllTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
}
}
</script>
TodoFooter.vue
<template>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" v-model="isAll"/>
</label>
<span>
<span>已完成{{todoCount}}</span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearTodo">清除已完成任务</button>
</div>
</template>
<script>
export default {
props:['todos','isAllTotal','clearAllTodo'],
name: "TodoFooter",
computed:{
total(){
return this.todos.length
},
isAll:{
get(){
return this.todoCount === this.total && this.total >0;
},
set(value){
this.isAllTotal(value)
}
},
todoCount(){
return this.todos.reduce((pre,todo)=>{
return pre + (todo.done ? 1 : 0); //如果勾选了就+1
},0)
// this.todos.reduce((pre,todo) => pre + (todo.done ? 1 : 0),0) //简写
/*
this.todos.reduce((pre,current)=>{},n) ,
调用次数= 数组长度
pre : 上一次调用函数的返回值
current: 数组里的每个对象, 这是是todo
n:统计的初始值
*/
/* forEach方式
let i = 0;
this.todos.forEach((todo)=>{
if (todo.done){
i++
}
});
return i;*/
/* filter方法
return this.todos.filter((todo)=>{
return todo.done !== false;
}).length*/
}
},
methods:{
clearTodo(){
if (this.todoCount<=0){ //没有勾选就不执行
return
}
if (confirm('是否清除?')){
this.clearAllTodo();
}
}
}
}
</script>
测试
9.8,总结TodoList案例
-
组件化编码流程: ? (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。 ? (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用: ? 1).一个组件在用:放在组件自身即可。 ? 2). 一些组件在用:放在他们共同的父组件上(状态提升)。 ? (3).实现交互:从绑定事件开始。 -
props适用于: ? (1).父组件 ==> 子组件 通信 ? (2).子组件 ==> 父组件 通信(要求父先给子一个函数) -
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的! -
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
10,浏览器本地存储-webStorage
sessionStorage
localStorage
- 5M容量, 一直存在, 除非用户清空缓存, 或调用api清除
这两个的使用方法一致
webStorage
-
存储内容大小一般支持5MB左右(不同浏览器可能还不一样) -
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。 -
相关API:
-
xxxxxStorage.setItem('key', 'value'); 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。 -
xxxxxStorage.getItem('person'); ? 该方法接受一个键名作为参数,返回键名对应的值。 -
xxxxxStorage.removeItem('key'); ? 该方法接受一个键名作为参数,并把该键名从存储中删除。 -
xxxxxStorage.clear() ? 该方法会清空存储中的所有数据。 -
备注:
- SessionStorage存储的内容会随着浏览器窗口关闭而消失。
- LocalStorage存储的内容,需要手动清除才会消失。
xxxxxStorage.getItem(xxx) 如果xxx对应的value获取不到,那么getItem的返回值是null。JSON.parse(null) 的结果依然是null。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>localStorage</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript" >
let p = {name:'张三',age:18}
function saveData(){
localStorage.setItem('msg','hello!!!')
localStorage.setItem('msg2',666)
localStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
const result = localStorage.getItem('person')
console.log(JSON.parse(result))
}
function deleteData(){
localStorage.removeItem('msg2')
}
function deleteAllData(){
localStorage.clear()
}
</script>
</body>
</html>
将Todo案例改成浏览器存储
<script>
export default {
name:'App',
components:{
},
data() {
return {
todos: JSON.parse(localStorage.getItem('todos')) || [] //到浏览器查找, 找不到为空数组[]
}
},
methods:{
//添加待办项
},
watch:{
todos:{
deep:true,//开启深度检测, 不然发现不了done改变
handler(newValue){
localStorage.setItem('todos',JSON.stringify(newValue));//检测到有修改就把新的todos更新到localStorage
/* if (this.todos.length<=0){
localStorage.removeItem('todos')
}*/
}
}
}
}
</script>
测试
11,组件自定义事件
需求: 儿子(Student)给父亲(App)传值
子给父传值的两种方式
11.1,通过props
App给Student传一个函数, stu通过props接收, 调用传参
App.vue
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name:'School',
props:['getSchoolName'],
data() {name:'123'},
methods: {
getSchoolName(schoolName){//通过props获取
console.log(schoolName);
},
}
</script>
School.vue
<button @click="sendSchoolName">把学校名给App</button>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name:'School',
props:['getSchoolName'],
data() {name:'123'},
methods: {
sendSchoolName(){
this.getSchoolName(this.name);
}
},
}
</script>
11.2,通过自定义事件
App.vue(通过@)
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @myStu="getStudentName"></Student>
<script>
export default {
name:'App',
components:{School,Student},
data() {},
methods: {
getSchoolName(schoolName){//通过props获取
console.log(schoolName);
}
}
}
</script>
App.vue(通过ref)
这种方式比较灵活,如可以加定时器, 在想要的时间绑定自定义事件
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="stu"/>
<script>
export default {
name:'App',
components:{School,Student},
data() {},
methods: {
getStudentName(studentName){//通过自定义事件
console.log(studentName);
}
},
mounted() {
setTimeout(()=>{ //延迟3秒后才能绑定事件
this.$refs.stu.$once('myStu',this.getStudentName)
},3000)
}
}
</script>
Student.vue
<button @click="sendStudentName">把学生名给App</button>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name:'Student',
data() {
return {
name:'张三',
sex:'男',
number:0
}
},
methods: {
sendStudentName(){//调用自定义事件,传参
this.$emit('myStu',this.name)
}
},
}
</script>
11.3,解绑事件
this.$off('myStu')
this.$off(['myStu','aaa'])
this.$off()
11.4,总结-自定义事件
-
一种组件间通信的方式,适用于:子组件 ===> 父组件 -
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。 -
绑定自定义事件:
-
第一种方式,在父组件中:<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/> -
第二种方式,在父组件中: <Demo ref="demo"/>
......
mounted(){
this.$refs.xxx.$on('atguigu',this.test)
}
-
若想让自定义事件只能触发一次,可以使用once 修饰符,或$once 方法。 -
触发自定义事件:this.$emit('atguigu',数据) -
解绑自定义事件this.$off('atguigu') -
组件上也可以绑定原生DOM事件,需要使用native 修饰符。 -
注意:通过this.$refs.xxx.$on('atguigu',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
11.5,修改Todo案例为自定义事件
list暂时不动, 等总线过了再研究
App.vue,修改成自定义事件
TodoHeader/TodoFooter.vue,修改成emit
12,全局事件总线(组件通信)
12.1,案例-全局事件总线
1,App.vue中定义数据总线$bus
关键点
beforeCreate() {
Vue.prototype.$bus = this ;
}
<template>
<div class="app">
<School />
<Student />
</div>
</template>
<script>
import Student from './components/Student'
import School from './components/School'
import Vue from "vue";
export default {
name:'App',
components:{School,Student},
data() {},
beforeCreate() {
Vue.prototype.$bus = this ;
}
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
Student.vue,兄弟组件(接收者)
关键点
mounted() {
this.$bus.$once('send',this.getSchoolName);
},
beforeDestroy() {
this.$bus.$off('send');
}
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
methods:{
getSchoolName(data){
console.log('Student收到了:',data)
}
},
mounted() {//谁接收数据谁向bus绑定自定义事件,回调接收数据
this.$bus.$once('send',this.getSchoolName);//向全局总线$bus里绑定一个自定义事件
},
beforeDestroy() {
this.$bus.$off('send'); //销毁前解绑, 以防占用
}
}
</script>
<style lang="css" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue,兄弟组件(传递者)
关键点
methods:{
sendSchoolName(){
this.$bus.$emit('send',this.name);
}
},
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">传送学校名字</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
methods:{
sendSchoolName(){
this.$bus.$emit('send',this.name);//调用$bus里的自定义事件send, 给student传数据
}
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
12.2,总结-全局事件总线
-
一种组件间通信的方式,适用于任意组件间通信。 -
安装全局事件总线: new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this
},
......
})
-
使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。 methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
-
提供数据:this.$bus.$emit('xxxx',数据) -
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
13,消息订阅与发布-pubsub-js
这里使用pubsub-js
安装pubsub
npm i pubsub-js
1,传递者(发布消息)
methods:{
sendSchoolName(){
pubsub.publish('hello',666)
}
},
2,接收者(订阅消息)
mounted() {
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
console.log('hello消息被回调了',msgName,data)
})
},
beforeDestroy() {
pubsub.unsubscribe(this.pubId)
}
3,总结(pubsub)
-
一种组件间通信的方式,适用于任意组件间通信。 -
使用步骤:
-
安装pubsub:npm i pubsub-js -
引入: import pubsub from 'pubsub-js' -
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。 methods(){
demo(data){......}
}
......
mounted() {
this.pid = pubsub.subscribe('xxx',this.demo)
}
-
提供数据:pubsub.publish('xxx',数据) -
最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid) 去取消订阅。
14,$nextTick
- 语法:
this.$nextTick(回调函数) - 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
14.1,给Todo案例增加修改代办项名称功能
TodoItem.vue
<template>
<li>
<label>
<!-- 这里用@click 或 @change都可以 -->
<input type="checkbox" :checked="todo.done" @change="handleChek(todo.id)"/>
<!-- v-model也能实现:checked+@change功能, 由于修改了props,违反原则,不建议使用 -->
<!-- <input type="checkbox" v-model="todo.done"/>-->
<span v-show="!todo.isEdit" >{{todo.title}}</span>
<input
type="text"
v-show="todo.isEdit"
:value="todo.title"
@blur="blurUpdateTitle(todo,$event)"
ref="inputTitle"
/>
</label>
<button class="btn btn-danger" @click="deleteItem(todo.id)">删除</button>
<button class="btn btn-update" v-show="!todo.isEdit" @click="handleEdit(todo)">修改</button>
</li>
</template>
<script>
// import pubsub from "pubsub-js";
export default {
//接收todo对象
props:['todo'],
name: "TodoItem",
methods:{
handleChek(id){
console.log(id)
this.$bus.$emit('updateTodo',id)
// pubsub.publish('updateTodo',id);
},
deleteItem(id){
if (confirm('确定删除?')){ //点确定为真, 取消为假
this.$bus.$emit('deleteTodo',id);
// pubsub.publish('deleteTodo',id);
}
},
handleEdit(todo){
// eslint-disable-next-line no-prototype-builtins
if (todo.hasOwnProperty('isEdit')) {//判断todo里有没有isEdit属性
todo.isEdit = true;
// console.log(todo)
}else {
this.$set(todo,'isEdit',true); //通过set来向todo中添加isEdit属性, 就会创建get,set
}
//点击修改时获取输入框焦点
// this.$refs.inputTitle.focus()
//由于直接Vue还没有重新编译,这时获取不到
//1,使用定时器
/* setTimeout(()=>{
this.$refs.inputTitle.focus()
},100)*/
//2,使用$nextTick
this.$nextTick(()=>{//下一轮执行(DOM节点更新完毕之后才执行)
// console.log('#',this) 箭头函数或普通函数this都是VueComponent
this.$refs.inputTitle.focus()
})
},
blurUpdateTitle(todo,event){//失去焦点就修改
todo.isEdit = false;
if (!event.target.value.trim()) return alert("输入不能为空")
// console.log(event.target.value);//获取输入的值
if (event.target.value.trim()===todo.title) return;//相等就不用改了
console.log("@")
this.$bus.$emit('updateTitle',todo.id,event.target.value);//给App传title修改
}
}
}
</script>
App.vue
<template>
<div>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<TdHeader @addTodo="addTodo"></TdHeader>
<!-- 传递给item需要通过list传 -->
<TodoList :todos="todos"></TodoList>
<TodoFooter :todos="todos" @isAllTotal="isAllTotal" @clearAllTodo="clearAllTodo"></TodoFooter>
</div>
</div>
</div>
</div>
</template>
<script>
// import pubsub from "pubsub-js";
//引入组件
import TodoFooter from "@/components/TodoFooter";
import TdHeader from "@/components/TodoHeader";
import TodoList from "@/components/TodoList";
import Vue from "vue";
export default {
name:'App',
components:{
TdHeader,TodoList,TodoFooter
},
data() {
return {
todos: JSON.parse(localStorage.getItem('todos')) || [] //到浏览器查找, 找不到为空数组[]
}
},
methods:{
//添加待办项
addTodo(todoObj){ //接收儿子传递的参数
this.todos.unshift(todoObj);
},
//修改待办项状态, done
updateTodo(id){
this.todos.forEach((todo)=>{
if (todo.id === id){
todo.done = !todo.done;
}
});
},
updateTitle(id,title){
this.todos.forEach((todo)=>{
if (todo.id === id){
todo.title = title;
}
});
},
//删除待办项
deleteTodo(id){
this.todos = this.todos.filter((todo)=>{
return todo.id !==id
})
// this.todos = this.todos.filter( todo=> todo.id !==id) //简写
},
//修改待办项状态
isAllTotal(done){
this.todos.forEach((todo)=>{
todo.done = done
})
},
//清除已完成的事项
clearAllTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
},
watch:{
todos:{
deep:true,//开启深度检测, 不然发现不了done改变
handler(newValue){
localStorage.setItem('todos',JSON.stringify(newValue));//检测到有修改就把新的todos更新到localStorage
}
}
},
beforeCreate() {
Vue.prototype.$bus = this
},
mounted() {
this.$bus.$on('updateTodo',this.updateTodo);
this.$bus.$on('deleteTodo',this.deleteTodo);
this.$bus.$on('updateTitle',this.updateTitle);
// this.pubId1 = pubsub.subscribe('updateTodo',this.updateTodo);
// this.pubId2 = pubsub.subscribe('deleteTodo',this.deleteTodo);
},
beforeDestroy() {
this.$bus.$off('updateTodo');
this.$bus.$off('deleteTodo');
// pubsub.unsubscribe(this.pubId1);
// pubsub.unsubscribe(this.pubId2);
}
}
//给修改按钮添加样式
</script>
<style>
.btn-update {
color: #fff;
background-color: #00bdfa;
border: 1px solid rgba(189, 54, 47, 0.07);
margin: 2px;
}
.btn-update:hover {
color: #fff;
background-color: #00afff;
}
</style>
15,动画技术
15.1,过渡效果
Test.vue-动画实现
<template>
<div>
<button @click="btnShow">显示/隐藏</button>
<!-- appear进入的时候就加载enter动画 -->
<transition name="hello" appear>
<h1 v-show="isShow" class="come">{{name}}</h1>
</transition>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Test",
data(){
return {
name:'你好啊',
isShow:true
}
},
methods:{
btnShow(){
this.isShow = !this.isShow
}
}
}
</script>
<style scoped>
h1{
background-color: rgba(92, 172, 238, 0.96);
}
@keyframes show {
from{
transform: translateX(-100%);/*从左边而来就是负数*/
}
to{
transform: translateX(0px);
}
}
/*必须安装vue定义的名字填写
v-enter-active 进入
v-leave-active 离开
如果transition起了名字,v换成你名字
*/
.hello-enter-active{
animation: show 0.5s linear;/*linear 匀速的*/
}
.hello-leave-active{
animation: show 0.5s reverse linear;/*reverse反转*/
}
</style>
Test2.vue-过渡实现
<template>
<div>
<button @click="btnShow">显示/隐藏</button>
<!-- appear进入的时候就加载enter动画 -->
<transition name="hello" appear>
<h1 v-show="isShow" class="come">{{name}}</h1>
</transition>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Test",
data(){
return {
name:'你好啊',
isShow:true
}
},
methods:{
btnShow(){
this.isShow = !this.isShow
}
}
}
</script>
<style scoped>
h1{
background-color: rgba(92, 172, 238, 0.96);
/*transition: 0.5s linear;*/
}
/*优化上面的动画,写到active中*/
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/*进入的起点-离开的终点*/
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
/*进入的终点-离开的起点*/
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
</style>
15.2,多个元素过渡
- 使用transaction-group标签
- 子元素绑定key
<!-- appear进入的时候就加载enter动画 -->
<transition-group name="hello" appear>
<h1 v-show="isShow" key="1">{{name}}</h1>
<h1 v-show="!isShow" key="2">我好啊</h1>
</transition-group>
15.3,集成3方动画
npmjs
npm install animate.css
Test3.vue
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
>
<h1 v-show="isShow" key="1">你好啊</h1>
<h1 v-show="!isShow" key="2">我好啊</h1>
</transition-group>
</div>
</template>
<script>
// import 'animate.css' //引入库不需要写from
import animated from 'animate.css'
import Vue from "vue";
Vue.use(animated)
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Test",
data(){
return {isShow : true}
}
}
</script>
<style scoped>
h1{
background-color: rgba(92, 172, 238, 0.96);
}
</style>
效果
动画没效果解决
Windows的锅,勾选窗口动画即可
15.4,Vue封装的过渡与动画总结
-
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。 -
图示: -
写法:
-
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
-
使用<transition> 包裹要过度的元素,并配置name属性: <transition name="hello">
<h1 v-show="isShow">你好啊!</h1>
</transition>
-
备注:若有多个元素需要过度,则需要使用:<transition-group> ,且每个元素都要指定key 值。
15.5,给Todo案例添加动画(新增删除时)
给list加-使用transaction-group, 给item加-使用transaction
TodoList.vue
<template>
<div>
<ul class="todo-main">
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__lightSpeedInRight"
leave-active-class="animate__zoomOut"
>
<TodoItem v-for="todoObj in todos"
:key="todoObj.id" :todo="todoObj"></TodoItem>
</transition-group>
</ul>
</div>
</template>
<script>
import 'animate.css'
效果
|