Vue框架

一、Vue框架概述

1. vue框架的介绍

渐进式javacript框架, 一套拥有自己规则的语法

Vue 是一套用于构建用户界面的渐进式框架. 与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。

Vue.js 是前端的主流框架之一,和 Angular.js、React.js 一起,并成为前端三大主流框架

官网地址: Vue.js - 渐进式 JavaScript 框架 | Vue.jsVue.js - 渐进式的 JavaScript 框架https://cn.vuejs.org/ (作者: 尤雨溪)

2. vue的特点

  • 渐进式

  • 声明式渲染

  • 数据驱动视图 (响应式)

  • 极少的去写DOM操作相关代码

  • 双向绑定

  • 组件系统

  • 不兼容IE8及以下浏览器

3. Vue的优点

1.体积小 
2.更高的运行效率
3.双向数据绑定,简化 Dom 操作
通过 MVVM 思想实现数据的双向绑定
4 生态丰富、学习成本低
市场上拥有大量成熟、稳定的基于 vue.js 的 ui 框架、常用组件 来即用实现
快速开发 对初学者友好、入门容易、学习资料多.

二、@vue/cli脚手架

1. @vue/cli 脚手架介绍

      @vue/cli是Vue官方提供的一个全局模块包(得到vue命令), 此包用于创建脚手架项目

脚手架是为了保证各施工过程顺利进行而搭设的工作平台

@vue/cli的好处

                             开箱即用

                             0配置webpack

                             babel支持

                             css, less支持

                             开发服务器支持

2. @vue/cli安装

目标: 把@vue/cli模块包按到全局, 电脑拥有vue命令, 才能创建脚手架工程

  • (1)全局安装命令

yarn global add @vue/cli
# OR
npm install -g @vue/cli

注意: 如果半天没动静(95%都是网速问题), 可以ctrl c停止重新来,换一个网继续重来

  • (2)查看vue脚手架版本

    vue -V

总结: 如果出现版本号就安装成功, 否则失败

3. @vue/cli 创建项目启动服务

目标: 使用vue命令, 创建脚手架项目

(1)创建项目

# vue和create是命令, vuecli-demo是文件夹名
vue create vuecli-demo

注意: 项目名不能带大写字母, 中文和特殊符号

(2)选择模板和包管理器, 等待脚手架项目创建完毕

a. 选择预置版本为vue2还是vue3(可以上下箭头选择, 弄错了ctrl+c重来)

b. 选择用什么方式下载脚手架项目需要的依赖包  

c. 回车等待生成项目文件夹+文件+下载必须的第三方包们  

 d. cd进入项目下, 启动内置的webpack本地热更新开发服务器
cd vuecil-demo
​
npm run serve
# 或
yarn serve
只要看到绿色成功了(底层node+webpack热更新服务)

 e. 如果未自动弹出浏览器, 手动打开浏览器输入上述地址

4.  @vue/cli 目录和代码分析

vuecil-demo        # 项目目录
    ├── node_modules # 项目依赖的第三方包
    ├── public       # 静态文件目录
      ├── favicon.ico# 浏览器小图标
      └── index.html # 单页面的html文件(网页浏览的是它)
    ├── src          # 业务文件夹
      ├── assets     # 静态资源
        └── logo.png # vue的logo图片
      ├── components # 组件目录
        └── HelloWorld.vue # 欢迎页面vue代码文件 
      ├── App.vue    # 整个应用的根组件
      └── main.js    # 入口js文件
    ├── .gitignore   # git提交忽略配置
    ├── babel.config.js  # babel配置
    ├── package.json  # 依赖包列表
    ├── README.md    # 项目说明
    └── yarn.lock    # 项目包版本锁定和缓存地址

主要文件及含义

node_modules下都是下载的第三方包
public/index.html – 浏览器运行的网页
src/main.js – webpack打包的入口文件
src/App.vue – vue项目入口页面
package.json – 依赖包列表文件

5. @vue/cli 项目架构了解

 

6. @vue/cli 自定义配置

目标:项目中没有webpack.config.js文件,因为@vue/cli用的vue.config.js

src并列处新建vue.config.js

/* 覆盖webpack的配置 */
module.exports = {
  devServer: { // 自定义服务配置
    open: true, // 自动打开浏览器
    port: 3000
  }
}

7. eslint了解

目标: 知道eslint的作用, 和如何暂时关闭, 它是一个代码检查工具

(1)如果写代码违反了eslint的规则-报错

演示: 在main.js中随便定义变量 – 不使用 – 观察eslint报错

 (2)处理eslint代码检查

方式1: 手动解决掉错误, 以后项目中会讲如何自动解决

方式2: 暂时关闭eslint检查(因为现在主要精力在学习Vue语法上), 在vue.config.js中配置后重启服务

8. @vue/cli 单vue文件讲解

目标: 单vue文件好处, 独立作用域互不影响

Vue推荐采用.vue文件来开发项目

template里只能有一个根标签

vue文件-独立模块-作用域互不影响

style配合scoped属性, 保证样式只针对当前template内标签生效

vue文件配合webpack, 把他们打包起来插入到index.html
<!-- template必须, 只能有一个根标签, 影响渲染到页面的标签结构 -->
<template>
  <div>欢迎使用vue</div>
</template>
​
<!-- js相关 -->
<script>
export default {
  name: 'App'
}
</script>
​
<!-- 当前组件的样式, 设置scoped, 可以保证样式只对当前页面有效 -->
<style scoped>
</style>
​

最终: Vue文件配合webpack, 把他们打包起来插入到index.html, 然后在浏览器运行

单vue文件好处?
             独立作用域, 不再担心变量重名问题

9. @vue/cli 欢迎界面清理

(1)assets 和 components 文件夹下的一切都删除掉 (不要默认的欢迎页面)
(2)src/App.vue默认有很多内容, 可以全部删除留下template和script和style的框

 

三、vue基础-MVVM设计模式

目的: 转变思维, 用数据驱动视图改变, 操作dom的事, vue源码内操作了

设计模式: 是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。

  • MVVM,一种软件架构模式,决定了写代码的思想和层次

    • M: model数据模型 (data里定义)

    • V: view视图 (html页面)

    • VM: ViewModel视图模型 (vue.js源码)

  • MVVM通过数据双向绑定让数据自动地双向同步 不再需要操作DOM

    • V(修改视图) -> M(数据自动同步)

    • M(修改数据) -> V(视图自动同步)

1. 在vue中,不推荐直接手动操作DOM

2. 在vue中,通过数据驱动视图,不再需操作DOM,而是如何操作数据

总结: vue源码内采用MVVM设计模式思想, 大大减少了DOM操作, 挺高开发效率  

mvvm和mvc区别是什么?

MVC: 也是一种设计模式, 组织代码的结构, 是model数据模型, view视图, Controller控制器, 
在控制器这层里编写js代码, 来控制数据和视图关联

MVVM: 即Model-View-ViewModel的简写。即模型-视图-视图模型, VM是这个设计模式的核心,
 连接v和m的桥梁, 内部会监听DOM事件, 监听数据对象变化来影响对方. 我们称之为数据绑定

四、vue基础

1. 插值表达式

目的: 在dom标签中, 直接插入内容

又叫: 声明式渲染/文本插值

语法: {{ 表达式 }}

msg和obj是vue数据变量
要在js中data函数里声明

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h2>{{ obj.name }}</h2>
    <h3>{{ obj.age > 18 ? '成年' : '未成年' }}</h3>
  </div>
</template>

<script>
export default {
  data() { // 格式固定, 定义vue数据之处
    return {  // key相当于变量名
      msg: "hello, vue",
      obj: {
        name: "小vue",
        age: 5
      }
    }
  }
}
</script>

<style>
</style>

 总结: dom中插值表达式赋值, vue的变量必须在data里声明

 2. vue指令-v-bind

目标: 给标签属性设置vue变量的值

vue指令, 实质上就是特殊的 html 标签属性, 特点: v- 开头

每个指令, 都有独立的作用

  • 语法:v-bind:属性名="vue变量"

  • 简写::属性名="vue变量"

<!-- vue指令-v-bind属性动态赋值 -->
<a v-bind:href="url">我是a标签</a>
<img :src="imgSrc">

总结: 把vue变量的值, 赋予给dom属性上, 影响标签显示效果

3. vue指令-v-on

目标: 给标签绑定事件

  • 语法

    • v-on:事件名="要执行的==少量代码=="

    • v-on:事件名="methods中的函数"

    • v-on:事件名="methods中的函数(实参)"

  • 简写: @事件名="methods中的函数"

