Vue的学习笔记(个人学习用)

222 阅读9分钟

Vue框架的优点:

  1. 轻量级框架

  2. 双向数据绑定。也即响应式数据绑定。vue.js会自动响应数据变化。

  3. 组件化。Vue.js通过组件,把一个单页应用中的各种模块拆分到一个一个单独的组件(component)中。使用步骤:

    1. 先在父级应用中写好各种组件标签(占坑),
    2. 如果要传参数的话,在组件标签中写好要传入组件的参数(就像给函数传入参数一样,这个参数叫做组件的属性)
    3. 然后再分别写好各种组件的实现(填坑)。
  4. 视图、数据、结构分离。也就是MVVM结构。可以让数据的更改更简单。

  5. 采用虚拟DOM。

    虚拟DOM,是一个轻量级的JavaScript对象。 工作流程:

    • 根据数据生成虚拟DOM =>
    • 根据虚拟DOM生成真实DOM =>
    • 数据改变 =>
    • 生成新的虚拟DOM树 =>
    • 对比(和上一轮的虚拟DOM) =>
    • 虚拟DOM树积累一定量的改变,将改变一次性应用到真实DOM上

基本知识

基本

el和data的两种写法

 //写法1
 new Vue({
 el'#root'
   data:{
   name:'sxy'
 }
 });
 //写法2
 const new Vue({
   data(){ //ES6对象内函数的简写,省略 : function
     return{
       name:'sxy';
    }
  }
 });
 v.$mount('#root');//更灵活,比如可以异步地挂载。$mount是Vue原型对象上的方法

计算属性与监视

  • 定义:要用的属性不存在,要通过已有的属性计算得来
  • 计算属性computed中的get
  1. 当读取计算属性值时,get就会被调用,且get的返回值就是计算属性值
  2. 初次读取计算属性值时,以及计算属性值所依赖的属性值发生变化时。get会被调用

computed和watch之间的区别: 1.computed能完成的功能,watch都可以完成。 2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。

MVVM模型

MVVM

View - ViewModel - Model

DOM结构 - Vue实例 - 数据对象

DOMListeners 工具监听View中 DOM的变化,并会转给Model

Data Bindings 工具监听Model数据变化,并将其更新给View

数据代理

通过一个对象代理对另一个对象中属性的操作

 //最简单的一个数据代理
 let obj = {x:100};
 let obj2 = {y:200};
 ​
 Object.defineProperty(obj2, 'x', {
   get(){
     return obj.x;
  },
   set(value){
     obj.x = value;
  }
 })

在这里插入图片描述

Vue内部指令

v-on: 绑定事件

v-on:click="function"简写 @click="function"

在Vue中,阻止默认行为除了JS原生的e.preventDefault()方法外,还可以 @click.prevent="function"。也就是事件修饰符

事件修饰符

常用:prevent 阻止默认事件、stop 阻止时间冒泡、once 事件只出发一次

不常用:capture 使用事件的捕获模式、self 只有event.target是当前操作的元素时才触发事件、passive 事件的默认行为立即执行,无需等待事件回调执行完毕

键盘事件

 e.keyCode //每个键盘对应的编码(JS原生)
 <input type="text" placeholder="按下回车" @keyup.enter="function"//.enter 即按下回车键后执行
  1. Vue提供的常用的几个按键别名:

回车 enter

删除 delete (捕获“删除”和“退格”键)

推出 esc

空格 space

换行 tab

上 up、下 down、左 left、右 right

  1. Vue为提供别名的按键,可以使用按键原始的key值绑定,要转为kebab-case

v-model

不同输入框 v-model 不同的使用方法

 <div id="root">
   <form>
     <!-- 普通的input框输入即value -->
    账号:<input type="text" v-model="account">
    密码:<input type="password" v-model="password">
 ​
     <!-- 单选框,需要设定value -->
    性别:
    男 <input type="radio" name="sex" v-model="sex" value="male"> 
    女 <input type="radio" name="sex" v-model="sex" value="female"> 
 ​
     <!-- 勾选框(多选),hobby初始值必须配制成数组,否则勾选框会连坐 -->
    爱好:
    学习 <input type="checkbox" v-model="hobby" value="study">
    吃饭 <input type="checkbox" v-model="hobby" value="eat">
    睡觉 <input type="checkbox" v-model="hobby" value="sleep">
 ​
     <!-- 下拉框 -->
    城市:
     <select v-model="city">
       <option value="bj">北京</option>
       <option value="cd">成都</option>
       <option value="hz">杭州</option>
     </select>
 ​
     <!-- 多行输入,和单行输入一致 -->
    其他信息:
     <textarea v-model="other"></textarea>
 ​
     <!-- 虽然也是checkbox,但不需要数组,直接收true还是false就行 -->
     <input type="checkbox" v-model="agree">
    阅读并接受用户协议
   </form>
 </div>
 ​
 <script>
   new Vue({
     el:'#root',
     data:{
       account:'',
       password:'',
       sex:'',
       hobby:[],
       city:'',
       other:'',
       agree:''
    }
  })
 </script>

