JAVA入门基础_Vue2~3的入门使用

目录

Vue核心

初识Vue(掌握Vue的引入以及JS表达式与JS语句的区别)

  • HTML
<body>
    <div id="app">
    <!-- 这里面写的是(JS表达式)  -->
        {{msg}}
    </div>
</body>
  • javascript脚本
<script type="text/javascript" src="../js/vue.js"></script>(写在head标签中)
<script>
    new Vue({
        el: "#app",  // 挂载一个容器
        data() {     // 绑定数据
            return {
                msg: "这是一条信息,hello vue~"
            }
        },
        mounted() {
            console.log("hello")
        }
    })
</script>

Vue的模板语法(2大类)

  • 插值语法:写JS表达式,用于解析标签体内容

  • 指令语法:写JS表达式,用于解析标签(包括属性、内容、事件)

Vue数据绑定的2种方式(只要data中的数据被改变,Vue就会重新渲染模板)

  • 单项数据绑定:数据只能从data流向页面。

  • 双向数据绑定:数据不仅能从data流向页面,还可以从页面流向data。

  • html代码

<div id="app">
    <!-- 数据单向绑定:只能从data将数据流向页面 -->
    <input v-bind:value="msg"></input>
    <input :value="msg"></input>
    <hr>
    <!-- 数据双向绑定:能从data将数据流向页面,也能将页面的数据流向data,适用于有value的选项 -->
    <input v-model:value="msg"></input>
    <input v-model="msg"></input>
</div>
  • javascript代码
<script  type="text/javascript">
        new Vue({
            el: "#app",  // 挂载一个容器
            data() {     // 绑定数据
                return {
                    msg: "这是一条信息,hello vue~"
                }
            }
        })
</script>

MVVM模型,还有VueComponent是什么?

  • M: Model模型,指的是Vue中的data数据

  • V: View视图,指的是页面中的模板

  • VM:ViewModel模型视图,指的是Vue的实例

  • VueComponent: Vue的组件。VM可以使用el、而VC不能使用el。并且VC中的data只能用函数写法

事件处理

事件的基本绑定使用(注意默认传参)

  • HTML代码
    <!-- 1、绑定点击事件 -->
    <button v-on:click="showMsg">点我提示一个信息</button>
    <button @click="showMsg">点我提示一个信息</button>

    <!-- 2、绑定修改事件 -->
    <input v-on:change="changeInput($event)" value="一个input"></input>
    <input @change="changeInput"  value="一个input"></input>
  • javascript代码
<script>
    new Vue({
        el: "#app",  // 挂载一个容器
        data() {     // 绑定数据
            return {
                msg: "这是一条信息,hello vue~"
            }
        },
        methods: {
            showMsg(event) {
                console.log("当前的事件信息为:" + event)
                alert(this.msg)
            },
            changeInput(event) {
                console.log(event)
                alert("当前的input已经被修改")
            }
        }
    })
</script>

事件修饰符及组合使用(6个)

  1. prevent:阻止默认事件(常用);

  2. stop:阻止事件冒泡(常用);

  3. once:事件只触发一次(常用);

  4. capture:使用事件的捕获模式;

  5. self:只有event.target是当前操作的元素时才触发事件;

  6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

  • HTML代码
<div id="app">
    <!-- 1、阻止事件冒泡,不要出现点第二个Div导致第一个Div的事件触发 -->
    <div @click="showMsg" style="width: 200px;height: 200px;background-color: azure">
       我是第一个Div
       <div @click.stop="showMsg" style="width: 100px;height: 100px;background-color: salmon;">
           我是第二个Div
       </div>
    </div>
    <hr>

    <!-- 2、阻止标签的默认行为,例如a标签、button标签 -->
    <div>
        <a @click.prevent="showMsg" href="http://www.baidu.com">点我调用方法,我是a标签</a>
        <form action="http://www.baidu.com">
            <button @click.prevent="showMsg" type="submit">点我调用方法,我是按钮</button>
        </form>
    </div>
    <hr>

    <!-- 3、绑定的事件只能被调用一次时使用 -->
    <div>
        <button @click.once="showMsg">点我调用方法,我是一个按钮标签</button>
    </div>
</div>
  • javascript
<script>
    new Vue({
        el: "#app",  // 挂载一个容器
        data() {     // 绑定数据
            return {
                msg: "这是一条信息,hello vue~"
            }
        },
        methods: {
            showMsg() {
                alert(this.msg)
            }
        }
    })
</script>

键盘事件

  • Vue中常用的按键别名:

    • 回车 => enter
    • 删除 => delete (捕获“删除”和“退格”键)
    • 退出 => esc
    • 空格 => space
    • 换行 => tab (特殊,必须配合keydown去使用)
    • 上 => up
    • 下 => down
    • 左 => left
    • 右 => right
  • 演示HTML代码

<div id="app">
    <!-- 键盘事件 -->
    请输入你的姓名:<input type="text" @keydown.enter="showName" />
</div>

计算属性

完整版写法

  • HTML
<div id="app">
    <div>
        <h2>计算属性</h2>
        <h3>firstName: {{firstName}}</h3>
        <h3>lastName: {{lastName}}</h3>
        <h3>fullName: {{fullName}}</h3>
    </div>
</div>
  • javascript
<script  type="text/javascript">
    // 通过控制台:vm.fullName = '王五',试着修改看看效果
    const vm = new Vue({
        el: "#app",  // 挂载一个容器
        data() {     // 绑定数据
            return {
                firstName: "张",
                lastName: "三"
            }
        },
        computed: {
            fullName: {
                // value为修改后的值
                set(value) {
                    console.log("啥?你竟然修改了计算属性的值,修改后的值为:" + value)
                },
                get(){
                    return this.firstName + this.lastName
                }
            }
        }
    })
</script>

简写

  • 当一个计算属性不需要使用set()时,也就是只使用get()时,可以使用简写形式

  • HTML

<div id="app">
    <div>
        <h2>计算属性</h2>
        <h3>firstName: <input type="text" v-model="firstName"></h3>
        <h3>lastName: {{lastName}}</h3>
        <h3>fullName: {{fullName}}</h3>
    </div>
</div>
  • javascript(这里只放了简写后的形式)
computed: {
    fullName(){
            return this.firstName + this.lastName
    }
}

监视属性

监视属性完整写法

  • 监视属性的3个参数
    (1)immediate:默认为false,如果调整为true,则在模板渲染后就立即执行一次handler方法
    (2)deep:默认为false,监视对象时只能监视对象的引用被改变,调整为true则开启深度监视
    (3)handler:回调函数,当监视的属性被修改时调用,有2个参数,分别为修改后的值修改前的值

  • HTML

<div id="app">
    <div>
        <h2>监视属性</h2>
        <!-- 这里使用双向绑定以便演示效果  -->
        <h3>你的姓名: <input type="text" v-model="person.name"></h3>
        <!--  若不开启深度监视,无法监视到该属性的变换  -->
        <h3>你的爱好: <input type="text" v-model="person.hobby.game"></h3>
    </div>
</div>
  • javascript
<script  type="text/javascript">
    new Vue({
      el: "#app",  // 挂载一个容器
      data() {     // 绑定数据
        return {
          person: {
            name:'张三',
            age: 15,
            hobby:{study:'看书', game: '亚索'}
            }
         }
      },
      watch:{
        // 1、可以监视一个对象,但是oldValue与newValue没有任何区别
        person: {
          immediate: true,
          deep: true,
          handler(newValue, oldValue) {
            console.log("当前的person发生了改变")
            console.log(newValue)
            console.log(oldValue)
          }
        }
      }
    })
</script>

监视属性简写形式

  • 当一个监视属性只需要使用handle方法时可以使用简写形式

  • HTML

<div id="app">
    <div>
        <h2>监视属性</h2>
        <!-- 这里使用双向绑定以便演示效果  -->
        <h3>你的姓名: <input type="text" v-model="msg"></h3>
    </div>
</div>
  • javascript
<script  type="text/javascript">
    new Vue({
      el: "#app",  // 挂载一个容器
      data() {     // 绑定数据
        return {
          msg: "这是一条信息"
        }
      },
      watch:{
        msg(newValue, oldValue) {
            console.log("当前的msg发生了改变")
            console.log(newValue)
            console.log(oldValue)
          }
      }
    })
</script>

绑定样式

绑定Class样式

  • 绑定Class样式的三种方式
    (1):class="myClass"字符串写法,适用于类名不确定,需要动态获取
    (2):class="arrClass"数组写法,适用于类名不确定,类名数量也不确定
    (3):class="objClass"对象写法,适用于类名确定、类名数量也确定,但是不确定使用还是不使用

  • 编写的一点点CSS样式

<style>
    .class1 {
        color: red;
    }

    .class2 {
        font-size: 30px;
    }

    .class3 {
        font-family: 隶书;
    }
</style>
  • HTML
<div id="app">
    <div>
        <p :class="myClass">我是使用字符串方式绑定的class</p>
        <hr>
        <p :class="arrClass">我是使用数组方式绑定的class</p>
        <hr>
        <p :class="objClass">我是使用对象方式绑定的class</p>
    </div>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        myClass: "class1",
        arrClass: ['class1', 'class2', 'class3'],
        objClass: {class1: true, class2: true, class3: false}
      }
    }
  })
</script>

绑定CSS样式

  • 绑定CSS样式的3种方式
    (1):style={color: strStyle},单个对象写法,其中的strStyle为data中的数据
    (2):style="objStyle",对象写法(常用)
    (3):style="arrStyle",数组写法

  • HTML

<div id="app">
    <div>
        <!--  1、通过对象的方式,其中第二个参数是动态指定的  -->
        <p :style="{color: myStyle}">我是使用字符串方式绑定的style</p>
        <hr>
        <!--  2、通过数组的方式  -->
        <p :style="arrStyle">我是使用数组方式绑定的style</p>
        <hr>
        <!--  3、通过对象的方式(常用)  -->
        <p :style="objStyle">我是使用对象方式绑定的style</p>
    </div>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        // 单对象方式
        myStyle: 'red',
        // 数组方式
        arrStyle: [
          {color: 'red', fontSize: '30px'},
          {fontFamily: '隶书'}
        ],
        // 对象方式
        objStyle: {color: 'red', fontSize: '30px', fontFamily: '隶书'}
      }
    }
  })