<!-- vue指令:   v-on事件绑定-->
<p>你要买商品的数量: {{count}}</p>
<button v-on:click="count = count + 1">增加1</button>
<button v-on:click="addFn">增加1个</button>
<button v-on:click="addCountFn(5)">一次加5件</button>
​
<button @click="subFn">减少</button>
​
<script>
    export default {
        // ...其他省略
        methods: {
            addFn(){ // this代表export default后面的组件对象(下属有data里return出来的属性)
                this.count++
            },
            addCountFn(num){
                this.count += num
            },
            subFn(){
                this.count--
            }
        }
    }
</script>

总结: 常用@事件名, 给dom标签绑定事件, 以及=右侧事件处理函数

练习-翻转世界

目标: 点击按钮 - 把文字取反显示 - 再点击取反显示(回来了)

提示: 把字符串取反赋予回去

<template>
  <div>
    <h1>{{ message }}</h1>
    <button @click="btn">逆转世界</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "HELLO, WORLD",
    };
  },
  methods: {
    btn(){
      this.message = this.message.split("").reverse().join("")
    }
  }
};
</script>

4. vue指令-v-on事件对象

目标: vue事件处理函数中, 拿到事件对象

  • 语法:

    • 无传参, 通过形参直接接收

    • 传参, 通过$event指代事件对象传给事件处理函数

<template>
  <div>
    <a @click="one" href="http://www.baidu.com">阻止百度</a>
    <hr>
    <a @click="two(10, $event)" href="http://www.baidu.com">阻止去百度</a>
  </div>
</template>
​
<script>
export default {
  methods: {
    one(e){
      e.preventDefault()
    },
    two(num, e){
      e.preventDefault()
    }
  }
}
</script>

5. vue指令-v-on修饰符

目的: 在事件后面.修饰符名 - 给事件带来更强大的功能

  • 语法:

    • @事件名.修饰符="methods里函数"

      • .stop - 阻止事件冒泡

      • .prevent - 阻止默认行为

      • .once - 程序运行期间, 只触发一次事件处理函数

<template>
  <div @click="fatherFn">
    <!-- vue对事件进行了修饰符设置, 在事件后面.修饰符名即可使用更多的功能 -->
    <button @click.stop="btn">.stop阻止事件冒泡</button>
    <a href="http://www.baidu.com" @click.prevent="btn">.prevent阻止默认行为</a>
    <button @click.once="btn">.once程序运行期间, 只触发一次事件处理函数</button>
  </div>
</template>
​
<script>
export default {
  methods: {
    fatherFn(){
      console.log("father被触发");
    },
    btn(){
      console.log(1);
    }
  }
}
</script>

总结: 修饰符给事件扩展额外功能

6. vue指令-v-on按键修饰符

目标: 给键盘事件, 添加修饰符, 增强能力

  • 语法:

    • @keyup.enter - 监测回车按键

    • @keyup.esc - 监测返回按键

更多修饰符https://cn.vuejs.org/v2/guide/events.html#%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6

<template>
  <div>
    <input type="text" @keydown.enter="enterFn">
    <hr>
    <input type="text" @keydown.esc="escFn">
  </div>
</template>
​
<script>
export default {
 methods: {
   enterFn(){
     console.log("enter回车按键了");
   },
   escFn(){
     console.log("esc按键了");
   }
 }
}
</script>

总结: 多使用事件修饰符, 可以提高开发效率, 少去自己判断过程

7. vue指令 v-model

目标: 把value属性和vue数据变量, 双向绑定到一起

  • 语法: v-model="vue数据变量"

  • 双向数据绑定

    • 数据变化 -> 视图自动同步

    • 视图变化 -> 数据自动同步

  • 演示: 用户名绑定 - vue内部是MVVM设计模式

<template>
  <div>
    <!-- 
        v-model:是实现vuejs变量和表单标签value属性, 双向绑定的指令
    -->
    <div>
      <span>用户名:</span>
      <input type="text" v-model="username" />
    </div>
    <div>
      <span>密码:</span>
      <input type="password" v-model="pass" />
    </div>
    <div>
      <span>来自于: </span>
      <!-- 下拉菜单要绑定在select上 -->
      <select v-model="from">
        <option value="北京市">北京</option>
        <option value="南京市">南京</option>
        <option value="天津市">天津</option>
      </select>
    </div>
    <div>
      <!-- (重要)
      遇到复选框, v-model的变量值
      非数组 - 关联的是复选框的checked属性
      数组   - 关联的是复选框的value属性
       -->
      <span>爱好: </span>
      <input type="checkbox" v-model="hobby" value="抽烟">抽烟
      <input type="checkbox" v-model="hobby" value="喝酒">喝酒
      <input type="checkbox" v-model="hobby" value="写代码">写代码
    </div>
    <div>
      <span>性别: </span>
      <input type="radio" value="男" name="sex" v-model="gender">男
      <input type="radio" value="女" name="sex" v-model="gender">女
    </div>
    <div>
      <span>自我介绍</span>
      <textarea v-model="intro"></textarea>
    </div>
  </div>
</template>
​
<script>
export default {
  data() {
    return {
      username: "",
      pass: "",
      from: "",
      hobby: [], 
      sex: "",
      intro: "",
    };
    // 总结:
    // 特别注意: v-model, 在input[checkbox]的多选框状态
    // 变量为非数组, 则绑定的是checked的属性(true/false) - 常用于: 单个绑定使用
    // 变量为数组, 则绑定的是他们的value属性里的值 - 常用于: 收集勾选了哪些值
  }
};
</script>

总结: 本阶段v-model只能用在表单元素上, 以后学组件后讲v-model高级用法

8. vue指令 v-model修饰符

目标: 让v-model拥有更强大的功能

  • 语法:

    • v-model.修饰符="vue数据变量"

      • .number 以parseFloat转成数字类型

      • .trim 去除首尾空白字符

      • .lazy 在change时触发而非inupt时

<template>
  <div>
    <div>
      <span>年龄:</span>
      <input type="text" v-model.number="age">
    </div>
    <div>
      <span>人生格言:</span>
      <input type="text" v-model.trim="motto">
    </div>
    <div>
      <span>自我介绍:</span>
      <textarea v-model.lazy="intro"></textarea>
    </div>
  </div>
</template>
​
<script>
export default {
  data() {
    return {
      age: "",
      motto: "",
      intro: ""
    }
  }
}
</script>

总结: v-model修饰符, 可以对值进行预处理, 非常高效好用

9. vue指令 v-text和v-html

目的: 更新DOM对象的innerText/innerHTML

  • 语法:

    • v-text="vue数据变量"

    • v-html="vue数据变量"

  • 注意: 会覆盖插值表达式

<template>
  <div>
    <p v-text="str"></p>
    <p v-html="str"></p>
  </div>
</template>
​
<script>
export default {
  data() {
    return {
      str: "<span>我是一个span标签</span>"
    }
  }
}
</script>

总结: v-text把值当成普通字符串显示, v-html把值当做html解析

10. vue指令 v-show和v-if

目标: 控制标签的隐藏或出现

  • 语法:

    • v-show="vue变量"

    • v-if="vue变量"

  • 原理

    • v-show 用的display:none隐藏 (频繁切换使用)

    • v-if 直接从DOM树上移除

  • 高级

    • v-else使用

<template>
  <div>
    <h1 v-show="isOk">v-show的盒子</h1>
    <h1 v-if="isOk">v-if的盒子</h1>
​
    <div>
      <p v-if="age > 18">我成年了</p>
      <p v-else>还得多吃饭</p>
    </div>
  </div>
</template>
​
<script>
export default {
  data() {
    return {
      isOk: true,
      age: 15
    }
  }
}
</script>

总结: 使用v-show和v-if以及v-else指令, 方便通过变量控制一套标签出现/隐藏

案例-折叠面板

目标: 点击展开或收起时,把内容区域显示或者隐藏

此案例使用了less语法, 项目中下载模块

yarn add less@3.0.4 less-loader@5.0.0 -D
<template>
  <div id="app">
    <h3>案例:折叠面板</h3>
    <div>
      <div class="title">
        <h4>芙蓉楼送辛渐</h4>
        <span class="btn" @click="isShow = !isShow">
          {{ isShow ? '收起' : '展开' }}
        </span>
      </div>
      <div class="container" v-show="isShow">
        <p>寒雨连江夜入吴, </p>
        <p>平明送客楚山孤。</p>
        <p>洛阳亲友如相问,</p>
        <p>一片冰心在玉壶。</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: false
    }
  }
}
</script>