v-model 修饰符

  • v-model.number。收集的数据直接转成number类型,一般和输入框的原生api type="number"配合使用。后者用于筛选是否是数字
  • v-model.lazy。一般用于多行输入,默认时是实时收集的。添加修饰符后只有再失去焦点才会收集数据
  • v-model.trim。会自动删除收集数据开头和结尾的空格。

v-bind

数据的单向绑定。

v-for时的:key.

v-for

 <body>
     <div id="root">
       <ul>
         <li v-for="(student, index) in students" :key="index">
          {{student.name}} {{student.age}}
         </li>
       </ul>
     </div>
     
   </body>
   
   
   <script type="text/javascript">
     const vm new Vue({
       el:'#root',
       data:{
         students:[
          {id:'01', name:'sxy', age:'23'},
          {id:'02', name:'chr', age:'24'},
          {id:'03', name:'xys', age:'25'},
        ]
      }
    })
   </script>  

v-if v-else v-show

两者都会导致页面的重绘和重排,但v-show只是改变dom的css,而v-if控制的是添加和删除dom,所以v-if在重绘重排前还进行了添加或删除dom元素的操作。

其它

v-text

 <div id="root">
   <div>{{name}}</div> 差值语法
   <div v-text="name"></div> v-text写法
 </div>
 ​
 <script>
   new Vue({
     el:'#root',
     data:{
       name:'sxy'
    }
  })
 </script>

差值语法更灵活。v-text会替换整个标签中的内容,无法添加。但是差值语法可以。

v-html

相比于v-text,v-html支持结构的解析。即可以填写html标签。

 <div id="root">
   <div v-text="<h3>name<h3>"></div> v-html写法
 </div>

v-cloak

  1. 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删除掉v-cloak属性
  2. 配合css就可以在Vue实例还没有创建时,比如网速太慢。不让未解析的比如差值语法直接显示出来。

v-once

  1. 和v-cloak一样都是一种属性。添加v-once属性的节点在初次动态渲染之后,将会被视为静态内容。
  2. 之后数据再发生任何改变都不会造成结构的更新。常用于性能优化。

自定义指令

函数式写法和对象式写法

  <body>
    <div id="root">
      <!-- 使用自定义指令实现数字乘以10 -->
      <div v-multiple="n"></div>
      <hr>
      <!-- 使用自定义指令实现加载后自动获取输入框焦点,并且还具有v-bind的功能 -->
      <input type="text" v-fbind="n">
    </div>
  </body>
  
  <script>
    new Vue({
      el:'#root',
      data:{
        n:1,
      },
      directives:{
        //函数式写法
        //接收两个参数,分别是使用自定义指令的DOM标签和绑定对象。绑定对象里面包含value,即自定义指令的值。
        //函数会在以下两个时间点被调用。指令和元素被成功绑定时(即刚开始的时候)。模版重新解析时。
        multiple(element, binding){
          element.innerText = binding.value * 10;
        },
        //对象写法在需要更精确地卡时间节点时使用。
        //其中有三个常用的生命周期函数
        fbind:{
          //指令和元素成功绑定时(第一次)
          bind(){
          },
          //指令所在元素被插入页面时
          inserted(){
          },
          //指令所在模版被重新解析时
          update(){
          }
        }
      }
    })
  </script>

生命周期

请添加图片描述

特殊的生命周期

keep-alive

  • 会缓存不活动的组件的状态

  • 作用:避免多次重复渲染降低性能

  • 配置项

    • include:字符串或正则表达式,只有匹配名称的组件会被缓存
    • exclude:字符串或正则表达式,任何匹配的组件都不会被缓存
    • max:数字,最多可以缓存多少组件实例
  • 使用方法

    • 将router-view组件用<keep-alive></keep-alive>包裹住,在里面写配置项
    • 也可以在路由配置中为组件添加meta元信息,配置项为keepAlive: true. 然后再在<keep-alive></keep-alive>里用v-if判断

nextTick

语法:this.$nextTick(回调函数)

作用:在下一次DOM更新结束后执行其指定的回调

