Vue 总结中

目录


一、slot是什么

解释

编译作用域

分类

默认插槽

具名插槽(子组件定义 多个 对应插入内容)

作用域插槽

二、Observable 是什么

三、Key是什么

使用场景

四、Keep-alive 是什么

使用场景

缓存后如何获取数据

beforeRouteEnter:每次组件渲染的时候,都会执行beforeRouteEnter

actived:在keep-alive缓存的组件被激活的时候,执行actived钩子

五、Vue中的修饰符

表单修饰符

事件修饰符

鼠标按钮修饰符 

键盘修饰符

v-bind修饰符

 六、自定义指令

自定义指令的案例

七、过滤器filter

八、虚拟DOM

什么是虚拟DOM

为什么需要虚拟DOM

如何实现虚拟DOM

九、diff算法

十、Vue中的axios 

Axios特性

如何使用Axios

如何封装Axios

如何实现Axios

十一、SSR

什么是SSR

SSR发展历程

SSR解决了什么

使用SSR同样存在以下缺点:

十二、vue项目的目录结构

单页面目录结构

多页面目录结构

十三、vue怎么做权限管理,控制到按钮级别的权限怎么做

如何权限控制

 十四、如何解决跨域

CORS

Proxy

十五、vue项目本地开发完成后部署到服务器后报404

为什么history模式下有问题

为什么hash模式下没有问题

解决方案

十六、处理vue项目中的错误

后端接口错误

代码逻辑问题

生命周期钩子


一、slot是什么

解释

slot (插槽) 是 Vue 的内容分发机制,组件内部的模板引擎使用 slot 元素作为承载分发内容的出口 (即Web组件内的一个占位符)。通俗来说,solt在组件模板中占好位置,当使用该组件标签时,组件标签里的内容就会自动填坑(替换组件模板中slot位置)。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示由父组件决定。

编译作用域

slot 能够在父组件中添加内容,并填充到到子组件的‘坑位’中。也可以添加父组件内任意的data值:

//父组件:(引用子组件 childbutton)
<template>
  <div>
      // 填充内容
      <childbutton>{{parent}}</childbutton>
  </div>
</template>
​
new Vue({
  el:'.app',
  data:{
    parent:'父组件'
  }
})

但是如果在父组件中使用子组件内的数据,是不成立的,因为父级模板里的所有内容是在父级作用域中编译,子模板里的所有内容是在子作用域中编译:

// 子组件 : (名为:childbutton)
<template>
  <div>
       <slot></slot>
  </div>
</template>
​
new Vue({
  el:'.button',
  data:{
    child:'子组件'
  }
})
​
// 父组件:(引用子组件 childbutton)
<template>
  <div>
     <childbutton>{{child}}</childbutton>
  </div>
</template>

分类

slot分为:默认插槽、具名插槽、作用域插槽

默认插槽

如果在父组件中的 <childbutton></childbutton> 中直接添加内容,不会渲染在页面上。想要父组件内容进行显示,只需要在子组件内添加slot 即可,在slot的位置就会显示父组件的内容(子组件可以在任意位置添加slot)

//子组件
<template>
  <div>
      <slot>默认内容</slot>   // slot这个位置就是父组件添加内容的显示位置
  </div>
</template>
​
//父组件
<template>
  <div>
      <childbutton></childbutton>
  </div>
</template>

注:如果在父组件没有填充内容之前,就会显示在slot标签内设定的默认内容

具名插槽(子组件定义 多个<slot></slot> 对应插入内容)

在有多个 <slot></slot> 时,想要对应父组件的内容,就为每个插槽命名即可,即添加name属性

//子组件
<template>
  <div>
      <slot name= 'one'>1</slot>
      <slot name='two'>2</slot>
      <slot name='three'>3</slot>
  </div>
</template>

父组件通过 'v-slot : name' 的方式添加内容:

//父组件
<template>
  <div class= 'app'>
     <childbutton> 
        <template v-slot:one>这是插入到one插槽的内容</template>
        <template v-slot:two>这是插入到two插槽的内容</template>
        <template v-slot:three>这是插入到three插槽的内容</template>
     </childbutton>
  </div>
