vue基础知识总结及测试遇到的前端bug分析

vue基础知识总结、测试遇到的前端bug分析原因

1、生命周期钩子
beforeCreate
第一个生命周期函数,表示实例完全被创建之前,会执行这个函数,在beforeCreate生命周期函数执行的时候,data 和 methods 中的数据还没有被初始化。
created(常用)
在 created 中,data 和 methods 都已经被初始化好了
如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作。

beforeMount
第三个生命周期函数,表示模板已经在内存中编译完成,但是尚未把模板渲染到页面中
在beforeMount执行的时候,页面中的元素没有被真正替换过来,只是之前写的一些模板字符串。

mounted(常用)
表示内存中的模板已经真实的挂载到页面中,用户已经可以看到渲染好的页面
这个mounted是实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,此时,如果没有其它操作的话,这个实例,就静静地在内存中不动。

beforeUpdate
表示 界面还没有被更新,但是数据肯定被更新了
得出结论:当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时data 数据是最新的,页面尚未和 最新的数据保持同步。

updated
updated事件执行的时候,页面和 data 数据已经保持同步了,都是最新的

beforeDestroy
当执行beforeDestory钩子函数的时候,Vue实例就已经从运行阶段到了销毁阶段,当执行beforeDestory的时候,实例身上所有的data和所有的methods以及过滤器,指令…都处于可用状态,还没有真正的执行销毁的过程。
destroyed(常用)
当执行到了destroyed的时候,组件已经被完全销毁了,此时,组件中所有的数据,方法,指令都不可用了。
测试:1、created和mounted加载前后的问题。2、html的dom节点还没有创建完成就直接去操作dom导致报错
前端:书写顺序规范问题
2、vue实例上的内容

data定义变量
data() {
    return {
    }
  },

测试:1、变量未定义就使用,导致undefined 2、定义变量的类型和赋值的类型不符合
前端:1、变量定义语义化。2、对于后端返回的值去赋值记得判空操作。

methods
methods: {}
内联处理器中的方法:

根据传入不同的内容,去处理不同的逻辑
也可传入$event,获取原生的事件对象
测试:方法中的逻辑有问题,引起的bug。
前端:
1、方法使用语义化的名称,尽量不要 data 对象的属性同名,造成一定的混淆。(方法return值在模板渲染)
2、如果方法多个地方用到,写到公共方法里面。
3、使用native修饰符来绑定构造器里的原生方法

跳转到test
其他事件修饰符
.stop .prevent .capture .self .once .native




...
...

如果要监听根元素的原生事件,可以使用 .native 修饰符
事件修饰符之间也有顺序,顺序不同,效果也不同
修饰符有:事件修饰符,按键修饰符,系统修饰键,.exact修饰符,鼠标按钮修饰符

测试:
1、点击穿透,只想点击上面一层的子元素,下面的父元素也被点击。(表格、折叠)
2、阻止默认事件(.prevent),表单

computed
计算属性
对于任何复杂逻辑,你都应当使用计算属性。
在模板中放入太多的逻辑会让模板过重且难以维护。
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。否则就会使用缓存中的属性值
在vue中,computed的属性可以被视为是data一样,可以读取和设值。在computed中可以分为getter(读取)和setter(设值)

computed: {
    // 计算属性的 getter
    reversedMessage () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }

当赋值给计算属性的时候,将调用setter函数。多用于在模板组件中需要修改计算属性自身的值的时候。
只有当计算属性中的属性被直接赋值的时候,才会走setter函数,而且,setter函数和getter函数是相互独立的

(示例)
computed: {
    fullName: {
      // getter
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set(newValue) {
        console.log(newValue, 'newVal')
        var names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      },
    },
  },

做联动效果的组件可考虑用这种方式。

watch

watch:{
     a(val, oldVal){//普通的watch监听
     },

     b:{//深度监听,可监听到对象、数组的变化
         handler(val, oldVal){
         },
         deep:true, //true 深度监听
				immediate: true  //立即执行
     }
 }

watch 的一个特点是,最初绑定的时候是不会执行的,如果想要一开始就让他最初绑定的时候就执行
immediate: true // 代表在wacth里声明了b这个方法之后立即先去执行handler方法
测试:1、数据联动改变 2、更改没起效果,又恢复成初始值。3、操作了一下,回来之后又消失了。
前端:1、在watch里面不要做过多的业务逻辑。2、不滥用监听(watch虽好,可不要贪杯哦~)

props
//props数组的形式
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

//props对象的形式
props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}


//props带有验证需求
props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的布尔值
    propF: {
      type: Boolean,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 带有默认值的对象也可
    meta: {
      type: Object,
      default: () => ({}),
    },
    // 带有默认值的数组  
    hotData: {
      type: Array,
      default: () => {
        return []
      },
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    },
  }