nextTick 是微任务还是宏任务

nextTick优先是微任务

如果当前运行环境不支持微任务的话,还是会选择宏任务的

和updated()的区别

  • 使用场景不同。有时候只需要下一次更新DOM时触发。回调函数写在updated()里会导致每次数据改变、DOM更新,回调函数都会被触发。
  • 触发顺序不同。响应式数据发生变化时,Vue会立刻将更新的全部微任务按照顺序(beforeUpdate() update() updated())插入到微任务队列中。之后Vue对虚拟DOM树进行更新。之后虚拟DOM树和真实DOM树进行对比更新。但此时浏览器不会立即渲染更新后的真实DOM树,而是会先清空微任务队列。在顺序执行微任务时,在updated()执行结束后会立即执行nextTIck(), 即便后面还有别的微任务。

组件

基本知识

实现应用中局部功能代码的资源的集合。复用及嵌套。相较于模块化,依赖关系不混乱,复用率高。

定义(创建)组件 => 注册组件 => 使用组件(写组件标签)

  • 定义组件

使用Vue.extend创建(可以省略),和创建Vue实例时几乎一样。(组件是可复用的 Vue 实例)区别是:

  1. 不写el。最终所有的组件都要经过一个vue实例的管理,由vue实例的el决定挂载在哪个容器
  2. data必须写成函数。如果写成对象形式,数据存在引用关系。写成函数,实现一个闭包。这样组件中的数据就是私有的。对象的形式只能是公有
  3. 每个组件必须有且只有一个根元素,如用一个div包裹这些子元素
  • 注册组件
  1. 局部注册。创建vue实例的时候传入components选项,在其中进行注册。k:v形式
  2. 全局注册,Vue.component() 注册,k:v形式

VueComponent

  1. 组件本质上是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
  2. 只要写了组件标签,Vue解析时会自动创建对应组件的实例对象。即自动执行 new VueComponent(options)
  3. 每次使用Vue.extend,都会生成一个新的VueComponent
  4. 组件创建时,data函数、methods函数、watch函数... 它们的this指向都是VueComponent实例对象。
  5. VueComponent.prototype.proto === Vue.prototype. 正常情况是会直接指向Object对象的,这样做是为了让组件实例可以访问到Vue原型上的属性方法

image-20220325102019104

组件间传参

props配置项

让组件接受外部传过来的数据。通常用于父向子组件通行。但也可以通过父组件给子组件传递函数类型的props实现子给父传递数据。

使用方法:在组件实例中加一个配置项props用于接收参数,然后在组件标签里用 v-bind:绑定参数。

用props配置项实现子给父组件传参方法:父组件准备一个回调函数传给子组件,子组件调用。代码演示见下方自定义事件

自定义事件

自定义事件

onon emit, 可以实现子给父通信,和兄弟组件通信。

使用方法:在父组件使用子组件时使用(v-on, @)绑定一个自定义事件。也就是给子组件的组件实例绑定自定义事件。

在子组件内触发自定义事件 this.$emit('自定义事件名', 数据)

自定义事件的回调函数在父组件中,在这个回调函数里就可以接收子组件传的数据了.

子给父传参,props和自定义事件方法代码演示

两者的相同:父组件定义一个回调函数,交给子组件。子组件调用。

两者的区别:父组件传递回调函数的方法和子组件调用的方法不同。

  • props:子组件专门使用props配置项,接收来自父组件传递的回调函数。调用时直接调用
  • 自定义事件:父组件定义的回调函数没有传给子组件,而是直接绑定给子组件作为一个自定义事件的回调。所以子组件通过$emit触发绑定在子组件实例上的自定义事件,此时回调函数触发。
父组件
<template>
  <div>
    <Child1 v-bind:getChildParams1 = "getChildParams1"/> props方法实现子父传参
    <Child2 v-on:getChildParams2="getChildParams2"/> 自定义事件方法实现子父传参(写法一)
    <Child3 ref="test"/> 自定义事件方法实现子父传参(写法二)
    
  </div>
</template>

<script>
  import Child from '...'

  export default {
  ...,
  methods:{
    getChildParams1(params){
      console.log(params);
    },
    getChildParams2(params){
      console.log(params);
    }
  },
  /* 写法二,更灵活,可以异步地绑定自定义事件 */
  mounted(){
    this.$refs.test.$on('getChildParams2', this.getChildParams2)
  },
}
</script>
子组件
<template>
  <button @click="sendParams1">给父组件传参(props方法)</button>
  <button @click="sendParams2">给父组件传参(自定义事件方法)</button>