</template>

作用域插槽

针对编译作用域中的问题,父组件可以添加内容并显示在子组件的插槽中,但反之不成功,可以通过作用域插槽解决,即 v-slot:

//子组件
<template>
  <div>
      <slot name= 'one' :value1='child1'>这就是默认值1</slot>    // 绑定child1的数据
      <slot :value2='child2'>这就是默认值2</slot>  // 绑定child2的数据,这里没有命名slot
  </div>           
</template>
​
new Vue({
  el:'.button',
  data:{
    child1:'数据1',
    child2:'数据2'
  }
})
​
//父组件
<template>
  <div>
     <childbutton> 
        // 通过v-slot,将插槽one的值赋值给svalue1
        <template v-slot:one = 'svalue1'>{{svalue1.value1}}</template>
        // 通过v-slot,将插槽2的值赋值给svalue2,由于子组件没有给slot命名,默认值为default
        <template v-slot:default = 'svalue2'> 
           {{ svalue2.value2 }}
        </template>
     </childbutton>
  </div>
</template>

二、Observable 是什么

Vue.observable让一个对象变成响应式数据。Vue 内部会用它来处理 data 函数返回的对象。返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新,也可以作为最小化的跨组件状态存储器

Vue 2.x 中,被传入的对象会直接被 Vue.observable 变更,它和被返回的对象是同一个对象

Vue 3.x 中,则会返回一个可响应的代理,而对源对象直接进行变更仍然是不可响应的

三、Key是什么

Vue实现了虚拟DOM,可以不直接操作DOM元素只操作数据便可以重新渲染页面,其中涉及到Diff算法,其核心是基于两个简单的假设:

1.两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构

2.同一层级的一组节点,他们可以通过唯一的id进行区分。

也就是说key的存在主要是为了高效的更新虚拟DOM

使用场景

假设 nums 为 [1, 2, 3, 5, 9]

<div v-for="num in nums">
    {{num}}
</div>

这种情况下应当是渲染了5个<div>元素,其中的内容分别对应nums中5个数字,此时如果nums变成了[0, 1, 2, 3, 5, 9],即在数组头部插入了一个数字0,在没有key属性的情况下,渲染输出的更新步骤是这样的:

原先内容为1的<div>元素内容变成0,原先内容为2的<div>元素内容变成1,……以此类推,最后新增一个<div>元素,内容为9。

在这种情况下,Vue会通过改变原来元素的内容和增加/减少元素来完成这个改变,因为没有key属性,Vue无法跟踪每个节点,只能通过这样的方法来完成变更。

1.在使用v-for时,给单元加上 key: index​​​​​​

<ul>
    <li v-for="item in items" :key="index"></li>
</ul>

index 对应了数组中每个元素的索引,使得每个元素的key值都不同

此时如果nums从[1, 2, 3, 5, 9] 变成 [0, 1, 2, 3, 5, 9],渲染输出的更新步骤:

新增一个<div>元素,它的内容为0,并将它插入原先内容为1的元素之前。

添加 key 属性之后,Vue会记住数组内各个元素的顺序,并根据这个顺序在适当的位置插入/删除元素,来完成更新,这种方法就比地复用策略效率更高。

2.+new Date()生成的时间戳作为key

<Comp :key="+new Date()"/> 

+new Date()生成的时间戳作为key,手动强制触发重新渲染。当拥有新值的rerender作为key时,拥有了新key的Comp出现了,那么旧key Comp会被移除,新key Comp触发渲染

四、Keep-alive 是什么

keep-alivevue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM


keep-alive可以设置以下props属性:

include - 字符串或正则表达式(只有名称匹配的组件会被缓存)

exclude - 字符串或正则表达式(任何名称匹配的组件都不会被缓存)

max - 数字(最多可以缓存多少组件实例)

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值),匿名组件不能被匹配

设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activateddeactivated):

首次进入组件时:beforeRouteEnter > beforeCreate > createdmounted > activated > ... ... > beforeRouteLeave > deactivated

再次进入组件时:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated

使用场景

