vue

282 阅读31分钟

一、生命周期

1. 谈谈你对 Vue 生命周期的理解?

  1. 生命周期是什么

vue 生命周期是 指 vue 实例对象从创建之初到销毁的过程, vue 所有功能的实现都是围绕它的生命周期进行的, 在生命周期的不同阶段调用对应的钩子函数实现组件 数据管理 和 DOM 渲染 两大重要功能。

  1. 生命周期的作用是什么?

它的生命周期中有多个事件钩子,让我们在控制整个 Vue 实例的过程时更容易形成好的逻辑。

生命周期描述
beforeCreate组件实例被创建之初,组件的属性生效之前
created组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用
beforeMount在挂载开始之前被调用:相关的 render 函数首次被调用
mountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdate组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
updated组件数据更新之后
activitedkeep-alive 专属,组件被激活时调用
deadctivatedkeep-alive 专属,组件被销毁时调用
beforeDestory组件销毁前调用
destoryed组件销毁后调用

1、beforeCreate 此时 $el、data 的值都为 undefined

2、创建之后,此时可以拿到 data 的值,但是 $el 依旧为 undefined

3、mount 之前,$el 的值为“虚拟”的元素节点

4、mount 之后,mounted 之前,“虚拟”的 dom 节点被真实的 dom 节点替换,并将其插入到 dom 树中,于是在触发 mounted 时,可以获取到 $el 为真实的 dom 元素()

myVue.$el===document.getElementById("app-8")  // true

挂载阶段

  • beforeCreate: 此阶段为实例初始化之后,此时数据观察和事件机制还没有形成,不能获取到 dom 节点;

  • created:此阶段的vue实例已经创建,仍不能获取DOM 节点.把vue 的一个实例给初始化了,只是存在于 js 内存的一个变量而已,这个时候并没有开始渲染;

  • beforeMount: 在这一阶段,我们虽然还不能获取到具体 DOM 元素,但 vue 挂载的根节点已经创建,下面 vue 对 DOM 的操作将围绕这个根元素继续进行, beforeMount 这个阶段是过渡性的,一般一个项目只能用到一两次;

  • mounted:组件真正绘制完成了,页面已经渲染完了,数据和 DOM 都已被渲染出来,一般我们的异步请求都写在这里

更新阶段

  • beforeUpdate: 这一阶段,vue遵循数据驱动DOM 的原则,beforeUpdate 函数在数据更新后没有立即更新数据,但是DOM 数据会改变,这是双向数据绑定的作用;

  • updated:这一阶段,DOM 会和更改过的内容同步

销毁阶段

  • beforeDestroy:在上一阶段 vue 已经成功通过数据驱动 DOM 的修改,当我们不再需要 vue 操纵 DOM 时,就要销毁 vue,也就是清除 vue 实例与 DOM 的关联,调用 destroy 方法可以销毁当前组件。在销毁前,会触发 beforeDestroy 钩子函数;

  • destroyed:在销毁后,会触发 destroyed 钩子函数

2. Vue 的父组件和子组件生命周期钩子函数执行顺序?

Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

  1. 加载渲染过程 : 父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  2. 子组件更新过程 : 父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  3. 父组件更新过程 : 父 beforeUpdate -> 父 updated

  4. 销毁过程 : 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

3.父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vue
<Child @mounted="doSomething"/>

// Child.vue
mounted() {
  this.$emit("mounted");
}

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},

//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},    
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如: created,updated 等都可以监听。

4. computed 和 watch

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

computed 运用场景: 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

watch 运用场景: 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

computed 传值: 利用 闭包 可以向计算属性来 传递参数

<p>总价格:{{ total({num: 3}) }}</p>

data () {
    return {
       priceA: 2,
       priceB: 2,
    }
},

computed: {
    // computed中如果按照 methods 中的传值方法会报错, 解决,需要闭包
    total () {
      return function (obj) {
        return obj.num + this.priceA + this.priceB
      }
    },
}

二、vue 组件

1. vue 组件与实例的关系

Vue 是由一个个实例构建而成的,一个组件就是一个 Vue 的实例,每个组件内部都可以写属性, 因此每一个组件都是一个 Vue 的实例。

2. mixin

juejin.cn/post/711456…

3. v-if 和 v-show 有什么区别?

  • v-if 是真正的条件渲染,会控制这个 DOM 节点的存在与否

  • v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS“display” 属性进行切换

  • 当我们需要经常切换某个元素的显示/隐藏时,使用 v-show 会更加节省性能上的开销;当只需要一次显示或隐藏时,使用 v-if 更加合理。

4. vue 中页面跳转传值的几种方式

①: router-link

例如:URL路径:http://localhost:8081/#/test?userid=1