</script>

条件渲染

  • v-if 与 v-show的区别在于v-show是控制元素的display属性,而v-if是直接移除掉DOM元素

  • v-if、v-else-if、else 配合使用时,中间不能被其他标签打断(也就是中间不能穿插其他标签)

  • HTML

<div id="app">
    <div>
        <!-- 1、v-if 与 v-show的使用   -->
        <h3 v-if="flag">hello,小伙伴,我使用了v-if</h3>
        <h3 v-show="flag">hello,小伙伴,我使用了v-show</h3>
        <button @click="flag = !flag">点我切换flag</button>

        <hr><hr><hr>

        <!-- 2、v-if 、v-else-if、else   -->
        <h3 v-if="num % 3 == 1">hello,小伙伴,我是v-if</h3>
        <h3 v-else-if="num % 3 == 2">hello,小伙伴,我是v-else-if</h3>
        <h3 v-else>hello,小伙伴,我是v-else</h3>

        <button @click="num++">点我num+1</button>
    </div>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        flag: true,
        num: 1
      }
    }
  })
</script>

列表渲染

  • key:非常重要,务必不要重复,因为Vue底层使用diff算法对虚拟DOM进行比较时,就是先判断的key值

  • index,从0开始

  • HTML

<div id="app">
    <ul>
        <!-- 1、遍历数组(最常用) -->
        <li v-for="(person,index) in personList" :key="id">
            {{index}} -- {{person.id}} -- {{person.name}} -- {{person.age}}
        </li>
    </ul>

    <!-- 2、遍历对象 -->
    <ul>
        <li v-for="(value,key,index) in student">
            索引:{{index}} --- 键:{{key}}  -- 值:{{value}}
        </li>
    </ul>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        personList: [
          {id: '001', name: '张三', age: 15},
          {id: '002', name: '李四', age: 19},
          {id: '003', name: '王五', age: 55}
        ],
        student: {name:'张三',age: 11}
      }
    }
  })
</script>

收集表单数据(text、radio、checkbox[1或多]、select)

  • HTML
<div id="app">
    <form action="">
        用户名:<input type="text" v-model="username"><br>
        密码:<input type="password" v-model="password"><br>
        性别:<input type="radio" v-model="sex" name="sex" value="0"> 男
             <input type="radio" v-model="sex"  name="sex" value="1"> 女<br>
        爱好:<input type="checkbox" v-model="hobby" value="0"> 篮球
            <input type="checkbox" v-model="hobby" value="1"> 足球
            <input type="checkbox" v-model="hobby" value="2"> 学习<br>
        籍贯:<select v-model="fromAddr">
                <option selected>--请选择--</option>
                <option value="beijing">北京</option>
                <option  value="shagnhai">上海</option>
                <option  value="shenzhen">深圳</option>
            </select>
        简介:<textarea v-model="intro"></textarea><br>
        <input v-model="isAgree" type="checkbox" >是否同意用户协议?
        <button>点击注册!</button>
    </form>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        username:'',
        password: '',
        sex: '',
        hobby: [],
        fromAddr: '',
        intro: '',
        isAgree: false
      }
    }
  })
</script>
  • 数据收集效果
    image

过滤器(Vue3中已弃用)

  • HTML
<div id="app">
    <!-- 1、请求使用过滤器只获取到前3个字符   -->
    <h3>{{msg | strSpilt3}}</h3>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        msg: '你好呀adsfasdfzcxv'
      }
    },
    filters: {
      strSpilt3(value) {
        return value.substr(0,3)
      }
    }
  })
</script>

其他内置指令与自定义指令

v-text

  • 作用:替换标签体中的内容

  • HTML

<div id="app">
    <h3 v-text="myText"></h3>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        myText: '你好呀,我也很好,谢谢您'
      }
    }
  })
</script>

v-html

  • 作用:替换标签体中的内容,但是可以解析html中的标签(千万不要在用户输入的功能上使用v-html)

  • HTMl

<div id="app">
    <h3 v-html="myHtml"></h3>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        myHtml: '<span style="color: red; font-size: 30px; font-family: 隶书">这是一段内容</span>'
      }
    }
  })
</script>

v-cloak 属性

  • 作用:** Vue在渲染模板时,会将v-cloak这个标签属性移除**,因此可以配合css样式达到当界面未渲染完毕时,不展示模板内容的效果。

  • CSS

<style>
    [v-cloak]{
        display: none;
    }
</style>
  • HTML
<div id="app" v-cloak>
    <h3>{{msg}}</h3>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        msg: '这是一个消息'
      }
    },
    beforeMount() {
      console.log("挂载之前")
      debugger;
    }
  })
</script>

v-once 属性

  • 添加了该标签属性后,该标签中只会渲染一次Vue中的数据

  • HTML

<div id="app">
    可以修改msg的值:<input type="text" v-model="msg"> <br>
    <h3 v-once v-text="msg"></h3>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        msg: '这是一个消息'
      }
    }
  })
</script>

v-pre属性

  • 作用:被v-pre标志的DOM节点中的所有元素均不会被Vue解析

  • HTML

<div id="app">
    <div v-pre>
        我是添加了v-pre属性的一个div,我的内容不会被Vue解析:{{msg}}
    </div>
    <div>
        我是没有添加v-pre属性的一个div,我的内容会被Vue解析:{{msg}}
    </div>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        msg: '这是一个消息'
      }
    }
  })
</script>

自定义指令

  • 在实现v-bind指令的效果前提下,实现获取焦点的功能,取名为f-bind

  • 自定义指令的属性
    (0)element:当前的元素节点, binding:当前的指令状态
    (1)bind(element,binding):指令与元素成功进行绑定时
    (2)inserted(element,binding):指令所在的元素被插入页面时
    (3)update(element, binding):当模板被重新渲染时

  • HTML

<div id="app">
    一个文本输入框,姓名:<input type="text" v-fbind:value="name"><br>
    <button @click="name+='!!'">点我名字发生变化</button>
</div>
  • javascript
<script type="text/javascript">
  new Vue({
    el: "#app",  // 挂载一个容器
    data() {     // 绑定数据
      return {
        name: '张三'
      }
    },
    directives: {
      // 1、简易写法,相当于 bind与update的结合
      // fbind(element, binding){
      //   element.value = binding.value;
      //   element.focus();
      // }
      // 2、完整写法
      fbind: {
        bind(element, binding) {
          element.value = binding.value
        },
        inserted(element, binding) {
          element.focus();
        },
        update(element, binding) {
          element.value = binding.value
          console.log("你好呀,模板被重新解析了")
        }
      }
    }
  })
</script>

共计11个生命周期钩子

  • beforeCreate() : 数据代理与数据监测之前(无法通过vm访问data中的数据或methods方法等)

  • created():数据代理与数据监测之后(可以通过vm访问数据及方法了)

  • beforeMount() : 数据挂载之前(此时页面还是未经Vue编译的DOM,在该钩子中对DOM进行的更改均不生效)

  • mountd() : 数据挂载之后(页面已经是被Vue编译后的真实DOM)

  • beforeUpdate():数据更新之前

  • updated(): 数据更新之后

  • beforeDestroy():Vue实例销毁之前

  • destroyed:Vue实例销毁之后

  • $nextTick:页面渲染完毕后执行该方法的回调函数

  • activated:组件通过路由路径被访问时触发(可以绑定一些定时器之类的)

  • deactivated:组件通过路由路径离开时触发(可以关闭一些定时器之类的)

非单文件组件(了解组件的使用3步骤)

  • 组件使用3步骤
    (1)创建组件(2种方式)
    (2)注册组件(2种方式)
    (3)使用组件

  • HTML

<body>
    <div id="app">
        <!--  3、使用组件  -->
        <School></School>
        <Student></Student>
    </div>
</body>
  • javascript
<script type="text/javascript">
  // 1、创建组件的2种方式
  const SchollComponent = Vue.extend({
    template: `<h2>你好,这是我的学校</h2>`
  })

  const StudentComponent = {
    template: `<h2>你好,我是一个学生,张三</h2>`
  }

  // 2、注册组件的2种方式,全局注册
  Vue.component('School', SchollComponent);
  // 2、局部注册
  new Vue({
    el: "#app",
    components: {
      Student: StudentComponent,
    }
  })
</script>

单文件组件的使用思路

  • (1)创建一个一般组件(非路由组件),后缀为.vue,并将组件使用export进行导出(暴露)

  • (2)创建一个组件,取名为App.vue,用于统一管理所有的Vue组件

  • (3)创建一个main.js,用于创建Vue实例,在Vue实例中注册App.vue这个组件,并且完成挂载

  • (4)既然需要挂载,所以创建一个index.html来作为容器使用

使用Vue配合Vue脚手架使用

创建一个Vue2的脚手架项目

下载并安装node.js来以便使用npm包管理器的命令

下载地址

为npm配置淘宝镜像

npm config set registry https://registry.npm.taobao.org

安装Vue脚手架

npm install -g @vue/cli

进入到一个目录,打开cmd命令行来创建一个Vue项目、项目结构分析

# 创建一个vue_cli项目并按照提示选择vue2的版本
vue create my_study_vue2_cli

image

image

npm包管理器的运行、打包等命令

  • npm run serve 运行

  • npm run build 打包项目

ref属性

  • 作用:给元素或子组件进行一个唯一的标识,可以通过当前实例的$refs属性中获取。

  • App.vue文件(引入了一个Student组件)

<template>
  <div id="app">
      <span ref="sp">我是一个span标签</span>
    <Student ref="stu"></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue'

export default {
  name: 'App',
  components: {
    Student
  },
  mounted() {
    // 获取到ref指定的元素
    console.log(this.$refs.sp);

    // 获取到ref指定的组件
    console.log(this.$refs.stu);
  }
}
</script>
  • 运行结果
    image

props属性,父给子传递数据(3种写法)

  • props的三种使用方式

    • 数组方式
      props: ['studentList']
    • 对象方式(限制类型)
    props: {
      studentList: Number,
      name: Array,
    }
    
    • 对象嵌套对象方式:
    props: {
      name: {
        // 类型
        type: Number,
        // 默认值,与require互斥
        default: '小王同学',
        // 是否必须
        required: true
      }
    }
    
  • 父组件