当我们在某些场景下不需要让页面重新加载时,可以使用 keepalive:

首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存) 

这时可以按需来控制页面的 keep-alive,在路由中设置 keepAlive属性判断是否需要缓存

{
  path: 'list',
  name: 'itemList', // 列表页
  component (resolve) {
    require(['@/pages/item/list'], resolve)
 },
 meta: {
  keepAlive: true,
  title: '列表页'
 }
}

// 使用 keep-alive
<div id="app" class='wrapper'>
     <keep-alive>
        <!-- 需要缓存的视图组件 --> 
        <router-view v-if="$route.meta.keepAlive"></router-view>
     </keep-alive>
     // 不需要缓存的视图组件 
     <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>

缓存后如何获取数据

解决方案可以有以下两种:

  • beforeRouteEnter

  • actived

beforeRouteEnter:每次组件渲染的时候,都会执行beforeRouteEnter

beforeRouteEnter(to, from, next){
    next(vm=>{
        console.log(vm)
        // 每次进入路由执行
        vm.getData()  // 获取数据
    })
},

actived:在keep-alive缓存的组件被激活的时候,执行actived钩子

注:服务器端渲染期间actived不被调用

activated(){
   this.getData() // 获取数据
},

五、Vue中的修饰符

vue中修饰符分为:表单修饰符、事件修饰符、鼠标按键修饰符、键值修饰符、v-bind修饰符

表单修饰符

关于表单的修饰符有:

lazy:当信息填写完成之后,光标离开标签时才会将值赋予给value

<input type="text" v-model.lazy="value">
<p>{{value}}</p>

trim:自动过滤用户输入的首空格字符,而中间的空格不会过滤

<input type="text" v-model.trim="value">

number:自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值

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

事件修饰符

stop:阻止了事件冒泡,相当于调用了event.stopPropagation方法

<div @click="shout(2)">
  <button @click.stop="shout(1)">ok</button> //只输出1
</div>

prevent:阻止了事件的默认行为,相当于调用了event.preventDefault方法

<form v-on:submit.prevent="onSubmit"></form>  

self:只当在 event.target 是当前元素自身时触发处理函数

<div v-on:click.self="doThat">...</div>

使用修饰符时,顺序很重要:

v-on:click.prevent.self 会阻止所有的点击

v-on:click.self.prevent 只会阻止对元素自身的点击

once:绑定了事件只能触发一次

<button @click.once="shout(1)">ok</button>

capture:使事件触发从包含这个元素的顶层开始往下触发

<div @click.capture="shout(1)">
    obj1
    <div @click.capture="shout(2)">
        obj2
        <div @click="shout(3)">
            obj3
            <div @click="shout(4)">
                obj4
            </div>
        </div>
    </div>
</div>
// 输出: 1 2 4 3 

passive:在移动端,监听元素滚动事件时,会一直触发onscroll事件,使用passive 相当于给onscroll事件配置了.lazy修饰符

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

不要把 .passive 和 .prevent 一起使用

因为 .prevent 将会被忽略,同时浏览器可能会展示一个警告

passive 会告诉浏览器你不想阻止事件的默认行为

native:让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件

<my-component v-on:click.native="doSomething"></my-component>

使用.native修饰符来操作普通HTML标签会令事件失效


鼠标按钮修饰符 

 left 左键点击 、right 右键点击 、middle 中键点击

<button @click.left="shout(1)">ok</button>
<button @click.right="shout(1)">ok</button>
<button @click.middle="shout(1)">ok</button>

键盘修饰符

键盘修饰符是用来修饰键盘事件(onkeyuponkeydown)的,keyCode存在很多,但vue为我们提供了别名,分为以下两种:

  • 普通键(enter、tab、delete、space、esc、up...)
  • 系统修饰键(ctrl、alt、meta、shift...)
// 只有按键为keyCode的时候才触发
<input type="text" @keyup.keyCode="shout()">

还可以自定义一些全局的键盘码别名

Vue.config.keyCodes.f2 = 123

v-bind修饰符

async:能对props进行双向绑定

