VUE2(穿插vue3)

VUE2是采用传统的html文件内引入js文件然后常见vue实例来书写代码,VUE3开始就需要使用vite或者其他框架来创建VUE项目,项目的书写方式也变成一个个的组件了

VUE初始

  1. 引入VUE
  2. 创建VUE实例new Vue({设置})
  3. 挂载使用VUE解析的DOM,两种方式
    1. 在设置参数对象中添加el:"选择器"键值对
    2. 使用.$mount("选择器")来挂载
  4. 使用data:{ 变量名1: 数据 , 变量名2: 数据 }来承载数据
  5. 在需要渲染的DOM元素内使用双括号{{变量名}}来直接将数据渲染到页面,也可以在双括号内执行逻辑语句

因此VUE不需要获取元素和自己写渲染语句,VUE重要的是页面结构,一定要注意结构,否则渲染出来的效果会出错

函数

在参数设置里写methods:{函数1,函数2}字段来承载函数,函数内使用data内变量时需要使用this指向

函数在使用时如果不需要传参可以省略括号

函数可以在所有可以书写js语句的地方使用,比如{{}}、比如指令内

VUE指令

一般都以v-指令名="js语句/变量/函数"的格式展示,直接在标签行内书写

v-for循环

格式为v-for="变量名 in 数组",或者v-for="(变量名,索引) in 数组"

会自动遍历数组并创建标签渲染数组内容

可以多层嵌套

也可以遍历对象,格式v-for="(属性值,键值) in 对象"

也可以遍历数字,格式v-for="(变量名,索引) in 100",表示打印从1到100

也可以遍历字符串,格式v-for="变量名 in 字符串"

JavaScript判断数据类型最准确的是Object.protopyte.toString.call(数据)

v-if、v-else判断

v-if是根据条件来判断元素的显示与否

格式为v-if="逻辑语句"v-else

v-else一定要和v-if或者v-else-if相邻才能够使用,中间不能出现别的元素

如果想要中间插入别的元素,一般可以用两个v-if来替代v-ifv-else

直接删除DOM,检查元素中不可见

v-if和v-for不能在一个标签上同时使用,因为优先级问题,这也是面试点

v-show显示隐藏

效果和v-if相似,但是实现原理是通过display属性来操作元素,因此他不会从结构中消失,在检查元素中仍可见

格式为v-show="逻辑语句"

v-show和v-if的使用场景不同,前者适用于频繁使用且数据不需要保证私密性的时候,因为不操作DOM性能更好,后者更加安全确保不会泄露数据

除非明确使用v-show,否则更推荐使用v-if,因为更安全

v-bind动态属性

属性分为静态属性和动态属性,我们常写的属性多是静态属性,也就是属性值以字符串表示,不能发生变动

v-bind可以将静态属性变成动态属性,此时我们就可以在属性上书写逻辑语句来实现动态属性

格式为v-bind:属性名="js或逻辑语句",也可以简写成:属性名="js或逻辑语句",此时我们需要注意如果要写字符串一定加上单引号,因为默认写在里面的都是变量

:class

:class有三种格式

适用于一个类名的判断class="isTrue?'class1':'class2'"

适用于多个类名的判断class = "{class1:boolean,class2:boolean,class3:boolean}"

适用于需要同时显示多个类名但不需要判断,使用数组来装载类名:class = '['class1','class2','class3']',使用频率较低

:style

可以使用对象来承载不同的多个属性,属性名是键,属性值是值

:style='{color:isTrue?"red":"",height:"100px"}'

如果属性过多也可以使用变量或者函数,只要表示的值是对象就可以

v-on事件绑定

格式为v-on:事件名="语句",简写为@事件名="语句"

v-model双向数据绑定

用于表单元素,可以将表单元素和变量绑定,任何一方改变都会影响另一方

格式为v-model="变量名"

v-html、v-text

v-html可以解析标签,主要用来展示富文本,比如评论区、邮件等

v-text不会解析标签,他的简写就是{{}}

v-cloak

在网络不好时{{}}会有一小段时间无法解析直接将模板输出到页面,而v-text因为书写在标签内因此不会出现问题

为了解决这个问题,我们可以现将元素隐藏,当资源加载出来再让其显示

v-cloak的功能就是让隐藏元素出现

他一般配合CSS属性选择器使用,因为除了VUE的指令,他在HTML中还表示了自定义属性

