首次使用VUE搭建简单项目问题汇总

    技术2022-07-11  84

    * 以下内容为学习VUE全家桶时遇到的问题整理,学习中复现的网站为‘卖座电影’移动端,问题的顺序不是线性的,只是起思路整理和提醒的作用,许多具体问题还要配合文档观看。*

    1. *跨域请求问题:*

    跨域请求问题的解决方案主要有三种,JSONP、cors方法和服务代理(反向代理),目前JSONP使用较少,这里详细说一下cors方法和服务代理。

    Cors方法:在服务端设置响应头”Access-Control-Allow-Origin:”这样所有人都可以向该服务器请求数据,通配符也可以替换成后端允许请求的域名。(当且仅当使用POST向PHP发送请求时,还需要在添加请求头)

    服务代理(反向代理):由于服务端与服务端的数据请求不存在跨域问题,因此可以通过客户端建立同域的服务端,与目标服务端完成数据请求,再返回给客户端,这种方法很适合用在开发调试阶段。

    例如当我们需要向’https//m.maoyan.com/ajax/mostexpected?aaa’请求数据时,需要先在package.json的同级目录下(也就是项目最外层)新建Vue.config.js文件,并使用以下方法:

    module.exports={ devServer:{ proxy:{ '/ajax':{ target:'https//m.maoyan.com', ws:true, changeOrigin:true } } } }

    这是最基本的使用get请求的写法,如果是POST方法,还需要在这里添加header对象,将所需数据模拟发送,也可以在使用axios的位置使用其完整写法:

    axios({ url:'https://.....' headers:{ } }.then(res=>{ console.log(res.data) })

    2. *Json-server的使用*

    ​ 在工作中,不可能等后端将所有数据弄好再进行页面的测试,因此我们需要使用一些将json数据自动处理的插件,json-server是比较优秀的一款。

    ​ 具体安装方法见文档,他可以自动将数据分割,还有模拟懒加载,分段请求数据等功能,想要启动服务器,只需要在json文件所在文件夹使用命令管理器:

    json-server --watch 文件名.json

    ​ 随着学习的深入,会更多的使用他的方法。

    3. *路由容器与路由配置中的一些问题*

    在路由配置文件中,Vue.use(VueRouter) 代表了注册路由插件,将router-view 定义成全局组件,这样就可以直接在页面中添加router-view标签,无需引入。

    Vue内的路由有两种模式,history和hash模式,在配置文件中可以更改。这两种模式最直观的不同在于history模式在域名中不会出现”#”。但其实这是两种基于不同原理的模式,history模式下,使用 history.pushState 切换,window.onpopstate监听路径切换;而hash模式则是使用location.hash切换,window.onhashchange监听路径切换。

    路由懒加载可以解决首页渲染过慢的问题。单文件开发后,使用bulid方式进行打包,结果是分散的组件会变成一个巨大的js文件,加载首页时,要先下载整个文件才能工作,影响用户第一感觉,因此在配置路由时,可以写成如下形式:

    { path: '/center', component: () => import('@/views/Center') }

    这样在封装后,会将这部分内容单独创建出对应的js文件,加快首页的加载速度。

    4. *搭建网站过程中指令创建的使用实例*

    ​ 在创建网页的过程中,有这样一种功能,在浏览器上方初始不显示某一元素,等到向下滑动固定距离时显示,这样的功能本可以使用在mounted中监听onscroll事件,在destroyed中销毁事件,但不可避免的会在vue内部进入dom操作,因此可以将这种方法封装为一个指令,将它与页面内元素绑定。

    ​ 要注意的是,如果在创建指令时添加了事件,一定要记得在指令销毁阶段(unbind)将其置为null。

    5. *路由拦截(守卫)*

    ​ 路由拦截分为两种方式,全局拦截和单个拦截,他们的主要作用是在跳转前进行判断,是否允许进入待跳转的界面。

    全局拦截的写法是将以下代码写在路由配置文件中:

    router.beforeEach((to, from, next)=>{})

    ​ to与next都是函数,含义可以从字面理解,next如果不添加参数,则代表前往前往本来应该前往的页面。这样写的弊端在于每次进行页面切换时,都会执行一次这个函数,因此有了单个拦截的方法,写法如下:

    beforeRouteEnter (to, from, next) {}

    ​ 将以上代码写在需要守卫的页面上,beforeRouteEnter是路由的生命周期的一环,他执行的非常早,在vue的生命周期beforecreate前,参数与之前的全局写法相似。

    6. *封装axios*

    ​ 在同一个项目中,我们在请求数据时,请求地址通常会有大量的重复性,同时,我们还想在每次请求发送及接受的过程中做些处理,这就可以将axios封装起来,方便之后使用。

    const http=axios.create({ ​ baseURL: '这里的内容会放在每次请求的url前' ​ headers:{'这里写入请求头需要附带的信息'} ​ params:{'这里写入与url一同传过去的参数'} ​ timeout:'这里写入请求超时的时间'......还有许多可以自定义的属性,请查阅文档 ​ }) ​ http.interceptors.request.use( ​ Req=>{'这里在请求前被拦截,可以写入显示加载框,加载框可以利用vant引入'}, ​ Err=>{}) ​ http.interceptors.response.use( ​ Req=>{'这里在响应前被拦截,可以写入取消加载框'}, ​ Err=>{'这里可以根据错误消息完成不同事件,如登录过期跳转至登陆界面等'})

    7. *Dom元素过多界面的滚动优化*

    ​ 在移动端,如果页面内dom元素过多,会导致页面不流畅的问题,使用better-scroll可以解决这个问题,他的解决方案是为元素添加动画,使其顺滑。使用方式与swiper基本一致,下载后引入需要使用的页面,通过构造函数的方法创建,第一个参数为需要better-scroll接管区域的class名,第二个参数为对象,里面可以进行一些设定。要注意的是,默认的better-scroll区域是无法进行click时间的,需要解除锁定。

    8. *Vue单页面开发时引入iconfont的方法*

    ​ 将iconfont文件夹放入public文件夹下,在index.html文件中使用link方式引入(注意与模板文件的一致性),然后就可以之间使用添加class的方式使用iconfont了。

    9. *vant组件库的使用*

    ​ Vant包含了六十多种封装好的组件,功能强大,在项目中引入vant的方式有两种,一种是全局引入:

    import Vue from 'vue';import Vant from 'vant';import 'vant/lib/index.css'; ​ Vue.use(Vant);

    ​ 可以看到,我们将vant文件内导出的所有内容全都接收在Vant内,同时使用Vue.use(Vant),与router类似,这也是全局注册,但浏览器工作量变大,用户下载量也变大,所以还是在vant使用数量不大的情况下,使用按需导入的方法:

    ​ 安装插件与更改配置文件这里就不展开了,可以忘记了可以在vant官网查看,下面是在页面内的代码(以单元格为例):

    import Vue from 'vue';import { Cell, CellGroup } from 'vant'; ​ Vue.use(Cell); ​ Vue.use(CellGroup);

    ​ 更多调整及自定义的方法需要在不断地学习中使用。要先熟悉vant都有哪些功能,然后在实际做项目时就自然会想到什么时候该用了。

    10. *一些零散的小问题*

    \1. 在引入组件时使用驼峰式命名,如topBar,在标签中可以写成< top-bar>;

    \2. 在组件中引入其他组件,不仅要使用import导入,还要记得加入components中;

    \3. 同一标签可以添加两个class,一个是固定不变的,直接使用class,另外可以动态绑定:class一个变化的,两者的类名都会添加到标签中;

    \4. 在处理时间问题时,引入了moment模块,能将数字很好的转化为各种时间格式;

    \5. 批量处理某些数据时,记得使用数组的map方法和Vue的filter过滤器;

    \6. 在pc端判断滚动条距离上方位置使用document.documentElement.scrolltop,在移动端则使用document.body.scrollTop;

    \7. this.$router是整个路由,里面封装了一些bom方法,因此可以使用push方法跳转页面,当然也可以使用forward和back进行前进与后退

    \8. this.$route是当前路由,可以用他进行传参,params里含有信息,需要时可先打印看看(动态绑定路由、命名式路由,两种方法);

    \9. 开启lint后,如果new了对象未使用,会报错,可以在eslintrc.js文件中可以将其关闭;

    \10. < router-link>用于底部导航,可以方便的实现切换功能;

    \11. 可以使用arr=Array.from(new Set(arr));来完成快速去重,这里可以回忆一下set和map这两个ES6的数据类型;

    \12. {…obj1,…obj2}可以的将两个对象合并成为一个对象,”…”叫做展开运算符

    \13. btoa转码方式不能将中文转换,执行后会报错

    11. *界面切换触发底线bug问题及解决方案*

    ​ 当电影列表采用懒加载方式,进入详情页面并且拖拽到底部后,再返回电影列表界面的话,会导致下次懒加载直接显示预设的到底部的事件。

    ​ 这是因为我们是单页面开发,在详情界面下拉一定长度,再返回之前的界面时,会认为滚动条还是在底部,默认触发了之前比较数组长度并选择继续请求数据或显示到底部的事件,此时列表长度与待比较的数据都是0,因此会显示到底的样子并且不再请求数据。

    ​ 由于之前显示触底事件的判断条件是加载出的列表数目与目标数目相同,因此解决方案是将显示触底事件的触发条件增加列表长度不为0。

    12. *同界面使用多个轮播的bug与处理方案*

    ​ 在一个项目中,可能会使用多次轮播组件,可以制作多个轮播组件应对不同的效果,但是这不符合我们希望的代码具有复用性的思想,因此为了造出更好的轮子,我们需要将之前的简单轮播组件进行加工。

    ​ 首先我们可以使用父子通信的方式来完成组件属性的设定,在轮播组件内接收状态的props内部,预先用对象嵌套的方式设定好状态值,如:loop/autoplay/slidesPerView/freeMode等,这些都是swiper组件已经设定好的功能,只需要在组件标签动态绑定状态即可。这样就可以实现不同的swiper样式。

    ​ 但是,在同一界面引入多个轮播组件时,由于创建轮播的方式都是new Swiper(‘.swiper-container’,{状态}),因此,由于重名问题,可能会导致状态赋给错误的位置上。为了解决这一问题,我们可以自定义一个状态’myName’,并在创建时赋给他独特的值,如未定义,则默认值为swiper-container,这样在创建实例化对象时,获取他的名字,保证两次的状态都能正确的显示在我们所需要的位置上。

    13. *界面切换后滚动条显示异常BUG及修复方案*

    ​ 再学会使用VUEX后,当界面之间切换,但两个界面所需的数据相同时,可以通过store中的state状态管理相同的的数据,这样减少用户的请求次数与下载量,界面的显示更加顺滑与流畅。但随之而来会出现一个小问题,举下面的例子看一下。

    ​ 例如我们影院列表页面(简称列表)和影院搜索页面(简称搜索)所需的数据相同,用户进入列表浏览无问题,点击搜索后,该页面使用store中的数据,搜索功能也可以实现,但再返回列表后,会发现拉动界面时滚动条始终显示在上方。****(注意这里在列表界面使用了better-scroll,所以才会造成这个问题)****这是因为初始化better-scroll的时间过早,早于dom的创建。第一次不会出现问题是因为axios请求数据后,使用promise对象.then的方法进行初始化,但第二次就是直接使用store中的数据了。

    ​ 修复的方法是使用$nextTick()方法,$nextTick只会在dom加载完成后执行一次,因此完美解决之前的问题。

    14. *轮播图pc端和移动端的图片间隔显示存在差异BUG及解决方案*

    ​ 在使用swiper组件时,可以自定义一页出现几张展示画面,也可以定义每个画面之间的距离,但是当我们设置距离后,如果一页显示一张,pc端将不会显示异常,但在拉动画面时会发现存在画面溢出的问题。在移动端上则会产生白边。

    ​ 修复方法很简单,父子通信、接收数据、定义默认值即可解决,这里主要描述一下这种bug出现的现象。

    15. *VUEX渐进式整理*

    ​ 这里会循序渐进的讲述项目中使用vuex的必要性。

    ​ 首先我们在项目中页面跳转时,有时会出现两个页面需要的数据一致,而且这两个页面用户会经常连续反复跳转,大量的数据请求和加载是影响用户体验的,因此这里我们就会想到是否能存储一组数据,在各个路由页面都可以调用呢?于是有了VUEX中的state,可以共享状态。当有页面需要控制这个状态就可以改变他了,但是如果有很多页面需要用到这个状态,很容易发生错乱,因此我们又引入了mutations(突变,异变),他可以间接通过函数改变state中的状态,在VUE文件中想调用mutations内的方法,需要使用commit($store也是指的本项目中的VUEX)来触发,写法为:

    this.$store.commit(函数名)

    ​ 这样操作会被记录(配合chrome插件devtools使用),就解决了内部调用混乱的问题,但是这个内部只能够执行同步操作。那么如果我们需要共享的异步操作呢,于是引入了actions,在其内部同步异步均可,但更多还是用来异步,就像mutation通过commit调用一样,actions需要用dispatch调用,写法如下:

    this.$store.dispatch(函数名)

    ​ 如果函数返回了promise对象,这里可以继续使用.then来执行异步操作。

    ​ 现在来回忆一下我们在VUE对象中使用过的计算属性,computed,他看起来像方法,但我们在使用时不加(),将它当作属性来用,现在,在VUEX中也有一个类似的用法,getters

    为了方便理解,可以将getters看作store中的computed,将某些数据处理的工作放在其中,调用时直接当作属性使用。

    ​ 以上基本介绍了VUEX的最基本的知识点,接下来回顾一下VUEX的高级一点的写法。

    ​ 为了使代码简化,我们使用map…的方式切割方法与状态。

    ​ 首先在页面从vuex引入mapstate,然后在computed内写:

    ...mapstate([状态名1,状态名2......])

    ​ mapstate其实会返回一个大对象,里面的属性就是状态本体,因此使用展开运算符,将他们放在计算属性中,页面内使用的话,直接使用类似computed方法即可。

    ​ 由于…mapstate返回的是状态,所以放在计算属性中,那么…mapmutation和…mapactions就应该放在methods中了,是吧。

    ​ 说白了,就是将store中的方法通过切割和打包放进要使用的页面,使用起来就像使用自己页面的东西一样,所以看起来优雅一点。

    ​ 实际开发中,不可能所有人都在一个store文件内进行编辑,因此引入接下来的方法,子store。

    ​ 在store文件夹下新建‘子store’分别对应不同工作环境,他的结构与store一致,将主store文件中与之相关的内容一一对应的放在相应位置,然后记得开启命名空间,也就是加一条属性:

    namespace:true

    ​ 最后将这些对象引入主store文件中,并在module中创建(与components相似),最后的最后,在刚刚的…map()中增加一个参数,变成…map(命名空间的名字,[]),就可以啦。

    Processed: 0.011, SQL: 9