Vue面试题(二)

8,810 阅读16分钟

六、 vue-router路由高频考题

1. vue-router是什么?

这里的路由就是SPA(单页应用)的路径管理器,vue-router是Vue.js官方的路由插件,它和Vue是深度集成的,适合用于构建单页面应用。Vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。

路由模块的本质:就是建立起url和页面之间的映射关系

至于我们为啥不能用a标签,这是因为用Vue做的都是单页应用(当你的项目准备打包时,运行npm run build时,就会生成dist文件夹,这里面只有静态资源和一个index.html页面),所以你写的标签是不起作用的,你必须使用vue-router来进行管理

2. vue-router的实现原理

SPA:单一页面应用程序,只有一个完整的页面;它在加载页面的时候,不会加载整个页面,而是只更新某个指定容器中的内容。

单页面应用(SPA)的核心之一:更新视图而不重新请求页面;vue-router在实现单页面前端路由时,提供了两种方式:Hash模式和History模式,根据mode参数来决定采用哪一种方式。

2.1 Hash模式

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

hash(#)是URL的锚点,代表的是网页中的一个位置,单单改变#后面的部分,浏览器只会滚动到相应的位置,不会重新加载网页,也就是说 hash出现在URL中,但不会被包含在http请求中,对后端完全没有影响,因此改变hash不会重新加载页面;

同时每一次改变#后面的部分,都会在浏览器的访问历史中增加一个记录,使用“后退”按钮,就可以回到上一个位置;所以说 Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。hash模式的原理是通过location.hash = "/hashpath"的方式修改浏览器的hash值,通过onhashchange 事件(监测hash值变化),可以在window上监听这个事件

2.2 History模式

由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'"

这种模式充分利用了html5 history interface 中新增的pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求。可以通过监听popstate事件监听history变化,也就是点击浏览器的前进或者后退功能时触发。

//main.js文件中
const router = new VueRouter({
    mode: 'history',
    routes: [...]
})

当你使用 history 模式时,URL 就像正常的 url,例如 yoursite.com/user/id,

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 oursite.com/user/id 就会返回 404 ,这就不好看了。 所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html页面,这个页面就是你 app 依赖的页面。

export const routes = [
{path: "/", name: "homeLink", component:Home}
{path: "/register", name: "registerLink", component: Register},
{path: "/login", name: "loginLink", component: Login},
{path: "*", redirect: "/"}]

此处就设置如果URL输入错误或者是URL 匹配不到任何静态资源,就自动跳到到Home页面

2.3使用路由模块来实现页面跳转的方式

  • 直接修改地址栏
  • this.$router.push("路由地址")
  • < router-link :to="路由地址"></ router-link> 样式类似于a标签

3. vue-router使用方式

  1. 下载 npm i vue-router -S
  2. 在main.js中引入 import VueRouter from 'vue-router'
  3. 安装插件 Vue.use(VueRouter)
  4. 创建路由对象并配置路由规则 let router=new VueRouter({router: [{path:'/home',component:Home}]})
  5. 将其路由对象传递给路由实例,options中加入 router:router
  6. 在app.vue中留位置 <router-view></router-view>

4. vue-router参数传递

声明式导航 <router-link :to="">和编程式的导航 router.push(...)都可以传参,接下来介绍前者的传参方式,同样的规则也适用于编程式的导航

<router-link :to="{name:xxx,params:{key:value}}">valueString</router-
link>
<!--或者-->
this.$router.push({name:xxx,params:{key:value}})

params传参(不显示在路由)

路由必须配置name:

{
  path: '/child,
  name: 'Child',
  component: Child
}

传递参数:通过name


//父路由编程式传参(一般通过事件触发)
this.$router.push({
    name:'Child',
    params:{
    	id:123
    }
})

使用 $route.params.id来进行接收

params传参(显示在路由)

在路由中如下配置:

 //接收参数的页面路由
 {
     path:'/params/:newsId/:newsTitle',
     component:Params
 }

传递参数:

//父路由编程式传参(一般通过事件触发)
this.$router.push({
    path:'/params/newsIdValue/newsTitleValue',
})

使用 $route.params.newsId来进行接收

query传参

<router-link :to="{ name:'Query',query: { queryId: status }}" >
router-link跳转Query
</router-link>

<!--或者-->
this.$router.push({ name:'Query',query: { queryId: status }})

对应路由配置:

{
    path: '/query',
    name: 'Query',
    component: Query
}

于是我们可以获取参数:

this.$route.query.queryId

params和query两种传参方式的区别

  • 区别一:

    使用params传参(路径必须使用name指定):

    this.$router.push({
        name:"detail", //params对应的是name,并且name不能加"/",不能使用
        params:{
            name:'nameValue',
            code:10011
        }
    });
    

    使用query传参(路径使用name或者path都可以)

    this.$router.push({
        name:"detail", (path:"/detial")
        query:{
            name:'nameValue',
            code:10011
        }
    });
    
  • 区别二:query更加类似于我们ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显

  • 区别三:获取值的方法不同

  • 区别四:query传值页面刷新数据还在,而params传值不在路由显示的会消失,在路由显示的不会消失

5. vue-router配置子路由(二级路由)

在User中加入 标签,给子模板提供插入位置

<template>
    <div class="hello">
        <h1>{{ msg }}</h1>
        <router-view></router-view>
    </div>
</template>

之后将 Profile和Posts两个组件的路由写在 User的children中

routes: [
    {
        path: '/',
        name: 'User',
        component: User,
        children: [
            //子路由的<router-view>User.vue中出现
            {path: '/Profile', name: 'Profile', component: Profile},
            {path: '/Posts', name: 'Posts', component: Posts}
        ]
    }
]

6. 单页面多路由区域操作

在一个页面里我们有两个以上<router-view>区域,我们通过配置路由的js文件,来操作这些区域的内容

  1. App.vue文件,在<router-view>下面新写了两行<router-view>标签,并加入了些CSS样式
<template>
    <div id="app">
        <img src="./assets/logo.png">
        <router-link :to="{name:'HelloWorld'}"><h1>H1</h1></router-link>
        <router-link :to="{name:'H1'}"><h1>H2</h1></router-link>
        <router-view></router-view>
        <router-view name="left" style="float:left;width:50%;background-
        color:#ccc;height:300px;"/>
        <router-view name="right" style="float:right;width:50%;background-
        color:yellowgreen;height:300px;"/>
    </div>
</template>
  1. 需要在路由里配置这三个区域,配置主要是在components字段里进行
export default new Router({
    routes: [
        {
            path: '/',
            name: 'HelloWorld',
            components: {
                default: HelloWorld,
                left:H1,//显示H1组件内容'I am H1 page,Welcome to H1'
                right:H2//显示H2组件内容'I am H2 page,Welcome to H2'
            }
        },
        {
            path: '/h1',
            name: 'H1',
            components: {
                default: HelloWorld,
                left:H2,//显示H2组件内容
                right:H1//显示H1组件内容
            }
        }
    ]
})

7. $router$route的区别

$route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。

$route.path 字符串,对应当前路由的路径,总是解析为绝对路径,如 "/order"。

$route.params 一个 key/value 对象,包含了 动态片段 和 全匹配片段, 如果没有路由参数,就是一个空对象。

$route.query 一个 key/value 对象,表示 URL 查询参数。 例如,对于路径 /foo?user=1,则有$route.query.user为 1 , 如果没有查询参数,则是个空对象。

$route.hash 当前路由的 hash 值 (不带 #) ,如果没有 hash 值,则为空字符串。

$route.fullPath 完成解析后的 URL,包含查询参数和 hash 的完整路径。

$route.matched 数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。

$route.name 当前路径名字

$router 是“路由实例”对象,即使用 new VueRouter创建的实例,包括了路由的跳转方法,钩子函数等。

$router常见跳转方法:

this.$router.go(-1)//跳转到上一次浏览的页面
this.$router.replace('/menu')//指定跳转的地址
this.$router.replace({name:'menuLink'})//指定跳转路由的名字下
this.$router.push('/menu')//通过push进行跳转
this.$router.push({name:'menuLink'})//通过push进行跳转路由的名字下

router.push和router.replace的区别:

  • 使用push方法的跳转会向 history 栈添加一个新的记录,当我们点击浏览器的返回按钮时可以看到之前的页面。
  • 使用replace方法不会向 history 添加新记录,而是替换掉当前的 history 记录,即当replace跳转到的网页后,‘后退’按钮不能查看之前的页面。

8. 设置 404 页面

用户会经常输错页面,当用户输错页面时,我们希望给他一个友好的提示页面,这个页面就是我们常说的 404 页面。vue-router也为我们提供了这样的机制。

  1. 设置我们的路由配置文件(/src/router/index.js)
{
    path:'*',
    component:Error
}

这里的path:'*'就是输入地址不匹配时,自动显示出Error.vue的文件内容

  1. 在/src/components/文件夹下新建一个Error.vue的文件。简单输入一些有关错误页面的内容。
<template>
    <div>
        <h2>{{ msg }}</h2>
    </div>
</template>
<script>
export default {
    data () {
        return {
            msg: 'Error:404'
        }
    }
}
</script>

9. vue-router如何响应路由参数的变化

当使用路由参数时,例如从 /user/foo导航到 user/bar, 原来的组件实例会被复用,因为两个路由都渲染同一个组件,比起销毁在创建,复用则显得非常有效。不过,这意味着组件的生命周期钩子函数不会再被调用

举个例子:比如动态路由articleDeail/:id,当从路由articleDeail/123跳转到articleDeail/234的时候,两个路由都渲染同一个组件

复用组件时,想对路由参数的变化作出响应的话, 可以watch (监测变化) $route 对象:

const User = {
    template: '...',
    watch: {
        '$route' (to, from) {
        // 对路由变化作出响应...
        }
    }
}

或者使用 2.2 中引入的 beforeRouteUpdate 守卫:

const User = {
    template: '...',
    beforeRouteUpdate (to, from, next) {
        // react to route changes...
        // don't forget to call next()
    }
}

10.完整的vue-router导航解析流程

  1. 导航被触发
  2. 在失活的组件里调用离开守卫
  3. 调用全局的 beforeEach 守卫
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResole 守卫 (2.5+)
  9. 导航被确认
  10. 调用全局的 afterEach 钩子
  11. 触发 DOM 更新
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数

导航守卫

导航:表示路由正在发生改变

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是conrmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或router.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved。

记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

  1. 全局前置导航( router.beforeEach )

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve之前一直处于等待中。

  1. 全局解析守卫( router.beforeResolve )

在导航被确认之前,同时在所有组件内守卫和一步路由组件被解析之后,解析守卫就被调用

  1. 全局后置钩子( beforeEnter )

这些钩子不会接受next函数也不会改变导航本身

  1. 路由独享的守卫 ( beforeEnter )

直接在路由配置上直接定义,这些与全局前值守卫的方法参数一样

5.组件内的守卫( beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave )

13.vue-router的动态路由匹配以及使用

动态路径匹配:即把某种模式匹配到的所有路由,全都映射到同个组件。使用动态路由参数来实现。

例如,我们有一个 User 组件,对于所有ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在vue-router的路由路径中使用“动态路径参数”(dynamic segment)来达到这个效果。

举例:A页面中是一个用户列表,点击每一个用户都会进入用户详情页B,每个用户的详情都是在B页面展示的,这时就会用到路由匹配,即可以在路由位置写 path:'/user/:id',这样就可以通过不同的id获取不同的用户信息在同一个B页面显示了。

14.vue-router实现路由懒加载(动态加载路由)

当打包构建应用时,JavaScript包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更高效了。

结合Vue的异步组件和 webpack的代码分割功能,轻松实现路由组件的懒加载

首先,可以将异步组件定义为返回一个Promise的工厂函数(该函数返回的Promise应该resolve组件本身)

const Foo = () => Promise.resolve({ /* 组件定义对象 */ })

第二,在webpack中,可以使用动态import语法来定义代码分快点(split point)

注意: 如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。

结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。

const Foo = () => import('./Foo.vue')

在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:

const router = new VueRouter({
    routes: [
        { path: '/foo', component: Foo }
    ]
})


import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

export default new VueRouter({
    routes: [
        {
            path: '/',
            name: 'Navigator',
            component: () => import(/* webpackChunkName: "navigator" */
            './../components/Navigator')
        },
        {
            path: '/tucao',
            name: 'Tucao',
            component: () => import(/* webpackChunkName: "tucao" */
            './../components/Tucao')
        }
    ]
})

以上是按照官方文档写的懒加载代码,但是发现chunk命名并没生效 ,再去仔细看看官方文档说明

“结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载”

其实还差一个webpack配置,就是webpack output需要加个chunkFilename

chunkFilename: '[name].js'

七、vuex状态管理高频试题

1.前言

当我们的应用遇到多个组件共享状态时,会需要多个组件依赖于同一状态亦或是来自不同视图的行为需要变更同一状态。以前的解决办法:

  • 将数据以及操作数据的行为都定义在父组件
  • 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)

传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。在搭建下面页面时,你可能会对 vue 组件之间的通信感到崩溃 ,特别是非父子组件之间通信。此时就应该使用vuex,轻松可以搞定组件间通信问题。

vuex通信麻烦.png

2.什么是Vuex

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。这里的关键在于集中式存储管理。

简单来说,vue应用中多个组件的共享状态进行集中式的管理(读/写)

3. Vuex的原理是什么

3.1 简要介绍vuex原理

vuex实现了一个单向数据流,在全局有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有(常见于调用后端接口异步获取更新数据)异步或批量的同步操作需要走Action,但Action也是无法直接修 改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

vuex原理.png

3.2 简要介绍各模块在流程中的功能

Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发,对应action进行回应。

dispatch:操作行为触发方法,是唯一能执行action的方法。

actions:操作行为处理模块,由组件中的$store.dispatch('action 名称', data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。

commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。

mutations:状态改变操作方法,由actions中的commit('mutation 名称')来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。

state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。

getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。

3.3.Vuex与localStorage

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态。

具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

let defaultCity = "上海"
try { // 用户关闭了本地存储功能,此时在外层加个try...catch
    if (!defaultCity){
        defaultCity =JSON.parse(window.localStorage.getItem('defaultCity'))
    }
}catch(e){}

export default new Vuex.Store({
    state: {
        city: defaultCity
    },
    mutations: {
        changeCity(state, city) {
            state.city = city
            try {
                window.localStorage.setItem('defaultCity',
                JSON.stringify(state.city));
                // 数据改变的时候把数据拷贝一份保存到localStorage里面
            } catch (e) {}
        }
    }
})

这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:

JSON.stringify(state.subscribeList); // array -> string
JSON.parse(window.localStorage.getItem("subscribeList")); // string
-> array

4. 什么时候使用Vuex

虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。 如果您的应用够简单,您最好不要使用 Vuex,因为使用 Vuex 可能是繁琐冗余的。一个简单的global event bus 就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

5. 如何使用Vuex

  1. 创建一个store.js文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {// 包含了多个直接更新state函数的对象
        INCREMENT(state) {
            state.count = state.count + 1;
        },
        DECREMENT(state) {
            state.count = state.count - 1;
        }
    },
    getters: { // 当读取属性值时自动调用并返回属性值
        evenOrOdd(state) {
            return state.count % 2 === 0? "偶数" : "奇数";
        }
    },
    actions: { // 包含了多个对应事件回调函数的对象
        incrementIfOdd({ commit, state }) { // 带条件的action
            if (state.count % 2 === 1) {
                commit('INCREMENT')
            }
        },
        incrementAsync({ commit }) { //异步的action
            setInterval(() => {
                commit('INCREMENT')
            }, 2000);
        }
    
    }
})
export default store //用export default 封装代码,让外部可以引用

  1. 在main.js中引入store.js文件