[v-cloak] {
    display:none
}

v-once

添加这个指令的标签只渲染一次,不再更新数据显示

v-pre

添加这个指令的标签VUE不进行解析,对于一些HTML静态资源节省性能

自定义指令

自定义指令

Vue.directive('hide', {    
    bind(el, value, vnode) {
        el.style.display = value.value ? 'none' : 'block'
    }
});
  • hide是自定义指令名
  • el是获取到的标签
  • value是指令获取到的值

bind函数里可以正常书写js语句来实现各种指令功能

组件

组件就是对HTML结构、CSS样式和数据逻辑的封装

组件创建

全局组件

全局组件默认注册在根实例中,页面中所有组件内都可以使用

创建方式为Vue.component(组件名,{配置}),或者Vue.component(组件名,局部组件名)

组件名和变量名一样,但注意驼峰命名在创建标签时两个单词使用-连接而非使用驼峰,因为HTML5不区分大小写

组件的使用和标签一样<组件名></组件名>

  Vue.component('comment', {
    data() {
      return {
        list: ['html', 'css', 'js', 'java']
      }
    },
    methods: {
      del(i) {
        this.list.splice(i, 1)
      }
    },
    template: '#commentList',
    components:{
    }
})
  <template id="commentList">
    <ul>
      <li v-for="(item, index) in list">
        <span>{{item}}</span>
        <span class="del" @click="del(index)">删除</span>
      </li>
    </ul>
  </template>
  • template,表示组件模板,可以写成字符串格式,也可以单独定义一个template标签来包裹模板,然后绑定标签的id,后者更方便一些,因为有代码提示
  • data,组件的数据承载,函数类型,返回一个对象,对象内是承载数据
  • methods,和Vue实例一样
  • components,当前组件内的局部组件注册的地方

局部组件

访问的时候需要注册,在哪个组件内使用就在哪个组件内注册,无法跨组件使用

局部组件格式为

const 组件名 = {
    template: "",
    data(){
        return {
        }
    },
    methods:{
    },
}

使用的时候需要在父组件内的components字段内注册,调用方法和全局组件一样

在哪个组件内使用就在哪个组件内注册,无法跨组件,也无法被子组件继承

组件嵌套

多个组件嵌套,将子组件在父组件的模板里调用即可

  <template id="父组件id">
    <div>
        <子组件1></子组件1>
        <子组件2></子组件2>
      </div>
  </template>

需要注意一个template标签只能包裹一个div,不能并排出现两个div,因此并行多个div标签可以使用另一个div标签进行包裹

如果是局部组件需要先注册才能使用

组件通信—父传子

组件作为模板并非一成不变,内容数据是有可能发生变化的,因此组件在使用的时候需要传入参数来实现复用

在组件标签上直接书写变量="内容",然后在组件的props:['变量1','变量2']字段中添加变量,之后就可以像data中的数据一样使用了

props的格式也可以写成对象的格式来声明类型和默认值

props: {
    til: {
      type: String, // 接收到的数据类型
      default: "按钮",  // 默认值
    },
  },

数据作用域

每一个组件它的数据方法都是私有的 ,没有继承关系,想要使用就只能通过组件通信

面试题

  • 组件内的data为什么是个函数

    因为函数有私有作用域,在组件复用过程中确保每个组件的data都是私有的,保证data数据不会污染,根实例之所以可以用对象是因为根实例不会复用,没有数据污染的问题

组件通信—子传父

子组件的数据传到父组件也需要组件通讯,使用的方法是自定义事件

  1. 子组件方法内书写this.$emit('事件名',数据)创建一个自定义事件
  2. 子组件标签上添加自定义事件并绑定一个父组件中的方法,方法不能加括号
  3. 父组件methods字段里书写绑定的方法来接收参数,这样就实现了子组件的数据传输到父组件
  <div id="app">
    <home></home>
  </div>

  <template id="home">   // 父组件
    <div class="home">
      <ipt @add-list="addList"></ipt> // 2. 子组件标签调用,添加了自定义事件绑定add方法
      <ul>
        <li v-for="item in list">{{item}}</li>
      </ul>
    </div>
  </template>

  <template id="ipt"> // 子组件
    <div>
      <input type="text" v-model="value">
      <button @click="submit">提交</button> // 0.点击提交调用子组件的submit方法
    </div>
  </template>
  Vue.component('home', { // 父组件
    template: '#home',
    data() {
      return {
        list: ['html', 'css', 'js']
      }
    },
    methods: {
      addList(value) { // 3. 子组件标签的自定义事件绑定的add方法,参数是从子组件的自定义事件中传过来的
        this.list.push(value)
      }
    }
  })

  Vue.component('ipt', { // 子组件
    template: '#ipt',
    data() {
      return {
        value: ''
      }
    },
    methods: {
      submit() {
        this.$emit('add-list', this.value) // 1. 创建自定义事件,将子组件数据传出去
      },
    },
  })