<template>
  <div id="app">
    <Student :studentList="studentList"></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue'

export default {
  name: 'App',
  components: {
    Student
  },
  data() {
    return {
      studentList: [
        {id:'001', name:'张三',age: 55},
        {id:'002', name:'李四',age: 22},
        {id:'003', name:'王五',age: 54}
      ]
    }
  }
}
</script>
  • 子组件
<template>
    <div>
       <h2>学生列表</h2>
        <ul>
            <li v-for="(student,index) in studentList" :key="student.id">
                {{student.id}} --- {{student.name}} --- {{student.age}}
            </li>
        </ul>
    </div>
</template>

<script>
  export default {
    name: "Student",
    props: ['studentList']
  }
</script>

mixin混入

  • 作用:相当于抽取公共的配置项,让其他组件可以通过导入使用(局部混入),或者使用Vue.mixin注册为全局混入

创建一个混入mixin.js,放在与main.js平级,并且将混入暴露

const testMixin = {
  data(){
    return {
      testMsg: "我是testMixin混入的信息"
    }
  },
  methods: {
    test() {
      alert("我是testMixin混入的test方法")
    }
  }
}

const demoMixin = {
  data(){
    return {
      demoMsg: "我是demoMixin混入的信息"
    }
  },
  methods: {
    demo() {
      alert("我是demoMixin混入的demo方法")
    }
  }
}

export {testMixin, demoMixin}

使用局部注册的方式使用

<template>
    <div>
       <h2>学生列表</h2>
        <h2>demoMixin中的msg: {{demoMsg}}</h2>
        <h2>testMixin中的msg: {{testMsg}}</h2>
        <button @click="demo">点我使用demoMixin的demo()方法</button>
        <button @click="test">点我使用testMixin的test()方法</button>
    </div>
</template>

<script>
  import {demoMixin} from "@/mixin";

  export default {
    name: "Student",
    mixins:[demoMixin]
  }
</script>

<style scoped>

</style>

使用全局引入的方式main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 引入一个混入并且进行全局注册
import {testMixin} from '@/mixin'
Vue.mixin(testMixin)

new Vue({
  render: h => h(App),
}).$mount('#app')

插件

  • 作用:扩展当前项目的功能。

  • 使用步骤
    (1)创建一个插件
    (2)在main.js中进行全局注册
    (3)各个组件就都可以使用了

  • 创建一个插件

export default  {
  // 导入该插件后,Vue就会自动帮我们调用该方法,并且会传递VM给我们
  install(Vue) {
    // 注册一个全局指令
    Vue.directive('fbind',(element,binding)=> {
      element.innerText = binding.value;
    })

    // 注册一个全局组件
    Vue.component('Demo',{
      template: `<h2>我是Demo组件,嘿嘿</h2>`
    })

    // ...还可以定义混入、插件等等,因此插件可以做很多很多事
  }
}
  • 在main.js中进行全局注册
import myPlugin from "@/plugins/myPlugin";
Vue.use(myPlugin)

scoped的style作用域

  • 作用:在组件中的style上标识了scoped属性后,则该组件中的样式只对当前演示有效

  • 演示

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

浏览器本地存储

localStorage

  <script type="text/javascript">
    // 1、添加本地存储
    localStorage.setItem('username','张三')
    localStorage.setItem('password','123456')
    
    // 1.1 存储一个对象
    let person = {id:'001',name:'王五',age:55}
    let personJson = JSON.stringify(person)
    localStorage.setItem('person',personJson)

    // 2、获取本地存储
    let username = localStorage.getItem('username')
    console.log(username)

    // 2.1 获取一个Json字符串并转换为对象
    let personStr = localStorage.getItem('person')
    let newPerson = JSON.parse(personStr)
    console.log(newPerson)

    // 3、删除本地存储
    localStorage.removeItem('username')

    // 4、修改本地存储(覆盖)
    localStorage.setItem('password','666666')

    // 5、清空本地存储
    localStorage.clear()
  </script>

sessionStorage

  <script type="text/javascript">
    // 1、添加本地存储
    sessionStorage.setItem('username','张三')
    sessionStorage.setItem('password','123456')
    
    // 1.1 存储一个对象
    let person = {id:'001',name:'王五',age:55}
    let personJson = JSON.stringify(person)
    sessionStorage.setItem('person',personJson)

    // 2、获取本地存储
    let username = sessionStorage.getItem('username')
    console.log(username)

    // 2.1 获取一个Json字符串并转换为对象
    let personStr = sessionStorage.getItem('person')
    let newPerson = JSON.parse(personStr)
    console.log(newPerson)

    // 3、删除本地存储
    sessionStorage.removeItem('username')

    // 4、修改本地存储(覆盖)
    sessionStorage.setItem('password','666666')

    // 5、清空本地存储
    sessionStorage.clear()
  </script>

组件的自定义事件(2种创建方式),可以让子给父传递数据

标签属性创建自定义事件

  • 父组件
<template>
  <div id="app">
    <Student @hello="sayHello" ></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue'

export default {
  name: 'App',
  components: {
    Student
  },
  methods: {
    sayHello(name){
      alert(name + ",你好呀~")
    }
  }
}
</script>
  • 子组件
<template>
    <div>
       <button @click="emitHello">点我触发当前组件上的hello自定义事件</button>
    </div>
</template>

<script>
  export default {
    name: "Student",
    methods: {
      emitHello()  {
        const name = '张三'
        this.$emit('hello',name)
      }
    }
  }
</script>

绑定ref组件后使用$on

  • 父组件
<template>
  <div id="app">
    <Student ref="stu" ></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue'

export default {
  name: 'App',
  components: {
    Student
  },
  mounted() {
    // 注意这里需要写箭头函数,不然this将会是调用者的vc
    this.$refs.stu.$on('hello',(name)=> {
      alert(name + ",你好呀!!!")
    })
  }
}
</script>
  • 子组件
<template>
    <div>
       <button @click="emitHello">点我触发当前组件上的hello自定义事件</button>
    </div>
</template>

<script>
  export default {
    name: "Student",
    methods: {
      emitHello()  {
        const name = '张三'
        this.$emit('hello',name)
      }
    }
  }
</script>

全局事件总线

  • 作用:实现所有组件之间的数据传输

  • 原理:创建一个所有人都能够访问到的VC,每个需要数据的组件都可以向该组件身上使用\(on**的方式绑定数据,每个发送数据的组件都可以通过向**该组件身上使用\)emit的方式调用方法来发送数据。

  • 实现步骤
    (1)在main.js中注册一个所有组件都能访问到的VC
    (2)在需要接收数据的组件中使用该VC来绑定事件
    (3)在发送数据的组件中使用该VC来调用事件发送数据

  • main.js(创建全局事件总线)

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false


new Vue({
  render: h => h(App),
  beforeCreate() {
    // 1、创建全局事件总线
    Vue.prototype.$bus = this
  }
}).$mount('#app')

  • 需要数据的组件向$bus绑定事件
<template>
  <div id="app">
    <Student></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue'

export default {
  name: 'App',
  components: {
    Student
  },
  mounted() {
    // 注意这里需要写箭头函数,不然this将会是调用者的vc
    this.$bus.$on('hello',(name)=> {
      alert(name + ",你好呀!!!")
    })
  }
}
</script>
  • 需要发送数据的组件向$bus发送事件
<template>
    <div>
       <button @click="emitHello">点我触发当前组件上的hello自定义事件</button>
    </div>
</template>

<script>
  export default {
    name: "Student",
    methods: {
      emitHello()  {
        const name = '张三'
        this.$bus.$emit('hello',name)
      }
    }
  }
</script>

消息订阅与发布

  • 作用:可以完成不同组件之间数据的传递

  • 使用步骤
    (1)使用npm安装第三方库npm install --save pubsub-js
    (2)引入插件import PubSub from 'pubsub-js'
    (3)需要数据的就调用订阅api,PubSub.subscribe
    (4)发送数据的就调用发布api,PubSub.publish

  • 订阅者

<template>
    <div>
       <button @click="emitHello">点我触发当前组件上的hello自定义事件</button>
    </div>
</template>

<script>
  import PubSub from 'pubsub-js'
  export default {
    name: "Student",
    methods: {
      emitHello()  {
        const name = '张三'
        PubSub.publish('hello',name)
      }
    }
  }
</script>
  • 发布者
<template>
  <div id="app">
    <Student></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue'
import PubSub from 'pubsub-js'

export default {
  name: 'App',
  components: {
    Student
  },
  mounted() {
    PubSub.subscribe('hello',(name)=> {
      alert(name + ",你好呀!!!")
    })
  }
}
</script>

nextTick钩子的使用

  • 作用:在页面渲染后再执行$nextTick()该方法的回调函数

  • 编写一个一段文字,旁边放一个按钮,实现点击编辑的时候将文字变成一个输入框,并且获取input的焦点

    • 发现的问题:Vue是在方法执行完毕后,才会去重新解析模板(总不能执行一条一句就解析一次模板吧)
    • 而本案例中的写法是原本input框是隐藏的,此时执行input.focus()并没有效果。然后方法执行完毕,Vue才去解析模板,将input框显示出来。所以这就是如果不使用nextTick()就会出现bug的原因。执行时机的问题
  • 测试代码(存在bug的)

<template>
    <div>
        <input v-show="isEdit" ref="oneInput" v-model="msg" type="text">
        <span v-show="!isEdit">{{msg}}</span>
       <button @click="getInputFocus">点我获取文本框的焦点</button>
    </div>
</template>

<script>
  export default {
    name: "Student",
    data() {
      return {
        msg: '一条信息',
        isEdit: false
      }
    },
    methods: {
      getInputFocus()  {
        // 展示文本输入框,并隐藏原本的span标签
        this.isEdit = true
        this.$refs.oneInput.focus()
      }
    }
  }
</script>
  • 测试代码(使用nextTick后)
<template>
    <div>
        <input v-show="isEdit" ref="oneInput" v-model="msg" type="text">
        <span v-show="!isEdit">{{msg}}</span>
       <button @click="getInputFocus">点我获取文本框的焦点</button>
    </div>
</template>