//父组件
<comp :myMessage.sync="bar"></comp> 
//子组件
this.$emit('update:myMessage',params);

使用async需要注意:

 1子组件传递的事件名格式必须为update:value,其中value必须与子组件中props中声明的名称完全一致

 2.注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用

 3.将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常

props:设置自定义标签属性,避免暴露数据,防止污染HTML结构

<input id="uid" title="title1" value="1" :index.prop="index">

camel:将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox

<svg :viewBox="viewBox"></svg>

 六、自定义指令

自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。

例如:当一个 input 元素被 Vue 插入到 DOM 中后,它会被自动聚焦

const focus = {
  mounted: (el) => el.focus()
}

export default {
  directives: {
    // 在模板中启用 v-focus
    focus
  }
}
<input v-focus />

自定义指令的案例

 表单防止重复提交(设置节流阀)、图片懒加载 、一键 Copy、拖拽指令、页面水印、权限校验

七、过滤器filter

过滤器实质不改变原始数据,只是对数据进行加工处理后返回过滤后的数据再进行调用处理

Vue 允许自定义过滤器,可被用于一些常见的文本格式化

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

 局部过滤器优先于全局过滤器被调用

一个表达式可以使用多个过滤器

过滤器之间需要用管道符 “|” 隔开,其执行顺序从左往右

八、虚拟DOM

什么是虚拟DOM

用 JavaScript 对象结构表示 DOM 树的结构,用这个树构建一个真正的 DOM 树,插到文档中, 当状态变更的时候,重新构造一棵新的对象树。将新树和旧树比较,记录差异,应用到所构建的真正 DOM 树中,从而实现视图更新。Virtual DOM 本质上是在 JS 和 DOM 之间做一个缓存。

为什么需要虚拟DOM

用传统的原生apijQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程

例如当需要更新10个DOM节点,浏览器收到第一个更新DOM请求后,会马上执行流程,最终执行10次流程。而通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attachDOM树上,避免大量的无谓计算

虚拟 DOM 优势之一是 diff 算法,减少 JS 操作真实 DOM 带来的性能消耗。

最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力。

如何实现虚拟DOM

步骤分为:
1.创建hyperscript function来实现dom渲染
2.创建一个简单的应用程序,使用hyperscript来渲染
3.使用虚拟dom实现动态渲染
4.实现diffing算法

九、diff算法

diff 算法是一种通过同层的树节点进行比较的高效算法,整体策略为:深度优先,同层比较

其有两个特点:

 1.比较只会在同层级进行, 不会跨层级比较 

2.在diff比较的过程中,循环从两边向中间比较

diff 算法在 vue 中作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较:

如果在比较中,发现了旧节点存在与新节点相同的节点,直接复用当前旧节点作为diff后的第一个真实节点,如果没有与当前新节点相同的节点,直接创建新的真实节点。

十、Vue中的axios 

axios 是支持 Promise的 HTTP库。基于 XMLHttpRequest 服务来执行 HTTP 请求(get 请求/post 请求),支持浏览器端和 Node.js 端发送请求。

Axios特性

1.可以在浏览器中发送 XMLHttpRequests
2.可以在 node.js 发送 http 请求
3.支持 Promise API
4.拦截请求和响应
5.转换请求数据和响应数据
6.能够取消请求
7.自动转换 JSON 数据
8.客户端支持保护安全免受 XSRF 攻击

如何使用Axios

1.安装

// 项目中安装
npm install axios --S

或者引入 

// cdn 引入
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

2.导入

import axios from 'axios'

3.发送请求

axios({        
  url:'http://www.baidu.com',    // 设置请求的地址
  method:"GET", // 设置请求方法
  params:{      // get请求使用params进行参数凭借,如果是post请求用data
    type: '',
    page: 1
  }
}).then(res => {  
  // res为后端返回的数据
  console.log(res);   
})

并发请求axios.all([])

function getUserAccount() {
    return axios.get('/user/12345');
}