注意,组件标签只有自定义事件,没有默认事件,也就是无法给组件标签添加@click指令

虚拟DOM

虚拟DOM是对真实DOM的抽象化,在VUE中是将真实DOM使用对象的形式保存起来,其中各个属性代表着标签的不同属性

虚拟DOM最大的作用就是减少DOM的操作次数来提升性能

在原生JS当中连续更新十次DOM那么浏览器就会从构建DOM树开始执行十遍流程,而VUE会将十次更新保存成一个新的虚拟DOM,将两者使用diff算法进行比较,略去一样的地方后将有变化的地方应用到页面

Key值的作用

如果使用v-for必须同时使用key值,且key值必须是同级唯一的

因为这样可以节省性能,VUE可以根据key值来判断元素是否改变进而影响渲染效率

比如向数组前插入元素,如果不设置key值,则默认使用下标比较,VUE会判断之前的下标0的元素和新的下标0的元素不一样就会重新渲染,以此类推所有的元素都重新渲染了

而如果设置了唯一的不会变的key值,VUE在判断时会认为除了新插入元素其他元素没有发生改变就只会更新渲染插入的元素

生命周期

生命周期分四个阶段,共八个周期,每个周期都存在一个周期函数也被称为钩子函数

  • 创建
    • 创建前,beforeCreate(),此时组件还未创建,数据和方法也不存在,无法对组件进行操作,但可以使用浏览器的方法
    • 创建后,created(),此时组件创建完毕,数据已经可以被获取到,此阶段可以进行ajax请求和组件操作
  • 挂载
    • 挂载前,beforeMount(),此时开始根据虚拟DOM创建真实DOM,但还未挂载到组件上,页面也不显示
    • 挂载后,mounted(),此时真实DOM已经挂载到组件上,开始渲染到页面,此阶段也可以进行ajax请求
  • 更新,默认不执行,只有内容更发生才会进入更新阶段
    • 更新前,beforeUpdate(),此时使用diff算法进行比对来更改元素,页面视图还未重新渲染,在此周期再次更改数据不会再次进入更新阶段
    • 更新后,updated(),此时页面视图重新渲染,在此周期更改数据会再次进入更新阶段
  • 销毁,默认不执行,当销毁组件时进入销毁阶段
    • 销毁前,beforeDestroy,组件销毁前,组件的数据和方法仍可以被使用,此时可以解除定时器等异步操作
    • 销毁后,destroyed(),组件销毁后,组件的数据和方法无法访问,此时也可以解除定时器等异步操作,销毁的只是组件,对DOM无影响,但是配合v-if会清除DOM

数据请求在created和mouted的区别

没区别,两者实际上都会导致页面渲染两次数据,因为ajax是异步操作,不影响页面渲染

生命周期在VUE3中有变化

销毁阶段的钩子函数变成beforeUnmount()unmounted()

vite创建vue3项目

目前只是创建VUE3项目,但是代码知识点仍是VUE2为主

准备工作

使用nvm安装nodenode自带npm(包管理工具)

npm命令

cmd命令行输入

  • 安装插件包:npm i / install 包名 [-下载修饰符]
    • -g,全局下载
    • -S或者--save,存到依赖路径,也就是上线后仍需要使用
    • -D或者--save-dev,存到开发依赖,上线后不需要使用
  • 卸载:npm uninstall jquery
  • 启动脚本:npm run 脚本命令
  • 创建package.json:npm init -y,-y的作用是一路选择yes

package.json

我们在上传项目的时候,插件的体积往往过大,于是我们在上传时舍去插件,在别人使用我们的项目的时候去重新下载

而package.json的作用就是标注需要下载哪些插件