<script>
  export default {
    name: "Student",
    data() {
      return {
        msg: '一条信息',
        isEdit: false
      }
    },
    methods: {
      getInputFocus()  {
        // 展示文本输入框,并隐藏原本的span标签
        this.isEdit = true
        
        // 将会在DOM解析完毕后调用该方法
        this.$nextTick(()=> {
          this.$refs.oneInput.focus()
        })
      }
    }
  }
</script>

Vue中的过度与动画

  • Vue为我们提供了2个组件,只需要为其指定class则会为我们完成动画效果

  • 使用animate.css这个样式库来完成动画效果

  • 动画效果完成思路
    (1)引入第三方样式库npm i animate.css
    (1.1)可以进入官网看看有哪些效果:https://animate.style
    (2)在需要使用动画的组件中使用transition或者transition-group来完成动画效果

  • transition 与 transition-group的相关属性
    (1)appear:第一次出现时就展示动画效果
    (2)name:需要引入的类样式的前缀(别漏了)
    (3)enter-active-class: 设置进入的样式
    (4)leave-active-class:设置离开的样式

  • 使用示例

<template>
    <div>
        <!-- 单个标签的动画  -->
        <transition
                appear
                name="animate__animated animate__bounce"
                enter-active-class="animate__backInDown"
                leave-active-class="animate__backOutDown">
            <p v-show="isShowP">我想要拥有动画效果,我是一个p标签</p>
        </transition>

        <hr><hr>
        
        <!-- 多个标签的动画  -->
        <transition-group
                appear
                name="animate__animated animate__bounce"
                enter-active-class="animate__backInDown"
                leave-active-class="animate__backOutDown">
            <p v-show="isShowP" key="1">我们是多个p标签,我们也想要动画效果</p>
            <p v-show="isShowP" key="2">我们是多个p标签,我们也想要动画效果</p>
            <p v-show="!isShowP" key="3">我们是多个p标签,我们也想要动画效果</p>
        </transition-group>

        <button @click="isShowP=!isShowP">点我可以切换p标签是否显示</button>
    </div>
</template>

<script>
  import 'animate.css'
  export default {
    name: "Student",
    data() {
      return {
        isShowP: true
      }
    }
  }
</script>

使用axios获取数据并解决跨域问题

安装axios并发送请求获取数据

  • 使用npm i axios安装axios

  • 发送网络请求向服务器请求数据

<template>
    <div>
        <h2>学生列表</h2>
        <ul>
            <li v-for="student in studentList" :key="student.id">
                {{student.id}} -- {{student.name}} --{{student.age}}
            </li>
        </ul>
    </div>
</template>

<script>
  import axios from 'axios'

  export default {
    name: "Student",
    data() {
      return {
        studentList: []
      }
    },
    mounted() {
      axios.get('http://localhost:5000/students')
      .then((response)=>{
        console.log(response)
      })
      .catch((err)=>{
        console.log("出错了,错误信息为:" + err)
      })
    }
  }
</script>

为什么会出现跨域问题以及解决思路

  • 跨域问题只会存在于服务器与浏览器之间

  • 并不是服务器不响应数据,而是浏览器会判断当前的 协议、域名、端口号 与访问的服务器是否相匹配,如果不匹配则会出现跨域问题,浏览器不接收数据。

  • 既然知道了跨域问题仅存在于服务器与浏览器之间,那么我们有2种解决思路。
    -(1)在服务器端进行修改,返回数据的时候携带一些特殊的响应头信息等解决
    -(2)前端使用代理服务器来获取信息,再将代理服务器获取到的信息返回给前端

使用VueCli配置代理服务器解决跨域问题(想想端口号该怎么写?)

  • 配置代理服务器的第一种方式,vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false,
  devServer: {
    proxy: 'http://localhost:5000'
  }
})
  • 再次发送请求时,需要访问本机的端口号,然后由代理服务器去访问数据服务器
  axios.get('http://localhost:8080/students')
  .then((response)=>{
    this.studentList = response.data
  })
  .catch((err)=>{
    console.log("出错了,错误信息为:" + err)
  })
  • 配置代理服务器的第二种方式
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false,
  devServer: {
    proxy: {
      // 如果路径是 /students为前缀时,访问该服务器
      '/students': {
        // 访问的服务器地址
        target: 'http://localhost:5000',

        // 用于支持websocket
        ws: true,

        // 意思是请求代理服务器时,将students前缀去掉
        // pathRewrite:{'^/students':''},

        // 用于控制请求头中的域名及端口号,
        // 若为true,则请求时会与target服务器的域名、端口号一致
        // 若为false,则请求时,自己是啥域名和端口号,请求头中的域名和端口号就是啥
        changeOrigin: true
      },
      // 如果路径是 /foo为前缀时,访问该服务器
      '/foo': {
        target: 'http://localhost:6000'
      }
    }
  }
})

插槽的使用

  • 作用:主要用于占位,当一个组件中存在一些不确定内容时,可以使用插槽。由使用者来决定具体展示的内容

  • 默认插槽:<slot></slot>

  • 具名插槽:<slot name="slot1"></slot>

  • 作用域插槽:<slot msg="这是一条信息"></slot>

默认插槽

  • 子组件定义插槽
<template>
    <div>
        <h2>学生列表</h2>
        <slot>我是一个插槽,如果你使用我这个组件不传标签的话,则显示这句话</slot>
    </div>
</template>

<script>
  export default {
    name: "Student"
  }
</script>
  • 父组件使用插槽
<template>
  <div id="app">
    <Student>
        <h2>我们要成为那个插座,我是一个h2标签</h2>
        <h2>我们要成为那个插座,我是一个h2标签</h2>
        <h2>我们要成为那个插座,我是一个h2标签</h2>
    </Student>
  </div>
</template>

<script>
    import Student from './components/Student.vue'

    export default {
      name: 'App',
      components: {
        Student
      }
    }
</script>

具名插槽

  • 子组件定义插槽
<template>
    <div>
        <h2>学生列表</h2>
        <slot name="slot1">我是一个插槽,如果你使用我这个组件不传标签的话,则显示这句话</slot>
    </div>
</template>

<script>
  export default {
    name: "Student"
  }
</script>
  • 父组件使用插槽
<template>
  <div id="app">
    <Student>
        <h2 slot="slot1">我们要成为那个插座,我是一个h2标签</h2>
        <h2>我们要成为那个插座,我是一个h2标签</h2>
        <h2>我们要成为那个插座,我是一个h2标签</h2>
    </Student>
  </div>
</template>

<script>
    import Student from './components/Student.vue'

    export default {
      name: 'App',
      components: {
        Student
      }
    }
</script>

作用域插槽(父组件调用子组件时,使用子组件传递的数据,注意必须使用template)

  • 子组件定义插槽
<template>
    <div>
        <h2>学生列表</h2>
        <slot name="slot1" :stu="student">我是一个插槽,如果你使用我这个组件不传标签的话,则显示这句话</slot>
    </div>
</template>

<script>
  export default {
    name: "Student",
    data() {
      return {
        student: {id: '001', name: '张三', age: 51}
      }
    }
  }
</script>
  • 父组件使用插槽
<template>
<div id="app">
    <Student>
        <!-- 采用普通语法 -->
        <!--<template slot="slot1" scope="childData">
            {{childData.stu.id}} -- {{childData.stu.name}} -- {{childData.stu.age}}
        </template>-->

        <!-- 采用解构语法 -->
        <template slot="slot1" scope="{stu}">
            {{stu.id}} -- {{stu.name}} -- {{stu.age}}
        </template>
    </Student>
</div>
</template>

<script>
  import Student from './components/Student.vue'

  export default {
    name: 'App',
    components: {
      Student
    }
  }
</script>

Vuex

  • 作用:Vuex可以实现所有组件中对数据的共享,让组件中的通信变得没有障碍。一般需要被所有组件所访问的数据,可以存放在Vuex中。Vuex的4大组成为: actions、mutations、state、store(用于管理前面3个)

  • 使用步骤
    (1)使用包管理器安装Vuex插件npm i vuex@3,这里之所以写3,是因为Vuex4的版本不支持Vue2版本,而默认是安装Vuex4,因此需要注意版本
    (2)在src目录下创建一个store目录用于表示仓库,在store目录下创建一个index.js
    (3)在index.js中编写Vuex所需要的4个组件,并且最后将store给导出

Vuex的原理图

image

Vuex的基本使用(非模块化)

Store的配置以及如何使用state、getters、actions、mutations

  • 下载安装Vuex3
    npm i vuex@3

  • 创建store目录和index.js,修改index.js的代码如下

import Vue from 'vue'
import Vuex from 'vuex'

// 1、使用vuex插件
Vue.use(Vuex)

// 2、配置actions,可以用来处理一些复杂的业务逻辑,可以理解为业务层
const actions = {
  addNum(context, value) {
    context.commit('ADDNUM',value)
  }
}

// 3、配置mutations,直接与数据打交道,进行修改数据,理解为DAO层
const mutations = {
  ADDNUM(state, value) {
    state.num = value
  }
}

// 4、配置getters,用于加工数据
const getters = {
  // 获取当前计数大于10倍的信息
  getBigNum(state) {
    return state.num * 10
  }
}

// 4、用于存储数据,理解为数据库
const state = {
  // 1、供Count组件使用
  num: 0,
  // 2、供Person组件使用
  personList: [
    {id: '001', name: '张三', age: 15},
    {id: '002', name: '李四', age: 44},
    {id: '003', name: '王五', age: 52}
  ]
}

export default new Vuex.Store({
  actions,
  mutations,
  getters,
  state
})
  • 页面中的使用
<template>
    <div>
        <h2>计数功能</h2>
        <h3>当前的计数是: {{$store.state.num}}</h3>
        <h3>当前的计数乘以10倍是: {{$store.getters.getBigNum}}</h3>
        <button @click="addNum">点我数量+1</button>
    </div>
</template>

<script>
  export default {
    name: "Count",
    methods: {
      addNum() {
        const addOneNum = this.$store.state.num + 1
        this.$store.dispatch('addNum', addOneNum)
      }
    }
  }
</script>

简化获取vuex中数据与方法的写法

  • store的代码
import Vue from 'vue'
import Vuex from 'vuex'

// 1、使用vuex插件
Vue.use(Vuex)

// 2、配置actions,可以用来处理一些复杂的业务逻辑,可以理解为业务层
const actions = {
  addNum(context, value) {
    context.commit('ADDNUM',value)
  }
}