</template>


<script>
  export default{
  ...,
  data(){
    return{
      param: '参数';
    }
  }
  props:['getChildParams'], //这里就是区别所在
  methods:{
    sendParams1(){
      this.getChildParams1(this.param)
    },
    sendParams2(){
      this.$emit('getChildParams2', this.param)
    },
  }
}
</script>

全局事件总线

$bus 全能。

使用方法:

安装全局事件总线:

new Vue({
  ...
  beforeCreated(){
  	Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
}
})

使用事件总线:和自定义事件类似

插槽

父给子组件,在子组件中使用<slot>标签接收父组件的数据

  • 默认插槽,直接在子组件<slot>标签中写入的就是默认值。父组件传值就会覆盖
  • 具名插槽,就是给<slot>标签加了name属性

其它

vuex

pubsub-js:vue 几乎不用,react 常用 全能

路由

基础知识

  1. 路由 route 是一组key(路径)-value(组件)的对应关系
  2. 多个路由由路由器 router 管理
  3. 路由存在的目的是为了实现单页面应用(single page web application)SPA。

基本使用代码演示

//新建文件专门用于保存整个应用的路由器:src/router/index.js
import VueRouter from 'vue-router'
//引入组件
import About from '...';
import Home from '...';
//创建并暴露一个路由器
export default new VueRouter({
  routes:[
    {
      path: '/about',
      component: About
    },
    {
      path:'/home',
      component: Home
    }
  ]
})
//main.js中配置
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
//引入写好的路由器
import router from '...'

Vue.use(VueRouter)

new Vue({
  el: '#app',
  router:router
})
<router-link to="/about">A</router-link>
<router-link to="/home">H</router-link>

<!-- 指定组件的呈现位置 -->
<router-view></router-view>

其它注意事项

路由组件和一般组件

  • 使用方法不同,一般组件直接用组件标签放置。路由组件通过指定标签位置
  • 一般在项目中,components文件夹放置一般组件,pages文件夹放置路由组件

路由组件的生命周期

被切换走的路由组件会被销毁,在下次使用时会重新创建、挂载。keep-alive可以解决这个问题造成的性能损耗。

路由组件的route 和 router 属性

$route 属性里面存储自己的路由信息

$router 属性存储整个路由器的信息

每个路由组件的route属性都是独有的不相同。但router属性是被所有路由组件公用的。

嵌套路由

也叫多级路由。一级路由、二级路由、三级路由.....

export default new VueRouter({
  routes:[
    {
      path: '/about',
      component: About
    },
    {
      path:'/home',
      component: Home,
      //二级 路由
      children:[
        {
          path:'news',
          component:News,
        },
        {
          path:'message',
          component:Message,
        }
      ]
    }
  ]
})
<!-- 在Home.vue中 -->
<router-link to="/home/news">N</router-link>
<router-link to="/home/message">M</router-link>

<!-- 指定组件的呈现位置 -->
<router-view></router-view>

路由传参

query 参数:/home?k=v&kv=,不需要占位。 通过问号分隔,多组参数通过&分隔。 此时路由组件的$route属性中的query参数就有值了。

<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`"></router-link>

<!-- 写法二 对象式写法 -->
<router-link :to="{
  path:'...',
  query:{
    id:m.id,
    title:m.title
  }
}"></router-link>

params 参数:属于路径的一部分,在配置路由的时候需要占位.

<router-link :to="`/home/001/001"></router-link>
export default new VueRouter({
  routes:[
    {
      //这里path需要改。即添加占位
      path:'/home/:id/:title',
      component: Home,
    }
  ]
})

路由导航

声明式路由导航

就是上面演示的<router-link></router-link>标签

router-link的replace属性

控制路由跳转时操作浏览器历史记录的模式。push模式是追加记录,replace是替换当前记录。

默认是push模式,历史记录依次回退。<router-link :replace="true">开启replace模式,历史记录不能回退

编程式路由导航

声明式导航,<router-link></router-link>标签实际渲染时会转换成<a>标签。有时需要用别的标签实现路由跳转。以及实现异步。

<button @click="pushTest(m)">编程式路由导航(push)</button>
<button @click="replaceTest(m)">编程式路由导航(replace)</button>

<script>
  export default {
    ...,
    methods:{
      pushTest(m){
        this.$router.push({
          path:"...",
          query:{
            id:m.id,
            title:m.title
          }
        })
      },
      replaceTest(m){
        this.$router.replace({
          path:"...",
          query:{
            id:m.id,
            title:m.title
          }
        })
      }
    }
  }
</script>