创建项目

  • 在需要创建项目的目录下打开命令行,输入npm init vite vue-study,选择VUE—>JavaScript
  • 进入项目,输入npm i,根据package.json安装依赖
  • 使用npm run dev启动服务器,默认端口5173

开发注意

这种方式开发我们就需要频繁的导入导出文件了,常使用的是esModule规范

导出:export default 项目

导入:import 变量名 from '文件路径'

路由

路由基本配置

首先需要下载依赖npm i vue-router -S

在项目src文件夹下创建router文件夹,在其中创建index.js作为路由配置

// 配置路由的代码
import { createRouter, createWebHashHistory } from "vue-router"
// 导入路由页面
import Home from "../views/home.vue"
import About from "../views/about.vue"
import Center from "../views/center.vue"

// 配置路由地址,绑定页面组件
const routes = [
    {
        path: "/",
        component: Home,
        name: "Home"
    },
    {
        path: "/center",
        component: Center,
        name: "Center"
    },
    {
        path: "/about",
        component: About,
        name: "About"
    }
]
// 创建路由对象
const router = createRouter({
    // 路由配置项
    history: createWebHashHistory(),  //路由模式
    routes: routes    //路由的主要配置
})
// 导出路由对象
export default router

在main.js中,我们需要导入路由对象,并且传给vue对象

import router from './router'      // /router      /router/index.js

// new Vue({}).$mount("#app")
const app = createApp(App)
app.use(router)
app.mount('#app')

之后在页面中可以使用<router-link to="路由路径"></router-link>跳转配合<router-view></router-view>显示页面来做到页面的切换显示

路由动态传参

  • 首先要在路由配置中将需要传参的路由路径上加上:参数占位,例如path:"/detail/:id"

  • 然后在router-link标签中将静态to属性变成动态to属性,之后将参数拼接到路径上就可以了<router-link :to='"/detail/"+参数变量'></router-link>

  • 在需要接收参数的页面使用路由信息this.$route.params来接受参数

    注意:一定是route

路由嵌套

多级路由的书写方法是

  • 在路由配置文件导入二级路由页面
  • 然后在路由配置中加入字段children:[]
  • 在数组中书写二级路由配置,和一级路由一样的格式,有pathcomponent
  • 在一级路由页面内添加<router-link></router-link><router-view></router-view>

如果想要三级路由是一样的格式

import Home from '../views/home.vue';
import About from '../views/about.vue';
import Link from '../views/link.vue';
import Detail from '../views/detail.vue';
import About1 from '../views/about/about1.vue';
import About2 from '../views/about/about2.vue';
import About3 from '../views/about/about3.vue';


export const routes = [{
  path: '/',
  name: 'Home',
  component: Home,
},
{
  path: '/about',
  name: 'About',
  component: About,
  redirect: '/about/about1', // 默认跳转路由
  children: [
    {
      path: 'about1',
      name: '关于1',
      component: About1,
    },
    {
      path: 'about2',
      name: '关于2',
      component: About2,
    },
    {
      path: 'about3',
      name: '关于3',
      component: About3,
    },
  ]
},
{
  path: '/link',
  name: 'Link',
  component: Link,
},
{
  path: '/detail/:name',
  name: 'Detail',
  component: Detail,
},
]

路由对象和路由信息

router-link常用属性

  • to,跳转地址,可以变成动态属性:to="{path:'/地址',name:''}":to="/地址",也可以通过路由名字来跳转:to="{name:'名字'}",如果参数过多可以使用query传参的方式<route-link :to='{path:"/地址" , query:{key:value}}'></route-link>value最好是基本数据类型
  • activeClass="",点击时设置的样式名,默认为router-link-active
  • replacepush,两种模式,前者跳转不产生后退记录,后者产生

this.$route

路由信息,包含路由的很多属性

  • params,动态路由传参的对象
  • path,路由地址
  • query,另外一种传参的对象
  • name,路由的名字
  • meta,路由自定义对象,其中可以存放自定义属性
  • matched,做面包屑的,包含所有等级路由对象

this.$router

路由对象,可以使用路由的方法

  • router.push(),路由跳转
  • router.replace(),和push一样的功能,但是不产生后退记录
  • router.go(),跳转,可以设置负数来后退

编程方式路由

有时候我们希望延时跳转,就不能使用router-link标签来跳转

此时在方法里可以使用this.$router来调用路由方法的push、replace或者go实现跳转