// 3、配置mutations,直接与数据打交道,进行修改数据,理解为DAO层
const mutations = {
  ADDNUM(state, value) {
    state.num += value
  },
  ADDNUM2(state, value) {
    state.num += value
  }
}

// 4、配置getters,用于加工数据
const getters = {
  // 获取当前计数大于10倍的信息
  getBigNum(state) {
    return state.num * 10
  }
}

// 4、用于存储数据,理解为数据库
const state = {
  // 1、供Count组件使用
  num: 0,
  // 2、供Person组件使用
  personList: [
    {id: '001', name: '张三', age: 15},
    {id: '002', name: '李四', age: 44},
    {id: '003', name: '王五', age: 52}
  ]
}

export default new Vuex.Store({
  actions,
  mutations,
  getters,
  state
})
  • Vue组件中的代码
<template>
    <div>
        <h2>计数功能</h2>
        <h3>当前的计数是: {{num}}</h3>
        <h3>当前的计数乘以10倍是:{{bigNum}} </h3>
        <button @click="addNum(1)">点我数量+1(通过actions)</button>
        <button @click="addNum2(2)">点我数量+2(使用mutations)</button>
    </div>
</template>

<script>
  import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'

  export default {
    name: "Count",
    computed: {
      // 获取state中的数据,对象写法:左边为当前computed的键,右边为对应state中的键
      // ...mapState({'num':'num'})

      // 获取state中的数据,数组写法:若是需要当前computed的键,与state中的键一致,则可以使用该写法
      ...mapState(['num']),

      // 获取getters中的数据,对象写法,还有一个数组写法,与mapState一致
      ...mapGetters({'bigNum':'getBigNum'})
    },
    methods: {
      // 获取actions中的方法
      ...mapActions({'addNum':'addNum'}),

      // 获取mutations中的方法
      ...mapMutations({'addNum2':'ADDNUM2'})
    }
  }
</script>

vuex的模块化开发

将针对不同组件的数据与方法拆分,拆分后放在不同的js文件中(别忘了namespaced)

  • person.js
export default {
  namespaced:true,
  actions: {
    addPerson(context, value) {
      context.commit('ADDPERSON',value)
    }
  },
  mutations: {
    ADDPERSON(state, value) {
      state.personList.unshift(value)
    }
  },
  getters: {
    // 获取所有人数的平均年龄
    personAgeAvg(state) {
      return state.personList.map(p => p.age).reduce((age,count) => age + count, 0) / state.personList.length
    }
  },
  state: {
    // 2、供Person组件使用
    personList: [
      {id: '001', name: '张三', age: 15},
      {id: '002', name: '李四', age: 44},
      {id: '003', name: '王五', age: 52}
    ]
  }
}
  • count.js
export default {
  namespaced:true,
  actions: {
    addNum(context, value) {
      context.commit('ADDNUM',value)
    }
  },
  mutations: {
    ADDNUM(state, value) {
      state.num += value
    },
    ADDNUM2(state, value) {
      state.num += value
    }
  },
  getters: {
    // 获取当前计数大于10倍的信息
    getBigNum(state) {
      return state.num * 10
    }
  },
  state: {
    // 1、供Count组件使用
    num: 0
  }
}

在store目录下的index.js中引入不同的模块

import Vue from 'vue'
import Vuex from 'vuex'

// 1、使用vuex插件
Vue.use(Vuex)

// 2、引入不同的模块
import countOptions from '@/store/count'
import personOptions from '@/store/person'


// 3、创建仓库
export default new Vuex.Store({
  modules: {
    countAbout: countOptions,
    personAbout: personOptions
  }
})

获取不同模块中的数据(原始方式)

<template>
    <div>
        <h2>人员列表</h2>
        <ul>
            <li v-for="(person,index) in personList" :key="person.id">
                {{person.id}} --- {{person.name}} --- {{person.age}}
            </li>
        </ul>
        <h3>员工的平均年龄为:{{personAgeAvg}}</h3>
        <div>
            员工id:<input type="text" v-model="id"><br>
            员工姓名:<input type="text" v-model="name"><br>
            员工年龄:<input type="number" v-model.number="age"><br>
        </div>
        <button @click="addPersonOfActions">点我添加一个员工(通过actions的方式)</button>
        <button @click="addPersonOfMutations">点我添加一个员工(通过mutations的方式)</button>
    </div>
</template>

<script>

  export default {
    name: "Person",
    data() {
      return {
        id: '',
        name: '',
        age: 0
      }
    },
    computed: {
      personList() {
        return this.$store.state.personAbout.personList
      },
      personAgeAvg() {
        return this.$store.getters["personAbout/personAgeAvg"]
      }
    },
    methods: {
      addPersonOfActions() {
        const person = {id: this.id, name: this.name, age: this.age}
        this.$store.dispatch("personAbout/addPerson", person)
      },
      addPersonOfMutations() {
        const person = {id: this.id, name: this.name, age: this.age}
        this.$store.commit("personAbout/ADDPERSON", person)
      }
    }
  }
</script>

获取不同模块中的数据(通过简写方式)

<template>
    <div>
        <h2>计数功能</h2>
        <h3>当前的计数是: {{num}}</h3>
        <h3>当前的计数乘以10倍是:{{bigNum}} </h3>
        <button @click="addNum(1)">点我数量+1(通过actions)</button>
        <button @click="addNum2(2)">点我数量+2(使用mutations)</button>
    </div>
</template>

<script>
  import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'

  export default {
    name: "Count",
    computed: {
      // 获取state中的数据,对象写法:左边为当前computed的键,右边为对应state中的键
      ...mapState('countAbout',{'num':'num'}),

      // 获取state中的数据,数组写法:若是需要当前computed的键,与state中的键一致,则可以使用该写法
      ...mapState('countAbout', ['num']),

      // 获取getters中的数据,对象写法,还有一个数组写法,与mapState一致
      ...mapGetters('countAbout', {'bigNum':'getBigNum'})
    },
    methods: {
      // 获取actions中的方法
      ...mapActions('countAbout', {'addNum':'addNum'}),

      // 获取mutations中的方法
      ...mapMutations('countAbout', {'addNum2':'ADDNUM2'})
    }
  }
</script>

Vue-Router

  • 作用: 就相当于是一个路由器,它管理着一个个路由,通过配置规则,使一个个路由通过不同的访问路径展示不同的组件,达到页面局部刷新的效果,是单页面应用程序(SPA--------single page application)开发时的必备选项。

  • 使用步骤
    (1)安装Vue-Router插件,由于我这使用的是vue2,所以安装vue-router3的版本。npm i vue-router@3
    (2)在src目录下创建2个目录,分别为pages(用于存放路由组件)和router(用于存放路由相关配置)
    (3)在router目录下创建一个index.js,用于编写路由规则
    (4)而后页面通过 router-link标签以及 router-view标签实现路由的跳转

路由的基本使用

  • 创建index.js配置路由规则
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'

// 引入组件
import Home from '@/pages/Home'
import About from '@/pages/About'

// 1、配置路由规则
const routes = [
  {
    path: '/home',
    component: Home
  },
  {
    path: '/about',
    component: About
  }
]

// 2、创建一个VueRouter
export default new VueRouter({
  routes
})
  • 在main.js中配置
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 引入VueRouter插件并使用
import VueRouter from "vue-router";
Vue.use(VueRouter)
// 引入路由规则
import router from "@/router";

new Vue({
  render: h => h(App),
  router: router
}).$mount('#app')

  • 使用router-link 以及 router-view配合实现路由跳转
<template>
<div id="app">
    <router-link to="/home">我的小屋</router-link>
    <br>
    <router-link to="/about">关于我的信息</router-link>
    <hr>
    <!-- 路由视图,用于展示不同的路由组件 -->
    <router-view></router-view>
</div>
</template>

<script>
  export default {
    name: 'App'
  }
</script>
  • 2个路由组件
// Home
<template>
    <div>
        <h2>欢迎来到你的小屋</h2>
    </div>
</template>

<script>
  export default {
    name: "Home"
  }
</script>


// About
<template>
    <div>
        <h2>欢迎来到关于你的信息</h2>
    </div>
</template>

<script>
  export default {
    name: "About"
  }
</script>

多级路由的使用(配合children属性,还要注意path路径)

  • 在router/index.js中配置多级路由规则
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'

// 引入组件
import Home from '@/pages/Home'
import HomeBasic from "@/pages/HomeBasic";
import HomeDetail from "@/pages/HomeDetail";

import About from '@/pages/About'

// 1、配置路由规则
const routes = [
  {
    path: '/home',
    component: Home,
    // 嵌套组件的必备属性
    children: [
      {
        // 注意这里的path不要再带 / 斜杠了
        path: 'basic',
        component: HomeBasic
      },
      {
        path: 'detail',
        component: HomeDetail
      },
    ]
  },
  {
    path: '/about',
    component: About
  }
]

// 2、创建一个VueRouter
export default new VueRouter({
  routes
})
  • 修改需要进行路由嵌套的组件
<template>
    <div>
        <h2>欢迎来到你的小屋</h2>
        <router-link to="/home/basic">点我即可查看小屋的基本信息</router-link><br>
        <router-link to="/home/detail">点我即可查看小屋的详细信息</router-link>

        <hr><hr>

        <!-- 用于展示不同的路由组件 -->
        <router-view></router-view>
    </div>
</template>

<script>
  export default {
    name: "Home"
  }
</script>
  • 新增2个路由组件
// HomeBasic
<template>
    <div>
        <h3>我是小屋的基本信息</h3>
    </div>
</template>

<script>
  export default {
    name: "HomeBasic"
  }
</script>


// HomeDetail
<template>
    <div>
        <h3>我是小屋的详细信息</h3>
    </div>
</template>

<script>
  export default {
    name: "HomeDetail"
  }
</script>

路由的query参数传递(给路由组件传)

通过字符串拼接的方式传递query参数

  • 修改router-link标签,采用字符串拼接
<router-link :to="`/home/basic?msg=${msg}&status=${status}`">
	点我即可查看小屋的基本信息
</router-link><br>

  • 路由组件通过$route.query来获取其中的参数