function getUserPermissions() {
    return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
    .then(axios.spread(function (res1, res2) { 
    // res1第一个请求的返回的内容,res2第二个请求返回的内容
    // 两个请求都执行完成才会执行
}));

在项目中,一般会有类似于设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等重复的操作,为避免冗余,可以在项目中二次封装一下 axios 再使用

如何封装Axios

设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分

利用node环境变量来作判断,用来区分开发、测试、生产环境,在本地调试的时候,还需要在vue.config.js文件中配置devServer实现代理转发,从而实现跨域

请求头 : 来实现一些具体的业务,必须携带一些参数才可以请求

大部分情况下,请求头都是固定的。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置

状态码: 根据接口返回的不同status,来执行不同的业务

请求方法:根据getpost等方法进行再次封装

先引入封装好的方法,在要调用的接口重新封装成一个方法暴露出去

// get 请求
export function httpGet({
  url,
  params = {}
}) {
  return new Promise((resolve, reject) => {
    axios.get(url, {
      params
    }).then((res) => {
      resolve(res.data)
    }).catch(err => {
      reject(err)
    })
  })
}

// post请求
export function httpPost({
  url,
  data = {},
  params = {}
}) {
  return new Promise((resolve, reject) => {
    axios({
      url,
      method: 'post',
      transformRequest: [function (data) {
        let ret = ''
        for (let it in data) {
          ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
        }
        return ret
      }],
      // 发送的数据
      data,
      // url参数
      params

    }).then(res => {
      resolve(res.data)
    })
  })
}

把封装的方法放在一个api.js文件中

import { httpGet, httpPost } from './http'
export const getorglist = (params = {}) => httpGet({ url: 'apps/api/org/list', params })

页面中直接调用

// .vue
import { getorglist } from '@/assets/js/api'

getorglist({ id: 200 }).then(res => {
  console.log(res)
})

这样可以把api统一管理起来,以后维护修改只需要在api.js文件操作即可

请求拦截器: 根据请求的请求头设定,来决定哪些请求可以访问

比如:必须密码正确才能登录成功,利用token字符串判断用户身份

响应拦截器: 根据 后端 返回的状态码判定执行不同业务

比如:购物车结算功能必须是处于登录状态时才可以使用,根据登录状态从而判断是否可以访问请求

如何实现Axios

这里参考一篇大佬的文章:http://t.csdn.cn/D7mit 

以get请求为例,实现一个axios

实现ajax的get请求

var Ajax={
        get: function(url, fn) {
            // XMLHttpRequest对象用于在后台与服务器交换数据
            var xhr = new XMLHttpRequest();
            // 发起get请求
            xhr.open('GET', url, true);
            xhr.onreadystatechange = function() {
                // readyState == 4说明请求已完成
                if (xhr.readyState == 4 && xhr.status == 200) {
                    // 从服务器获得数据
                    fn.call(this, xhr.responseText);
                }
            };
            // 发送请求
            xhr.send();
        }
    }
 

封装Ajax,实现Axios进行回调

var Axios = {
        get: function(url) {
            return new Promise((resolve, reject) => {
                var xhr = new XMLHttpRequest();
                xhr.open('GET', url, true);
                xhr.onreadystatechange = function() {
                    // readyState == 4说明请求已完成
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        // 从服务器获得数据
                        resolve(xhr.responseText)
                    }
                };
                xhr.send();
            })
        },
    }

十一、SSR

什么是SSR

SSR (Server-Side Rendering) 指由服务器完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程

此处参考文献:服务端渲染SSR及实现原理 - 掘金

以下两种情况使用 SSR 

  • 需更好的支持 SEO 优势在于同步。搜索引擎爬虫是不会等待异步请求数据结束后再抓取信息的,如果 SEO 对应用程序至关重要,但页面是异步请求数据时

  • 需更快的到达时间 优势在于慢网络和运行缓慢的设备场景。传统 SPA 需完整的 JS 下载完成才可执行,而SSR 服务器渲染标记在服务端渲染 html 后即可显示,用户会更快的看到首屏渲染页面。关注首屏渲染时间转化率时

SSR发展历程

传统web开发

网页内容在服务端渲染完成,⼀次性传输到浏览器,打开页面查看源码,浏览器拿到的是全部的dom结构

单页应用SPA