注意to 前面一定要加 : ,冒号一定要记得加!记得加!加! to 后面 { 中的 name 值(这里是 userid)要与路由中的 name 值一致

<router-link :to="{path:'/test',query: {userid: id}}">跳转</router-link>
var id = this.$route.query.userid

②:this.$router.push()

使用 path+query URL路径:http://localhost:8081/#/selectCate?userid=1

var id = 1;
this.$router.push({path:'/selectCate',query:{userid:id}});

var id = this.$route.query.userid;

这里注意接收到的是字符串,但 id 是数字,所以需要转化一下:

var id = parseInt(this.$route.query.userid);

③:使用 name + params

URL路径:`http://localhost:8081/#/selectCate

var id = 1;
this.$router.push({name:'selectCate',params:{userid:id}});

var id = this.$route.params.userid;

总结:使用 query,传输的值会在 url 后面以参数的形式显示出来,可以刷新页面, 数据不变,而 params 不会,一刷新传的值就没了

④:使用 name + params刷新值没了的解决方法

在 route.js 中, 跳转后的路由配置项里 path 添加参数 key 值, 如下, 这样, this.$router.push({name:'zl', params:{id: 3}}) 跳转到 zl 这个页面,如何页面如何刷新,id 值都不会消息啦,都能 用 this.$route.params.id 获取到

{
  path: '/zl/:id',
  name: 'zl',
  component: zl
},

5. Vue 组件间通信有哪几种方式?

  1. 父子组件通信 [ props / $emit ]

  2. 父子组件通信 [ ref 与 parent/parent / children ]

    ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例 parent/parent / children:访问父 / 子实例

    ref 有三种用法:

    • ref 加在普通的元素上,用 this.ref.name 获取到的是 dom 元素
    • ref 加在子组件上,用 this.ref.name 获取到的是组件实例,可以使用组件的所有方法。
    • 如何利用 v-for 和 ref 获取一组数组或者dom 节点
    • ref 需要在 dom 渲染完成后才会有,在使用的时候确保dom已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。
    • 如果 ref 是循环出来的,有多个重名,那么 ref 的值会是一个数组 , 此时要拿到单个的 ref 只需要循环就可以了。
  3. 适用于 父子、隔代、兄弟组件通信 [ EventBus (emit/emit / on) ] 这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件, 从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

  4. 适用于 父子、隔代、兄弟组件通信 [ vuex ] Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

  5. 有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容, 我们可以使用一对 provide 和 inject. 父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据

    // 父组件:
    data() {
        return {
          arr: ['这是', '一些文本内容333']
        }
    },
    provide() {
        return {
          text: this.arr.join(',')
        }
    }
    
    // 子组件
    inject: ['text'],
    
    <span style="color: green;">son: {{text}}</span>
    

6. Class 与 Style 如何动态绑定?

一、Class 可以通过对象语法和数组语法进行动态绑定:

  1. 对象语法:
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
data: {
	isActive: true,
  	hasError: false
}

2.数组语法:

<div :class="[isActive ? activeClass : '', errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

二、Style 也可以通过对象语法和数组语法进行动态绑定

  1. 对象语法:
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
  activeColor: 'red',
  fontSize: 30
}

2.数组语法:

<div :style="[styleColor, styleSize]"></div>
data: {
  styleColor: {
     color: 'red'
   },
  styleSize:{
     fontSize:'23px'
  }
}

7. 谈谈你对 keep-alive 的了解?

概念:

keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 transition 相似,keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。

作用:

在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性。

原理:

在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染。

Props:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。其中 exclude 的优先级比 include 高;
  • max - 数字。最多可以缓存多少组件实例。

生命周期函数:

  1. activated
  • 在 keep-alive 组件激活时调用
  • 该钩子函数在服务器端渲染期间不被调用
  1. deactivated
  • 在 keep-alive 组件停用时调用
  • 该钩子在服务器端渲染期间不被调用
  • 被包含在 keep-alive 中创建的组件,会多出两个生命周期的钩子: activated 与 deactivated
  • 使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 activated 阶段获取数据,承担原来 created 钩子函数中获取数据的任务。
  • 注意: 只有组件被 keep-alive 包裹时,这两个生命周期函数才会被调用,如果作为正常组件使用,是不会被调用的,以及在 2.1.0 版本之后,使用 exclude 排除之后,就算被包裹在 keep-alive 中,这两个钩子函数依然不会被调用!另外,在服务端渲染时,此钩子函数也不会被调用。

图片.png

图片.png

8. Vue 中的 key 有什么作用?

需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点。作用 主要是为了高效的更新虚拟 DOM

9. vue slot

cn.vuejs.org/v2/guide/co…

简单来说,假如父组件需要在子组件内放一些 DOM,那么这些 DOM 是显示、不显示、在哪个地方显示、如何显示,就是 slot 分发负责的活。

1655453555123.jpg

10.v-if 和 v-for 的优先级

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。

所以,不推荐 v-if 和 v-for 同时使用。如果 v-if 和 v-for 一起用的话,vue 中的的会自动提示 v-if 应该放到外层去。

11.active-class 是哪个组件的属性

active-class 是 vue-router 模块的 router-link 组件中的属性,用来做选中样式的切换。children 数组来定义子路由

12. vue中 refs 与 ref

详情见:www.cnblogs.com/xumqfaith/p…

ref 有三种用法:

  • ref 加在普通的元素上,用 this.refs.name 获取到的是 dom 元素
  • ref 加在子组件上,用 this.refs.name 获取到的是组件实例,可以使用组件的所有方法。
  • 如何利用 v-for 和 ref 获取一组数组或者dom 节点

ref 注意:

  • ref 需要在dom渲染完成后才会有,在使用的时候确保dom已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。
  • 如果 ref 是循环出来的,有多个重名,那么 ref 的值会是一个数组 ,此时要拿到单个的 ref 只需要循环就可以了。

13. this.$nextTick()

当你修改了 data 的值然后马上获取这个 dom 元素的值,是不能获取到更新后的值,你需要使用 $nextTick 这个回调,让修改后的 data 值渲染更新到 dom 元素之后在获取,才能成功。

页面更新完以后再执行方法。

this.$nextTick() 方法主要是用在随数据改变而改变的 dom 应用场景中,vue 中 数据和dom 渲染由于是异步的,所以,要让 dom 结构随数据改变这样的操作都应该放进 this.$nextTick() 的回调函数中。

created() 中使用的方法时,dom 还没有渲染,如果此时在该钩子函数中进行 dom 赋值数据(或者其它 dom 操作)时无异于徒劳,所以,此时this.$nextTick() 就会被大量使用,而与 created() 对应的是 mounted() 的钩子函数则是在 dom 完全渲染后才开始渲染数据,所以在 mounted() 中操作 dom 基本不会存在渲染问题。

Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个异步队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

14. v-on可以监听多个方法吗?

可以,栗子:

<input type="text" v-on="{ input:onInput,focus:onFocus,blur:onBlur, }">

15. vue 自定义指令 ?

cn.vuejs.org/v2/guide/cu…

juejin.cn/post/707780…

16. vue 中 name 属性的作用

组件是有name属性的,匹配的就是组件的name。和vue-router中配置的name是不同的含义。

  • 当项目使用 keep-alive,可搭配组件name进行缓存过滤 配置<keep-alive>标签的 exclude 或者 include 属性做组件筛选

    实例:

    <div id="app"> 
        <keep-alive exclude="Detail">
            <router-view/>
        </keep-alive>
    </div>
    
  • DOM 做递归组件时在自身组件调用自身的时候,可以通过定义name的值进行递归调用

    实例:

    <div> 
        <div v-for="(item,index) of list" :key="index"> 
            <div> 
                <span class="item-title-icon"></span>{{item.title}} 
            </div> 
            <div v-if="item.children" > 
                <detail-list :list="item.children"></detail-list> 
            </div> 
        </div> 
    </div>
    
    <script>
    export default {
        name:'DetailList',//递归组件是指组件自身调用自身
        props:{
            list:Array
        }
    }
    </script>
    
  • 当你用 vue-tools 时 vue-devtools 调试工具里显示的组见名称是由 vue 中组件name决定的.

17. scoped 的实现原理以及scoped穿透的用法

什么是scoped:

  • 在Vue文件中的style标签上有一个特殊的属性,scoped。当一个 style 标签拥有scoped 属性时候,它的 css 样式只能用于当前的 Vue 组件,可以使组件的样式不相互污染。

  • 如果一个项目的所有 style 标签都加上了 scoped 属性,相当于实现了样式的模块化。

scoped的实现原理:

  • Vue中的 scoped 属性的效果主要是通过 PostCss 实现的。以下是转译前的代码:

    <style scoped>
    .example{
        color:red;
    }
    </style>
    
    <template>
        <div>scoped测试案例</div>
    </template>
    

    转译后:

    example[data-v-5558831a] {
      color: red;
    }
    
    <template>
        <div class="example" data-v-5558831a>scoped测试案例</div>
    </template>
    

    既: PostCSS 给一个组件中的所有 dom 添加了一个独一无二的动态属性,给 css 选择器额外添加一个对应的属性选择器,来选择组件中的 dom,这种做法使得样式只作用于含有该属性的 dom 元素(组件内部的dom)

scoped的渲染规则

  • 给 HTML 的 dom 节点添加一个不重复的 data 属性(例如: data-v-5558831a)来唯一标识这个 dom 元素

  • 在每句 css 选择器的末尾(编译后生成的css语句)加一个当前组件的 data 属性选择器(例如:[data-v-5558831a])来私有化样式

scoped穿透:

  • scoped 看起来很好用,当时在 Vue 项目中,当我们引入第三方组件库时(如使用 vue-awesome-swiper 实现移动端轮播),需要在局部组件中修改第三方组件库的样式,而又不想去除 scoped 属性造成组件之间的样式覆盖。这时我们可以通过特殊的方式穿透 scoped。

stylus的样式穿透 使用>>>:

```
`外层 >>> 第三方组件`

`     ``样式`

`     ` 

`.wrapper >>> .swiper-pagination-bullet-active`

` ``background: #fff`
```

sass和less的样式穿透 使用/deep/

`外层 /deep/ 第三方组件 {`
`    ``样式`
`}`

`.wrapper /deep/ .swiper-pagination-bullet-active{`
`  ``background: #fff;`
`}`

在组件中修改第三方组件库样式的其它方法:

  • 在 vue 组件中不使用 scoped 属性在 vue 组建中使用两个 style 标签,一个加上scoped 属性,一个不加 scoped 属性,把需要覆盖的 css 样式写在不加 scoped 属性的 style 标签里, 建立一个reset.css(基础全局样式)文件,里面写覆盖的 css 样式,在入口文件main.js 中引入

三、vue 数据相关

1. 组件中 data 为什么是一个函数?

为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里, data 可以直接是一个对象?

  • 因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响

  • 如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

2. 怎样理解 Vue 的单向数据流?

  • 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到 子组件中,但是反过来则不行。 这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

  • 额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

3. vm.$set

直接给一个数组项赋值,Vue 能检测到变化吗?不能

由于 JavaScript 的限制,vue故意屏蔽了这些赋值响应,为了提供性能提高,Vue 不能检测到
以下数组的变动:
当你利用索引直接设置一个数组项时,
例如:vm.items[indexOfItem] = newValue

当你修改数组的长度时,
例如:vm.items.length = newLength


为了解决第一个问题,Vue 提供了以下操作方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

为了解决第二个问题,Vue 提供了以下操作方法:
vm.items.splice(newLength)

4. vm.$set() 解决对象新增属性不能响应的问题 ?

如果目标是数组,直接使用数组的 splice 方法触发相应式;

如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加getter 和 setter 的功能所调用的方法)

5.delete 和 Vue.delete 删除数组的区别

delete 只是被删除的元素变成了 empty/undefined, 其他的元素的键值还是不变。Vue.delete 直接删除了数组 改变了数组的键值。

var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[1]
console.log(a)   // [1, empty, 3, 4]
this.$delete(b,1)
console.log(b)   // [1, 3, 4]

四、vue-router 路由

1. vue-router 路由模式有几种?

vue路由的两种模式

  • hash模式,其原理是 onhashchange 事件,可以在window对象上监听这个事件;

  • history模式,可利用history.pushState的 API 来完成 URL 跳转。

  • vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

  • 如果不想要很丑的 hash,因为hash带“#”号,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

    const router = new VueRouter({
      mode: 'history',
      routes: [...]
    })
    
  • history 模式需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

  • 所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

    后端nginx配置
    location / {
      try_files $uri $uri/ /index.html;
    }
    

2. vue 路由懒加载

一、为什么要使用路由懒加载

为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。

二、定义

 > 懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载。

三、使用

常用的懒加载方式有两种:即使用 vue 异步组件 和 ES 中的 import

1、未用懒加载,vue中路由代码如下
 import Vue from 'vue'
 import Router from 'vue-router'
  import HelloWorld from '@/components/HelloWorld'
  Vue.use(Router)
     export default new Router({
          routes: [
             {
                 path: '/',
                 name: 'HelloWorld',
                 component:HelloWorld
              }
           ]
  })

2、vue异步组件实现懒加载
    方法如下:component:resolve=>(require(['@/components/backStage/home']),resolve)
import Vue from 'vue'
import Router from 'vue-router'
  /* 此处省去之前导入的HelloWorld模块 */
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: resolve=>(require(["@/components/HelloWorld"],resolve))
    }
  ]
})