import store from './store'
new Vue({
    el: '#app',
    router,
    store,//注册上vuex的store: 所有组件对象都多一个属性$store
    components: { App },
    template: '<App/>'
})

  1. 创建一个模板
<template>
    <div class="hello">
        <p>click {{ count }} times,count is {{ evenOrOdd }}</p>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
        <button @click="incrementIfOdd">increment if odd</button>
        <button @click="incrementAsync">increment async</button>
    </div>
</template>
<script>
export default {
    name: "HelloWorld",
    computed: {
        count() {
            return this.$store.state.count;
        },
        evenOrOdd() {
            return this.$store.getters.evenOrOdd;
        }
    },
    methods: {
        increment() {
            this.$store.commit("INCREMENT");
        },
        decrement() {
            this.$store.commit("DECREMENT");
        },
        // 只有是奇数才加 1
        incrementIfOdd() {
            this.$store.dispatch("incrementIfOdd"); //触发store中对应的action调
            用
        },
        // 过两秒才加 1
        incrementAsync() {
            this.$store.dispatch("incrementAsync");
        }
    }
};
</script>
    

由于 store 中的状态是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。改变store 中的状态的唯一途径就是显式地提交 (commit) mutations。

如何通mapState等辅助函数优化上面代码?

import { mapActions, mapGetters, mapState, mapMutations } from "vuex";
...
computed: {
    ...mapState(["count"]),
    ...mapGetters(["evenOrOdd"])
}
methods: {
    ...mapActions(["incrementIfOdd", "incrementAsync"]),
    ...mapMutations(["increment", "decrement"])
}

