Vue2阶段性总结 - P2

首页 / 技术积累 / 正文

这次就总结完关于Vue2的全部基础知识点了,可能还会有,但那也是后面做项目的补充了吧,知识点都不难,难的可能就是应用层面上了,做了几个小项目才知道很多东西都和想象的不太一样,一个在我们看来没必要封装的东西,在项目中就都会封装,可能这就是大局观吧,为了后续扩展功能打基础。

组件扩展 - 插槽

有些时候,为了让组件能够有更高的复用性,通常会使用插槽来让用户自定义内容,其实这个功能在原生的时候就有类似的,比如div内部的内容就是用户自定义的。

具名插槽

定义: 就是给slot加了个名字

<div class="header">
  <slot name="header"></slot>
</div>
<div class="content">
  <slot>这是后备内容</slot>
</div>
<div class="footer">
  <slot name="footer"></slot>
</div>

使用: v-slot:可以简写成#

<my-dialog>
  <template v-slot:header>
    <h3>这是大标题</h3>
  </template>

  <template v-slot:default>
    <p>这是内容</p>
  </template>

  <template v-slot:footer>
    <button>确认</button>
    <button>取消</button>
  </template>
</my-dialog>

其实默认插槽也是具名插槽,只不过默认不写系统内部会帮我们添加上name="default",同时也要注意,是所有没有名字的插槽都会分配给默认插槽。

作用域插槽

在定义插槽时,我们可以给插槽传入数据,供外部使用。

// 定义赋值
<slot name="bottom" :yes="yes" :no="no" money="100"></slot>

// 使用
<template #bottom="obj">
  <button>{{ obj.yes }}</button>
  <button>{{ obj.no }}</button>
  <button>{{ obj.money }}</button>
</template>

// 解构赋值简化
<template #bottom="{ yes, no, money }">
  <button>{{ yes }}</button>
  <button>{{ no }}</button>
  <button>{{ money }}</button>
</template>

ref的使用

在使用别人的组件时,有些时候我们希望访问的到其内部的实例对象,这时候就需要ref了,在需要访问的组件/标签上添加一个ref属性,在js中通过$refs.属性值即可获取到这个组件的实例对象了。

import Jack from './jack.vue'
export default {
  methods: {
    fn () {
      console.log(this.$refs.box)
      console.log(this.$refs.jack)
      this.$refs.jack.sayHi()
    }
  },
  components: {
    Jack
  }
}

$nextTick

穿插个小知识点,有这么一个需求,如果使用v-if控制一个输入框的留存,我希望在输入框显示时立刻聚焦怎么办,你可能会说直接调用.focus() 方法不就好了?但是事实是如果直接调用会报错,这是因为vue为了渲染效率是异步更新dom的,也就是说你dom都没渲染就调用了.focus函数。

<template>
  <div>
    <!-- 需求: 点击按钮, 切换显示输入框 -->
    <input ref="inp" type="text" v-if="isShowInput">
    <button @click="fn" v-else>点此搜索</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      isShowInput: false
    }
  },
  methods: {
    fn () {
      this.isShowInput = true
      this.$nextTick(() => {
        this.$refs.inp.focus()
      })
    }
  }
}
</script>

组件的 $nextTick(callback) 方法,会把 callback 回调推迟到下一个 DOM 更新周期之后执行,通俗的理解是:等组件的DOM 刷新之后,再执行 callback 回调函数。从而能保证 callback 函数可以操作到最新的 DOM 元素。

自定义指令(补充)

其实我们都知道,v-if那些都是vue内部给我们提供的指令,然鹅,在特定情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义一些方法去完成,Vue也支持我们自定义一些指令。

以下只列出了全局注册自定义指令的方式,局部使用同理。

Vue.directive('指令名',{
   bind:  // 绑定时,类似于beforeMount,指令绑定于相应dom时执行,这时还没有完成渲染
     
   inserted:  // 指令所在dom添加到父节点时执行(类似于mounted,渲染时)
   
   update:  // 更新时,指令所在组件有更新时执行,并不保证指令所在dom更新完成
   
   componentUpdated:  // 更新完成时,指令所在组件包含子组件都更新时执行,类似于updated
   
   unbind:  // 销毁前,类似于destroyed
   
   bind(dom,obj,vnode){
     dom:  // 指令所在dom
     obj:  // 属性,修饰符,指令名,值
     vnode:  // 节点信息 *
        context:  // 指令所在组件的实例对象(指令所在组件的this)
   }
})

生命周期

生命周期(Life Cycle)是指一个组件从创建-> 运行 -> 销毁的整个阶段,强调的是一个时间段,在每一个时间点Vue都会给出一个钩子函数,它会自动触发无需调用(也不能调用),这也意味着它不会因为代码是异步或同步而停下脚步。

1.png

笔记如下

    beforeCreate: 创建前,实例化还没有完成,还不能访问data与methods
    
    created:创建后,实例化已完成,可以访问data与methods, vue内部的dom还没有渲染,常用于进入页面接口请求
    
    beforeMount:渲染前,读取了template需要渲染的部分,但是还没有完成渲染,还是不能访问vue渲染后的dom
    
    mounted:渲染后,vue内的dom已渲染完成,可以访问vue渲染后的dom,进入页面需要有dom操作就在这里进行

    beforeUpdate:更新前,vue内部使用的相关数据已修改,但还没有完成相应数据的渲染
    
    updated:更新后,vue内部使用的相关数据已修改,且完成相应数据的渲染

    beforeDestroy:销毁前,还没有销毁,所以什么都可以访问,常用于做一些善后工作

    destroyed:销毁后,销毁实际就是中断渲染,这时候还是可以访问data与methods,只是不能访问vue渲染后的dom,它也可以做一些善后工作