<style lang="less">
body {
  background-color: #ccc;
  #app {
    width: 400px;
    margin: 20px auto;
    background-color: #fff;
    border: 4px solid blueviolet;
    border-radius: 1em;
    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
    padding: 1em 2em 2em;
    h3 {
      text-align: center;
    }
    .title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      border: 1px solid #ccc;
      padding: 0 1em;
    }
    .title h4 {
      line-height: 2;
      margin: 0;
    }
    .container {
      border: 1px solid #ccc;
      padding: 0 1em;
    }
    .btn {
      /* 鼠标改成手的形状 */
      cursor: pointer;
    }
  }
}
</style>

11. vue指令-v-for

目标: 列表渲染, 所在标签结构, 按照数据数量, 循环生成

  • 语法

    • v-for="(值, 索引) in 目标结构"

    • v-for="值 in 目标结构"

  • 目标结构:

    • 可以遍历数组 / 对象 / 数字 / 字符串 (可遍历结构)

  • 注意:

    v-for的临时变量名不能用到v-for范围外

<template>
  <div id="app">
    <div id="app">
      <!-- v-for 把一组数据, 渲染成一组DOM -->
      <!-- 口诀: 让谁循环生成, v-for就写谁身上 -->
      <p>学生姓名</p>
      <ul>
        <li v-for="(item, index) in arr" :key="item">
          {{ index }} - {{ item }}
        </li>
      </ul>
​
      <p>学生详细信息</p>
      <ul>
        <li v-for="obj in stuArr" :key="obj.id">
          <span>{{ obj.name }}</span>
          <span>{{ obj.sex }}</span>
          <span>{{ obj.hobby }}</span>
        </li>
      </ul>
​
      <!-- v-for遍历对象(了解) -->
      <p>老师信息</p>
      <div v-for="(value, key) in tObj" :key="value">
        {{ key }} -- {{ value }}
      </div>
​
      <!-- v-for遍历整数(了解) - 从1开始 -->
      <p>序号</p>
      <div v-for="i in count" :key="i">{{ i }}</div>
    </div>
  </div>
</template>
​
<script>
export default {
  data() {
    return {
      arr: ["小明", "小欢欢", "大黄"],
      stuArr: [
        {
          id: 1001,
          name: "孙悟空",
          sex: "男",
          hobby: "吃桃子",
        },
        {
          id: 1002,
          name: "猪八戒",
          sex: "男",
          hobby: "背媳妇",
        },
      ],
      tObj: {
        name: "小黑",
        age: 18,
        class: "1期",
      },
      count: 10,
    };
  },
};
</script>

总结: vue最常用指令, 铺设页面利器, 快速把数据赋予到相同的dom结构上循环生成

为什么避免v-for和v-if在一起使用

​	Vue 处理指令时,v-for 比 v-if 具有更高的优先级, 虽然用起来也没报错, 
但是性能不高, 如果你有5个元素被v-for循环, v-if也会分别执行5次.

 练习-购物车

目标: 完成商品浏览和删除功能, 当无数据给用户提示

  • 需求1: 根据给的初始数据, 把购物车页面铺设出来

  • 需求2: 点击对应删除按钮, 删除对应数据

  • 需求3: 当数据没有了, 显示一条提示消息

    <template>
      <div id="app">
        <table class="tb">
          <tr>
            <th>编号</th>
            <th>品牌名称</th>
            <th>创立时间</th>
            <th>操作</th>
          </tr>
          <!-- 循环渲染的元素tr -->
          <tr v-for="(item,index) in list" :key="item.id">
                <td>{{item.id}}</td>
                <td>{{item.name}}</td>
                <td>{{item.time}}</td>
                <td>
                    <button @click="del(index)">删除</button>
                </td>
            </tr>
          <tr v-if="list.length === 0">
            <td colspan="4">没有数据咯~</td>
          </tr>
        </table>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          list: [
            { id: 1, name: "奔驰", time: "2020-08-01" },
            { id: 2, name: "宝马", time: "2020-08-02" },
            { id: 3, name: "奥迪", time: "2020-08-03" },
          ],
        };
      },
      methods: {
        del(index) {
          // 删除按钮 - 得到索引, 删除数组里元素
          this.list.splice(index, 1);
        },
      },
    };
    </script>
    
    <style>
    #app {
      width: 600px;
      margin: 10px auto;
    }
    
    .tb {
      border-collapse: collapse;
      width: 100%;
    }
    
    .tb th {
      background-color: #0094ff;
      color: white;
    }
    
    .tb td,
    .tb th {
      padding: 5px;
      border: 1px solid black;
      text-align: center;
    }
    
    .add {
      padding: 5px;
      border: 1px solid black;
      margin-bottom: 10px;
    }
    </style>
    

    12. vue基础 v-for更新监测

    目标: 当v-for遍历的目标结构改变, Vue触发v-for的更新

    情况1: 数组翻转

    情况2: 数组截取

    情况3: 更新值

    口诀:
    
         数组变更方法, 就会导致v-for更新, 页面更新
    
         数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或this.$set()

这些方法会触发数组改变, v-for会监测到并更新页面

push()

pop()

shift()

unshift()

splice()

sort()

reverse()

这些方法不会触发v-for更新

slice()

filter()

concat()

  

<template>
  <div>
    <ul>
      <li v-for="(val, index) in arr" :key="index">
        {{ val }}
      </li>
    </ul>
    <button @click="revBtn">数组翻转</button>
    <button @click="sliceBtn">截取前3个</button>
    <button @click="updateBtn">更新第一个元素值</button>
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      arr: [5, 3, 9, 2, 1]
    }
  },
  methods: {
    revBtn(){
      // 1. 数组翻转可以让v-for更新
      this.arr.reverse()
    },
    sliceBtn(){
      // 2. 数组slice方法不会造成v-for更新
      // slice不会改变原始数组
      // this.arr.slice(0, 3)
​
      // 解决v-for更新 - 覆盖原始数组
      let newArr = this.arr.slice(0, 3)
      this.arr = newArr
    },
    updateBtn(){
      // 3. 更新某个值的时候, v-for是监测不到的
      // this.arr[0] = 1000;
​
      // 解决-this.$set()
      // 参数1: 更新目标结构
      // 参数2: 更新位置
      // 参数3: 更新值
      this.$set(this.arr, 0, 1000)
    }
  }
}
</script>
​
<style>
​
</style>

注意: vue不能监测到数组里赋值的动作而更新, 如果需要请使用Vue.set() 或者this.$set(), 或者覆盖整个数组

总结: 改变原数组的方法才能让v-for更新

13. vue基础 v-for就地更新

v-for 的默认行为会尝试原地修改元素而不是移动它们。

详解v-for就地更新流程

 这种虚拟DOM对比方式(同级对比,标签与标签对比,相同则复用), 可以提高性能 - 但是还不够高

14. vue基础_虚拟dom

(1)真实DOM:在document对象上, 渲染到浏览器上显示的标签

 (2)虚拟DOM:本质是保存节点信息, 属性和内容的一个JS对象

       a.  .vue文件中的template里写的标签, 都是模板, 都要被vue处理成虚拟DOM对象, 才会渲染显示到真实DOM页面上

    b.  内存中生成一样的虚拟DOM结构(本质是个JS对象)

(3)vue数据更新

- 生成新的虚拟DOM结构
- 和旧的虚拟DOM结构对比
- 利用diff算法, 找不不同, 只更新变化的部分(重绘/回流)到页面 - 也叫打补丁

好处1: 提高了更新DOM的性能(不用把页面全删除重新渲染)

好处2: 虚拟DOM只包含必要的属性(没有真实DOM上百个属性)

总结: 虚拟DOM保存在内存中, 只记录dom关键信息, 配合diff算法提高DOM更新的性能

在内存中比较差异, 然后给真实DOM打补丁更新上

15. vue基础_diff算法

vue用diff算法, 新虚拟dom和旧的虚拟dom比较

(1)同级比较-根元素变化-整个dom树删除重建

 情况1: 根元素变了, 删除重建

旧虚拟DOM

<div id="box">
    <p class="my_p">123</p>
</div>

新虚拟DOM

<ul id="box">
    <li class="my_p">123</li>
</ul>

(2)同级比较-根元素不变-属性改变更新属性

情况2: 根元素没变, 属性改变, 元素复用, 更新属性

旧虚拟DOM

<div id="box">
    <p class="my_p">123</p>