有点必须要注意:HelloWorld.vue文件中increment函数名称要跟store.js文件mutations中一致,才可以写成 ...mapMutations(["increment", "decrement"]),同样的道理,incrementIfOdd和incrementAsync也要和store.js文件actions保持一致。

注意点:

  1. 如何理解getters

getters表面是获得的意思,可以把它看做在获取数据之前进行的一种再编辑,相当于对数据的一个过滤加工。getters就像计算属性一样,getter的返回值会根据他们的依赖被缓存起来,且只有当它的以来只发生了变化才会被重新计算。

  1. actions和mutations的区别

actions和上面的Mutations功能基本一样,不同点是,actions是异步的改变state状态,而Mutations是同步改变状态。

6. Vuex和单纯的全局对象有以下两点不同(重点)

  1. Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,那么相应的组件也会相应的得到高效更新。
  2. 你不能直接改变store中的状态。改变store中的状态的唯一途径就是显式地提交(commit)mutation。这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

7.vuex在vue-cli中的应用

第一步:npm下载vuex资源包:

npm install vuex --save

第二步:在 src/main.js 中引入

import Vue from 'vue'

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

import store from './vuex/store'

第三步:在 src 下 新建 vuex 文件夹

vuex下:
        * modelus //文件夹,存放不同模块需要的共享状态文件
        * index.js
        *        *store.js
    *types.js