例如router.push('/about/'+this.id)router.go({path:"/about',query:{key:value}})

命名视图

router-view标签被称为视图

在一个页面实际上可以有多个router-view标签,而想要区分不同的视图就需要起名字

  • 在路由配置中需要更改components组件的写法变成对象格式

    • {
           path: "/",
           components: {
               default: Home,  //默认的 router-view
               aside: Aside1 //      router-view name='aside'
           },
           name: "首页",
      },
      
  • 在书写视图标签时需要加上name属性来匹配对应的组件

路由懒加载

为了用户体验,很多组件没有必要在首屏就全部加载出来,会导致加载时间过长,这时候就需要懒加载

懒加载书写格式为

const Api = () => import("../views/Api.vue")

导入组件的时候将导入格式书写成上述格式,只有在调用组件的时候才会真正加载组件

除了懒加载还有其他方式来提高用户体验

  • 引导页
  • 骨架屏(美团、饿了吗之类的app打开时灰色的加载块)

路由元信息

this.$route获取路由信息是无法获取自定义属性的,但我们可以将自定义属性添加到meta对象中,此时就可以通过路由信息获取到了

  • 在路由配置文件中,在需要设置自定义属性的路由配置中加入meta:{自定义属性}字段来设置自定义属性

路由守卫(路由的钩子函数)

路由的生命周期有六个,分三种

全局

  • router.beforeEach()全局前置守卫,路由切换但未渲染页面时触发

    • 较为常用,可以用来判断登录状态

    • 三个参数,

      • to:目标路由
      • from:源路由
      • next:渲染函数,必须调用否则页面不渲染
    • router.beforeEach((to, from, next) => {
          // 修改title
          document.title = to.name
          // 每次路由切换的时候都会触发
          // 判断登录才能访问  每次访问页面前都判断是否登录了,如果登录则正常访问,否则跳转到登录页面
          // 获取登录状态
          let isLogin = localStorage.getItem('isLogin')
          console.log(to, 'to')
          //  /login ---> /login ---> /login
          if (isLogin || to.name == '登录') {
              //登录
              next()
          } else {
              // 重定向到登录页面
              next({ path: '/login' })
          }
      })
      
  • router.afterEach(),全局后置守卫,路由跳转后页面渲染完触发

    • 不常用,一般配合全局前置使用,做加载条

    • 两个参数,没有next函数

    • import nprogress from 'nprogress'
      import 'nprogress/nprogress.css'
      router.beforeEach((to, from, next) => {
        nprogress.start() // 开启进度条
        next()
      })
      router.afterEach((to, from) => {
          setTimeout(() => {
               //关闭进度条
              nprogress.done() // 进度完成
          }, 3000)
      })
      

路由独享

在路由配置中添加beforeEnter:(to,from,next)=>{}键值对,不常用

{
    path: "/about",
    component: About,
    name: "关于",
    beforeEnter: (to, from, next) => {
        // ...
        console.log("独享 about")
        next()
    }
},

组件内部

和生命周期同级,不常用

beforeRouteEnter(to, from, next) {
  console.log("beforeRouteEnter");
  next();
},
beforeRouteLeave(to, from, next) {
  console.log("beforeRouteLeave");
  next();
},
  // 路由更新的时候执行,例如参数更新的时候会监听执行 
beforeRouteUpdate(to, from, next) {
  console.log("to, from, next");
  next();
},

路由模式

hash模式

  • 原理: window.onhashchang事件 监听hash改变出发
  • 路径样式 #/center ,有#号
  • 打包上线可以直接使用,没有404问题
  • http://127.0.0.1:8089/#/api ,hash后面的不作为访问路径,因此实际上访问路径是#前的http://127.0.0.1:8089
  • 锚点链接可能出问题

history模式

  • 原理 : 利用h5新增historyAPI的方法。pushStatereplaceState
  • 路径样式,/center ,没有#
  • 打包上线需要服务器配置资源路径,否则刷新出现404页面

出现404问题的原因

vue是单页面应用,在打包后我们会发现生成的页面实际上只有一个index.html,然而我们在点击路由跳转的时候地址栏是会发生变化的,此时如果我们刷新页面会去向服务器请求当前路径的静态资源,可实际上并没有这个页面于是就会404

hash之所以没有这个问题是因为hash的地址中#后面的部分不参与请求,所以请求地址只有前面的服务器地址加端口,而默认会跳转到index,因此不会出现404