单页应用优秀的用户体验,使其逐渐成为主流,页面内容由JS渲染出来,这种方式称为客户端渲染,打开页面查看源码,浏览器拿到的仅有宿主元素#app,并没有内容

服务端渲染SSR

后端渲染出完整的首屏dom结构返回,前端拿到的内容包括首屏及完整spa结构,应用激活后依然按照spa方式运行

Vue SSR是一个在SPA上进行改良的服务端渲染

通过Vue SSR渲染的页面,需要在客户端激活才能实现交互

Vue SSR将包含服务端渲染的首屏和交互的SPA


SSR解决了什么

seo:搜索引擎优先爬取页面HTML结构,使用SSR时,服务端已经生成了和业务相关联的HTML,有利于seo

首屏呈现渲染:用户无需等待页面所有js加载完成就可以看到页面视图,需要权衡哪些用服务端渲染,哪些交给客户端

使用SSR同样存在以下缺点:

整个项目的复杂度、库的支持性,代码兼容、性能问题

1.每个请求都是n个实例的创建,消耗变得很大

2.缓存 node serve、 nginx判断当前用户有没有过期,如果没过期就缓存

3.降级:监控cpu、内存占用过多,就spa,返回单个的壳

4.相对于前后端分离服务器,只需要提供静态资源,服务器负载更大

使用SSR前,需要考虑

1.需要SEO的页面是否数量较少,这些是否可以使用预渲染(Prerender SPA Plugin)实现

2.首屏的请求响应逻辑是否复杂,数据返回是否大量且缓慢


如何实现SSR:参考文献:服务端渲染SSR及实现原理 - 掘金

十二、vue项目的目录结构

划分项目结构的时候,需要遵循一些基本的原则

1.文件夹和文件夹内部文件的语义一致性

2.单一入口/出口

3.就近原则,紧耦合的文件应该放到一起,且应以相对路径引用

4.公共的文件应该以绝对路径的方式从根目录引用

5./src 外的文件不应该被引入

单页面目录结构

project
│  .browserslistrc
│  .env.production
│  .eslintrc.js
│  .gitignore
│  babel.config.js
│  package-lock.json
│  package.json
│  README.md
│  vue.config.js
│  yarn-error.log
│  yarn.lock
│
├─public
│      favicon.ico
│      index.html
│
|-- src
    |-- components
        |-- input
            |-- index.js
            |-- index.module.scss
    |-- pages
        |-- seller
            |-- components
                |-- input
                    |-- index.js
                    |-- index.module.scss
            |-- reducer.js
            |-- saga.js
            |-- index.js
            |-- index.module.scss
        |-- buyer
            |-- index.js
        |-- index.js

多页面目录结构

my-vue-test:.
│  .browserslistrc
│  .env.production
│  .eslintrc.js
│  .gitignore
│  babel.config.js
│  package-lock.json
│  package.json
│  README.md
│  vue.config.js
│  yarn-error.log
│  yarn.lock
│
├─public
│      favicon.ico
│      index.html
│
└─src
    ├─apis //接口文件根据页面或实例模块化
    │      index.js
    │      login.js
    │
    ├─components //全局公共组件
    │  └─header
    │          index.less
    │          index.vue
    │
    ├─config //配置(环境变量配置不同passid等)
    │      env.js
    │      index.js
    │
    ├─contant //常量
    │      index.js
    │
    ├─images //图片
    │      logo.png
    │
    ├─pages //多页面vue项目,不同的实例
    │  ├─index //主实例
    │  │  │  index.js
    │  │  │  index.vue
    │  │  │  main.js
    │  │  │  router.js
    │  │  │  store.js
    │  │  │
    │  │  ├─components //业务组件
    │  │  └─pages //此实例中的各个路由
    │  │      ├─amenu
    │  │      │      index.vue
    │  │      │
    │  │      └─bmenu
    │  │              index.vue
    │  │
    │  └─login //另一个实例
    │          index.js
    │          index.vue
    │          main.js
    │
    ├─scripts //包含各种常用配置,工具函数
    │  │  map.js
    │  │
    │  └─utils
    │          helper.js
    │
    ├─store //vuex仓库
    │  │  index.js
    │  │
    │  ├─index
    │  │      actions.js
    │  │      getters.js
    │  │      index.js
    │  │      mutation-types.js
    │  │      mutations.js
    │  │      state.js
    │  │
    │  └─user
    │          actions.js
    │          getters.js
    │          index.js
    │          mutation-types.js
    │          mutations.js
    │          state.js
    │
    └─styles //样式统一配置
        │  components.less
        │
        ├─animation
        │      index.less
        │      slide.less
        │
        ├─base
        │      index.less
        │      style.less
        │      var.less
        │      widget.less
        │
        └─common
                index.less
                reset.less
                style.less
                transition.less