说明:

vuex文件夹下store.js:

import Vue from 'vue'
import Vuex from 'vuex'
//引入不同模块需要的共享变量:
import index from './modules/index'

//使用vuex
Vue.use(Vuex)

//对外暴露
export default new Vuex.Store({
    modules: {
        index
    }
})

vuex文件夹下 modelus文件夹下 index.js:


//引入一个常量,保证 action 和 mutations 的统一。
import * as types from '../types'
/**
* App通用配置
*/
const state = {
    //vuex初始化值
    count: 0
}
const actions = {
    increment({ commit }, n) {
        commit(types.TEST_INCREMENT, n)
    },
    decrement({ commit }, state) {

        commit(types.TEST_DEREMENT, state)
    }
}
const getters = {
    count: state => state.count
}
const mutations = {
    [types.TEST_INCREMENT](state, n) {
        console.log(n);
        state.count = state.count + 5 + n
    },
    [types.TEST_DEREMENT](state, status) {
        state.count = state.count - 3
    }
}
export default {
    state,
    actions,
    getters,
    mutations
}

vuex文件夹下type.js:

//暴露常量
export const TEST_INCREMENT='TEST_INCREMENT'
export const TEST_DEREMENT='TEST_DEREMENT'

八、vue如何优化首屏加载速度?