<div>
    <h3>我是小屋的基本信息</h3>
    <h5>我是小屋,我接收到了query参数:其中msg为:{{$route.query.msg}}</h5>
    <h5>我是小屋,我接收到了query参数:其中status为:{{$route.query.status}}</h5>
</div>

通过router-link中to的对象写法来传递

  • 修改router-link标签,采用to的对象写法
<router-link
  :to="{
    path: '/home/basic',
    query: {
      msg: msg,
      status: status
    }
       }">
    点我即可查看小屋的基本信息
</router-link><br>
  • 路由组件还是使用一样的方式接收即可
<div>
    <h3>我是小屋的基本信息</h3>
    <h5>我是小屋,我接收到了query参数:其中msg为:{{$route.query.msg}}</h5>
    <h5>我是小屋,我接收到了query参数:其中status为:{{$route.query.status}}</h5>
</div>

命名路由(当路由的嵌套层数过多时可以使用)

  • 作用: 给路由取一个名字。如果想要通过路由的名字实现路由的跳转,则router-link中的to必须为对象写法将path变更为name,再写上路由名即可。

  • 为一个路由取名

// 1、配置路由规则
const routes = [
  {
    // 注意这里取了一个名字!!!!!!!!!!
    name: 'myHome',
    path: '/home',
    component: Home,
    // 嵌套组件的必备属性
    children: [
      {
        // 注意这里的path不要再带 / 斜杠了
        path: 'basic',
        component: HomeBasic
      },
      {
        path: 'detail',
        component: HomeDetail
      },
    ]
  },
  {
    path: '/about',
    component: About
  }
]
  • 通过路由的名称完成路由的跳转
<router-link :to="{name:'myHome'}">我的小屋</router-link>

路由的params参数传递

to采用对象写法时需要命名路由

  • 如果传递的params采用对象写法,则必须配置为命名路由,并且不再指定path占位符
{
  name: 'xiangqing',
  path: 'detail',
  component: HomeDetail
}
  • 传递参数(对象写法)
  <router-link :to="{
    name: 'xiangqing',
    params: {
      id: '001',
      msg: '详情信息:我只是一条单纯的详细信息'
    }
  }">
    点我即可查看小屋的详细信息
  </router-link>
  • 接收参数
  <div>
    <h3>我是小屋的详细信息</h3>
    <h4>Home路由传递过来的消息id为: {{$route.params.id}}</h4>
    <h4>Home路由传递过来的详细消息为: {{$route.params.msg}}</h4>
  </div>

to采用字符串写法时,必须配置path占位符

  • 如果传递的params采用字符串写法,则需要配置路由的path占位符
{
  // name: 'xiangqing',
  path: 'detail/:id/:msg',
  component: HomeDetail
}
  • 传递参数
  <router-link :to="`/home/detail/111/${detailMsg}`">
    点我即可查看小屋的详细信息
  </router-link>
  
注意: 这里的detailMsg已经在data中配置了。
  • 接收参数
  <div>
    <h3>我是小屋的详细信息</h3>
    <h4>Home路由传递过来的消息id为: {{$route.params.id}}</h4>
    <h4>Home路由传递过来的详细消息为: {{$route.params.msg}}</h4>
  </div>

路由的props配置(解决总采用$route.的方式获取参数)

第一种配置方式(对象形式)

  • 第一种写法,props的值为对象,路由组件可以通过 props属性来获取对应的值
{
  path: 'basic',
  component: AboutBasic,
  props: {
    name: '张三',
    age: 15
  }
}
  • 路由组件获取参数
<template>
  <div>
    <h4>我是基础的个人信息</h4>
    <h5>姓名: {{name}}</h5>
    <h5>年龄: {{age}}</h5>
  </div>
</template>

<script>
  export default {
    name: "AboutBasic",
    props: ['name', 'age']
  }
</script>

布尔值方式(也必须遵循params传递参数的规则)

  • 第二种方式,返回一个布尔值,此时会将所有的params参数通过props参数传递给路由组件
{
  name: 'basicAbout', // 因为也必须遵循params传递参数的规则
  path: 'basic',
  component: AboutBasic,
  props: true
}
  • router-link的写法
  <router-link :to="{
    name: 'basicAbout',
    params: {
      name: '李四',
      age: 15
    }
  }">
    点我查看当前的基础个人信息
  </router-link><br>
  • 路由组件接收参数
<template>
  <div>
    <h4>我是基础的个人信息</h4>
    <h5>姓名: {{name}}</h5>
    <h5>年龄: {{age}}</h5>
  </div>
</template>

<script>
  export default {
    name: "AboutBasic",
    props: ['name', 'age']
  }
</script>

函数方式(非常灵活,可以拿到$route,不过依然要遵循params参数的获取规则)

  • 第三种方式:函数形式,该函数可以拿到$route,最后返回一个对象,非常灵活
  {
    name: 'detailAbout',
    path: 'detail',
    component: AboutDetail,
    props: function (route) {
      const allParam = {
        id: route.query.id,
        name: route.query.name,
        age: route.query.age,
        hobby: route.params.hobby,
        height: route.params.height
      }
      return allParam
    }
  }
  • router-link的写法,传递了多种类型的参数
<router-link
  :to="{
    name: 'detailAbout',
    query: {id:'001',name:'张三', age: 55},
    params: {hobby:'学习',height: '180厘米'}
  }">
  点我查看当前的详细个人信息
</router-link>
  • 路由组件使用props接收参数
<template>
  <div>
    <h4>我是详细的个人信息</h4>
    <h5>身份编号为: {{id}}</h5>
    <h5>姓名为: {{name}}</h5>
    <h5>年龄为: {{age}}</h5>
    <h5>爱好为: {{hobby}}</h5>
    <h5>身高为: {{height}}</h5>
  </div>
</template>

<script>
  export default {
    name: "AboutDetail",
    props: ['id','name','age','hobby','height']
  }
</script>

router-link的replace属性

  • 一旦给router-link标签添加上replace属性,那么就会覆盖默认的push规则

  • 使用方式,添加个属性即可

<router-link replace></router-link>

编程式路由导航$router(push、replace、back、forward、go)

  • 作用:当我们无法使用router-link标签时,可以采用编程式路由导航完成路由的跳转

  • 使用实例

<template>
  <div>
    <h2>欢迎来到你的小屋</h2>
      <button @click="resetHomeBasic">点我即可查看小屋的基本信息,我只是个button</button>
      <button @click="resetHomeDetail">点我即可查看小屋的详细信息,我只是个button</button>

      <hr><hr>

      <!-- 用于展示不同的路由组件 -->
      <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: "Home",
    methods: {
      resetHomeBasic() {
        this.$router.push({
          path: '/home/basic',
          query: {id: '001', name: '茅草屋'},
        })
      },
      resetHomeDetail() {
        this.$router.replace({
          path: '/home/detail',
          query: {id: '002', name: '精装房'}
        })
      }
    }
  }
</script>
  • 额外3个与历史记录有关的api
   this.$router.forward() //前进
   this.$router.back() //后退
   this.$router.go() //可前进也可后退