十三、vue怎么做权限管理,控制到按钮级别的权限怎么做

权限是对特定资源的访问许可,权限控制是确保用户只能访问到被分配的资源

所有的请求发起都触发自前端路由或视图,对触发权限的源头进行控制,最终实现:

路由方面,用户登录后只能看到自己有权访问的导航菜单,也只能访问自己有权访问的路由地址,否则将跳转 4xx 提示页

视图方面,用户只能看到自己有权浏览的内容和有权操作的控件

最后再加上请求控制作为最后一道防线,路由可能配置失误,按钮可能忘了加权限,这种时候请求控制可以用来兜底,越权请求将在前端被拦截

如何权限控制

1.接口权限控制

一般采用jwt的形式来验证,没有通过的话一般返回401,跳转到登录页面重新进行登录,登录成功后拿到token并存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token


2.路由权限控制

方案一:初始化即挂载全部路由,并且在路由上标记相应的权限信息,每次路由跳转前做校验

这种方式存在以下四种缺点:

  • 加载所有的路由,如果路由很多,而用户并不是所有的路由都有权限访问,对性能会有影响

  • 全局路由守卫里,每次路由跳转都要做权限判断

  • 菜单信息写死在前端,要改个显示文字或权限信息,需要重新编译

  • 菜单跟路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示,还要多加字段进行标识

方案二:初始化先挂载不需要权限控制的路由,比如登录页,404等。如果用户通过URL进行强制访问,则会直接进入404,相当于从源头上做了控制。登录后,获取用户的权限信息,然后筛选有权限访问的路由,在全局路由守卫里进行调用addRoutes添加路由

按需挂载,在用户登录进来时要知道当前用户拥有哪些路由权限

这种方式也存在了以下的缺点:

  • 全局路由守卫里,每次路由跳转都要做判断
  • 菜单信息写死在前端,要改个显示文字或权限信息,需要重新编译
  • 菜单跟路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示,还要多加字段进行标识

3.菜单权限控制

菜单权限可以理解成将页面与路由进行解耦

方案一:菜单与路由分离,后端返回菜单,前端定义路由信息

name字段都不为空,需要根据此字段与后端返回菜单做关联,后端返回的菜单信息中必须要有name对应的字段,并且做唯一性校验,全局路由守卫做判断。每次路由跳转的时候都要判断权限,菜单的name与路由的name是一一对应的,而后端返回的菜单就已经是经过权限过滤的。如果根据路由name找不到对应的菜单,就表示用户有没权限访问

如果路由很多,可以在应用初始化的时候,只挂载不需要权限控制的路由。取得后端返回的菜单后,根据菜单与路由的对应关系,筛选出可访问的路由,通过addRoutes动态挂载。

这种方式的缺点:

  • 菜单需要与路由做一一对应,前端添加了新功能,需要通过菜单管理功能添加新的菜单,如果菜单配置的不对会导致应用不能正常使用
  • 全局路由守卫里,每次路由跳转都要做判断

方案二:菜单和路由都由后端返回,前端统一定义路由组件

在将后端返回路由通过addRoutes动态挂载之间,需要将数据处理一下,将component字段换为真正的组件。如果有嵌套路由,后端功能设计时,要注意添加相应的字段,前端拿到数据也要做相应的处理

这种方法也会存在缺点:

  • 全局路由守卫里,每次路由跳转都要做判断
  • 前后端的配合要求更高