注意更新期是有条件的,因为性能问题并不会无脑触发,是需要vue内部(html)使用的相关数据已修改,它才会执行该时期。

vuex(全局状态管理器)

官方说法可能有点难理解,其实这里状态指的就是数据,我们都知道父子传值真的非常的繁琐,特别是如果一个页面很大的时候就更不好维护了,这时候vuex的作用就体现出来了,他就能实现全局数据共享。

vuex.png

可以理解为就是找了个代理,需要/存储数据时都找它,下面只介绍module的使用,因为后期当项目变得很大时,vuex就会显得很臃肿,而module其实就是对state、mutation、action、getter属性进行模块化的封装,使项目更易维护。


// vuex主文件
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'

/* vuex持久化插件 */
import persistedstate from 'vuex-persistedstate'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    user
  },
  plugins: [
    persistedstate({
      paths: ['user.token']
    })
  ]
})

// user模块下的vuex
import { sysLogin } from '@/api/user'
export default {
  namespaced: true,
  state: {
    token: ''
  },
  mutations: {
    setToken(state, value) {
      state.token = value
    }
  },
  actions: {
    async login({ commit }, value) {
      const res = await sysLogin(value)
      commit('setToken', res.data)
    }
  }
}

vuex-persistedstate 数据持久化

我们都知道在vuex中的数据一般都是刷新浏览器就恢复初始值了,没办法持久化,但是我们同样可以封装localstorage的方式进行持久化,但每次都那样调方法也怪麻烦的,于是我们可以借助一个插件帮我们进行持久化操作,那就是vuex-persistedstate。

配置如上面代码,plugins即插件的意思,vuex的插件都配置在此处,不过这都不是需要关注的重点,重点是persistedstate的配置有什么,其实除了上面使用的paths之外,还有很多,下面只列出3个常用的配置项供大家参考。

(我也只会使用这三个,其他太深奥了ԅ(¯﹃¯ԅ))

namespaced 命名空间

namespaced这个有必要说一说,默认情况下,模块内部的 action,Getter和 mutation 仍然是注册在全局命名空间的,即namespaced的值默认为false,也就是说,当你注册了两个一样的方法(action/mutation)或者计算属性时,就会发生一个有趣的现象,方法不会报错,但会同时调用,计算属性会直接报错!

所以,命名空间这个概念就显得很有必要了,当namespaced的值为true时,调用方法或者计算属性时就需要加入'模块名/方法名',这样就不会有命名冲突的问题了。

vue-router(路由)

Vue全家桶其一,这就得扯一扯单页面程序和多页面程序了,其实我们都知道传统的多页面每一次跳转返回的都是一个全新的页面,但Vue不太一样,他的跳转是改变组件的显示,也就是只有一个页面。

好处: 减少了请求体积,加快页面响应速度,降低了对服务器的压力,更好的用户体验。

缺点: 不利于SEO搜索引擎优化,虽然有解决办法,但目前还没学,23333。

其实自己也可以通过封装路由的方法去写一个路由,无非就是改浏览器的hash值,但是过程太麻烦,而vue-router就是Vue官方提供的一个路由解决方案,是一个插件,所以需要下载导入使用,不过一般创建项目时就可以配置。

简单配置如下:

// 4. 创建一个路由对象
const router = new VueRouter({
  // 路由的规则
  // route: 一条路由规则
  routes: [
    {
      path: '/find',
      component: Find,
    },
    {
      path: '/my',
      component: My,
    },
    {
      path: '/friend',
      component: Friend,
    },
  ],
})

多重嵌套路由

其实也就是路由的规则多了一个children属性。

{
  path:'/layout',
  ...
  children:[
    {
      path:'/layout/home', // 你也可以直接写/home,因为是相对于父级的
      component:home组件
    }
    // 当访问/layout/home时就可访问layout的页面并展示它的子集页面home
  ]
}

注意: 一定要有路由出口即

路由跳转

声明式导航: <router-link to="/xxx"> 相当于a标签,稍微有点不同的是多了一个选中的class属性,即当前页面的路由是这个标签,允许你设置css进行高亮。

编程式导航: this.$router.push('path地址') ,由于编程式导航使用了Promise的原因,多次调用同一个接口会报出一个错误,但不影响使用,如果介意可以加入以下代码进行重写push规则,声明式导航因为内部已经适配了所以没有这个问题。

const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
  return originalPush.call(this, location).catch(err => err)
}

注意: 是在路由配置下面加上(在导出之前)

最后

算是总结完了吧,其实很多小细节都没列举出来,因为我觉得这些都没必要(实属懒癌患者...),同时这里没细讲很多知识点的应用场景,是因为需要配合项目可能我才能写出来,所以后面我可能会总结一下做过的项目,很多都是关于封装的思路,都挺实用的,拓展了自己很多思维or大局观?

评论区
头像