</div>

新虚拟DOM

<div id="myBox" title="标题">
    <p class="my_p">123</p>
</div>

16. vue基础_diff算法-key

情况3: 根元素没变, 子元素没变, 元素内容改变

(1)无key - 就地更新(最大限度尝试就地修改/复用相同类型元素)

 

<ul id="myUL">
    <li v-for="str in arr">
        {{ str }} 
        <input type="text">
    </li>
</ul>
<button @click="addFn">下标为1的位置新增一个</button>
export default {
    data(){
        return {
            arr: ["老大", "新来的", "老二", "老三"]
        }
    },
    methods: {
        addFn(){
            this.arr.splice(1, 0, '新来的')
        }
    }
};

旧虚拟DOM结构 和 新虚拟DOM结构 对比过程  

性能不高, 从第二个li往后都更新了  

(2)有key - 值为索引(还是就地更新 )

有key属性, 基于key的来比较新旧虚拟DOM, 移除key不存在元素

        先产生新旧虚拟DOM, 根据key比较, 因为新旧虚拟DOM对比, key存在就复用此标签更新内容, 如果不存在就直接建立一个新的,因此还是就地更新

<ul id="myUL">
    <li v-for="(str, index) in arr" :key="index">
        {{ str }} 
        <input type="text">
    </li>
</ul>
<button @click="addFn">下标为1的位置新增一个</button>
export default {
    data(){
        return {
            arr: ["老大", "新来的", "老二", "老三"]
        }
    },
    methods: {
        addFn(){
            this.arr.splice(1, 0, '新来的')
        }
    }
};

 v-for先循环产生新的DOM结构, key是连续的, 和数据对应

 然后比较新旧DOM结构, 找到区别, 打补丁到页面上

 最后补一个li, 然后从第二个往后, 都要更新内容

(3)有key - 值为id

key的值只能是唯一不重复的, 字符串或数值

v-for不会移动DOM, 而是尝试复用, 就地更新,如果需要v-for移动DOM, 
需要用特殊 attribute key 来提供一个排序提示

新DOM里数据的key存在, 去旧的虚拟DOM结构里找到key标记的标签, 复用标签

新DOM里数据的key存在, 去旧的虚拟DOM结构里没有找到key标签的标签, 创建

旧DOM结构的key, 在新的DOM结构里没有了, 则==移除key所在的标签
<template>
  <div>
    <ul>
      <li v-for="obj in arr" :key="obj.id">
        {{ obj.name }}
        <input type="text">
      </li>
    </ul>
    <button @click="btn">下标1位置插入新来的</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [
        {
          name: '老大',
          id: 50
        },
        {
          name: '老二',
          id: 31
        },
        {
          name: '老三',
          id: 10
        }
      ],
    };
  },
  methods: {
    btn(){
      this.arr.splice(1, 0, {
        id: 19, 
        name: '新来的'
      })
    }
  }
};
</script>

<style>
</style>

先产生新旧虚拟DOM, 根据key比较

 总结: 不用key也不影响功能(就地更新), 添加key可以提高更新的性能

(4)有key和无key小结

 

17. vue基础_动态class

目标: 用v-bind给标签class设置动态的值

  • 语法:

    • :class="{类名: 布尔值}"

<template>
  <div>
    <!-- 语法:
      :class="{类名: 布尔值}"
      使用场景: vue变量控制标签是否应该有类名
     -->
    <p :class="{red_str: bool}">动态class</p>
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      bool: true
    }
  }
}
</script>
​
<style scoped>
  .red_str{
    color: red;
  }
</style>

总结: 就是把类名保存在vue变量中赋予给标签

18. vue基础-动态style

目标: 给标签动态设置style的值

  • 语法

    • :style="{css属性: 值}"

<template>
  <div>
    <!-- 动态style语法
      :style="{css属性名: 值}"
     -->
    <p :style="{backgroundColor: colorStr}">动态style</p>
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      colorStr: 'red'
    }
  }
}
</script>
​
<style>
​
</style>

总结: 动态style的key都是css属性名

案例-品牌管理

  • 需求1: 把默认数据显示到表格上

  • 需求2: 注意资产超过100的, 都用红色字体标记出来

  • 实现数据增删

细节:

① 先铺设静态页面 

② 此案例使用bootstrap, 需要下载, 并导入到工程main.js中

   bootstrap, 工程化开发, 模块化用npm/yarn下载引入使用

arn add bootstr

   在main.js - 引入bootstrap  

import "bootstrap/dist/css/bootstrap.css" 
// 默认找文件夹下的index文件(但是这个不是所以需要写路径)

③ 用v-for配合默认数据, 把数据默认铺设到表格上显示

④ 直接在标签上, 大于100价格, 动态设置red类名

<template>
  <div id="app">
    <div class="container">
      <!-- 顶部框模块 -->
      <div class="form-group">
        <div class="input-group">
          <h4>品牌管理</h4>
        </div>
      </div>

      <!-- 数据表格 -->
      <table class="table table-bordered table-hover mt-2">
        <thead>
          <tr>
            <th>编号</th>
            <th>资产名称</th>
            <th>价格</th>
            <th>创建时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
           <tr v-for="obj in list" :key="obj.id">
              <td>{{ obj.id }}</td>
              <td>{{ obj.name }}</td>

              <!-- 如果价格超过100,就有red这个类 -->
              <td :class="{red: obj.price > 100}">{{ obj.price }}</td>
              <td>{{ obj.time }}</td>
              <td><a href="#" >删除</a></td>
          </tr>
      </tbody>
          <!-- 
        <tfoot >
          <tr>
            <td colspan="5" style="text-align: center">暂无数据</td>
          </tr>
        </tfoot>
            -->
      </table>

      <!-- 添加资产 -->
      <form class="form-inline">
        <div class="form-group">
          <div class="input-group">
            <input
              type="text"
              class="form-control"
              placeholder="资产名称"
              v-model="name"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <div class="form-group">
          <div class="input-group">
            <input
              type="text"
              class="form-control"
              placeholder="价格"
              v-model.number="price"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <!-- 4. 阻止表单提交(刷新网页数据又回去了) -->
        <button class="btn btn-primary" @click.prevent="addFn">添加资产</button>
      </form>
    </div>
  </div>
</template>

<script>

export default {
  data() {
    return {
      name: "", // 名称
      price: 0, // 价格
      list: [
        { id: 100, name: "外套", price: 199, time: new Date('2010-08-12')},
        { id: 101, name: "裤子", price: 34, time: new Date('2013-09-01') },
        { id: 102, name: "鞋", price: 25.4, time: new Date('2018-11-22') },
        { id: 103, name: "头发", price: 19900, time: new Date('2020-12-12') }
      ],
    };
  },
methods: {
    addFn(){
      // 5. 判断是否为空
      if (this.name.trim().length === 0 || this.price === 0) {
        alert("不能为空")
        return
      }
      // 3. 把值以对象形式-插入list
      this.list.push({
        // 当前数组最后一个对象的id+1作为新对象id值
        id: this.list[this.list.length - 1].id + 1,
        name: this.name,
        price: this.price,
        time: new Date()
      })
    }
     // ...其他代码
    delFn(id){
      // 通过id找到这条数据在数组中下标
      let index = this.list.findIndex(obj => obj.id === id)
      this.list.splice(index, 1)
    }
  }

};
</script>

<style >
.red{
  color: red;
}
</style>

五、vue过滤器

1. vue过滤器-定义使用

目的: 转换格式, 过滤器就是一个函数, 传入值返回处理后的值

 例子:

  • 全局定义字母都大写的过滤器

  • 局部定义字符串翻转的过滤器

<template>
  <div>
    <p>原来的样子: {{ msg }}</p>
    <!-- 2. 过滤器使用
      语法: {{ 值 | 过滤器名字 }}
     -->
    <p>使用翻转过滤器: {{ msg | reverse }}</p>
    <p :title="msg | toUp">鼠标长停</p>
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      msg: 'Hello, Vue'
    }
  },
  // 方式2: 局部 - 过滤器
  // 只能在当前vue文件内使用
  /*
     语法: 
     filters: {
       过滤器名字 (val) {
         return 处理后的值
       }
     }
  */
  filters: {
    toUp (val) {
      return val.toUpperCase()
    }
  }
}
</script>
​
<style>
​
</style>

总结: 把值转成另一种形式, 使用过滤器, Vue3用函数替代了过滤器.

全局注册最好在main.js中注册, 一处注册到处使用

2. vue过滤器-传参和多过滤器