3ES 提出的import方法,(------最常用------)
  方法如下:const HelloWorld = ()=>import('需要加载的模块地址')
  不加 { } ,表示直接returnimport Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

const HelloWorld = ()=>import("@/components/HelloWorld")
export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component:HelloWorld
    }
  ]
})

3.Vue-router 跳转和 location.href 有什么区别

使用 location.href='/url' 来跳转,简单方便,但是刷新了页面;

使用 history.pushState('/url'),无刷新页面,静态跳转;
引进 router,然后使用 router.push('/url') 来跳转,使用了 diff 算法,实现了按需加载,减少了dom 的消耗。 其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为 vue-router 就是用了history.pushState(),尤其是在 history 模式下。

4.怎么定义 vue-router 的动态路由? 怎么获取传过来的值

router 目录下的 index.js 文件中,对 path 属性加上 /:id 。 使用 router 对象的 params.id

5. $router$route 的区别

详情:www.jianshu.com/p/758bde4d9…

$router: 是 vueRouter 的实例,在 script 标签中想要导航到不同的URL,使用 $router.push 方法。 返回上一个历史 history 用 $router.to(-1)

$route$route 为当前 router 跳转对象,可以获取当前路由的 name, path, query, parmas等。

6. Vue里面 router-link 在电脑上有用,在安卓上没反应怎么解决?