测试:
1、传值类型错误,导致前端报错。
2、定义类型不一致。
3、子组件更改父组件传过来的值导致报错。
前端:
1、传值类型保证一致性。
2、书写规范,注意定义变量的一致性。
3、前端子组件不能随意更改父组件中的值。(抛事件,.sync)

provide/inject
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
需要注意的是这里不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据。

provide:Object | () => Object
inject:Array | { [key: string]: string | Symbol | Object }

provide选项是一个对象或返回一个对象的函数。
inject选项是:
一个字符串数组,或
一个对象
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
注意:要实现父子组件响应,父组件传递的数据类型必须是对象Object,子组件接收的数据类型必须是对象Object,其他数据类型都是不好使的(示例)
补充:数组和对象的更新检测
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

//数组的更新检测
方式1:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
方式2:
this.$set(this.data, 1, {name:"huangenai",age:"22"})

//对象的更新检测
方式1:
this.$set(this.someObject,'b',2)
方式2:
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

测试:1、纯前端添加删除内容,页面没有更新到。2、操作了数据没有更新渲染到页面。
前端:两种方式灵活选择。尽量做到代码简洁。
3、vue-router
分为history模式和hash模式
hash 模式url 尾巴后带 # 号
vue-router 默认 hash 模式
命名路由

routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]

要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:

跳转到user
这跟代码调用 router.push() :
router.push({ name: 'user', params: { userId: 123 } })
这两种方式都会把路由导航到 /user/123 路径。

嵌套路由

routes: [
    {
      path: '/user/:id',
      component: User,
      children: [
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的  中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的  中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]

路由的跳转方式

1、声明式

2、编程式
router.push(...)

// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }}) // -> /user/123

// 带查询参数
router.push({ path: 'register', query: { plan: 'private' }}) // ->  /register?plan=private

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123

// 注意:如果你写path,后面写params传参。这里的 params 不生效
router.push({ path: ‘/user’, params: { userId }}) // -> /user
这样的规则也适用于 router-link 组件的 to 属性
router.replace
跟 router.push很像,唯一的不同就是:它不会向 history 添加新记录,是替换掉当前的 history 记录。
router.go
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步
正数向前,负数后退
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)

测试:
1、该跳转的页面跳转错误。
2、前进后退一步操作多了,乱掉了。
3、权限路由。
前端:注意跳转带参数url有大小限制(Google8182字节,IE:2048字节)
路由重定向

routes: [
    { path: '/a', redirect: '/b' }
  ]

命名视图




//如果 router-view 没有设置名字,那么默认为 default



routes: [
    {
      path: '/',
      components: {
        default: Default, // Default组件
        header: Header, // Header组件
        content: Content, // Content组件
        footer: Footer // Footer组件
      }
    }
  ]

路由的导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

  beforeRouteEnter (to, from, next) {
     // 不能获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }

beforeRouteEnter 守卫不能访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
} 

注意:
beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteLeave
导航守卫-离开守卫
可用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

beforeRouteLeave (to, from, next) {
  const answer = window.confirm('Do you really want to leave? ')
  if (answer) {
    next()
  } else {
    next(false)
  }
}
beforeRouteLeave(to, from, next) {
  // 从列表页去到别的页面,如果不是详情页,则不缓存列表页,不好用
  if (to.path === '/detail') {
    from.meta.keepAlive = true;
  } else {
    from.meta.keepAlive = false;
  }
  next();
}

测试:
1、做权限的管理认证
2、路由遇到特定标记做跳转
3、每次路由切换或页面的刷新去判断用户是否已经登录

前端:注意next()。必须调用next() 才能继续往下执行下一个钩子,否则路由跳转会停止。

next()//直接进to 所指路由
next(false) //中断当前路由
next('route') //跳转指定路由
next('error') //跳转错误路由

4、组件

组件化思想
造轮子
提升代码复用性
示例

slot插槽-组件的扩展
在vue中实现的一套分发内容的API,将slot元素作为承载内容分发出口。
具名插槽

//插槽可以这样写

//用法


  

  

A paragraph for the main content.

And another one.

具名插槽的缩写

v-slot 缩写成#
 

作用域插槽
作用域插槽主要解决的问题是:当父组件向子组件插槽传递模板内容时,父组件访问子组件的数据。


  

当父组件引用子组件时,想将子组件中的user.name替换为user.age时,不可以直接在父组件中更改子组件中的user.name,因为在官方文档中,父级模板所有内容都是在父级作用域中编译的,子级模板所有内容都是在子作用域中编译的。
为了解决子组件的user不能在父组件中使用的问题,我们可以将user用属性绑定的方式传给父组件,父组件用一个自定义名称进行接收



 



2.6.0之前slot-scope
antd表格