目标: 可同时使用多个过滤器, 或者给过滤器传参

  • 语法:

    • 过滤器传参: vue变量 | 过滤器(实参)

    • 多个过滤器: vue变量 | 过滤器1 | 过滤器2

<template>
  <div>
    <p>原来的样子: {{ msg }}</p>
    <!-- 1.
      给过滤器传值
      语法: vue变量 | 过滤器名(值)
     -->
    <p>使用翻转过滤器: {{ msg | reverse('|') }}</p>
    <!-- 2.
      多个过滤利使用
      语法: vue变量 | 过滤器1 | 过滤器2
     -->
    <p :title="msg | toUp | reverse('|')">鼠标长停</p>
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      msg: 'Hello, Vue'
    }
  },
  filters: {
    toUp (val) {
      return val.toUpperCase()
    }
  }
}
</script>
​
<style>
​
</style>

总结: 过滤器可以传参, 还可以对某个过滤器结果, 后面在使用一个过滤器

3. 案例-品牌管理(时间格式化)

(1)下载moment处理日期的第三方工具模块

           moment官网文档: Moment.js 中文网 | 开发文档

yarn add moment

(2)定义过滤器, 把时间用moment模块格式化, 返回我们想要的格式

// 目标: 处理时间
// 1. 下载moment模块
import moment from 'moment'
​
​
// 2. 定义过滤器, 编写内部代码
filters: { 
    formatDate (val){
        return moment(val).format('YYYY-MM-DD')
    }
}
​
<!-- 3. 使用过滤器 -->
<td>{{ obj.time | formatDate }}</td>

六、vue计算属性

1. vue计算属性-computed

目标: 一个数据, 依赖另外一些数据计算而来的结果

语法:

  • computed: {
        "计算属性名" () {
            return "值"
        }
    }

需求:

  • 需求: 求2个数的和显示到页面上

<template>
  <div>
    <p>{{ num }}</p>
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      a: 10,
      b: 20
    }
  },
  // 计算属性:
  // 场景: 一个变量的值, 需要用另外变量计算而得来
  /*
    语法:
    computed: {
      计算属性名 () {
        return 值
      }
    }
  */
 // 注意: 计算属性和data属性都是变量-不能重名
 // 注意2: 函数内变量变化, 会自动重新计算结果返回
  computed: {
    num(){
      return this.a + this.b
    }
  }
}
</script>
​
<style>
​
</style>

注意: 计算属性也是vue数据变量, 所以不要和data里重名, 用法和data相同

总结: 一个数据, 依赖另外一些数据计算而来的结果

2. vue计算属性-缓存

目标: 计算属性是基于它们的依赖项的值结果进行缓存的,只要依赖的变量不变, 都直接从缓存取结果

<template>
  <div>
    <p>{{ reverseMessage }}</p>
    <p>{{ reverseMessage }}</p>
    <p>{{ reverseMessage }}</p>
    <p>{{ getMessage() }}</p>
    <p>{{ getMessage() }}</p>
    <p>{{ getMessage() }}</p>
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      msg: "Hello, Vue"
    }
  },
  // 计算属性优势:
  // 带缓存
  // 计算属性对应函数执行后, 会把return值缓存起来
  // 依赖项不变, 多次调用都是从缓存取值
  // 依赖项值-变化, 函数会"自动"重新执行-并缓存新的值
  computed: {
    reverseMessage(){
      console.log("计算属性执行了");
      return this.msg.split("").reverse().join("")
    }
  },
  methods: {
    getMessage(){
      console.log("函数执行了");
      return this.msg.split("").reverse().join("")
    }
  }
}
</script>
​
<style>
​
</style>

总结: 计算属性根据依赖变量结果缓存, 依赖变化重新计算结果存入缓存, 比普通方法性能更高

3. vue计算属性-完整写法

目标: 计算属性也是变量, 如果想要直接赋值, 需要使用完整写法

语法:

computed: {
    "属性名": {
        set(值){
            
        },
        get() {
            return "值"
        }
    }
}

需求:

  • 计算属性给v-model使用

页面准备输入框

<template>
  <div>
      <div>
          <span>姓名:</span>
          <input type="text" v-model="full">
      </div>
  </div>
</template>
​
<script>
// 问题: 给计算属性赋值 - 需要setter
// 解决:
/*
    完整语法:
    computed: {
        "计算属性名" (){},
        "计算属性名": {
            set(值){
​
            },
            get(){
                return 值
            }
        }
    }
*/
export default {
    computed: {
        full: {
            // 给full赋值触发set方法
            set(val){
                console.log(val)
            },
            // 使用full的值触发get方法
            get(){
                return "无名氏"
            }
        }
    }
}
</script>
​
<style>
​
</style>

总结: 想要给计算属性赋值, 需要使用set方法

4. 案例-品牌管理(总价和均价)

目标: 基于之前的案例, 完成总价和均价的计算效果

此处只修改了变化的代码

<tr style="background-color: #EEE">
     <td>统计:</td>
     <td colspan="2">总价钱为: {{ allPrice }}</td>
     <td colspan="2">平均价: {{ svgPrice }}</td>
</tr>
​
<script>
// 目标: 总价和均价显示
// 1. 末尾补tr - 显示总价和均价
export default {
  // ...源代码省略
  // 2. 计算属性
  computed: {
      allPrice(){
          // 3. 求总价
          return this.list.reduce((sum, obj) => sum += obj.price, 0)
      },
      avgPrice(){
          // 4. 求均价 - 保留2位小数
          return (this.allPrice / this.list.length).toFixed(2)
      }
  }
}
</script>

总结: 总价来源于所有数据计算而来的结果, 故采用计算属性

5. 案例-全选反选

<template>
  <div>
    <span>全选:</span>
    <!-- 4. v-model 关联全选 - 选中状态 -->
    <input type="checkbox" v-model="isAll"/>
    <button>反选</button>
    <ul>
      <li v-for="(obj, index) in arr" :key="index">
        <!-- 3. 对象.c - 关联 选中状态 -->
        <input type="checkbox" v-model="obj.c"/>
        <span>{{ obj.name }}</span>
      </li>
    </ul>
  </div>
</template>

<script>
// 目标: 小选框 -> 全选
// 1. 标签+样式+js准备好
// 2. 把数据循环展示到页面上
export default {
  data() {
    return {
      arr: [
        {
          name: "猪八戒",
          c: false,
        },
        {
          name: "孙悟空",
          c: false,
        },
        {
          name: "唐僧",
          c: false,
        },
        {
          name: "白龙马",
          c: false,
        },
      ],
    };
  },
  // 5. 计算属性-isAll
  computed: {
        isAll: {
      set(val){
        // 7. 全选框 - 选中状态(true/false)
        this.arr.forEach(obj => obj.c = val)
      },
      get(){
        // 6. 统计小选框状态 ->  全选状态
        // every口诀: 查找数组里"不符合"条件, 直接原地返回false
        return this.arr.every(obj => obj.c === true)
      }
    }
  }
methods: {
    btn(){
      // 8. 让数组里对象的c属性取反再赋予回去
      this.arr.forEach(obj => obj.c = !obj.c)
    }
  }
};
</script>

七、 vue侦听器

1. vue侦听器-watch

目标: 可以侦听data/computed属性值改变

语法:

watch: {
    "被侦听的属性名" (newVal, oldVal){
        
    }
}

完整例子代码:

<template>
  <div>
    <input type="text" v-model="name">
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      name: ""
    }
  },
  // 目标: 侦听到name值的改变
  /*
  语法:
    watch: {
      变量名 (newVal, oldVal){
        // 变量名对应值改变这里自动触发
      }
    }
  */
  watch: {
    // newVal: 当前最新值
    // oldVal: 上一刻值
    name(newVal, oldVal){
      console.log(newVal, oldVal);
    }
  }
}
</script>
​
<style>
​
</style>

总结: 想要侦听一个属性变化, 可使用侦听属性watch

2. vue侦听器-深度侦听和立即执行

目标: 侦听复杂类型, 或者立即执行侦听函数

  • 语法:

    watch: {
        "要侦听的属性名": {
            immediate: true, // 立即执行
            deep: true, // 深度侦听复杂类型内变化
            handler (newVal, oldVal) {
                
            }
        }
    }

完整例子代码:

<template>
  <div>
    <input type="text" v-model="user.name">
    <input type="text" v-model="user.age">
  </div>