Vue 路由在 Android 机上有问题,babel问题,安装 babel polypill 插件解决。

7. Vue2 中注册在 router-link 上事件无效解决方法

使用@click.native。原因:router-link 会阻止 click 事件,.native 指直接监听一个原生事件。

8. vue-router 是什么?它有哪些组件

vue 用来写路由一个插件。router-linkrouter-view

9. vue-router 有哪几种路由守卫

vue 有三种,分别为:

  1. 全局守卫

  2. 路由独享守卫

  3. 组件内的守卫

a. 全局守卫:

1. 路由的前置守卫

所谓全局路由守卫,就是小区大门,整个小区就这一个大门,你想要进入其中任何一个房子,都需要经过这个大门的检查
全局路由守卫有个两个:一个是全局前置守卫,一个是全局后置守卫 , 钩子函数按执行顺序包括beforeEachbeforeResolveafterEach三个。

[beforeEach]:在路由跳转前触发,参数包括to,from,next(参数会单独介绍)3个,这个钩子作用主要是用于登录验证,就是路由还没跳转前告知,跳转后再通知就晚了。

[beforeResolve]:在组件被解析后,这个钩子函数和beforeEach类似,也是路由跳转前触发,参数也是to,from,next三个。

[afterEach]:和beforeEach相反,它是在路由跳转完成后触发,参数包括to,from没有next,它发生在beforeEach和beforeResolve之后,beforeRouteEnter之前。

接下来是用路由前置守卫实现登录的例子:

router.beforeEach((to,from,next)=>{
    // state.loginData是自己模拟的数据 
    // 如果没有数据并且不再登录页就跳回登录页
    if(!store.state.loginData && to.name!='login'){
        next('/login')
        return
    }
    // 如果有数据并且再登录页就进入首页
    if(store.state.loginData && to.name=='login'){
      next('/')
        return
    }
    next()
})

注:如果出现报错:
maximum call stack size exceeded 堆栈溢出 死循环

2.后置钩子函数:后置钩子函数没有next

// router.afterEach((to,from,next)=>{
//   console.log(to)
//   console.log(from)
//   console.log(next) //没有
// })

b组件路由守卫

在页面中和 data 同级, 组件路由守卫就是在组件内执行的钩子函数,类似于组件内的生命周期钩子函数按执行顺序为 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave 三个。

[beforeRouteEnter]:路由进入之前调用,参数包括to,from,next。该钩子函数在全局守卫beforeEach 和独享守卫 beforeEnter 之后,全局 beforeResolve 和全局 afterEach 之前调用。

[beforeRouteUpdate] :在当前路由改变时,并且该组件被复用时调用,可以通过 this 访问实例。参数包括 to,from,next。当前路由 query 变更时,该守卫会被调用

[beforeRouteLeave]:导航离开该组件的对应路由时调用,可以访问组件实例 this,参数包括 to,from,next。

路由进入之前
beforeRouteEnter((to,from,next){
    进入路由前什么也没触发
    所以在写的时候注意可不可以进入这个路由
	如果不可以就 next()
})
路由更新
beforeRouteUpdate((to,from,next){
    路由更新页面不变时触发
})
路由离开
beforeRouteLeave
//路由离开时触发

c、路由独享守卫

是在配置路由的地方写的,和 component、name 或 path 同一级, 目前它只有一个钩子函数beforeEnter

image.png

d. 路由守卫钩子三个参数

