第二部分 Vue讲解(11-21)(代码版)

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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值