</template>
​
<script>
export default {
  data(){
    return {
      user: {
        name: "",
        age: 0
      }
    }
  },
  // 目标: 侦听对象
  /*
  语法:
    watch: {
      变量名 (newVal, oldVal){
        // 变量名对应值改变这里自动触发
      },
      变量名: {
        handler(newVal, oldVal){
​
        },
        deep: true, // 深度侦听(对象里面层的值改变)
        immediate: true // 立即侦听(网页打开handler执行一次)
      }
    }
  */
  watch: {
    user: {
      handler(newVal, oldVal){
        // user里的对象
        console.log(newVal, oldVal);
      },
      deep: true,
      immediate: true
    }
  }
}
</script>
​
<style>
​
</style>

总结: immediate立即侦听, deep深度侦听, handler固定方法触发

3. 案例-品牌管理(数据缓存)

目标: 侦听list变化, 同步到浏览器本地

  • 需求: 把品牌管理的数据实时同步到本地缓存

分析:

① 在watch侦听list变化的时候, 把最新的数组list转成JSON字符串存入到localStorage本地

② data里默认把list变量从本地取值, 如果取不到给个默认的空数组

效果:

新增/删除 – 刷新页面 – 数据还在

<script>
import moment from "moment";
export default {
  data() {
    return {
      name: "", // 名称
      price: 0, // 价格
      // 3. 本地取出缓存list
      list: JSON.parse(localStorage.getItem('pList')) || [],
    };
  },
  // ...其他代码省略
  watch: {
    list: {
      handler(){
        // 2. 存入本地
        localStorage.setItem('pList', JSON.stringify(this.list))
      },
      deep: true
    }
  }
};
</script>

八、vue组件

1. vue组件_概念

组件是可复用的 Vue 实例, 封装标签, 样式和JS代码

组件化 :封装的思想,把页面上 可重用的部分 封装为 组件,从而方便项目的 开发 和 维护

一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)

2. vue组件_基础使用

口诀: 哪部分标签复用, 就把哪部分封装到组件内

     (重要): 组件内template只能有一个根标签

      (重要): 组件内data必须是一个函数, 独立作用域

步骤:

(1)创建组件 components/Pannel.vue

封装标签+样式+js - 组件都是独立的, 为了复用

<template>
  <div>
    <div class="title">
      <h4>芙蓉楼送辛渐</h4>
      <span class="btn" @click="isShow = !isShow">
        {{ isShow ? "收起" : "展开" }}
      </span>
    </div>
    <div class="container" v-show="isShow">
      <p>寒雨连江夜入吴,</p>
      <p>平明送客楚山孤。</p>
      <p>洛阳亲友如相问,</p>
      <p>一片冰心在玉壶。</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: false,
    };
  },
};
</script>

<style scoped>
.title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border: 1px solid #ccc;
  padding: 0 1em;
}
.title h4 {
  line-height: 2;
  margin: 0;
}
.container {
  border: 1px solid #ccc;
  padding: 0 1em;
}
.btn {
  /* 鼠标改成手的形状 */
  cursor: pointer;
}
</style>

 (2)注册组件: 创建后需要注册后再使用

全局 - 注册使用

全局入口在main.js, 在new Vue之上注册

语法:

import Vue from 'vue'
import 组件对象 from 'vue文件路径'

Vue.component("组件名", 组件对象)

main.js

// 目标: 全局注册 (一处定义到处使用)
// 1. 创建组件 - 文件名.vue
// 2. 引入组件
import Pannel from './components/Pannel'
// 3. 全局 - 注册组件
/*
  语法: 
  Vue.component("组件名", 组件对象)
*/
Vue.component("PannelG", Pannel)

全局注册PannelG组件名后, 就可以当做标签在任意Vue文件中template里用

单双标签都可以或者小写加-形式, 运行后, 会把这个自定义标签当做组件解析, 使用组件里封装的标签替换到这个位置

<PannelG></PannelG>
<PannelG/>
<pannel-g></pannel-g>
局部 - 注册使用

语法:

import 组件对象 from 'vue文件路径'
​
export default {
    components: {
        "组件名": 组件对象
    }
}

任意vue文件中中引入, 注册, 使用

<template>
  <div id="app">
    <h3>案例:折叠面板</h3>
    <!-- 4. 组件名当做标签使用 -->
    <!-- <组件名></组件名> -->
    <PannelG></PannelG>
    <PannelL></PannelL>
  </div>
</template>
​
<script>
// 目标: 局部注册 (用的多)
// 1. 创建组件 - 文件名.vue
// 2. 引入组件
import Pannel from './components/Pannel_1'
export default {
  // 3. 局部 - 注册组件
  /*
    语法: 
    components: {
      "组件名": 组件对象
    }
  */
  components: {
    PannelL: Pannel
  }
}
</script>

(3)组件使用总结

(1)(创建)封装html+css+vue到独立的.vue文件中

(2)(引入注册)组件文件 => 得到组件配置对象

(3)(使用)当前页面当做标签使用

4. vue组件-scoped作用

目的: 解决多个组件样式名相同, 冲突问题

需求: div标签名选择器, 设置背景色

问题: 发现组件里的div和外面的div都生效了

解决: 给Pannel.vue组件里style标签上加scoped属性即可

<style scoped>

在style上加入scoped属性, 就会在此组件的标签上加上一个随机生成的data-v开头的属性

而且必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

 总结: style上加scoped, 组件内的样式只在当前vue组件生效

九、vue组件通信

因为每个组件的变量和值都是独立的

组件通信先暂时关注父传子, 子传父

父: 使用其他组件的vue文件

子: 被引入的组件(嵌入)

例如: App.vue(父) MyProduct.vue(子)

1. vue组件通信_父向子-props

目的: 从外面给组件内传值

(1)父组件 -> 子组件 传值

步骤:

    (1)创建组件components(父)/MyProduct.vue (子)

    (2)子组件内在props定义变量, 用于接收外部传入的值

    (3)父组件中引入注册组件, 使用时, 传入具体数据给组件显示

components/MyProduct.vue - 准备标签(子组件内, 定义变量, 准备接收, 然后使用变量)

<template>
  <div class="my-product">
    <h3>标题: {{ title }}</h3>
    <p>价格: {{ price }}元</p>
    <p>{{ intro }}</p>
  </div>
</template>
​
<script>
export default {
  props: ['title', 'price', 'intro']
}
</script>
​
<style>
.my-product {
  width: 400px;
  padding: 20px;
  border: 2px solid #000;
  border-radius: 5px;
  margin: 10px;
}
</style>

App.vue中使用并传入数据(引入组件, 注册组件, 使用组件, 传值进去)

<template>
  <div>
    <!-- 
      目标: 父(App.vue) -> 子(MyProduct.vue) 分别传值进入
      需求: 每次组件显示不同的数据信息
      步骤(口诀):
        1. 子组件 - props - 变量 (准备接收)
        2. 父组件 - 传值进去
     -->
    <Product title="好吃的口水鸡" price="50" intro="开业大酬宾, 全场8折"></Product>
    <Product title="好可爱的可爱多" price="20" intro="老板不在家, 全场1折"></Product>
    <Product title="好贵的北京烤鸭" price="290" :intro="str"></Product>
  </div>
</template>
​
<script>
// 1. 创建组件 (.vue文件)
// 2. 引入组件
import Product from './components/MyProduct'
export default {
  data(){
    return {
      str: "好贵啊, 快来啊, 好吃"
    }
  },
  // 3. 注册组件
  components: {
    // Product: Product // key和value变量名同名 - 简写
    Product
  }
}
</script>
​
<style>
​
</style>

总结: 组件封装复用的标签和样式, 而具体数据要靠外面传入

(2)vue组件通信_父向子-配合循环

目的: 把数据循环分别传入给组件内显示

数据

list: [
    { id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: '开业大酬宾, 全场8折' },
    { id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: '好吃不腻, 快来买啊' },
    { id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: '炎热的夏天, 来个冰激凌了' },
],
<template>
  <div>
    <MyProduct v-for="obj in list" :key="obj.id"
    :title="obj.proname"
    :price="obj.proprice"
    :intro="obj.info"
    ></MyProduct>
  </div>
</template>
​
<script>
// 目标: 循环使用组件-分别传入数据
// 1. 创建组件
// 2. 引入组件
import MyProduct from './components/MyProduct'
export default {
  data() {
    return {
      list: [
        {
          id: 1,
          proname: "超级好吃的棒棒糖",
          proprice: 18.8,
          info: "开业大酬宾, 全场8折",
        },
        {
          id: 2,
          proname: "超级好吃的大鸡腿",
          proprice: 34.2,
          info: "好吃不腻, 快来买啊",
        },
        {
          id: 3,
          proname: "超级无敌的冰激凌",
          proprice: 14.2,
          info: "炎热的夏天, 来个冰激凌了",
        },
      ],
    };
  },
  // 3. 注册组件
  components: {
    // MyProduct: MyProduct
    MyProduct
  }
};
</script>
​
<style>
</style>