缓存路由组件(部分或全部)

  • 作用: 当一个路由组件不希望被销毁时,可以缓存路由组件,不然每次切换路由时,路由组件都会被执行销毁。

  • 在router-view外包一层keep-alive,该标签还有个inlude属性,可以指定需要被缓存的路由组件(注意:这里写的是组件名

  • 缓存全部路由组件

<template>
  <div>
    <h2>欢迎来到你的小屋</h2>
      <router-link to="/home/basic">点我即可查看小屋的基本信息</router-link><br>
      <router-link to="/home/detail">点我即可查看小屋的详细信息</router-link>

      <hr><hr>

      <!-- 缓存所有被包裹的路由组件  -->
      <keep-alive>
        <router-view></router-view>
      </keep-alive>
  </div>
</template>

<script>
  export default {
    name: "Home"
  }
</script>
  • 缓存部分路由组件
<template>
  <div>
    <h2>欢迎来到你的小屋</h2>
      <router-link to="/home/basic">点我即可查看小屋的基本信息</router-link><br>
      <router-link to="/home/detail">点我即可查看小屋的详细信息</router-link>

      <hr><hr>

      <!-- 缓存部分路由组件,多个组件之间用逗号分割  -->
      <keep-alive include="HomeBasic,HomeDetail">
        <router-view></router-view>
      </keep-alive>
  </div>
</template>

<script>
  export default {
    name: "Home"
  }
</script>

路由组件的2个生命周期钩子(一般用于开启和关闭资源,比如定时器)

  • 作用:可以监视路由组件在通过路由的方式访问时以及离开时。

  • 钩子函数: activated 和 deactivated

<template>
<div>
    <h3 :style="{opacity: opacity}">我是小屋的基本信息</h3>
    我是一个输入框,我需要被缓存:<input type="text" value="">
</div>
</template>

<script>
  export default {
    name: "HomeBasic",
    data() {
      return {
        opacity: 1
      }
    },
    activated() {
      console.log("我是HomeBasic,我被访问了,进入了活动状态")
      // 开启定时器
      this.timer = setInterval(() => {
        console.log("###")
        this.opacity = this.opacity - 0.02
        if(this.opacity <= 0) {
          this.opacity = 1
        }
      },15)

    },
    deactivated() {
      console.log("我是HomeBasic,路由从我这离开了,失活了")
      // 关闭定时器
      clearInterval(this.timer)
    }
  }
</script>

路由组件

作用: 用于对路由进行权限控制

全局路由组件(包含路由的元数据定义)

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'

// 引入组件
import Home from '@/pages/Home'
import HomeBasic from "@/pages/HomeBasic";
import HomeDetail from "@/pages/HomeDetail";

import About from '@/pages/About'

// 1、配置路由规则
const routes = [
  {
    // 注意这里取了一个名字!!!!!!!!!!
    name: 'myHome',
    path: '/home',
    component: Home,
    // 配置路由的元数据
    meta: {
      title: '我的小屋'
    },
    // 嵌套组件的必备属性
    children: [
      {
        path: 'basic',
        component: HomeBasic
      },
      {
        path: 'detail',
        component: HomeDetail
      },
    ]
  },
  {
    path: '/about',
    component: About,
    // 配置路由的元数据
    meta: {
      title: '关于~~'
    }
  }
]


// 2、创建一个VueRouter
const router =  new VueRouter({
  routes
})

// 3、 配置前置全局路由守卫
router.beforeEach((to, from , next) => {
  // console.log(to)
  // console.log(next)
  // to:要到哪个路由去
  // from: 从哪个路由而来
  // next:这是一个函数,调用后即可放行

  // 这里可以做一些权限判断,例如从localStrong中取一个username,如果为zhangsan,则放行,否则不放行
  const username = localStorage.getItem("username")
  if(username === 'zhangsan') {
    next()
  }
})

// 4、配置后置路由守卫(可以用来修改标题之类的)
router.afterEach((to, from) => {
  // 如果该前往的路由的原信息中有title,则将当前网页的标题修改
  if(to.meta.title) {
    document.title = to.meta.title
  }else {
    document.title = '学习vue的测试网页'
  }
})

export default router

独享路由组件

  {
    path: '/about',
    component: About,
    // 独享路由守卫,只有前置,没有后置
    beforeEnter: function (to, from, next) {
      // 这里可以做一些权限判断之类的
      // 放行
      next()
    }
  }

组件内路由守卫(通过路由规则进入和离开时生效)

<template>
  <div>
    <h2>欢迎来到关于你的信息</h2>
  </div>
</template>

<script>
  export default {
    name: "About",
    beforeRouteEnter(to, from, next) {
      // 通过路由规则进入该组件时生效
    },
    beforeRouteLeave(to, from, next) {
      // 通过路由规则离开该组件时生效
    }
  }
</script>

history和hash模式的区别

  • history的网址路径看起来比较好看,但是兼容性较差

  • hash模式的网址路径中会携带一个#,看起来不好看,但是兼容性好

  • 使用过程中的区别是:hash模式不会将#后面的内容发送给服务器,但是history模式会

  • 因此需要history模式时,需要后端的配合

使用element-ui组件库

  • 使用步骤
    (1)安装element-ui组件库,npm i element-ui
    (2)在main.js中引入相对应的组件,使其全局可用(注意此时引入了全部的组件,非常大)
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

(3)接下来页面想用就直接拿来用即可

如果想要按需引入

  • 安装如下插件npm install babel-plugin-component -D

  • 修改babel.config.js,添加上如下内容

{
  "presets": [["@babel/preset-env", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
  • 接下来就可以进行按需引入了,在main.js中
import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';

Vue.component(Button.name, Button);
Vue.component(Select.name, Select);

Vue3技术

使用Vue-Cli创建Vue3的项目

  • 需要建立在已经安装好了node.js,拥有了npm命令

  • 并且已经为npm配置了淘宝的镜像时使用

  • 创建Vue3项目vue create 需要的项目名,而后选择vue3,点击创建
    image

分析Vue3项目结构中的main.js

// 引入createApp函数,用于创建实例对象-app
// 类似于vue2中的vm,不过这个app实例对象比vm更轻
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

常用的Compsitions API

初识setup,感受一下vue2到vue3的变化

<template>
  <h2>第一个Vue3项目</h2>
  <h3>{{msg}}</h3>
  <!-- 1、目前发现数据并不是响应式的 -->
  <input type="text" v-model="title"/>
  
  <!-- 2、即便修改了值,页面也不会发生变化 -->
  <button @click="updateTitle">点我修改title的值为张三</button>
  <button @click="showTitle">点我查看当前title的值</button>
</template>

<script>
  export default {
    name: 'App',
    setup: function() {
      // 1、定义2个变量
      const msg = "我是一条信息"
      let title = "我是标题"

      // 2、定义2个方法
      function updateTitle() {
        title = '张三'
      }

      function showTitle() {
        alert("当前title的值为: " + title)
      }
      
      // 3、返回一个对象,模板中可以直接使用
      return {msg, title, updateTitle,showTitle}
    }
  }
</script>

注意:Vue3可以兼容vue2的写法,但是vue2的写法无法读取Vue3的数据

ref函数,用于使数据成为响应式的(基础类型、对象|数组类型,使用方式、修改方式)

  • 使用步骤
    (1)先引入ref函数
    (2)赋值时,将需要进行响应式的数据用函数包裹即可
    (3)js语法操作数据时需要 xxx.value,而在模板中d读取数据时,不需要.value
    (4)ref函数可以包裹基本数据类型,内部使用Object.defineProperty()的get、set完成的响应式
    (5)也可以包裹对象或者数组数据类型,内部求助了Vue3的新函数reactive()将类型转换为Proxy

  • 使用演示

<template>
  <h2>第一个Vue3项目</h2>
  <h3>当前的标题是:{{title}}</h3>
  <input type="text" v-model="title"/>
  <button @click="addTitleHi">点我给标题最后加一个感叹号</button>
</template>

<script>
  import {ref} from 'vue'

  export default {
    name: 'App',
    setup: function() {
      let title = ref("我是标题")

      function addTitleHi() {
        console.log(title)
        title.value = title.value += '!'
      }

      return {title, addTitleHi}
    }
  }
</script>

reactive函数

  • 使用步骤
    (1)先引入reactive函数
    (2)用于包裹对象数据类型,使其成为响应式,内部通过Proxy完成响应式(数据劫持),通过Reflect操作源对象内部的数据。
    (3)修改时不需要像RefImpl对象一样xxx.value了,以前对象如何修改,现在就如何修改

  • 使用示例

<template>
  <h2>第一个Vue3项目</h2>
  <h3>id: {{person.id}}</h3>
  <h3>姓名: {{person.name}}</h3>
  
  <button @click="updatePersonName">点我修改person的姓名为赵六</button>
</template>

<script>
  import {reactive} from 'vue'

  export default {
    name: 'App',
    setup: function() {
      let person = reactive({id:'001',name:'李四'})

      function updatePersonName() {
        person.name = '赵六'
      }
      
      return {person, updatePersonName}
    }
  }
</script>

setup的两个注意点

执行时机与次数

  • setup在beforeCreate之前执行一次,并且只执行一次

setup的两个参数

  • props : 用于接收组件从外部传递过来,并且在props中声明接收了的属性

  • context:上下文对象

    • attrs : 外部传递的数据中,如果props中没有声明接收该属性,则会将其放到这里
    • solts:收到的未被当前组件使用的插槽数据
    • emit:访问当前VC实例的自定义事件函数

计算属性与监视

计算属性(函数,一个参数)

  • 使用步骤
    (1)引入计算属性这个函数
    (2)使用时,参数可以是一个对象(用于写set和get方法),也可以直接是一个函数,相当于get()

  • 使用示例

<template>
  <h2>计算属性的使用</h2>
  <h3>当前的数字为: {{num}}</h3>
  <h3>当前的数字 * 10倍后为: {{bigNum}}</h3>
  可以通过该input对num进行修改:<input type="number" v-model.number="num">
</template>

<script>
  import {computed, ref} from 'vue'

  export default {
    name: 'App',
    setup: function() {
      let num = ref(10);

      // 1、完整写法
      /*let bigNum = computed({
        get() {
          return num.value * 10
        },
        set() {

        }
      })*/

      // 2、简写形式
      let bigNum = computed(() => {
        return num.value * 10
      })

      return {num, bigNum}
    }
  }
</script>

watch属性监视(监视对象、数组时的小坑)

  • 使用步骤
    (1)引入watch函数
    (2)该函数有3个参数
    参数1:需要监视的属性
    参数2:回调函数(相当于vue2时的handler函数)
    参数3:对象形式,可以指定watch的额外参数,例如deep和immediate属性
    (3)监视对象|数组属性时,其newValue与oldValue均为修改后的值(注意)

  • 使用示例

<template>
  <h2>监视属性的使用</h2>
  <h3>当前的数字为: {{num}}</h3>
  可以通过该input对num进行修改:<input type="number" v-model.number="num">

  <hr>
  <hr>

  <h3>当前的Person信息</h3>
  <h4>{{person.name}} -- {{person.age}} -- {{person.job.study}} -- {{person.job.play}} -- </h4>
  <button @click="person.name = '王老五'">点我即可将person.name修改为王老五</button>
  <button @click="person.job.play = '英雄联盟'">点我即可将person.job.play修改为英雄联盟</button>

  <hr>
  <hr>
  <h3>当前的爱好为</h3>
  <ul>
    <li v-for="(h,index) in hobby" :key="index">
      {{h}}
    </li>
  </ul>
  <button @click="hobby[0] = '看电视'">点我修改第一个爱好为:看电视</button>
</template>

<script>
  import {watch, ref, reactive} from 'vue'

  export default {
    name: 'App',
    setup: function () {
      // 准备数据
      let num = ref(10);
      let num2 = ref(20);
      let person = reactive({name: '张三', age: '李四', job: {study: '看书', play: '羽毛球'}})
      let hobby = reactive(['羽毛球','篮球','足球'])

      // 1、监视单个ref属性
      watch(num, (newValue, oldValue) => {
        console.log("修改后的num值为:" + newValue)
        console.log("修改前的num值为:" + oldValue)
      }, {immediate: false, deep: true})

      // 2、监视多个ref属性,此时得到的newValue和oldValue将会是一个数组
      watch([num, num2], (newValue, oldValue) => {
        console.log("num或num2中任意一个值发生了变化,变化前:" + oldValue)
        console.log("num或num2中任意一个值发生了变化,变化后:" + newValue)
      }, {immediate: false, deep: true})

      // 3、监视reactive定义的响应式数据,默认就强制开启深度监视,此时的deep属性无效,其实相当于是对一个对象的第一层进行了监视
      watch(person, (newValue, oldValue) => {
        console.log("person被进行了修改,变化前:")
        console.log(oldValue)
        console.log("person被进行了修改,变化后:")
        console.log(newValue)
      }, {immediate: false, deep: false})

      // 4、监视reactive定义的响应式数据中的某个属性中的属性变化,此时开启deep深度监视有效
      watch(() => person.job, (newValue, oldValue) => {
        console.log("person中的job的play属性被进行了修改,变化前:")
        console.log(oldValue)
        console.log("person中的job的play属性被进行了修改,变化后:")
        console.log(newValue)
      }, {immediate: false, deep: true})

      // 5、监视多个reactive定义的响应式数据中的某个属性中的属性变化,此时开启deep深度监视有效(在这也变成了一个数组,数组中为Proxy对象)
      watch([() => person.job, ()=> person.job.study], (newValue, oldValue) => {
        console.log("person中的job的play或者study属性被进行了修改,变化前:")
        console.log(oldValue)
        console.log("person中的job的play或者study属性被进行了修改,变化后:")
        console.log(newValue)
      }, {immediate: false, deep: true})

      // 6、监视一个数组,
      watch(hobby, (newValue, oldValue) => {
        console.log("hobby被进行了修改,变化前:")
        console.log(oldValue)
        console.log("hobby被进行了修改,变化后:")
        console.log(newValue)
      }, {immediate: false, deep: false})

      return {num, num2, person, hobby}
    }
  }
</script>

watchEffect

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
    const x1 = sum.value
    const x2 = person.age
    console.log('watchEffect配置的回调执行了')
})

Vue3的生命周期钩子

  • beforeCreate===>setup()

  • created=======>setup()

  • beforeMount ===>onBeforeMount

  • mounted=======>onMounted

  • beforeUpdate===>onBeforeUpdate

  • updated =======>onUpdated

  • beforeUnmount ==>onBeforeUnmount

  • unmounted =====>onUnmounted

  • 使用示例

<template>
  <h2>生命周期钩子的使用示例</h2>
  <h4>当前num为:{{num}}</h4>
  <button @click.once="num++">点我num + 1,只能点击一次</button>
</template>

<script>
  import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'

  export default {
    name: 'App',
    setup() {
      let num = ref(10)

      onBeforeMount(() => {
        console.log("挂载之前")
      })

      onMounted(() => {
        console.log("挂载完毕")
      })

      onBeforeUpdate(() => {
        console.log("数据更新之前")
      })

      onUpdated(() => {
        console.log("数据更新之后")
      })

      onBeforeUnmount(() => {
        console.log("解除挂载之前")
      })

      onUnmounted(() => {
        console.log("解除挂载之后")
      })

      return {num}
    }
  }
</script>

自定义hook函数(一般hook文件以use开头,而且默认导出为一个函数)

  • 作用: 相当于vue2的mixin混入,本质的作用是将代码进行封装,使代码更加清晰易懂。

  • 为什么默认导出为一个函数? 因为需要保证每一个人使用到的都是不同的数据,不能出现数据错乱

  • 使用步骤
    (1)在src目录下创建一个hooks目录
    (2)在该目录下创建一个js文件,用于实现某个公用的功能,需要返回核心的数据
    (3)需要使用的地方直接引入即可

  • 创建一个点击屏幕则显示坐标功能的hook,名为usePoint.js

import {reactive,onMounted,onBeforeUnmount} from 'vue'

export default function() {
  let point = reactive({
    x: 0,
    y: 0
  })

  function savePoint(event) {
    point.x = event.pageX
    point.y = event.pageY
  }

  onMounted(() => {
    window.addEventListener('mousemove', savePoint)
  })

  onBeforeUnmount(() => {
    window.removeEventListener('mousemove', savePoint)
  })

  return point
}
  • 接下来需要使用到该功能的组件直接引入即可使用
<template>
  <h2>hook的使用示例</h2>
  <h4>当前x轴为:{{point.x}}, y轴为: {{point.y}}</h4>
  <h4>小提示:只要对着屏幕移动鼠标,即可看到对应的坐标位置</h4>
</template>

<script>
  import {reactive} from 'vue'
  import usePoint from '@/hooks/usePoint'

  export default {
    name: 'App',
    setup() {
      let point = reactive(usePoint())
      return {point}
    }
  }
</script>

toRef与toRefs

  • 作用:可以获取对象中的一个属性的引用,并且将指向传递给一个变量,使页面代码更加简洁

  • toRef是一个个属性的处理,而toRefs是将整个对象一起处理

  • 使用示例(toRef)

<template>
  <h2>hook的使用示例</h2>
  <h4>当前用户的id为: {{id}}</h4>
  <h4>当前用户的name为: {{name}}</h4>
  <h4>当前用户的age为: {{age}}</h4>
  <h4>当前用户的birthday为: {{birthday}}</h4>
  <h4>当前用户的job为: {{job}}</h4>
</template>

<script>
  import {toRef, reactive} from 'vue'

  export default {
    name: 'App',
    setup() {
      let person = reactive({id: '001', name: '张三', age: 15, birthday: '1111-10-21',job: '摸鱼'})

      let id = toRef(person, 'id')
      let name = toRef(person, 'name')
      let age = toRef(person, 'age')
      let birthday = toRef(person, 'birthday')
      let job = toRef(person, 'job')

      return {id, name, age, birthday, job}
    }
  }
</script>

  • 示例toRefs
<template>
  <h2>hook的使用示例</h2>
  <h4>当前用户的id为: {{id}}</h4>
  <h4>当前用户的name为: {{name}}</h4>
  <h4>当前用户的age为: {{age}}</h4>
  <h4>当前用户的birthday为: {{birthday}}</h4>
  <h4>当前用户的job为: {{job}}</h4>
</template>

<script>
  import {toRefs, reactive} from 'vue'

  export default {
    name: 'App',
    setup() {
      let person = reactive({id: '001', name: '张三', age: 15, birthday: '1111-10-21',job: '摸鱼'})

      return { ...toRefs(person) }
    }
  }
</script>

其他Composition API(组合式API)

shallowReactive 与 shallowRef

  • shallowReactive的作用:用于对象数据,仅监视对象第一层的变化(浅层次),第二层及其以下的对象数据无法被监视到

  • shallowRef的作用:用于基本数据类型 或 对象类型。

    • 当应用于基本数据类型时,就跟ref一样
    • 当应用于对象类型时,该对象将成为一个普通的对象(没有响应式)

readonly 与 shallowReadonly

  • readonly:深只读,被该函数处理后的数据,无法被进行任何修改

  • shallowReadonly:浅只读,被该函数处理后的数据的第一层,无法被进行修改,但是第二层及其之后的层数都可以被修改

toRaw与markRaw(一个是需要普通对象,一个是让其永远失去响应式)

  • toRaw:

    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:

    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

customRef自定义Ref

  • 作用: 创建一个自定义的Ref,对其依赖项更新触发进行显示控制

  • customRef: 是一个函数,有2个参数,分别为track追踪函数,trigger告诉Vue渲染模板函数

  • 使用示例:实现用户修改的数据在500毫秒后再重新渲染,实现防抖效果

<template>
  <h2>customRef使用示例</h2>
  <hr><hr><hr>

  <h4>当前的信息为: {{info}}</h4>
  <h4>可以通过该输入框修改info信息:<input type="text" v-model="info"></h4>
</template>

<script>
  import {customRef} from 'vue'

  export default {
    name: 'App',
    setup() {

      function myRef(value, delay) {
        let timer
        return customRef(((track, trigger) => {
          return {
            get(){
              // 属性追踪
              track()
              return value
            },
            set(newValue){
              // 实现防抖效果
              clearTimeout(timer)
              // 添加一个计时器
              timer = setTimeout(() => {
                value = newValue
                // 告知Vue渲染模板
                trigger()
              },delay)
            }
          }
        }))
      }

      let info = myRef('我是初始化信息', 500)

      return { info }
    }
  }
</script>

provide 与 inject(祖先组件给其后代组件传递数据)

  • 作用: 祖先组件给后代组件提供数据,后代组件可以直接获取到。值得注意的是:仅仅是存在于祖先组件给后代组件提供数据,或者父组件给后代组件传递数据。

    • 兄弟组件无法通过这种方式获取数据
    • 子组件提供的数据,父组件同样无法获取
    • 如果想要数据是响应式的,要么祖先先使其变成响应式再提供,要么后代组件自行处理。(注意:无论是哪种方式,都不会改变数据提供者的数据,亲测
  • 祖先组件

<template>
  <Student></Student>
  <Person></Person>
</template>

<script>
  import Student from "@/components/Student";
  import Person from "@/components/Person";

  import {provide} from 'vue'

  export default {
    name: 'App',
    components: {
      Student,Person
    },
    setup() {
      provide('appData', '这是App组件提供的一条数据')
    }
  }
</script>

  • 孙子组件
<template>
  <h2>我是一个小学生,位于第三层</h2>
  <h4>我是小学生,这是祖先传递过来的数据: {{appData}}</h4>
  <h4>我是小学生,这是Student正常大小学生传递过来的数据: {{studentData}}</h4>
  <button @click="appData = appData += '!!'">点我修改appData</button>
</template>

<script>
  import  {ref, inject} from 'vue'
  export default {
    name: "SmallStudent",
    setup() {
      // 接收祖传递过来的数据,若要实现响应式,还得用上ref处理下
      let appData = ref(inject('appData'))
      let studentData = inject('studentData')

      return {appData, studentData}
    }
  }
</script>

响应式数据的判断(4个)

  • isRef 判断是否为一个RefImpl对象

  • isReactive 判断是否为一个由reactive()创建的对象

  • isReadOnly 判断是否为一个深只读对象

  • isProxy 判断一个对象是否为reactive() 或者 readonly() 创建的代理

新的组件

Fragment

  • 在Vue2中: 组件必须有一个根标签

  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中

  • 好处: 减少标签层级, 减小内存占用(因此Vue3的template中不需要再嵌套一个div)

Teleport

  • Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

  • 实例代码

<teleport to="移动位置">
	<div v-if="isShow" class="mask">
		<div class="dialog">
			<h3>我是一个弹窗</h3>
			<button @click="isShow = false">关闭弹窗</button>
		</div>
	</div>
</teleport>

Suspense异步组件

  • 作用: 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤
    (1)引入异步组件定义功能,使用异步组件来引入组件

import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

(2)使用Suspense包裹组件,并配置好defaultfallback

<template>
	<div class="app">
		<h3>我是App组件</h3>
		<Suspense>
			<template v-slot:default>
				<Child/>
			</template>
			<template v-slot:fallback>
				<h3>加载中.....</h3>
			</template>
		</Suspense>
	</div>
</template>

Vue2修改元素不奏效的情况,在Vue3中均已解决

通过数组下标修改元素不奏效

直接添加对象元素或删除对象元素时不奏效

需要借助Vue.set()或者 vm.$set()

posted @ 2022-09-09 18:36  CodeStars  阅读(144)  评论(0编辑  收藏  举报