4.按钮权限控制

方案一:按钮权限也可以用v-if判断

但是如果页面过多,每个页面都要获取用户权限role和路由表里的meta.btnPermissions,然后再做判断

方案二:通过自定义指令进行按钮权限的判断

 十四、如何解决跨域

跨域本质是浏览器基于同源策略的一种安全手段。同源策略是一种约定,它是浏览器最核心也最基本的安全功能,所谓同源(即指在同一个域)具有以下三个相同点:

  • 协议相同(protocol)
  • 主机相同(host)
  • 端口相同(port)

非同源请求是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域

CORS

跨域资源共享 是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应

CORS 实现只需要增加一些 HTTP 头,让服务器能声明允许的访问来源

添加中间件,直接设置Access-Control-Allow-Origin响应头

app.use(async (ctx, next)=> {
  ctx.set('Access-Control-Allow-Origin', '*');
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
  ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  if (ctx.method == 'OPTIONS') {
    ctx.body = 200; 
  } else {
    await next();
  }
})

Proxy

代理 也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击

方案一:通过vue-cli脚手架工具搭建项目,可以通过webpack起一个本地服务器作为请求的代理对象。通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域

方案二:通过服务端实现代理请求转发

方案三:通过配置nginx实现代理

十五、vue项目本地开发完成后部署到服务器后报404

HTTP 404 错误意味着链接指向的资源不存在,vue项目在本地时运行正常,但部署到服务器中,刷新页面,出现了404错误

为什么history模式下有问题

Vue是属于单页应用,而SPA是一种网络应用程序或网站的模型,所有用户交互是通过动态重写当前页面,不管应用有多少页面,构建物都只会产出一个index.html

nginx配置

server {
  listen  80;
  server_name  www.xxx.com;

  location / {
    index  /data/dist/index.html;
  }
}

可以根据 nginx 配置得出,当在地址栏输入 www.xxx.com 时,这时会打开 dist 目录下的 index.html 文件,然后在跳转路由进入到 www.xxx.com/login。当在 xxx.com/login 页执行刷新操作,nginx location 是没有相关配置的,所以就会出现 404 的情况

为什么hash模式下没有问题

router hash 模式我们都知道是用符号#表示的,如 website.com/#/loginhash 的值为 #/login

它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对服务端完全没有影响,因此改变 hash 不会重新加载页面。hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 website.com/#/login 只有 website.com 会被包含在请求中 ,因此对于服务端来说,即使没有配置location,也不会返回404错误

解决方案

问题的本质是路由是通过JS来执行视图切换的,当进入到子路由时刷新页面,web容器没有相对应的页面此时会出现404。只需要配置将任意页面都重定向到 index.html,把路由交由前端处理

nginx配置文件.conf修改,添加try_files $uri $uri/ /index.html;

server {
  listen  80;
  server_name  www.xxx.com;

  location / {
    index  /data/dist/index.html;
    try_files $uri $uri/ /index.html;
  }
}

修改完配置文件后记得配置的更新

nginx -s reload

但所有路径都会返回 index.html 文件。在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})

十六、处理vue项目中的错误

错误来源包括:后端接口错误、代码本身逻辑错误

后端接口错误

通过axiosinterceptor实现网络请求的response先进行一层拦截

apiClient.interceptors.response.use(
  response => {
    return response;
  },
  error => {
    if (error.response.status == 401) {
      router.push({ name: "Login" });
    } else {
      message.error("出错了");
      return Promise.reject(error);
    }
  }
);

代码逻辑问题

设置全局错误处理函数

Vue.config.errorHandler = function (err, vm, info) {
  // handle error
}

生命周期钩子

errorCaptured 是 2.5.0 新增的一个生命钩子函数,当捕获到一个来自子孙组件的错误时被调用

(err: Error, vm: Component, info: string) => ?boolean

此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。

此钩子可以返回 false 以阻止该错误继续向上传播

参考文献:面试官:SSR解决了什么问题?有做过SSR吗?你是怎么做的? | web前端面试 - 面试官系列 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

低保和光头哪个先来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值