2. 单向数据流

从父到子的数据流向,叫单向数据流

在vue中需要遵循单向数据流原则

父组件的数据发生了改变,子组件会自动跟着变

子组件不能直接修改父组件传递过来的props, props变量本身是只读不能重新赋值

注意:
      父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,
      对象是引用类型, 互相更新

原因: 子组件修改, 不通知父级, 造成数据不一致性

        第一个MyProduct.vue内自己修改商品价格为5.5, 但是App.vue里原来还记着18.8 - 数据 不一致了

总结: props变量本身是不能重新赋值的

3. vue组件通信_子向父

目标: 从子组件把值传出来给外面使用

语法:

  • 父: @自定义事件名="父methods函数"

  • 子: this.$emit("自定义事件名", 传值) - 执行父methods里函数代码

  

(1)父组件内, 绑定自定义事件和事件处理函数
          @自定义事件名="父methods里函数名"

  App.vue

<template>
  <div>
    <!-- 目标: 子传父 -->
    <!-- 1. 父组件, @自定义事件名="父methods函数" -->
    <MyProduct v-for="(obj, ind) in list" :key="obj.id"
    :title="obj.proname"
    :price="obj.proprice"
    :intro="obj.info"
    :index="ind"
    @subprice="fn"
    ></MyProduct>
  </div>
</template>

<script>

import MyProduct from './components/MyProduct_sub'
export default {
  data() {
    return {
      list: [
        {
          id: 1,
          proname: "超级好吃的棒棒糖",
          proprice: 18.8,
          info: "开业大酬宾, 全场8折",
        },
        {
          id: 2,
          proname: "超级好吃的大鸡腿",
          proprice: 34.2,
          info: "好吃不腻, 快来买啊",
        },
        {
          id: 3,
          proname: "超级无敌的冰激凌",
          proprice: 14.2,
          info: "炎热的夏天, 来个冰激凌了",
        },
      ],
    };
  },
  components: {
    MyProduct
  },
  methods: {
    fn(inde, price){
      // 逻辑代码
      this.list[inde].proprice > 1 && (this.list[inde].proprice = (this.list[inde].proprice - price).toFixed(2))
    }
  }
};
</script>

<style>
</style>

(2)子组件内, 恰当的时机, 触发父给我绑的自定义事件, 导致父methods里事件处理函数执行

 components/MyProduct_sub.vue

<template>
  <div class="my-product">
    <h3>标题: {{ title }}</h3>
    <p>价格: {{ price }}元</p>
    <p>{{ intro }}</p>
    <button @click="subFn">宝刀-砍1元</button>
  </div>
</template>

<script>
import eventBus from '../EventBus'
export default {
  props: ['index', 'title', 'price', 'intro'],
  methods: {
    subFn(){
      this.$emit('subprice', this.index, 1) // 子向父
      eventBus.$emit("send", this.index, 1) // 跨组件
    }
  }
}
</script>

<style>
.my-product {
  width: 400px;
  padding: 20px;
  border: 2px solid #000;
  border-radius: 5px;
  margin: 10px;
}
</style>

总结: 父自定义事件和方法, 等待子组件触发事件给方法传值

3. vue组件通信-EventBus

目标: 常用于跨组件通信时使用

两个没有任何引入关系的组件, 要如何互相通信呢?

两个组件的关系非常的复杂,通过父子组件通讯是非常麻烦的。

这时候可以使用通用的组件通讯方案:事件总线(event-bus)

  

       总结: 空的Vue对象, 只负责$on注册事件, $emit触发事件, 一定要确保$on先执行

十、vue的生命周期

一组件从 创建 到 销毁 的整个过程就是生命周期

Vue_生命周期

1. 钩子函数

目标: Vue 框架内置函数,随着组件的生命周期阶段,自动执行

作用: 特定的时间点,执行特定的操作

场景: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据

分类: 4大阶段8个方法

  • 初始化

  • 挂载

  • 更新

  • 销毁

阶段方法名方法名
初始化beforeCreatecreated
挂载beforeMountmounted
更新beforeUpdateupdated
销毁beforeDestroydestroyed

官网文档https://cn.vuejs.org/v2/guide/instance.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9B%BE%E7%A4%BA

下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

2. 初始化阶段

目标: 掌握初始化阶段2个钩子函数作用和执行时机

含义讲解:

1.new Vue() – Vue实例化(组件也是一个小的Vue实例)

2.Init Events & Lifecycle – 初始化事件和生命周期函数

3.beforeCreate – 生命周期钩子函数被执行

4.Init injections&reactivity – Vue内部添加data和methods等

5.created – 生命周期钩子函数被执行, 实例创建

6.接下来是编译模板阶段 –开始分析

7.Has el option? – 是否有el选项 – 检查要挂到哪里
 
​	               没有. 调用$mount()方法

​	               有, 继续检查template选项

 

 components/Life.vue - 创建一个文件


<script>
export default {
    data(){
        return {
            msg: "hello, Vue"
        }
    },
    // 一. 初始化
    // new Vue()以后, vue内部给实例对象添加了一些属性和方法, data和methods初始化"之前"
    beforeCreate(){
        console.log("beforeCreate -- 执行");
        console.log(this.msg); // undefined
    },
    // data和methods初始化以后
    // 场景: 网络请求, 注册全局事件
    created(){
        console.log("created -- 执行");
        console.log(this.msg); // hello, Vue

        this.timer = setInterval(() => {
            console.log("哈哈哈");
        }, 1000)
    }
}
</script>

App.vue - 引入使用

<template>
  <div>
    <h1>1. 生命周期</h1>
 	<Life></Life>
  </div>
</template>

<script>
import Life from './components/Life'
export default {
  components: {
    Life
  }
}
</script>

3. 挂载阶段

目标: 掌握挂载阶段2个钩子函数作用和执行时机

含义讲解:

1.template选项检查

有 - 编译template返回render渲染函数

无 – 编译el选项对应标签作为template(要渲染的模板)

2.虚拟DOM挂载成真实DOM之前

3.beforeMount – 生命周期钩子函数被执行

4.Create … – 把虚拟DOM和渲染的数据一并挂到真实DOM上

5.真实DOM挂载完毕

6.mounted – 生命周期钩子函数被执行

​​​​​​​

<template>
  <div>
      <p>学习生命周期 - 看控制台打印</p>
      <p id="myP">{{ msg }}</p>
  </div>
</template>

<script>
export default {
    // ...省略其他代码
    
    // 二. 挂载
    // 真实DOM挂载之前
    // 场景: 预处理data, 不会触发updated钩子函数
    beforeMount(){
        console.log("beforeMount -- 执行");
        console.log(document.getElementById("myP")); // null

        this.msg = "重新值"
    },
    // 真实DOM挂载以后
    // 场景: 挂载后真实DOM
    mounted(){
        console.log("mounted -- 执行");
        console.log(document.getElementById("myP")); // p
    }
}
</script>

4. 更新阶段

目标: 掌握更新阶段2个钩子函数作用和执行时机

含义讲解:

1.当data里数据改变, 更新DOM之前

2.beforeUpdate – 生命周期钩子函数被执行

3.Virtual DOM…… – 虚拟DOM重新渲染, 打补丁到真实DOM

4.updated – 生命周期钩子函数被执行

5.当有data数据改变 – 重复这个循环

​​​​​​​

components/Life.vue - 创建一个文件

准备ul+li循环, 按钮添加元素, 触发data改变->导致更新周期开始

<template>
  <div>
      <p>学习生命周期 - 看控制台打印</p>
      <p id="myP">{{ msg }}</p>
      <ul id="myUL">
          <li v-for="(val, index) in arr" :key="index">
              {{ val }}
          </li>
      </ul>
      <button @click="arr.push(1000)">点击末尾加值</button>
  </div>
</template>

<script>
export default {
    data(){
        return {
            msg: "hello, Vue",
            arr: [5, 8, 2, 1]
        }
    },
    // ...省略其他代码

    // 三. 更新
    // 前提: data数据改变才执行
    // 更新之前
    beforeUpdate(){
        console.log("beforeUpdate -- 执行");
        console.log(document.querySelectorAll("#myUL>li")[4]); // undefined
    },
    // 更新之后
    // 场景: 获取更新后的真实DOM
    updated(){
        console.log("updated -- 执行");
        console.log(document.querySelectorAll("#myUL>li")[4]); // li
    }
}
</script>