to:目标路由对象;

from:即将要离开的路由对象;

next:它是最重要的一个参数,调用该方法后,才能进入下一个钩子函数。

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

先来看一下钩子函数执行后输出的顺序吧

图片.png

五、vue 数据绑定和监听

1. v-model 双向数据绑定的原理?

v-model 的 特点是 数据双向绑定, 原理是我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性

并抛出不同的事件

1.inputtextarea 元素使用 value 属性和 input 事件;

2.checkboxradio 使用 checked 属性和 change 事件;

3.select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:
<input v-model='something'>
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">

image.png

代码实现:

<body>
  <input type="text" id="username"></br>

  显示值:<span id="uName"></span>

  <script>
    let obj = {};
    Object.defineProperty(obj, 'username', {
      get: function() {
        console.log('取值')
      },
      set: function(val) {
        document.getElementById('uName').innerText = val;
      }
    })

    document.getElementById('username').addEventListener('keyup', function () {
      obj.username = event.target.value;
    })
  </script>
</body>

2. 组件上使用 v-model 原理

1.给子组件的 value 传个变量

2.监听子组件的 input 事件,并且把传过来的值赋给父组件的变量

具体用法见: juejin.cn/editor/draf…

3. Vue 是如何实现数据双向绑定的?

如果被问到 Vue 怎么实现数据双向绑定,大家肯定都会回答 通过 Object.defineProperty() 
对数据进行劫持,但是 Object.defineProperty() 只能对属性进行数据劫持,不能对整个对象进行劫持。
同理无法对数组进行劫持,但是我们在使用 Vue 框架中都知道,Vue 能检测到对象和数组
(部分方法的操作)的变化,那它是怎么实现的呢?我们查看相关代码如下:
/**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])  // observe 功能为监测数据的变化
    }
  }

  /**
   * 对属性进行递归遍历
   */
  let childOb = !shallow && observe(val) // observe 功能为监测数据的变化


通过以上 Vue 源码部分查看,我们就能知道 Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 
Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。

4. Vue 响应式原理 ?

1.当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 gettersetter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新), 并且在内部追踪依赖,在属性被访问和修改时通知变化。

