11.工程化开发&脚手架
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<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>05.工程化开发&脚手架Vue CLI</title>
<!-- 开发Vue的两种方式:
1.核心包传统开发模式,基于html/css/js文件,直接引入核心包,开发Vue
2.工程化开发模式,基于构建工具(webpack)的环境中开发Vue-->
<!-- Vue CLI 是官方提供的一个全局命令工具,可以帮助我们快速创建一个开发Vue项目的标准化基础架子【集成了webpack配置】
好处:(1)开箱即用,零配置;
(2)内置babel等工具;
(3)标准化;
使用步骤:
(1)全局安装(一次):yarn global add @vue/cli 或 npm i @vue/cli -g;
(2)查看Vue版本:vue --version;
(3)创建项目架子:vue create project-name(项目名-不能用中文);
(4)启动项目:yarn serve或npm run serve(找package.json);
-->
<!--脚手架目录文件介绍&项目运行流程-->
<!--常用文件:
index.html:index.html模板文件
App.vue:App根组件->项目运行看到的内容就在这里编写
main.js:入口文件->打包或运行,第一个执行的文件-->
</head>
<body>
</body>
</html>
12. vue框架基础介绍
12.1 App.vue(根组件)
在根组件中引入其他组件(局部注册),对其他组件内容进行渲染。
<!--组件化开发:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用->提升开发效率
组件分类:普通组件、根组件(整个应用最上层的组件,包裹所有普通小组件)-->
<!--App.vue文件(单文件组件)的三个组成部分:
(1)结构:template(有且只能一个根元素)
(2)行为:js逻辑
(3)样式:style(可支持less,需要装包)
-->
<!--让组件支持less
(1)style标签,lang="less" 开启less功能
(2)装包:yarn add less-loader
-->
<!--(1)结构-->
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<!-- 组件-->
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<!--(2)行为-->
<!--App根组件--->
<!--导出的是当前组件的配置项
里面可以提供data(特殊),methods,computed,watch生命周期(8大钩子)-->
<script>
// import HelloWorld from './components/HelloWorld.vue'
export default {
// name: 'App',
// components: {
// HelloWorld
// }
methods:{
created(){
console.log("我是created")
},
fn(){
alert(`你好`)
}
}
}
</script>
<!--(3)样式-->
<style lang="less">
/*让style支持less
1.给style加上lang='less';
2.安装依赖包:less less-loader
yarn add less less-loader -D(安装开发依赖,只在开发的时候应用)
*/
#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>
12.2 main.js
在该文件中引入其他组件(全局注册),可以全局调用其他组件。
//文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html
//1.导入Vue核心包
import Vue from 'vue'
//2.导入App.vue(根组件)
import App from './App.vue'
//提示文本,提示当前处于什么环境(生产环境/开发环境)
Vue.config.productionTip = false
//3.Vue实例化,提供render()方法->基于App.vue创建结构渲染index.html
new Vue({
//el指定vue管理的范围,如:管理index.html中的app容器,el和${'选择器'}作用一致,用于指定vue管理的容器
// el:'#app', //和.$mount('#app')作用一样
// render: h => h(App),
// render:(h)=>{
// return h(App)
//完整render写法,createElement:创建元素
render: (createElement) => {
//基于App创建元素结构
return createElement(App)
}
}).$mount('#app')
12.3 components 组件文件夹(存放普通组件)
12.3.1 Common.vue
<!--普通组件的注册使用
组件注册的两种方式:
(1)局部注册:只能在注册的组件内使用
1)创建.vue文件(三个组成部分)
2)在使用的组件内导入并注册
(2)全局注册:所有组件内都能使用
使用:
1)当成html标签使用<组件名></组件名>
注意:
1)组件名规范->大驼峰命名法,如HmHeader
-->
<!--组件的使用(在)
导入需要注册的组件
import 组件对象 from '.vue文件路径'
import HmHeader from './components/HmHeader'
export default{
//局部注册
components:{
'组件名':组件对象,
HmHeader:HmHeader
}
}
-->
<template>
<div></div>
</template>
<script></script>
<style scoped></style>
12.3.2 HelloWorld.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
12.3.3 HmHeader.vue
<!--头部-->
<template>
</template>
<script>
export default {
name: "HmHeader"
}
</script>
<style scoped>
</style>
12.3.4 HmMain.vue
<!--主体-->
<template>
</template>
<script>
export default {
name: "HmMain"
}
</script>
<style scoped>
</style>
12.3.5 HmFooter.vue
<!--底部-->
<template>
</template>
<script>
export default {
name: "HmFooter"
}
</script>
<style scoped>
</style>
13.Vue注册
13.1 局部注册
13.1.1 App.vue(局部注册组件导入)
<!--局部注册:哪里使用哪里注册(导入)
(1)创建.vue文件(三个组成部分)(单文件组件);
(2)在App.vue根组件(或使用组件内)script中导入局部.vue文件;
(3)在App.vue根组件(或使用组件内)template中使用.vue文件(直接使用标签的格式进行);
-->
<!--技巧:一般都用局部注册,如果发现确实是通用组件,在抽离到全局-->
<template>
<div class="App">
<!-- 头部组件-->
<YYHeader></YYHeader>
<!-- 主体组件-->
<YYMainBody></YYMainBody>
<!-- 底部组件-->
<YYFooter></YYFooter>
<!-- 如果YYHeader+tab出不来,就需配置开发工具
设置中搜索 trigge on tab -> 勾上-->
</div>
</template>
<script>
import YYHeader from "./components/YYHeader"
import YYMainBody from "./components/YYMainBody";
import YYFooter from "./components/YYFooter";
export default {
components: {
//组件名(大驼峰):组件对象
// eslint-disable-next-line vue/no-unused-components
// YYHeader: YYHeader,
// YYMainBody:YYMainBody,
// YYFooter:YYFooter
// 简写:
YYHeader,
YYMainBody,
YYFooter
}
}
</script>
<style>
.App {
width: 600px;
height: 700px;
background-color: #87ceeb;
margin: 0 auto;
padding: 20px;
}
</style>
13.1.2 main.js
//文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html
//1.导入Vue核心包
import Vue from 'vue'
//2.导入App.vue(根组件)
import App from './App.vue'
//提示文本,提示当前处于什么环境(生产环境/开发环境)
Vue.config.productionTip = false
//3.Vue实例化,提供render()方法->基于App.vue创建结构渲染index.html
new Vue({
//el指定vue管理的范围,如:管理index.html中的app容器,el和${'选择器'}作用一致,用于指定vue管理的容器
// el:'#app', //和.$mount('#app')作用一样
// render: h => h(App),
// render:(h)=>{
// return h(App)
//完整render写法,createElement:创建元素
render: (createElement) => {
//基于App创建元素结构
return createElement(App)
}
}).$mount('#app')
13.1.3 components
13.1.3.1 YYHeader.vue
<!--头部组件-->
<template>
<div class="yy-header">
yueyue-header
</div>
</template>
<script>
export default {
name: "yyHeader"
}
</script>
<style scoped>
.yy-header{
height: 100px;
line-height: 100px;
text-align: center;
font-size: 30px;
background-color: #8064a2;
color: white;
}
</style>
13.1.3.2 YYMainBody.vue
<template>
<div class="yy-main">
yueyueMainBody
</div>
</template>
<script>
export default {
name: "YYMainBody"
}
</script>
<style scoped>
.yy-main{
background-color: #fff;
line-height: 100px;
text-align: center;
height: 500px;
font-size: 40px;
color: #42b983;
}
</style>
13.1.3.3 YYFooter.vue
<template>
<div class="yy-footer">
yueyueFooter
</div>
</template>
<script>
export default {
name: "YYFooter"
}
</script>
<style scoped>
.yy-footer{
height: 100px;
line-height: 100px;
text-align: center;
font-size: 30px;
background-color: deeppink;
color: aqua;
}
</style>
13.2 全局注册
13.2.1 App.vue
<!--全局注册
(1)创建.vue文件(三个组成部分);
(2)main.js中进行全局注册;-->
<!--技巧:一般都用局部注册,如果发现确实是通用组件,在抽离到全局-->
<template>
<div class="App">
<!-- 头部组件-->
<YYHeader></YYHeader>
<!-- 主体组件-->
<YYMainBody></YYMainBody>
<!-- 底部组件-->
<YYFooter></YYFooter>
<!-- 如果YYHeader+tab出不来,就需配置开发工具
设置中搜索 trigge on tab -> 勾上-->
</div>
</template>
<script>
import YYHeader from "./components/YYHeader"
import YYMainBody from "./components/YYMainBody";
import YYFooter from "./components/YYFooter";
export default {
components: {
//组件名(大驼峰):组件对象
// eslint-disable-next-line vue/no-unused-components
// YYHeader: YYHeader,
// YYMainBody:YYMainBody,
// YYFooter:YYFooter
// 简写:
YYHeader,
YYMainBody,
YYFooter
}
}
</script>
<style>
.App {
width: 600px;
height: 700px;
background-color: #87ceeb;
margin: 0 auto;
padding: 20px;
}
</style>
13.2.2 main.js(全局注册组件导入)
//文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html
//1.导入Vue核心包
import Vue from 'vue'
//2.导入App.vue(根组件)
import App from './App.vue'
//3.导入需要全局注册的组件
import YYButton from "./components/YYButton";
//提示文本,提示当前处于什么环境(生产环境/开发环境)
Vue.config.productionTip = false
//进行全局注册,Vue.component('组件名(大驼峰)',组件对象)(有多个全局注册需要写多个)
Vue.component('YYButton',YYButton)
//3.Vue实例化,提供render()方法->基于App.vue创建结构渲染index.html
new Vue({
//完整render写法,createElement:创建元素
render: (createElement) => {
//基于App创建元素结构
return createElement(App)
}
}).$mount('#app')
13.2.3 components(组件)
13.2.3.1 YYButton.vue(全局组件)
<!--全局组件-->
<template>
<button class="yy-button">通用按钮</button>
</template>
<script>
export default {
name: "YYButton"
}
</script>
<style scoped>
.yy-button{
height:50px;
line-height: 50px;
padding: 0 20px;
background-color: burlywood;
border-radius: 15px;
cursor:pointer;
}
</style>
13.2.3.2 YYHeader.vue
<!--头部组件-->
<template>
<div class="yy-header">
yueyue-header
<YYButton>yueyue-header</YYButton>
</div>
</template>
<script>
export default {
name: "yyHeader"
}
</script>
<style scoped>
.yy-header{
height: 100px;
line-height: 100px;
text-align: center;
font-size: 30px;
background-color: #8064a2;
color: white;
}
</style>
13.2.3.3 YYMainBody.vue
<template>
<div class="yy-main">
yueyueMainBody
<YYButton>yueyuemain</YYButton>
</div>
</template>
<script>
export default {
name: "YYMainBody"
}
</script>
<style scoped>
.yy-main{
background-color: #fff;
line-height: 100px;
text-align: center;
height: 500px;
font-size: 40px;
color: #42b983;
}
</style>
13.2.3.4 YYFooter.vue
<template>
<div class="yy-footer">
yueyueFooter
<YYButton>yueyueFooter</YYButton>
</div>
</template>
<script>
export default {
name: "YYFooter"
}
</script>
<style scoped>
.yy-footer{
height: 100px;
line-height: 100px;
text-align: center;
font-size: 30px;
background-color: deeppink;
color: aqua;
}
</style>
14.组件通信
14.1 组件通信
<!--结构:只能有一个根元素-->
<template>
<div>data是一个函数</div>
</template>
<!--逻辑:el根实例独有,data是一个函数,其他配置项一致-->
<!--
什么是组件通信
组件通信,就是指组件与组件之间的数据传递。
(1)组件的数据是独立的,无法直接访问其他组件的数据;
(2)想用其他组件的数据->组件通信-->
<!--不同的组件关系和组件通信方案分类
组件关系分类:
(1)父子关系
(2)非父子关系
组件通信解决方案:
(1)父子关系:
1)props(父->子):父组件通过props将数据传递给子组件;
流程:(1)在父组件中的数据中写具体数据值;
(2)在父组件中在标签的属性中绑定父组件中的数据值;
(3)在子组件的props中接收父组件标签中传过来的数据(子组件props中的属性名和父组件标签中的属性名保持一致);
(4)在子组件中使用接收的数据(字段名保持一致);)
2)$emit(子->父):子组件利用$emit通知父组件,进行修改更新;
(2)非父子关系:
1)provide & inject
2)eventbus
(3)通用解决方案:VueX(适合复杂业务场景)
-->
<script>
export default {
}
</script>
<!--样式:-->
<style scoped>
</style>
14.2 App.vue
<template>
<div id="app" style="border:3px solid #ff0000; margin:10px;">
我是App组件
<!-- 1.给组件标签,添加属性的方式,传值-->
<!-- (2) 父组件监听事件-->
<Son :title="myTitle" @changeTitle="changeFn"></Son>
</div>
</template>
<script>
import Son from "./components/Son";
export default {
data(){
return {
myTitle:'学习Vue'
}
},
methods:{
// (3)提供处理函数,形参中获取参数
changeFn(newTitle){
this.myTitle=newTitle
}
},
components:{
Son
}
}
</script>
<style scoped>
</style>
14.3 Son.vue
<template>
<div style="border:3px solid #df5000;margin: 10px;">
<!-- 3.模板中直接使用从父组件中获取的数据-->
Son组件{{ title }}
<button @click="handleFn">修改title</button>
</div>
</template>
<script>
export default {
// 2.子组件内部通过props接收,其中title要与传值的父组件中的属性名要保持一致
props: ['title'],
methods:{
handleFn(){
// (1)$emit触发事件,给父组件发送消息通知(changeTitle与父组件中的绑定事件名相同)
this.$emit("changeTitle","子传父")
}
}
}
</script>
<style scoped>
</style>
15.props详细讲解
15.1 props讲解
<!--结构:只能有一个根元素-->
<template>
<div>data是一个函数</div>
</template>
<!--什么是prop
prop定义:组件上注册的一些自定义属性
prop作用:向子组件传递数据
特点:(1)可以传递任意数量的prop;
(2)可以传递任意类型的prop;
(具体实现:
(1)在父组件中的script标签中写属性及其对应的数据;
(2)在父组件的结构标签中定义属性字段,其绑定下面script中的属性;
(3)在子组件的script中使用props接收父组件传过来的字段数据;
(4)在子组件的结构体标签中使用;
)
-->
<!--props校验
作用:为组件的prop指定验证要求,不符合要求,控制台就会有错误提示->帮助开发者,快速发现错误;
语法:
(1)类型校验;
(2)非空校验;
(3)默认值;
(4)自定义校验;
-->
<!--(1)类型校验-->
<!--子组件接收数据的语法:
props:{
校验的属性名(就是父组件传来的属性名):类型(Number String Boolean...)
}-->
<!--(2)非空校验-->
<!--子组件接收数据的语法:
校验的属性名:{
type:类型,(Number String Boolean...)
required: true, //是否必填
default: 默认值, //默认值
validator(value){
//自定义校验逻辑
return 是否通过校验
}
}
-->
<!--prop & data 单向数据流 -->
<!--prop & data
共同点:都可以给组件提供数据;
区别:(1)data的数据是自己的->随便改;
(2)prop的数据是外部的->不能直接改,要遵循单向数据流
-->
<script>
export default {
}
</script>
<!--样式:-->
<style scoped>
</style>
15.2 App.vue
<template>
<div id="app">
<UserInfo
:username="username"
:age="age"
:car="car"
:hobby="hobby"
></UserInfo>
<BaseProgress :w="width">
</BaseProgress>
<BaseCount
@changeCount="handleChange"
:count="count"></BaseCount>
</div>
</template>
<script>
import UserInfo from "./components/UserInfo";
import BaseProgress from "./components/BaseProgress";
import BaseCount from "./components/BaseCount";
export default {
data() {
return {
username: 'yueyeu',
age: 20,
car: {
brand: "汽车"
},
hobby: ['篮球', '足球', '羽毛球'],
width:60,
count:999
}
},
methods:{
handleChange(newCount){
// console.log(newCount)
this.count=newCount
}
},
components: {
UserInfo,
BaseProgress,
BaseCount
}
}
</script>
<style scoped>
</style>
15.3 UserInfo.vue
<template>
<div class="userinfo">
<h3>个人信息组件</h3>
<div>姓名:{{username}}</div>
<div>年龄:{{age}}</div>
<div>座驾:{{car.brand}}</div>
<div>兴趣爱好:{{hobby.join(",")}}</div>
</div>
</template>
<script>
export default {
name: "UserInfo",
props:[
"username","age","car","hobby"
]
}
</script>
<style scoped>
.userinfo{
width:300px;
border:3px solid #000;
padding:20px;
}
</style>
15.4 BaseProgress.vue
<template>
<div class="base-progress">
<div class="inner" :style="{width: w+'%'}">
<span>{{ w }}%</span>
</div>
</div>
</template>
<script>
export default {
name: "BaseProgress",
// props:["w"]
//1.基础写法(类型校验)
/*props:{
w:Number
}*/
// 2.完整写法(类型 非空 默认值 自定义校验)
props: {
w: {
type: Number,
required: true,
default: 0,
validator(value) {
if (value >= 0 && value <= 100) {
return true
} else {
return false
}
}
}
}
}
</script>
<style scoped>
</style>
15.5 BaseCount.vue
<template>
<div class="base-count">
<button @click="handleSub">-</button>
<span>{{ count }}</span>
<button @click="handleAdd">+</button>
</div>
</template>
<script>
export default {
//1.自己的数据,随便改(谁的数据谁负责)
/*data(){
return{
count:100
}
}*/
// 2.prop传递过来的数据(外部的数据)
// 外部的数据不能直接改
// 单向数据流:父组件的prop更新,会单向的向下流动,会影响到子组件
props: {
count: {
type: Number
}
},
methods: {
handleSub() {
//通知父组件修改数据(子传父),this.$emit(事件名,参数)
this.$emit('changeCount', this.count - 1)
},
handleAdd() {
this.$emit('changeCount',this.count+1)
}
}
}
</script>
<style scoped>
</style>
16.yueyue记事本(组件版)
16.1 yueyue记事本
<!--结构:只能有一个根元素-->
<template>
<div>data是一个函数</div>
</template>
<!--
需求说明:
(1)拆分基础组件;
(2)渲染代办任务;
(3)添加任务;
(4)删除任务;
(5)底部合计和清空功能;
(6)持久化存储;
-->
<script>
export default {
}
</script>
<!--样式:-->
<style scoped>
</style>
16.2 App.vue
<template>
<!-- 主体区域 -->
<section id="app">
<TodoHeader @add="handleAdd"></TodoHeader>
<TodoMain :list="list" @del="handelDel"></TodoMain>
<TodoFooter :list="list" @clear="clear"></TodoFooter>
</section>
</template>
<script>
import TodoHeader from './components/TodoHeader.vue'
import TodoMain from './components/TodoMain.vue'
import TodoFooter from './components/TodoFooter.vue'
// 渲染功能:
// 1.提供数据: 提供在公共的父组件 App.vue
// 2.通过父传子,将数据传递给TodoMain
// 3.利用 v-for渲染
// 添加功能:
// 1.手机表单数据 v-model
// 2.监听事件(回车+点击都要添加)
// 3.子传父,将任务名称传递给父组件 App.vue
// 4.进行添加 unshift(自己的数据自己负责)
// 5.清空文本框输入的内容
// 6.对输入的空数据 进行判断
// 删除功能
// 1.监听事件(监听删除的点击) 携带id
// 2.子传父,讲删除的id传递给父组件的App.vue
// 3.进行删除filter(自己的数据 自己负责)
// 底部合计:父传子 传list 渲染
// 清空功能:子传父 通知父组件 → 父组件进行更新
// 持久化存储:watch深度监视list的变化 -> 往本地存储 ->进入页面优先读取本地数据
export default {
data() {
return {
list: JSON.parse(localStorage.getItem('list')) || [
{ id: 1, name: '打篮球' },
{ id: 2, name: '看电影' },
{ id: 3, name: '逛街' },
],
}
},
components: {
TodoHeader,
TodoMain,
TodoFooter,
},
watch: {
list: {
deep: true,
handler(newVal) {
localStorage.setItem('list', JSON.stringify(newVal))
},
},
},
methods: {
handleAdd(todoName) {
// console.log(todoName)
this.list.unshift({
id: +new Date(),
name: todoName,
})
},
handelDel(id) {
// console.log(id);
this.list = this.list.filter((item) => item.id !== id)
},
clear() {
this.list = []
},
},
}
</script>
<style>
</style>
16.3 TodoHeader.vue
<template>
<!-- 输入框 -->
<header class="header">
<h1>yueyue记事本</h1>
<input placeholder="请输入任务" class="new-todo" v-model="todoName" @keyup.enter="handleAdd"/>
<button class="add" @click="handleAdd">添加任务</button>
</header>
</template>
<script>
export default {
data(){
return {
todoName:''
}
},
methods:{
handleAdd(){
// console.log(this.todoName)
this.$emit('add',this.todoName)
this.todoName = ''
}
}
}
</script>
<style>
</style>
16.4 ToMain.vue
<template>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item, index) in list" :key="item.id">
<div class="view">
<span class="index">{{ index + 1 }}.</span>
<label>{{ item.name }}</label>
<button class="destroy" @click="handleDel(item.id)"></button>
</div>
</li>
</ul>
</section>
</template>
<script>
export default {
props: {
list: {
type: Array,
},
},
methods: {
handleDel(id) {
this.$emit('del', id)
},
},
}
</script>
<style>
</style>
16.5 ToFooter.vue
<template>
<!-- 统计和清空 -->
<footer class="footer">
<!-- 统计 -->
<span class="todo-count"
>合 计:<strong> {{ list.length }} </strong></span
>
<!-- 清空 -->
<button class="clear-completed" @click="clear">清空任务</button>
</footer>
</template>
<script>
export default {
props: {
list: {
type: Array,
},
},
methods:{
clear(){
this.$emit('clear')
}
}
}
</script>
<style>
</style>
17.非父子通信
17.1 非父子通信(拓展)-provide& $inject
<!--结构:只能有一个根元素-->
<template>
<div>data是一个函数</div>
</template>
<!--
provide & inject 作用:跨层级共享数据
1.父组件provide提供数据
export default{
provide(){
//普通类型(非响应式)
color:this.color,
//复杂类型(响应式)
userInfo:this.userInfo,
}
}
2.子/孙组件inject取值使用
export default{
inject:['color','userInfo'],
created(){
console.log(this.color,this.userInfo)
}
}
-->
<script>
export default {
}
</script>
<!--样式:-->
<style scoped>
</style>
17.2 App.vue
<template>
<div class="app">
<span>我是App组件</span>
<button @click="change">修改数据</button>
<BaseA></BaseA>
<BaseB></BaseB>
</div>
</template>
<!--非父子通信(拓展)-event bus事件总线-->
<!--作用:非父子组件之间,进行简易消息传递(复杂场景->Vuex)-->
<!--1.创建一个都能访问到的事件总线(空Vue实例)(中间媒介)->utils/EventBus.js
import Vue from 'vue'
const Bus=new Vue()
export default Bus
-->
<!--2.A组件(接收方),监听Bus实例的事件($on事件监听)
created(){
Bus.$on('sendMsg',(msg)=>{
this.msg=msg
})
}
-->
<!--3.B组件(发送方),触发Bus实例的事件($emit触发事件)
Bus.$emit('sendMsg','这是一个消息')
-->
<script>
import BaseA from "../component/BaseA"
import BaseB from "../component/BaseB"
export default {
components: {
BaseA,
BaseB
},
//共享的数据
provide() {
return {
color: this.color, //简单类型(非响应式)
userInfo: this.userInfo //复杂类型(响应式---推荐)
}
},
data() {
return {
color: 'pink',
userInfo: {
name: "yueyue",
age: 18
}
}
},
methods: {
change() {
// this.color = "green"
this.userInfo.name = "cc"
}
}
}
</script>
<style>
</style>
17.3 BaseA.vue
<template>
<div class="baseA">
我是A组件(接收方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from "../utils/EventBus"
export default {
// 2.在A组件(接收方),进行监听Bus的事件(订阅消息)
created(){
Bus.$on("sendMsg",(msg)=>{
this.msg=msg
console.log(msg)
})
}
}
</script>
<style scoped>
.baseA{
width:200px;
height:150px;
padding:10px;
margin-top: 10px;
border:3px solid #000;
border-radius: 5px;
}
</style>
17.4 BaseB.vue
<template>
<div class="baseB">
我是B组件(发布方)
<button @click="clickSend">发布通知</button>
</div>
</template>
<script>
import Bus from "../utils/EventBus"
export default {
methods:{
clickSend(){
//3.B组件(发送方)触发事件的方式传递参数(发布消息)
Bus.$emit("sendMsg","今日晴天,适合郊游")
}
}
}
</script>
<style scoped>
.baseB{
width:200px;
height:150px;
padding:10px;
border:3px solid #000;
border-radius:5px;
font-size:20px;
}
</style>
17.5 GrandSon.vue
<template>
<div class="grandSon">
我是GrandSon组件
{{color}}-{{userInfo.name}}-{{userInfo.age}}
</div>
</template>
<script>
export default {
//接收App共享的数据
inject:['color','userInfo']
}
</script>
<style scoped>
.grandSon {
width: 200px;
height: 150px;
padding: 10px;
margin-top: 10px;
border: 3px solid #000;
border-radius: 5px;
}
</style>
17.6 EventBus.vue
<template>
</template>
<script>
export default {
name: "EventBus"
}
</script>
<style scoped>
</style>
18.v-model原理
18.1 v-model原理
<!--结构:只能有一个根元素-->
<template>
<div id="app">
<!-- v-model相当于:value="XXX" @input="msg=XXX"-->
<input v-model="msg" type="text">
<input :value="msg" @input="msg=$event.target.value" type="text">
</div>
</template>
<!--v-model原理
原理:v-model本质上是一个语法糖,例如应用在输入框上,就是value属性和input事件的合写
作用:提供数据的双向绑定;
(1)数据变,视图跟着变:value;
(2)视图变,数据跟着变@input;
注:$event用于在模板中,获取事件的形参
-->
<!--表单类组件封装&v-model简化代码
1.表单类组件封装
(1)父传子:数据应该时父组件props传递过来的,v-model拆解绑定数据
(2)子传父:监听输入,子传父传值给父组件修改
-->
<script>
export default {
}
</script>
<!--样式:-->
<style scoped>
</style>
18.2 App.vue
<template>
<div class="app">
<BaseSelect v-model="selectId"></BaseSelect>
</div>
</template>
<!--v-model原理
原理:v-model本质上是一个语法糖,例如应用在输入框上,就是value属性和input事件的合写
作用:提供数据的双向绑定;
(1)数据变,视图跟着变:value;
(2)视图变,数据跟着变@input;
注:$event用于在模板中,获取事件的形参
-->
<script>
import BaseSelect from "./components/BaseSelect"
export default {
data() {
return {
selectId: "102"
}
},
components: {
BaseSelect
}
}
</script>
<style>
</style>
18.3 BaseSelect.vue
<template>
<div>
<select :value="value" @change="handleChange">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">武汉</option>
<option value="104">深圳</option>
<option value="105">广州</option>
</select>
</div>
</template>
<script>
export default {
props: {
value:String
},
methods:{
handleChange(e){
// console.log(e.target.value)
this.$emit("input",e.target.value)
}
}
}
</script>
<style scoped>
.grandSon {
width: 200px;
height: 150px;
padding: 10px;
margin-top: 10px;
border: 3px solid #000;
border-radius: 5px;
}
</style>
19 .sync修饰符
19.1 .sync修饰符
<!--结构:只能有一个根元素-->
<template>
<div id="app">
<!-- v-model相当于:value="XXX" @input="msg=XXX"-->
<input v-model="msg" type="text">
<input :value="msg" @input="msg=$event.target.value" type="text">
</div>
</template>
<!-- .sync修饰符
作用:可以实现子组件与父组件数据的双向绑定,简化代码
特点:prop属性名,可以自定义,非固定为value
场景:封装弹框类的基础组件,visible属性true显示true显示,false隐藏
本质:就是:属性名和@update:属性名合写
父组件(使用):
<BaseDialog :visible.sync="isShow"/>
<BaseDialog :visible=isShow" @update:visible="isShow=$event"/>
子组件(封装):
props:{
visible:Boolean
},
this.$emit("update:visible",false)
-->
<script>
export default {
}
</script>
<!--样式:-->
<style scoped>
</style>
19.2 App.vue
<template>
<div class="app">
<button class="logout" @click="isShow=true">退出按钮</button>
<!-- :visible相当于:visible+@update:visible-->
<BaseSelect :visible.sync="isShow"></BaseSelect>
</div>
</template>
<script>
import BaseSelect from "./components/BaseSelect"
export default {
data() {
return {
isShow:false
}
},
components: {
BaseSelect
}
}
</script>
<style>
</style>
19.3 BaseSelect.vue
<template>
<div v-show="visible" class="base-dialog-mask">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button class="close">关闭</button>
</div>
<div class="content">
<p>你确定要退出本系统吗?</p>
</div>
<div class="footer">
<button>确定</button>
<button>取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
visible:Boolean
},
methods:{
close(){
// console.log(e.target.value)
this.$emit("update:visible",false)
}
}
}
</script>
<style scoped>
.grandSon {
width: 200px;
height: 150px;
padding: 10px;
margin-top: 10px;
border: 3px solid #000;
border-radius: 5px;
}
</style>
20.ref 和 $refs
20.1 ref & $refs
<!--结构:只能有一个根元素-->
<template>
<div id="app">
<!-- v-model相当于:value="XXX" @input="msg=XXX"-->
<input v-model="msg" type="text">
<input :value="msg" @input="msg=$event.target.value" type="text">
</div>
</template>
<!-- ref 和 $refs(配套使用)
ref 写在标签中,$refs 写在javascript中
作用:利用ref和$refs可以用于获取dom元素,或 组件实例
特点:查找范围->当前组件内(更精确稳定)
-->
<!--
1.获取dom:
(1)目标标签-添加ref属性
<div ref="chartRef">渲染图表的容器</div>
(2)恰当时机,通过this.$refs.xxx(其中xxx时ref中对应的属性值,如chartRef),获取目标标签
mounted(){
console.log(this.$refs.chartRef)
}
-->
<!--
2.获取组件:
(1)目标组件-添加ref属性
<BaseForm ref="baseForm"></BaseForm>
(2)恰当时机,通过this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法
this.$refs.baseForm.组件方法()
-->
<script>
export default {}
</script>
<!--样式:-->
<style scoped>
</style>
20.2 App.vue
<template>
<div class="app">
<div style="width:100px;height:100px;" class="base-chart-box">
捣乱的盒子
</div>
<BaseChart></BaseChart>
<BaseForm ref="baseform"></BaseForm>
<button @click="handleGet">获取数据</button>
<button @click="handleReset">重置数据</button>
</div>
</template>
<script>
import BaseChart from "./components/BaseChart"
import BaseForm from "./components/BaseForm"
export default {
data() {
return {}
},
methods: {
handleGet() {
console.log(this.$refs.baseform.getValue())
},
handleReset() {
this.$refs.baseform.resetValue()
}
},
components: {
BaseChart,
BaseForm
}
}
</script>
<style>
</style>
20.3 BaseChart.vue
<template>
<div ref="mychart" class="base-chart-box">
</div>
</template>
<script>
//装包 yarn add echarts
import * as echarts from 'echarts';
export default {
mounted() {
// 基于准备好的dom,初始化echarts实例,获取dom元素
// const myChart=echarts.init(document.querySelector(".base-chart-box"))
console.log(this.$refs.mychart)
const myChart=echarts.init(this.$refs.mychart)
// 指定图表的配置项和数据
const option={
}
// 使用刚指定的配置项和数据显示图表
myChart.setOption(option)
},
}
</script>
<style scoped>
.grandSon {
width: 200px;
height: 150px;
padding: 10px;
margin-top: 10px;
border: 3px solid #000;
border-radius: 5px;
}
</style>
20.4 BaseForm.vue
<template>
<form action="">
账号:<input type="text" v-model="account">
密码:<input type="password" v-model="password">
</form>
</template>
<script>
export default {
name: "BaseForm",
data() {
return {
account: "",
password: ""
}
},
methods: {
//方法1:收集表单数据,返回一个对象
getValue() {
return {
account: this.account,
password: this.password
}
},
//方法2:重置表单
resetValue() {
this.account = ""
this.password = ""
}
}
}
</script>
<style scoped>
</style>
21.vue异步更新
21.1 vue异步更新
<!--结构:只能有一个根元素-->
<template>
<div id="app">
<!-- v-model相当于:value="XXX" @input="msg=XXX"-->
<input v-model="msg" type="text">
<input :value="msg" @input="msg=$event.target.value" type="text">
</div>
</template>
<!--Vue异步更新、$nextTick
$nextTick:等dom更新后,才会触发执行此方法里的函数体
语法:this.$nextTick(函数体)
this.$nextTick(()=>{
this.$refs.inp.focus()
})
-->
<script>
export default {}
</script>
<!--样式:-->
<style scoped>
</style>
21.2 App.vue
<template>
<div class="app">
<!-- 是否在编辑状态-->
<div v-if="isShowEdit">
<input ref="inp" v-model="editValue" type="text">
<button>确认</button>
</div>
<!-- 默认状态-->
<div v-else>
<span>{{ title }}</span>
<button @click="handleEdit">编辑</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
title: "大标题",
editValue: '',
isShowEdit: false
}
},
methods: {
handleEdit() {
// 1.显示输入框
this.isShowEdit = true
// 2.让输入框获取焦点($nextTick等dom更新完,立刻执行完多有的函数体)
// console.log(this.$refs.inp)
this.$nextTick(()=>{
this.$refs.inp.focus()
})
/*setTimeout(()=>{
this.$refs.inp.focus()
},1000)*/
},
handleReset() {
this.$refs.baseform.resetValue()
}
},
}
</script>
<style>
</style>