5. 销毁阶段

目标: 掌握销毁阶段2个钩子函数作用和执行时机

含义讲解:

1.当$destroy()被调用 – 比如组件DOM被移除(例v-if)

2.beforeDestroy – 生命周期钩子函数被执行

3.拆卸数据监视器、子组件和事件侦听器

4.实例销毁后, 最后触发一个钩子函数

5.destroyed – 生命周期钩子函数被执行

 components/Life.vue - 准备生命周期方法(Life组件即将要被删除)

<script>
export default {
    // ...省略其他代码
    
    // 四. 销毁
    // 前提: v-if="false" 销毁Vue实例
    // 场景: 移除全局事件, 移除当前组件, 计时器, 定时器, eventBus移除事件$off方法
    beforeDestroy(){
        // console.log('beforeDestroy -- 执行');
        clearInterval(this.timer)
    },
    destroyed(){
        // console.log("destroyed -- 执行");
    }
}
</script>

主要: App.vue - 点击按钮让Life组件从DOM上移除 -> 导致Life组件进入销毁阶段

<Life v-if="show"></Life>
<button @click="show = false">销毁组件</button>

<script>
    data(){
        return {
            show: true
        }
    },
</script>

十一、$refs和$nextTick

1.  $refs-获取DOM

目标: 利用 ref 和 $refs 可以用于获取 dom 元素

components/More.vue

<template>
  <div>
      <p>1. 获取原生DOM元素</p>
      <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1>
  </div>
</template>

<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
export default {
    mounted(){
        console.log(document.getElementById("h")); // h1
        console.log(this.$refs.myH); // h1
    }
}
</script>

<style>

</style>

总结: 通过id / ref, 都可以获取原生DOM标签

2. $refs-获取组件对象

目标: 获取组件对象, 调用组件里方法

components/Child/Demo.vue

<template>
  <div>
      <p>我是Demo组件</p>
  </div>
</template>
​
<script>
export default {
    methods: {
        fn(){
            console.log("demo组件内的方法被调用了");
        }
    }
}
</script>

More.vue - 获取组件对象 - 调用组件方法

<template>
  <div>
      <p>1. 获取原生DOM元素</p>
      <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1>
      <p>2. 获取组件对象 - 可调用组件内一切</p>
      <Demo ref="de"></Demo>
  </div>
</template>
​
<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
import Demo from './Child/Demo'
export default {
    mounted(){
        console.log(document.getElementById("h")); // h1
        console.log(this.$refs.myH); // h1
​
        let demoObj = this.$refs.de;
        demoObj.fn()
    },
    components: {
        Demo
    }
}
</script>

总结: ref定义值, 通过$refs.值 来获取组件对象, 就能继续调用组件内的变量

 3.  $nextTick使用

Vue更新DOM-异步的

目标: 点击count++, 马上通过"原生DOM"拿标签内容, 无法拿到新值

components/Move.vue - 继续新增第三套代码

<template>
  <div>
      <p>1. 获取原生DOM元素</p>
      <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1>
      <p>2. 获取组件对象 - 可调用组件内一切</p>
      <Demo ref="de"></Demo>
      <p>3. vue更新DOM是异步的</p>
      <p ref="myP">{{ count }}</p>
      <button @click="btn">点击count+1, 马上提取p标签内容</button>
  </div>
</template>
​
<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
import Demo from './Child/Demo'
export default {
    mounted(){
        console.log(document.getElementById("h")); // h1
        console.log(this.$refs.myH); // h1
​
        let demoObj = this.$refs.de;
        demoObj.fn()
    },
    components: {
        Demo
    },
    data(){
        return {
            count: 0
        }
    },
    methods: {
        btn(){
            this.count++; // vue监测数据更新, 开启一个DOM更新队列(异步任务)
            console.log(this.$refs.myP.innerHTML); // 0
​
            // 原因: Vue更新DOM异步
            // 解决: this.$nextTick()
            // 过程: DOM更新完会挨个触发$nextTick里的函数体
             this.$nextTick(() => {
                console.log(this.$refs.myP.innerHTML); // 1
            })
        }
    }
}
</script>

总结: 因为DOM更新是异步的

4.  $nextTick使用场景

目标: 点击搜索按钮, 弹出聚焦的输入框, 按钮消失

components/Tick.vue

<template>
  <div>
      <input ref="myInp" type="text" placeholder="这是一个输入框" v-if="isShow">
      <button v-else @click="btn">点击我进行搜索</button>
  </div>
</template>
​
<script>
// 目标: 点按钮(消失) - 输入框出现并聚焦
// 1. 获取到输入框
// 2. 输入框调用事件方法focus()达到聚焦行为
export default {
    data(){
        return {
            isShow: false
        }
    },
    methods: {
        async btn(){
            this.isShow = true;
            // this.$refs.myInp.focus()
            // 原因: data变化更新DOM是异步的
            // 输入框还没有挂载到真实DOM上
            // 解决:
            // this.$nextTick(() => {
            //     this.$refs.myInp.focus()
            // })
            // 扩展: await取代回调函数
            // $nextTick()原地返回Promise对象
            await this.$nextTick()
            this.$refs.myInp.focus()
        }
    }
}
</script>

5. 组件name属性使用

目标: 可以用组件的name属性值, 来注册组件名字

问题: 组件名不是可以随便写的?

答案: 我们封装的组件-可以自己定义name属性组件名-让使用者有个统一的前缀风格

components/Com.vue

<template>
  <div>
      <p>我是一个Com组件</p>
  </div>
</template>
​
<script>
export default {
    name: "ComNameHaHa" // 注册时可以定义自己的名字
}
</script>

App.vue - 注册和使用

<template>
  <div>
    <h1>1. 生命周期</h1>
    <Life v-if="show"></Life>
    <button @click="show = false">销毁组件</button>
    <hr>
    <h1>2. axios使用</h1>
    <UseAxios></UseAxios>
    <hr>
    <h1>3. $refs的使用</h1>
    <More></More>
    <hr>
    <h1>4. $nextTick使用场景</h1>
    <Tick></Tick>
    <hr>
    <h1>5. 组件对象里name属性</h1>
    <ComNameHaHa></ComNameHaHa>
  </div>
</template>
​
<script>
import Life from './components/Life'
import UseAxios from './components/UseAxios'
import More from './components/More'
import Tick from './components/Tick'
import Com from './components/Com'
export default {
  data(){
    return {
      show: true
    }
  },
  components: {
    Life,
    UseAxios,
    More,
    Tick,
    [Com.name]: Com // 对象里的key是变量的话[]属性名表达式
    // "ComNameHaHa": Com
  }
}
</script>

  • 19
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Vue框架是指使用Java语言和Vue.js框架来开发Web应用程序的一种技术架构。Java是一种跨平台的编程语言,具有强大的面向对象特性和丰富的生态系统,被广泛应用于企业级应用程序开发。而Vue.js是一种轻量级的JavaScript框架,用于构建用户界面,具有简洁的语法和灵活的组件化开发模式。 Java Vue框架的优势在于它能够充分发挥Java的后端能力和Vue.js前端特性。Java作为后端语言,可以处理各种复杂的业务逻辑和数据处理,同时提供了可靠的安全性和性能。而Vue.js作为前端框架,能够提供丰富的用户界面和良好的用户体验,支持响应式设计和组件化开发。 使用Java Vue框架可以迅速构建现代化的Web应用程序。Java提供了强大的后端支持,包括数据库访问、事务处理、安全认证等功能。Vue.js提供了灵活的前端组件化开发模式,可以构建出复杂的用户界面,并实现前后端数据的无缝交互。这使得开发人员可以按照自己的需求,选择合适的组件和技术,快速构建出高质量的Web应用程序。 此外,Java Vue框架也具有良好的可维护性和扩展性。Java的面向对象特性使得代码可以更加模块化和可复用,便于团队协作和项目管理。Vue.js的组件化开发模式使得前端代码可以更好地组织和维护,而且可以根据需求进行灵活的扩展和定制。 综上所述,Java Vue框架结合了Java的后端能力和Vue.js前端特性,可以快速构建现代化的Web应用程序,具有良好的可维护性和扩展性。这使得Java Vue框架成为开发人员的首选技术之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值