2.每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性 (进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时, setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件 重新渲染。

3.一句话总结:vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调

响应式:index.js 代码如下

// 如何实现 vue2.0响应式?
// 通过发布订阅模式 + 数据双向绑定 = 可实现基本的响应式

// 1. 订阅器模型
let Dep = {
  clientList: {},
  // 添加订阅者:
  listen: function(key, fn) {
    // if(!this.clientList[key]) {
    //   this.clientList[key] = [];
    // }
    // this.clientList[key].push(fn);
    (this.clientList[key] || (this.clientList[key] = [])).push(fn)
  },
  // 推送方法
  trigger: function() {
    let key = Array.prototype.shift.call(arguments),
        fns = this.clientList[key];
    if(!fns || fns.length === 0) {
      return;
    }
    for(let i = 0, fn; fn=fns[i++];) {
      fn.apply(this, arguments)
    }
  }

}

// 数据劫持
let dataHiJack = function({data, tag, dataKey, selector}) {
  let value = '',
    el = document.querySelector(selector);
  Object.defineProperty(data, dataKey, {
    get: function() {
      console.log('取值')
      return value;
    },
    set: function(val) {
      console.log('设置值')
      value = val;
      // 数据变化了
      Dep.trigger(tag, val)
    }
  })

  // 添加订阅者
  Dep.listen(tag, function(text) {
    el.innerHTML = text;
  })
}

响应式 html 代码如下

<body>
  <div id="app">
    响应式一:<p class="box_1"></p>
    响应式一:<p class="box_2"></p>
  </div>

  <script src="./index.js"></script>
  <script>
    let dataObj = {};
    dataHiJack({
      data: dataObj,
      tag: 'view-1',
      dataKey: 'one',
      selector: '.box_1'
    })
    dataHiJack({
      data: dataObj,
      tag: 'view-2',
      dataKey: 'two',
      selector: '.box_2'
    })
    dataObj.one = 'xxl'
    dataObj.two = 'wkp'
  </script>
</body>

5.响应式是懒惰性的

1655453555112.jpg

六、修饰符

1. 修饰符

1.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >

2.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加number修饰符

<input v-model.number="age" type="number">

3.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<div id='other'>
    <input v-model.trim='trim'>
    <p ref='tr'>{{trim}}</p>
    <button @click='getStr'>获取</button>
</div>

4.sync

.sync 修饰符可以实现子组件与父组件的双向绑定,并且可以实现子组件同步修改父组件的值。

.sync修饰符本质:

正常父传子:
<son :a="num" :b="num2"></son> 

加上sync之后父传子: <son :a.sync="num" .b.sync="num2"></son> 

它等价于 <son :a="num" @update:a="val=>num=val" :b="num2" @update:b="val=>num2=val"></son> 

相当于多了一个事件监听,事件名是update:a,回调函数中,会把接收到的值赋值给属性绑定的数据项中。

这里面的传值与接收与正常的父向子传值没有区别,唯一的区别在于往回传值的时候$emit所调用的事件名必须是update:属性名 ,事件名写错不会报错,但是也不会有任何的改变,这点需要多注意。

sync🌰例子 父组件代码

<template>
  <div>
    <my-sync :message.sync="age"></my-sync>
  </div>
</template>

<script>
import mySync from '@/components/mySync.vue'
export default {
  name: "",
  data () {
    return {
      age: 0
    }
  },
  components: {
    mySync,
  }
}
</script>

子组件

<template>
  <div>
    <p>message的值是:{{ message }} </p>
    <button @click="addNum">值+1</button>
  </div>
</template>

<script>
export default {
  name: "",
  props: {
    message: {
      type: Number,
      default: ''
    }
  },
  methods: {
    addNum() {
      this.$emit('update:message', this.message + 1)
    }
  }
}
</script>

.sync与v-model区别

相同点:都是语法糖,都可以实现父子组件中的数据的双向通信。

区别点:格式不同: v-model=“num”, :num.sync=“num”

v-model: @input + value
:num.sync: @update:num

另外需要特别注意的是:  v-model只能用一次;.sync可以有多个。

2. 事件修饰符

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管
我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

注意:

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止
所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

3. 按键修饰符

常需要检查常见的键值。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:
全部的按键别名:
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right

七、vue 和 react , Mvvm, vue3.0

1. mvvm 框架是什么

vue 是实现了双向数据绑定的 mvvm 框架,当视图改变更新模型层,当模型层改变更新视图层。在 vue 中,使用了双向绑定技术,就是 View 的变化能实时让 Model 发生变化,而 Model 的变化也能实时更新到 View。

MVVM与MVC的区别有:1、mvvm各部分的通信是双向的,而mvc各部分通信是单向的;2、mvvm是真正将页面与数据逻辑分离放到 js 里去实现,而mvc里面未分离。

2. 谈谈你对 MVVM 开发模式的理解?

MVVM 分为 Model、View、ViewModel 三者。

Model 代表数据模型,数据和业务逻辑都在 Model 层中定义;

View 代表 UI 视图,负责数据的展示;

ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;

Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的, Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的 数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。

这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注

3. 谈谈 vue 和 react ?

一、两者的本质区别

vue - 本质是 MVVM 框架,由 MVC 发展而来

React - 本质是前端组件化框架,由后端组件化发展而来

vue - 使用模板(最初由 angular 提出)

React - 使用 JSX(jsx不是react独有的,已经成了一种标准)

React 本身就是组件化,没有组件化就不是 React

vue 也支持组件化,不过是在 MVVM 上的扩展

二、两者共同点

都支持组件化

都是数据驱动视图

4. 对于 vue3.0 特性你有什么了解的吗?

vue2 的双向数据绑定是利用ES5 的一个 API ,Object.definePropert()对数据进行劫持 结合 发布订阅模式的方式来实现的。
vue3 中使用了 es6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每一个对象都包一层 Proxy,通过 Proxy 监听属性的变化,从而实现对数据的监控。

Vue 3.0 的目标是让 Vue 核心变得更小、更快、更强大,因此 Vue 3.0 增加以下这些新特性:

(0)增加了摇树功能,比如一个js,里面定义 a 和 b, 但是只导出了 a, b 没有导出, 这时 b 不会被打包。,代码体积变小了

(1)监测机制的改变

3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中 基于 Object.defineProperty 的实现所存在的很多限制:

  1. 只能监测属性,不能监测对象

  2. 检测属性的添加和删除;

  3. 检测数组索引和长度的变更;

  4. 支持 Map、Set、WeakMap 和 WeakSet。 (2)Vue 3.0 响应式实现:

let obj = {
  name: 'xxl',
  age: 18
}
let p = new Proxy(obj, {
  // 读取属性
  get: function (target, property) {
    console.log(`读取了 ${target}${property} 的属性`);
    return target[property]
  },
  // 添加或修改属性
  set: function (target, property, value) {
    console.log(`添加或修改了 ${target}${property} 的属性`);
    target[property] = value;
  },
  // 删除属性
  deleteProperty: function (target, property) {
    console.log(`删除了 ${target}${property} 的属性`);
    return delete target[property]
  }
})
p.name = 'gg';
console.log(p, obj)

image.png

5. Proxy 替代 defineProperty?

1655453555098.jpg

6.vue 和 jQuery 的区别

jQuery是使用选择器($)选取 DOM 对象,对其进行赋值、取值、事件绑定等操作,

其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。比如需要获取label标签的内容:$("lable").val();,它还是依赖 DOM 元素的值。

Vue 则是通过 Vue 对象将数据和 View 完全分离开来了。对数据进行操作不再需要引用相应的 DOM 对象, 可以说数据和 View 是分离的,他们通过 Vue 对象这个 vm 实现相互的绑定。这就是传说中的 MVVM。

7. vue 如何实现服务端异步请求

www.cnblogs.com/chy18883701…

八、服务端渲染、SPA 单页面

1. 使用过 Vue SSR (服务端渲染)吗?说说 SSR?**

1.Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后 将这些静态标记"激活"为客户端上完全可交互的应用程序。

服务端渲染SSR大致的意思:就是 vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。

服务端渲染 SSR 的优缺点如下:

(1)服务端渲染的优点

更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;

更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

(2) 服务端渲染的缺点:

更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些\外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;

更多的服务器负载: 在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。

2. 说说你对 SPA 单页面的理解?

首先:SPA的英文是single-page application ,整个项目中只有一个页面。
其次,单页面的实现思路:
就是在 Web 页面初始化时加载所有的 HTML、JavaScript 和 CSS,页面的内容的变化,靠动态
创建dom。
也就是一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新请求(加载)或跳转;
取而代之的是利用路由机制实现 HTML 内容的动态变换,UI 与用户的交互,避免页面的重新加载。

优点:
1. 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
2. 基于上面一点,SPA 对服务器的压力小;
3. 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:
1. 初次加载耗时多,为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将
 JavaScript、CSS 统一加载,部分页面按需加载;
2. 前进后退路由管理,由于单页应用在一个页面中显示所有的内容,所以不能使用
浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;

3. SEO 难度较大,由于所有的内容都在一个页面中动态替换显示,所以在 SEO上其有着天然的弱势


SPA首屏加载慢如何解决
答:安装动态懒加载所需插件;使用CDN资源。

3. 预渲染

blog.csdn.net/qq_38290251…

九、vue 文件 及 项目优化

1. 你有对 Vue 项目进行哪些优化?

1)代码层面的优化

