本篇属于vue的应用篇

# vue进阶

# 自定义指令

除了核心功能默认内置的指令 (v-modelv-show),Vue 也允许注册自定义指令。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

比如,你需要当页面加载时,某元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。

<template>
	<input v-focus>
</template>
<script>
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
//局部自定义
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

自定义指令具有几个钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind:只调用一次,指令与元素解绑时调用。

# vue.set、this.set

vue.js默认不能检测到对象属性的添加或者删除,因为受到es5的限制,vuejs在初始化实例时将属性转换为getter/setter,所以属性必须在对象上才能让vuejs转换它,并且成为响应式的数据。

因此,要添加对象的新属性,使用this.$set语法

<script>
export default(){
  data(){
    return{
      student:{
         name:'',
         sex:'',
      }
    }
	}
 mounted(){
   this.$set(this.student,"age",24)
 }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

或者也可以使用vue.set向嵌套对象添加响应式属性

var vm = new Vue({
  el:'#test',
  data:{
    info:{
      name:'小明';
    }
  }
});
//给info添加属性
Vue.set(vm.info,'sex','男')
1
2
3
4
5
6
7
8
9
10

vue不允许动态添加根级响应式属性

# vue动画

Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:

  • 在 CSS 过渡和动画中自动应用 class
  • 可以配合使用第三方 CSS 动画库,如 Animate.css
  • 在过渡钩子函数中使用 JavaScript 直接操作 DOM
  • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

V-enter:定义进入过渡的开始。v-enter-active:定义进入过渡生效时的状态 v-enter-to:定义进入过渡的结束状态

V-leave:定义离开过度的开始状态 v-leave-active:定义离开过渡生效时的状态 v-leave-to:定义离开过渡的结束状态

动画钩子函数:

<transition
   v-on:before-enter="beforeEnter"
   v-on:enter="enter"
   v-on:after-enter="afterEnter"
   v-on:enter-cancelled="enterCancelled"
            
   v-on:before-leave="beforeleave"
   v-on:leave="leave"
   v-on:after-leave="afterleave"
   v-on:leave-cancelled="leaveCancelled"
>
   
</transition>
1
2
3
4
5
6
7
8
9
10
11
12
13

# axios

跨域包

//get请求
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
  .then(response=>{
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
//换个姿势实现
axios.get('/user', {
    params: {                         //区别:  get是用params传值
      ID: 12345
    }
  })
  .then(response=>{
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
//post请求
axios.post('/user', {                  //        post是用对象传值      
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(response=>{
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# Vue语法总结

想学习几个模块或者模板的使用并非难事,对于想从事前端开发的人来说,强调更懂vue底层原理。此部分也是vue的面试题

# mini Vue

vue由三大模块组成

1.数据响应模块:

数据响应模块将数据对象初始化为响应式数据对象

2.编译器:

编译器将视图模版编译为渲染函数

3.视图渲染函数

编译器原理:

Parse模版字符串

Transform转换标记:如v-if v-on v-for

Generate AST:

渲染函数

Patch phase:数据模型一旦变化,会被调用生成新的虚拟DOM

# new Vue

vue的架构设计,它的架构是分层式的。最底层是一个ES5的构造函数,再上层在原型上会定义一些_init$watch_render等这样的方法,再上层会在构造函数自身定义全局的一些API,如setnextTickuse等(以上这些是不区分平台的核心代码),接着是跨平台和服务端渲染(这些暂时不在讨论范围)及编译器。将这些属性方法都定义好了之后,最后会导出一个完整的构造函数给到用户使用,而new Vue就是启动的钥匙。

vue的内部,_符号开头定义的变量是供内部私有使用的,而$ 符号定义的变量是供用户使用的,而且用户自定义的变量不能以_$开头,以防止内部冲突

new Vue时依次执行方法:

initMixin(Vue):定义_init方法。

stateMixin(Vue):定义数据相关的方法$set,$delete,$watch方法。

eventsMixin(Vue):定义事件相关的方法$on$once$off$emit

lifecycleMixin(Vue):定义_update,及生命周期相关的$forceUpdate$destroy

renderMixin(Vue):定义$nextTick_render将render函数转为vnode

# v-model原理-vue2、vue3

v-model基于object.defineproperty实现

vue官方文档中提到:

由于JavaScript语言的限制,vue不能检测数组和对象的变化

对于对象,vue无法检测property的添加或移除

对于数组,当你利用数组的索引设置值,或者修改数组的长度时,无法检测到变动

var vm = new Vue({
  data:{
    a:1;
    items:['a','b','c']
  }
})

vm.b = 2; //添加vm.b为2,但是vm.b是非响应的

vm.items[1] = 'x' //不是响应式的
vm.items.length = 2 //不是响应式的
1
2
3
4
5
6
7
8
9
10
11

原因:

ES6中对象有两种属性,数据属性和访问属性,数据属性的描述符为:Configurable、Enumerable、Writable、Value,访问属性的描述符为:Configurable、Enumerable、set、get属性

vue初始化时将对象和数组中的数据属性转换为访问属性,而我们后面再次使用普通的赋值,仅仅是赋值了一个数据属性,这个属性不会有访问器属性的事件监听功能。

对于数组,js中length属性初始为non-configurable、non-enumerable、writable,也就是通过object.defineproperty是不可枚举或者改变length属性的

官方解决方法:

vue2中:

对于对象,使用vue.set进行响应式数据的添加

Vue.set(vm.someObject,'b',2)
//this.$set是Vue.set的别名
this.$set(this.someObject,'b',2)
1
2
3

对于数组使用同样的方法

Vue.set(vm.items,indexOfItem,1,newValue)
vm.$set(vm.items,indexOfItem,1,newValue)

vm.items.splice(indexOfItem,1,newValue)

//改变数组长度
vm.items.splice(newLength)
1
2
3
4
5
6
7

vue3中使用proxy进行劫持

vue3中proxy与reflect进行搭配使用,proxy的语法是在语言层面作出修改,属于一种元编程,proxy在对象之前设置一层拦截,当被监听的对象被访问时,都必须经过这层拦截,可以在拦截中对原对象处理,返回需要的数据格式。也就是无论访问对象的什么属性,不管是定义好的还是新增的,都会走到拦截中进行处理,而不是像vue2中初始化时通过object.defineproperty遍历对象转换成访问器属性再进行监听

Reflect是一个内置对象,不是一个函数对象不可构造,它提供拦截JavaScript操作的方法,这些方法与proxy handler的方法相同

# diff算法

Vue diff 算法就是用来比较新老 virtal DOM 的差别,然后尽可能少的调用 api 来操作真实 DOM。

patch比对新旧节点后,为 DOM 进行打补丁操作。

# vue-cli原理

下载模版、生成模版、

构建自定义模版

# 响应式原理data、props

在组件new Vue()后的执行vm._init()初始化过程中,当执行到initState(vm)时就会对内部使用到的一些状态,如propsdatacomputedwatchmethods分别进行初始化,再对data进行初始化的最后有这么一句:

function initData(vm) {  //初始化data
  ...
  observe(data) //  info:{name:'cc',sex:'man'}
}
1
2
3
4

observe方法就是Observer这个类的工厂方法,

export class Observer {
  constructor(value) {
    this.value = value
    this.walk(value)  // 遍历value
  }
  
  walk(obj) {
    const keys = Object.keys(obj)
    for(let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])  // 只传入了两个参数
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

当执行new Observer时,首先将传入的对象挂载到当前this下,然后遍历当前对象的每一项,执行defineReactive这个方法,看下它的定义:

export function defineReactive(obj, key, val) {

  const dep = new Dep()  // 依赖管理器
  
  val = obj[key]  // 计算出对应key的值
  observe(val)  // 递归包装对象的嵌套属性
  
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      ... 收集依赖
    },
    set(newVal) {
      ... 派发更新
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这个方法的作用就是使用Object.defineProperty创建响应式数据。首先根据传入的objkey计算出val具体的值;如果val还是对象,那就使用observe方法进行递归创建,在递归的过程中使用Object.defineProperty将对象的每一个属性都变成响应式数据:

https://juejin.cn/post/6844903911669628941

# computed、watch、data异同

computed与watch异同

1.watch监听是vm.data的一个属性,它的getter不需另做处理,而computed监听是一个新的属性,它需要被代理到vm实例上,并且getter需要重新创建

2.底层都是watcher:watch创建watcher内部默认会对属性取值,它要有老值嘛;computed watcher的创建就不会取值,只有组件渲染需要计算属性值时,才会取值

3.computed对计算属性的值有缓存处理,只要依赖的vm.data属性值不变,重复取值,不会重复计算

data与computed异同

data与computed的最核心的区别在于data中的属性并不会随着赋值变量的改动而改动,而computed会

# v-bind原理

v-bind常用于绑定输入框的值等

获取输入框的value并赋值给vm实例的text属性,利用obj.defineproperty将data中的text劫持为vm的访问器属性,就会给vm.text赋值就会触发set方法。

在set方法中做两件事,一件是更新属性的值,第二是提供发布/订阅模式通知dom元素值的变化。让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时会通知所有的观察者对象。set方法的第二件事就是作为发布者发出通知:有值改变,DOM节点是订阅者,收到通知后就会执行相应的更新操作。

更具体地来说,new一个vue对象时,会做两件事:一是监听数据observe(data),二是编译html:nodeToFragement(id)

在一监听数据的过程中,会为每个data中的属性生成一个主题对象dep

在编译HTML的过程中,会为每个与数据绑定相关的节点生成一个订阅者watcher,watcher会将自己添加到相应属性的dep中

watcher函数的实现

function Watcher(vm,node,name){
  Dep.target = this;
  this.name = name;
  this.node = node;
  this.vm = vm;
  this.update();
  Dep.target = null;
}

Watcher.prototype = {
  update:function(){
    this.get();
    this.node.nodeValue = this.value;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# v-model与v-bind的区别

v-model本质是语法糖,双向绑定表单元素实际上是绑定了value、checked、select属性,表单可以拿到vue的数据,表单中的数据也可以传到vue中。

v-bind:value只能是表单拿到vue的数据,vue无法拿到表单的数据。

# vue父组件与子组件生命周期钩子执行顺序

加载渲染过程:

​ 父组件:beforecreate ->created -> beforemount

​ 子组件:-> beforeCreate -> created -> beforeMount -> mounted

​ 父组件:->mounted

子组件更新过程:

​ 父组件beforeUpdate -> 子组件beforeUpdate -> 子组件updated -> 父组件updated

父组件更新过程:

​ 父组件beforeUpdate -> (子组件deactivated) -> 父组件updated

销毁过程:

​ 父组件beforeDestroy->子组件beforeDestroy->父组件destroyed

总结:由外到内,再由内到外

# vue3.0与2.0的区别

vue3.0有一个基于 Proxy 的观察者,它会提供全语言覆盖的响应式跟踪。相比于 2.x 版本里基于 Object.defineProperty 的观察者,新的实现更加强大:

​ 可以检测属性的新增和删除

​ 可以检测数组索引的变化和 length 的变化

​ 支持 Map、Set、WeakMap 和 WeakSet

支持typescript

https://juejin.im/post/6854573210717929480#heading-1

# vue模版解析原理

vue解析器将模版解析为AST(抽象语法树)

# html解析器

实例

<div>
    <h1>我是Berwin</h1>
    <p>我今年23岁</p>
</div>
1
2
3
4

上述模版解析过程:

模板的开始位置是div的开始标签,于是会触发钩子函数startstart触发后,会先构建一个div节点。此时发现栈是空的,这说明div节点是根节点,因为它没有父节点。将div节点推入栈中,并将模板字符串中的div开始标签从模板中截取掉。

第二行模板的开始位置是一些空格,这些空格会触发文本节点的钩子函数,在钩子函数里会忽略这些空格。同时会在模板中将这些空格截取掉。

空格去掉之后是h1的开始标签,于是会触发钩子函数start。与前面流程一样,start触发后,会先构建一个h1节点。此时发现栈的最后一个节点是div节点,这说明h1节点的父节点是div,于是将h1添加到div的子节点中,并且将h1节点推入栈中,同时从模板中将h1的开始标签截取掉。

h1标签之后是一段文本,于是会触发钩子函数charschars触发后,会先构建一个文本节点,此时发现栈中的最后一个节点是h1,这说明文本节点的父节点是h1,于是将文本节点添加到h1节点的子节点中。由于文本节点没有子节点,所以文本节点不会被推入栈中。最后,将文本从模板中截取掉。

文本后是h1结束标签,于是会触发钩子函数endend触发后,会把栈中最后一个节点弹出来。

p标签与h1标签的解析过程相同,解析空格、钩子函数start、解析文本、结束钩子函数end

到了div的结束标签,于是会触发钩子函数end。其逻辑与之前一样,把栈中的最后一个节点弹出来,也就是把div弹了出来,并将div的结束标签从模板中截取掉。

此时模板已经被截取空了,也就代表着HTML解析器已经运行完毕。这时我们会发现栈已经空了,但是我们得到了一个完整的带层级关系的AST语法树。这个AST中清晰写明了每个节点的父节点、子节点及其节点类型。

html解析器是一个函数,用伪代码实现的话如下

parseHTML(template, {
    start (tag, attrs, unary) {
        // 每当解析到标签的开始位置时,触发该函数
    },
    end () {
        // 每当解析到标签的结束位置时,触发该函数
    },
    chars (text) {
        // 每当解析到文本时,触发该函数
    },
    comment (text) {
        // 每当解析到注释时,触发该函数
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 文本解析器

vue中允许文本中带有变量,遇到带有变量的文本需要调用文本解析器进行解析

# vue-router的实现原理

vue属于spa(单一页面应用),当它加载页面时不会加载整个页面的内容,而是更新某个指定容器的内容

单页面应用的核心之一是

1.更新视图而不请求页面

2.vue-router的三种模式

vue-router提供三种模式:

hash:使用url hash值来作为路由,为默认模式

history:依赖HTML5 History API和服务器配置

abstract:支持所有javascript运行环境

移动端weex使用abstract模式

vue-router 源码分析部分

  • 注册: 执行 install 方法,注入生命周期钩子初始化
  • vueRouter: 当组件执行 beforeCreate 传入 router 实例时,执行 init 函数,然后执行 history.transitionTo 路由过渡
  • matcher : 根据传入的 routes 配置创建对应的 pathMapnameMap ,可以根据传入的位置和路径计算出新的位置并匹配对应的 record
  • 路由模式: 路由模式在初始化 vueRouter 时完成匹配,如果浏览器不支持则会降级
  • 路由 切换: 哈希模式下底层使用了浏览器原生的 pushStatereplaceState 方法
  • router-view: 调用父组件上存储的 $route.match 控制路由对应的组件的渲染情况,并且支持嵌套。
  • router-link: 通过 to 来决定点击事件跳转的目标路由组件,并且支持渲染成不同的 tag,还可以修改激活路由的样式。

hash模式js实现代码

class Routers {
  constructor() {
    // 储存hash与callback键值对
    this.routes = {};
    // 当前hash
    this.currentUrl = '';
    // 记录出现过的hash
    this.history = [];
    // 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
    this.currentIndex = this.history.length - 1;
    this.refresh = this.refresh.bind(this);
    this.backOff = this.backOff.bind(this);
    // 默认不是后退操作
    this.isBack = false;
    window.addEventListener('load', this.refresh, false);
    window.addEventListener('hashchange', this.refresh, false);
  }

  route(path, callback) {
    this.routes[path] = callback || function() {};
  }

  refresh() {
    this.currentUrl = location.hash.slice(1) || '/';
    if (!this.isBack) {
      // 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
      // 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
      // 避免再次造成指针的不匹配,我们直接截取指针之前的数组
      // 此操作同时与浏览器自带后退功能的行为保持一致
      if (this.currentIndex < this.history.length - 1)
        this.history = this.history.slice(0, this.currentIndex + 1);
      this.history.push(this.currentUrl);
      this.currentIndex++;
    }
    this.routes[this.currentUrl]();
    console.log('指针:', this.currentIndex, 'history:', this.history);
    this.isBack = false;
  }
  // 后退功能
  backOff() {
    // 后退操作设置为true
    this.isBack = true;
    this.currentIndex <= 0
      ? (this.currentIndex = 0)
      : (this.currentIndex = this.currentIndex - 1);
    location.hash = `#${this.history[this.currentIndex]}`;
    this.routes[this.history[this.currentIndex]]();
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

history模式js实现代码

class Routers {
  constructor() {
    this.routes = {};
    // 在初始化时监听popstate事件
    this._bindPopState();
  }
  // 初始化路由
  init(path) {
    history.replaceState({path: path}, null, path);
    this.routes[path] && this.routes[path]();
  }
  // 将路径和对应回调函数加入hashMap储存
  route(path, callback) {
    this.routes[path] = callback || function() {};
  }

  // 触发路由对应回调
  go(path) {
    history.pushState({path: path}, null, path);
    this.routes[path] && this.routes[path]();
  }
  // 监听popstate事件
  _bindPopState() {
    window.addEventListener('popstate', e => {
      const path = e.state && e.state.path;
      this.routes[path] && this.routes[path]();
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

调用上述方法

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>h5 router</title>
</head>
<body>
  <ul>
      <li><a href="/">turn yellow</a></li>
      <li><a href="/blue">turn blue</a></li>
      <li><a href="/green">turn green</a></li>
  </ul>
</body>
</html>
<script>
window.Router = new Routers();
Router.init(location.pathname);
const content = document.querySelector('body');
const ul = document.querySelector('ul');
function changeBgColor(color) {
  content.style.backgroundColor = color;
}

Router.route('/', function() {
  changeBgColor('yellow');
});
Router.route('/blue', function() {
  changeBgColor('blue');
});
Router.route('/green', function() {
  changeBgColor('green');
});

ul.addEventListener('click', e => {
  if (e.target.tagName === 'A') {
    e.preventDefault();
    Router.go(e.target.getAttribute('href'));
  }
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

router-view & router-link

vue-routerinstall 时全局注册了两个组件一个是 router-view 一个是 router-link,这两个组件都是典型的函数式组件

路由切换原理

route.push

hash 模式的 push 方法会调用路径切换方法 transitionTo,接着在回调函数中调用pushHash方法,这个方法调用的 pushState 方法底层是调用了浏览器原生 history 的方法。pushreplace 的区别就在于一个将 url 推入了历史栈,一个没有,最直观的体现就是 replace 模式下浏览器点击后退不会回到上一个路由去 ,另一个则可以。

# vue.use

可以看到 Vueuse 方法会接受一个 plugin 参数,然后使用 installPlugins 数组保存已经注册过的 plugin

首先保证 plugin 不被重复注册,然后将 Vue 从函数参数中取出,将整个 Vue 作为 plugininstall 方法的第一个参数,这样做的好处就是不需要麻烦的另外引入 Vue,便于操作。

接着就去判断 plugin 上是否存在 install 方法。存在则将赋值后的参数传入执行 ,最后将所有的存在 install 方法的 plugin 交给 installPlugins维护。

# vue.$nextTick原理

vue的nextTick方法的实现原理了:

vue用异步队列的方式来控制DOM更新和nextTick回调先后执行

microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕

因为兼容性问题,vue不得不做了microtask向macrotask的降级方案

在vue2.5的源码中,macrotask降级的方案依次是:setImmediate、MessageChannel、setTimeout.

setImmediate是最理想的方案了,可惜的是只有IE和nodejs支持。

MessageChannel的onmessage回调也是microtask,但也是个新API,面临兼容性的尴尬...

所以最后的兜底方案就是setTimeout了,尽管它有执行延迟,可能造成多次渲染,算是没有办法的办法了。

# vuex实现原理

Vuex是一个专为Vue服务,用于管理页面数据状态、提供统一数据操作的生态系统。它集中于MVC模式中的Model层,规定所有的数据操作必须通过 action - mutation - state change 的流程来进行,再结合Vue的数据视图双向绑定特性来实现页面的展示更新

统一的页面状态管理以及操作处理,可以让复杂的组件交互变得简单清晰,同时可在调试模式下进行时光机般的倒退前进操作,查看数据改变过程,使code debug更加方便。

Vuex的设计思想,借鉴了Flux、Redux,将数据存放到全局的store,再将store挂载到每个vue实例组件中,利用Vue.js的细粒度数据响应机制来进行高效的状态更新

vue的store挂载到组件

利用vue的插件机制 (opens new window),使用Vue.use(vuex)时,会调用vuex的install方法,装载vuex

vuex是利用vue的mixin混入机制,在beforeCreate钩子前混入vuexInit方法,vuexInit方法实现了store注入vue组件实例,并注册了vuex store的引用属性$store。applyMixin方法使用vue混入机制 (opens new window),vue的生命周期beforeCreate钩子函数前混入vuexInit方法,

vuex的state和getters映射到组件实例中响应式更新

Vuex的state状态是响应式,是借助vue的data是响应式,将state存入vue实例组件的data中;Vuex的getters则是借助vue的计算属性computed实现数据实时监听。

# 组件keep-alive实现原理

# chromevue调试工具

在谷歌应用商店搜索vuejsdevtools,不使用beta版使用正式版,添加至chrome扩展程序并启动。

启动之后本地在chrome中打开vue调试页面时,vuedevtool会自动检测到vue页面,变成绿色,不是vue页面则默认为灰色。

打开开发者工具,在最后一栏为vue,打开就能显示页面的组件和动画,以及组件所用到的props、data、computed。

非常方便。

# Graphql

graphql客户端写法

安装包

npm install apollo-boost graphql-tag
1

设置client和url

import ApplloCient from "apollo-boost"

const client = new ApplloCient({
    uri:"gql"
})

export default client;
1
2
3
4
5
6
7

实例

import gql from "graphql-tag"
//query操作
  client
    .query({
        query: gql`
            query($input: SendSmsCodeType!) {
                sendSmsCode(input: $input) {
                    success
                    msg
                }
            }
           `,
    variables: {
            input: {
                phone: this.phone,
                },
            },
    })
    //返回数据后执行的函数,res为返回结果
    .then((res) => {
        const rlt = res.data.sendSmsCode;
        if (rlt.success) {
            zs.$toast("发送成功");
        } else {
            zs.$toast(zs.msg);
        }
    });
//mutation操作
     client
        .mutate({
          mutation: gql`
            mutation($input: RegisterType!) {
              register(input: $input) {
                success
                msg
              }
            }
          `,
          variables: {
            input: {
              phone: this.phone,
              code: this.phonecode,
              password: this.password,
              invite_code: this.invitecode,
            },
          },
        })
       //返回数据后执行的函数,res为返回结果
        .then((res) => {
          console.log(res);
          const rlt = res.data.register;
          if (rlt.success) {
            zs.$toast("注册成功");
            this.$router.push({
              name: "success",
            });
          } else {
            zs.$toast(rlt.msg);
          }
        });
    },

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

以上代码为带参数的query和mutation,copy时需要根据后端的grapgql playground去查schema和docs,修改对应的参数

官网链接:

"https://apollo.vuejs.org/guide/apollo"

https://www.jianshu.com/p/95954887271f

https://blog.csdn.net/winnie__wei/article/details/80598309

https://www.cnblogs.com/lhxsoft/p/11904388.html

# 使用bootstrap

安装

npm install vue bootstrap-vue bootstrap
1

全局注册bootstrap组件

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'

Vue.use(BootstrapVue)
Vue.use(IconsPlugin)
1
2
3
4
5
6

# 使用antd

安装

npm install ant-design-vue --save
1

在app.js中引用

import ant from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
Vue.use(ant)
1
2
3

在组件中添加antd代码

表单


1

日期选择表单

<a-form-item label=“success" has-feedback validate-status="success">
<a-date-picker style="width:100%"/>
</a-form-item>
1
2
3

# Element-UI

# 选择框

单选/多选框

多个选项单选互斥框

<template>
  <el-radio-group v-model="radio">
    <el-radio :label="3">备选项</el-radio>
    <el-radio :label="6">备选项</el-radio>
    <el-radio :label="9">备选项</el-radio>
  </el-radio-group>
</template>
1
2
3
4
5
6
7

多个选项多选不互斥框

<template>
  <el-checkbox-group v-model="checkList">
    <el-checkbox label="复选框 A"></el-checkbox>
    <el-checkbox label="复选框 B"></el-checkbox>
    <el-checkbox label="复选框 C"></el-checkbox>
    <el-checkbox label="禁用" disabled></el-checkbox>
    <el-checkbox label="选中且禁用" disabled></el-checkbox>
  </el-checkbox-group>
</template>
1
2
3
4
5
6
7
8
9

下拉选择框

普通单选下拉选择框

<template>
  <el-select 
     v-model="value" 
     clearable <!--可选择是否具有一键清除选项-->
     disabled  <!--可选择是否具有一键禁用选项-->
     placeholder="请选择"
   >
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value"
      :disabled="item.disabled" <!--可选择是否具有禁用单独选项选项-->
    >
    </el-option>
  </el-select>
</template>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

可多选下拉选择框

<template>
  <el-select 
    v-model="value1" 
    multiple  <!--添加多选选项-->
    collapse-tags <!--可选择是否展示全部标签-->
    placeholder="请选择"
    >
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value">
    </el-option>
  </el-select>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

多选下拉框返回数组

时间/日期选择框

穿梭选择框

布尔开关

# 弹出框

抽屉

通知

消息提示

消息盒

对话框

# 弹出框可移动功能

在项目中新建directives.js文件

import Vue from 'vue'

// v-dialogDrag: 弹窗拖拽
Vue.directive('dialogDrag', {
  bind(el, binding, vnode, oldVnode) {
    const dialogHeaderEl = el.querySelector('.el-dialog__header')
    const dragDom = el.querySelector('.el-dialog')
    dialogHeaderEl.style.cursor = 'move'

    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)

    dialogHeaderEl.onmousedown = (e) => {
      // 鼠标按下,计算当前元素距离可视区的距离
      const disX = e.clientX - dialogHeaderEl.offsetLeft
      const disY = e.clientY - dialogHeaderEl.offsetTop

      // 获取到的值带px 正则匹配替换
      let styL, styT

      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
      if (sty.left.includes('%')) {
        styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100)
        styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100)
      } else {
        styL = +sty.left.replace(/\px/g, '')
        styT = +sty.top.replace(/\px/g, '')
      }

      document.onmousemove = function(e) {
        // 通过事件委托,计算移动的距离
        const l = e.clientX - disX
        const t = e.clientY - disY

        // 移动当前元素
        dragDom.style.left = `${l + styL}px`
        dragDom.style.top = `${t + styT}px`

        // 将此时的位置传出去
        // binding.value({x:e.pageX,y:e.pageY})
      }

      document.onmouseup = function(e) {
        document.onmousemove = null
        document.onmouseup = null
      }
    }
  }
})

// v-dialogDragWidth: 弹窗宽度拖大 拖小
Vue.directive('dialogDragWidth', {
  bind(el, binding, vnode, oldVnode) {
    const dragDom = binding.value.$el.querySelector('.el-dialog')

    el.onmousedown = (e) => {
      // 鼠标按下,计算当前元素距离可视区的距离
      const disX = e.clientX - el.offsetLeft

      document.onmousemove = function(e) {
        e.preventDefault() // 移动时禁用默认事件

        // 通过事件委托,计算移动的距离
        const l = e.clientX - disX
        dragDom.style.width = `${l}px`
      }

      document.onmouseup = function(e) {
        document.onmousemove = null
        document.onmouseup = null
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

在main.js中导入该文件

import./utils/directives.js’
1

在使用 el-dialog 的地方加入 v-dialogDrag

		<el-dialog
      :visible.sync="dialogVisible"
      v-dialogDrag>
      // ...
    </el-dialog>
1
2
3
4
5

# 数据展示页面

table表格

进度条

分页

树形展示

标签

标记

# 展示页面

走马灯

加载中

卡片

日历

折叠面板

# 布局导航组件

导航条

tabs标签页

页头

面包屑

下拉菜单

步骤条

# 表单

form

input

上传

评分

滑块

计数器

# 灵活小组件

小弹出框

小提示框

文字提示框

气泡确认框

时间线/分割线

回到顶部

# 自定义样式

element-u支持自定义样式

比如默认的时间选择控件time-picker,默认为padding-left:30px,padding-right:30px,使用chrome工具调试后,发现该样式在.el-input-prefix、.el-input-inner下,这是element自带的样式,所以在组件对应的样式下添加.el-input-inner:padding-left:0px;padding-right:0px;就可以修改默认的样式

# 移动端组件库vant

轻量、可靠的移动端库

安装

npm install vant -S
1

一次性导入所有组件

import Vant from 'vant';
import 'vant/lib/index.css'

Vue.use(Vant);
1
2
3
4

或者按需导入组件,首字母要大写,多个组件使用.use的方法链式调用注册到全局

import {
    Tab, Tabs, Toast, Popup, DatetimePicker, Area, Icon, Uploader, Picker, List, Rate,       Field, Tag, Collapse, CollapseItem, Button, Image, Grid, GridItem, RadioGroup, Radio,     Loading, Cell, CellGroup, NoticeBar, Checkbox, CheckboxGroup, Progress, ActionSheet,     Dialog
} from 'vant'

Vue.use(Tab).use(Tabs)
1
2
3
4
5

底部安全区适配

iphone x等机型底部存在指示条,指示条的操作区域与页面底部容易重合,容易导致用户误操作。Vant组件提供safe-area-inset-bottom属性。

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,viewport-fit=cover"

<vant-number-keyboard safe-area-inset-bottom />
1
2
3

# Spata-UI(IE)

# uni-UI

https://github.com/dcloudio/uni-ui

Last Updated: 2/19/2024, 12:16:30 AM