问题描述:

在Vue项目中,引入到工程中的所有js、css文件,编译时都会被打包进vendor.js,浏览器在加载该 文件之后才能开始显示首屏。若是引入的库众多,那么vendor.js文件体积将会相当的大,影响首 屏的体验。

解决方法是:

将引用的外部js、css文件剥离开来,不编译到vendor.js中,而是用资源的形式引用,这样浏览器 可以使用多个线程异步将vendor.js、外部的js等加载下来,达到加速首开的目的。外部的库文件, 可以使用CDN资源,或者别的服务器资源等。

几种常用的优化方法:

  1. 使用npm run build --report命令进行大文件定位

  2. 路由的按需加载

  3. 将打包生成后 index.html页面 里面的JS文件引入方式放在 body 的最后

  4. 用文档的cdn文件代替 npm 安装包

  5. UI库的按需加载

  6. 开启 Gzip 压缩

详解:

1 、大文件定位:

我们可以使用 webpack可视化插件Webpack Bundle Analyzer查看工程js文件大小,然后有目的的 解决过大的js文件。 使用命令:

npm run build --report

2 、路由的按需加载

3 、将JS文件引入方式放在 body 的最后 :

默认情况下,build 后的 index.html 中,js 的引入是在 head 中,使用html-webpack-plugin插 件,将inject的值改为body。就可以将 js 引入放到 body 最后。

首先下载插件:(一般vue-cli项目里默认有,可以package.json里面检查是否含有)

npm install html-webpack-plugin@2 --save-dev

在 build文件夹下webpack.base.conf.js配置:

var htmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    entry: './src/script/main.js',
    output: {
        filename: 'js/bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new htmlWebpackPlugin({
            inject: 'body'
        })
    ]
}

即在 plugins 里面加上 htmlWebpackPlugin 插件。

4 、用文档的cdn文件代替 npm 安装包:

用文档的cdn文件代替,而不用打包到vender里面去。具体的做法是:

1 、在index.html里面引入依赖库js文件

// index.html
<script src="https://cdn.bootcss.com/vue/2.3.3/vue.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.16.2/axios.min.js">
</script>

2 、在mian.js里面去掉第三方js的import,因为在第一步已经通过script标签引用进来了。

3 、把第三方库的js文件从打包文件里去掉

即在 build/webpack.base.conf.js文件的module里面与rules同层加入externals:

5 、UI库的按需加载:

一般 UI库 都提供按需加载的方法,按照文档即可配置。

6.、开启 Gzip 压缩

在 cong/index.js 设置 productionGzip 为 true,开启 Gzip 压缩