v-if 和 v-show 区分使用场景
computed 和 watch 区分使用场景
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
长列表性能优化
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染 SSR or 预渲染
发包到 npm 
全局组件封装引入
_____________________________________________________________________

(2)Webpack 层面的优化

Webpack 对图片进行压缩
减少 ES6 转为 ES5 的冗余代码
提取公共代码
模板预编译
提取组件的 CSS
优化 SourceMap
构建结果输出分析
Vue 项目的编译优化

_____________________________________________________________________

(3)基础的 Web 技术的优化

开启 gzip 压缩
浏览器缓存
CDN 的使用
使用 Chrome Performance 查找性能瓶颈

1.如果一个对象后续不需要进行修改的话,可以通过 Object.freeze 把这个对象冻结起来,vue.js 源码会判断 这个对象是否冻结,如果是冻结状态,直接不用进行再次循环了,减少性能的消耗。

let data = new Array(10000).fill(null).map((ele, i) => ele=i);
// 冻结无需改变的数据,避免框架继续遍历其属性,完成响应式(双向数据绑定)
this.list = Object.freeze(data) ;

2.vue.cli 项目中 src 目录每个文件夹和文件的用法

assets 文件夹是放静态资源;components 是放组件;router 是定义路由相关的配置; app.vue 是一个应用主组件;main.js 是入口文件。

3. vue-loader是什么?使用它的用途有哪些?

vue 文件的一个加载器,将 template/js/style 转换成 js 模块。

用途:js 可以写 es6、style 样式可以 scss 或 less、template 可以加 jade 等

4.assets 和 static 的区别

相同点:
assets 和 static 两个都是存放静态资源文件。项目中所需要的资源文件图片,
字体图标,样式文件等都可以放在这两个文件下,这是相同点

不相同点:
assets 中存放的静态资源文件在项目打包时,也就是运行 npm run build 时会将 assets  
中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。
而压缩后的静态资源文件最终也都会放置在 static 文件中跟着 index.html 一同上传至服务器。
static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,
直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 static
中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 assets 中打包后的文件提交
较大点。在服务器中就会占据更大的空间。
建议:将项目中 template 需要的样式文件 js 文件等都可以放置在 assets 中,走打包这一流程。
减少体积。而项目中引入的第三方的资源文件如 iconfoont.css 等文件可以放置在 static 中,
因为这些引入的第三方文件已经经过处理,我们不再需要处理,直接上传。

5.vue 项目是打包了一个 js 文件,一个 css 文件,还是有多个文件

根据 vue-cli 脚手架规范,一个 js 文件,一个 CSS 文件。

6. 封装全局组件方法

juejin.cn/post/711011…

7. 发布组件到 npm 官网

juejin.cn/post/711011…

十、Vuex

1. Vuex 总结

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store (仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。 
     这样使得我们可以方便地跟踪每一个状态的变化。
     this.$store.commit('updateBusinessobj', obj);

主要包括以下几个模块:
1. State => 基本数据,定义了应用状态的数据结构,可以在这里设置默认的初始状态。

2. Getter => 从基本数据派生的数据,允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅
   是将 store 中的 getter 映射到局部计算属性, 不会改变 源数据。

3. Mutation => 是唯一更改 store 中状态的方法,且必须是同步函数。

4. Action => 像一个装饰器,包裹 mutations,使之可以异步。用于提交 mutation,而不是直接
   变更状态,可以包含任意异步操作。

5. Module => 模块化Vuex,允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

 created () {
        //在页面加载时读取sessionStorage里的状态信息
        if (sessionStorage.getItem("store")) {
            this.$store.replaceState(Object.assign({}, 
            this.$store.state, JSON.parse(sessionStorage.getItem("store"))))
        }

        //在页面关闭刷新时将vuex里的信息保存到sessionStorage里
        window.addEventListener("beforeunload", () => {
            sessionStorage.setItem("store", JSON.stringify(this.$store.state))
        })
},

2. mutation 和 action 的详细区别

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})
1、流程顺序
“相应视图—>修改State”拆分成两部分,视图触发Action,Action再触发Mutation。

2、角色定位
基于流程顺序,二者扮演不同的角色。
Mutation:专注于修改 State,理论上是修改  。
Action:业务代码、异步请求。

3、限制
角色不同,二者有不同的限制。
Mutation:必须同步执行。
Action:可以异步,但不能直接操作 State。

总结:
action 的功能和 mutation 是类似的,都是去变更 store 里的 state


action 和 mutation 有两点不同:

1、action 主要处理的是异步的操作,mutation 必须同步执行,而 action 就不受这样的限制,也就是说 action 中我们既可以处理同步,也可以处理异步的操作

2、action 改变状态,最后是通过提交 mutation

3. 使用 Vuex 统一管理状态的好处、和用处

Vuex 统一管理状态的好处:

能够在 vuex 中集中管理共享的数据,易于开发和后期维护

能够高效地实现组件之间的数据共享,提高开发效率

存储在vuex中的数据都是响应式的,能够实时保持数据与页面的同步

用处:什么样的数据适合存储到 Vuex 中:

一般情况下,只有组件之间共享的数据,才有必要存储到vuex中;

对于组件中的私有数据,依旧存储在组件自身的data中即可。

4. vuex 的安装

1.安装 vuex 依赖包

npm i vuex - save

2.在 store.js 中 导入 vuex 包

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

3.创建 store 对象

const store = new Vuex.Store({
    // state 中存放的就是全局共享的数据
    state: {

    },
    mutations: {

    },
    actions: {

    },
    getters: {

    }
})
// 导出 store
export default store

4.main.js 引入 store

import store from './store/index.js'