路由独有的两个生命周期

keep-alive

  • 会缓存不活动的组件的状态

  • 作用:避免多次重复渲染降低性能

  • 配置项

    • include:字符串或正则表达式,只有匹配名称的组件会被缓存
    • exclude:字符串或正则表达式,任何匹配的组件都不会被缓存
    • max:数字,最多可以缓存多少组件实例
  • 使用方法

    • 将router-view组件用<keep-alive></keep-alive>包裹住,在里面写配置项
    • 也可以在路由配置中为组件添加meta元信息,配置项为keepAlive: true. 然后再在<keep-alive></keep-alive>里用v-if判断

生命周期函数(activated)

使用keep-alive缓存组件后,mounted 和 destroyed 生命周期函数失去了原本的作用(因为组件不会被销毁、重新挂载了)。

  • activeted(). 组件被激活了。
  • deactivated(). 组件失活了。

路由命名

什么时候需要为路由配置name属性(命名)

路由重定向

路由守卫

全局前置路由首位

  • 触发

    • 初始化时(不需要进入)会被调用
    • 每次路由切换前会调用
  • 参数

    • to 原本要去的地方
    • from 从哪个组件来
  • 判断是否放行

    • 根据to, from的path属性判断是否放行
    • 大量路由组件需要进行守卫时,可以路由信息中配置元信息meta。判断时直接判断是否具有meta属性
const router = new VueRouter({
  routes:[
    {
      ...
      meta:{guard: true}
    }
  ]
})

//配置全局前置路由守卫
router.beforeEach((to, from, next)=>{
  if(to.path == '/home/news' || to.path == '/home/message'){
    //if(to.meta.guard) 元信息判断法
    if(localStorage.getItem('id') == '管理员'){
      //如果身份验证通过,放行
      next();
    }else{
      alert('...')
    }
  }else{
    //其它路由不需要守卫
    next();
  }
})

全局后置路由守卫

  • 触发

    • 初始化时(不需要进入)会被调用
    • 每次路由切换后会调用
  • 参数

    • 没有next
router.afterEach((to, from)=>{
  document.title = to.meta.title || '默认名称';
})

独享路由守卫

beforeEnter()配置在路由信息中,其它逻辑和全局前置路由守卫一致

const router = new VueRouter({
  routes:[
    {
      ...
      meta:{guard: true},
      beforeEnter:((to, from, next)=>{
        if(to.path == '/home/news' || to.path == '/home/message'){
          if(localStorage.getItem('id') == '管理员'){
            //如果身份验证通过,放行
            next();
          }else{
            alert('...')
          }
        }else{
          //其它路由不需要守卫
          next();
        }
      })
    }
  ]
})

组件路由守卫

其它

Vuex数据管理库

vuex 状态管理库

vuex 是官方提供的一个插件,集中式管理项目中组件共用的数据。如果是小项目,不需要使用 vuex。如果是大项目,组件多、数据多,数据维护难度大,使用 vuex。

  • state:存储数据的地方。初始化
  • mutations:修改数据的唯一方法
  • actions:可以异步地执行mutation。(AJAX请求)
  • getters:计算属性
  • modules:将store分割成小的modules

使用方法:在vue的mounted函数中,使用this.$store.dispatch派发Vuex的actions。actions中发送AJAX请求,异步地调用mutation。mutation用服务器返回的数据修改state中的数据。然后在组件computed计算属性中捞取数据

基本使用方法:

import Vue from "vue";
import Vuex from "vuex";

//需要使用插件一次
Vue.use(Vuex);

const state = {
  count: 1
};
const mutations = {
  ADD(state){
    state.count++;
  }
};
const actions = {
  add({commit}){
    commit("ADD");
  }
};
const getters = {};
//Vuex是一个对象。它包含一个Store方法,也是一个构造函数,用于初始化Vuex仓库。
//对外暴露Store类的一个实例
export default new Vuex.Store({
  state,
  mutations,
  actions,
  getters
});
//在组件中
import {mapState} from 'vuex';
...
computed:{
...mapState(['count'])
},
methods:{
  add(){
    //派发action
    this.$store.dispatch('add');
  },
},

Vuex和Pinia的区别

  • pinia 没有 mutations,actions的使用不同(pinia支持同步异步),getters的使用是一致的

  • pinia 没有总出口全是模块化,需要定义模块名称,当多个模块需要协作的时候需要引入多个模块,vuex是有总出口的,在使用模块化的时候不需要引入多个模块

  • pinia 在修改状态的时候不需要通过其他api,vuex需要通过commit,dispatch去修改


\