// 4. 将 store 对象挂载到 vue 实例中
new Vue({
    el: '#app',
    render: h=>h(app),
    router,
    // 将创建的共享数据对象,挂载到 Vue 实例中
    // 所有的组件,就可以直接从 store 中 获取全局的数据 了
    store,
})

5. 组件访问 state 中的数据的两种方式

1.通过 this.$store.state 访问

6b62d2240833bf58f6a2ff2a559d0c5.png

2.mapState 函数访问

a1056720a8e787ed33308f70974a99a.png

6. Mutations 用于变更 state 中的数据

Mutation 用于变更 Store 中的数据。

只能通过 mutation 变更 Store 数据,不可以直接操作 Store 中的数据。

通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监控所有数据的变化。

1. 触发 Mutations 函数 无参数

// 定义 Mutation
const store = new vuex.store({
    state: { 
        count: 0
    },
    mutations: {
        add(state) {
            // 变更状态 
            state.count++
        }
    }
})

methods: {
    // 触发mutation 
    handle1() {
        // 触发 mutations 的第一种方式
        this.$store.commit('add')
    }
}

2. 触发 Mutations 函数 有参数

// 定义 Mutation
const store = new vuex.store({
    state: { 
        count: 0
    },
    mutations: {
        add(state, params) {
            // 变更状态 
            state.count += params;
        }
    }
})

methods: {
    // 触发mutation 
    handle1() {
        // 触发 mutations 时在第二个参数里传递要的值
        // commit 的作用,专门用来触发 某个 mutation 函数
        this.$store.commit('add', 3)
    }
}

3. mapMutations 来调用

this.$store.commit() 是触发 mutations 的第一种方式,触发 mutations 的第二种方式:

// 1.从vuex 中按需导入 mapMutations函数
import { mapMutations } from 'vuex!

通过刚才导入的mapMutations函数,将需要的mutations函数,映射为当前组件的methods 方法:

methods:{
    // 将指定的 mutations 函数,映射为当前组件的 methods 函数 
    ...mapMutations(['add', 'addn']),
    addHandle() {
        this.add()
    }
}

4. Mutation 为什么不能使用异步函数

image.png 重点事情: 为什么必须是同步更新?

Mutation 异步函数 会 造成状态改变的不可追踪

造成状态改变的不可追踪
造成状态改变的不可追踪

可以看到在 Mutation 中使用异步和同步最终页面的总和都是正确的,也就是说在Mutation 中使用异步不会对数据造成丢失和其他影响。然而我们注意 Vue Devtools 显示结果,当我们去查看多次 Mutation 状态时,发现同步的显示 Ok,异步的 Count 显示为 0 和我们预期结果不一致,所以会造成状态改变的不可追踪,所以官方说我们Mutation 是同步的!

5.Mutation 才能修改 state 中的数据

5.只有 mutations 中定义的函数,才有权利来变更 state 中的函数.jpg

7.actions 异步任务用于辅助 mutations 来异步执行变更 state 中的数据

1.actions 定义

Action用于处理异步任务。如果通过异步操作变更数据,必须通过 Action,而不能使用 Mutation,但是在 Action 中还是要通过触发 Mutation 的方式间接变更数据。

// 定义 Action
const store = new Vuex.store({
    //...省略其他代码
    mutations: {
        add(state) {
            state.count++
        }
    },

    actions: {
        addAsync(context) {
            setTimeout(() => {
                context.commit('add')
            }, 1000);
        }
    }
})

// 触发 Action 
methods: {
    handle(){
        // 触发 actions 的第一种方式, dispatch 函数,专门用来触发 action
        this.$store.dispatch('addAsync')
    }
}

2.actions 中,不能直接更改 state 中的数据

actions: {
    addAsync(context){
        setTimeout(() => {
            // 在 actions 中,不能直接修改 state 中的数据;
            // 必须通过 context.commit()触发某个 mutation 才行 I 
            context.commit('add')
        }, 1000)
    }
}

3. 触发 action 异步任务 携带参数

// 定义 Action
const store = new Vuex.Store({
    // ...省略其他代码
    mutations: {
        addNumN(state, params) {
            state.count += params;
        },
    },
    actions: {
        addNAsync(context, params) {
            setTimeout(() => {
                context.commit('addNumN', params)
            }, 1000);
        },
    },
})

// 触发 Aciton
methods: {
    addHandle4() {
        // 触发 Aciton时,第二个参数为要的 值
        this.$store.dispatch('addNAsync', 3)
    },
}

4. mapActions 触发 actions

this.$store.dispatch() 是触发 actions 的第一种方式,触发 actions 的第二种方式:

1.从 vuex 中按需导入 mapActions 函数

import { mapActions } from 'vuex'

2.通过刚才导入的 mapActions 函数,将需要的 actions 函数,映射为当前组件的 methods 方法: 将指定的 actions 函数,映射为当前组件的 methods 函数

methods:{
    ...mapActions(['addasync', 'addnAsync']),
    subHandle2() {
        this.addasync(3)
    }
}

8. getters

Getter 用于对 Store 中的数据进行加工处理形成新的数据。

Getter 可以对 Store 中已有的数据加工处理之后形成新的数据,类似 Vue 的计算属性。 Store 中数据发生变化,Getter 的数据也会跟着变化。Getter 不会影响 state 中的源数据

1.定义 getter

// 定义 Getter
const store = new vuex.store({
    state: {
        count: 0
    },
    getters: {
        showNum: state => {
            return `当前最新的数量是【' + state.count + '】!`
        }
    }
})

2.使用getters的第一种方式

this.$store.getters.名称
例如:this.$store.getters.showNum

3.使用getters的第二种方式:

import { mapgetters } from 'vuex'
computed:{
    ...mapGetters(['showNum'])
}