当前位置: 首页 > 科技观察

Vue开发过程的探索与实践

时间:2023-03-16 22:14:49 科技观察

@import"common";.test{background-image:url(~@img/test/bg.png);}本文主要讲述结合vue开发过程中实际业务的探索与实践。业务介绍以目标用户的孩子画像为基础,连接聚合京东现有体系的相关资源,建立共生关系的开放生态平台,覆盖家庭多维需求,陪伴孩子的成长。涵盖场景导购、精准推荐、专属权益等,为京东有孩家庭提供优质的购物体验。我们在项目开发中遇到的问题主要有以下三个:接口多:近90个数据接口,数据字段不规范、不统一、难以理解,接口开发经常延迟、频繁变更;交互复杂度:各种交互和State,而且一个状态是多用途的,展示给用户的是多个状态共同作用的结果,用户操作异步更新页面;快速上线:多版本同时规划,多版本并行开发。技术选型技术选型要对症下药。为了统一管理界面和数据,采用的框架必须有一个统一的数据中心,可以实现视图和逻辑的分离,用数据驱动视图,工程可以应对快速上线,以及方便后期维护。在学习成本上,Vue更易用,更轻量。结合Vuex管理状态,视图逻辑与数据耦合度低,项目结构清晰,Vue的扩展性也很好。Vue的核心技术主要有以下几点:声明式渲染:通过简洁的模板语法,声明式地将数据渲染到DOM中,DOM状态是数据状态的映射。组件系统:和大多数前端框架一样,将UI结构拆解成小的、可重用的组件树,然后像零件一样组装。Vue还有一个独特的特点,就是单文件组件。属于同一个组件的模板、脚本、样式都放在一个文件中,这样是不是很酷,这样你就不用同时为一个组件维护多个文件了。客户端路由:结合vue-router,vue可以实现一个SPA应用,主要是通过hash值来控制路由,路由可以传递状态参数给组件。状态管理:Vue的基本状态触发流程是用户行为导致状态改变,状态改变触发视图的更新。结合Vuex,可以管理全局数据。项目详解项目结构项目开发将分为以下几个方面进行讲解:开发辅助、路由、组件化、mixins、常量管理、数据中心、环境兼容性、滚动行为。依赖项目的开发使用Webpack,结合ESLint、Babel等进行开发编译打包。Webpack的基本配置就不详细说了。在基本配置的基础上,进一步分为开发环境和生产环境的配置://开发配置module.exports=merge(base,{plugins:[newwebpack.HotModuleReplacementPlugin(),newwebpack.NoErrorsPlugin(),newwebpack.DefinePlugin({'process.env.NODE_ENV':JSON.stringify(process.env.NODE_ENV||'development')}),newHtmlWebpackPlugin({filename:'index.html',template:'../index.html'})]})//产品配置module.exports=merge.smart(base,{module:{loaders:[{test:/\.s[a|c]ss$/,loader:ExtractTextPlugin.提取({fallbackLoader:“样式加载器”,加载器:'css!sass'})}]},插件:[newExtractTextPlugin('style.css'),newwebpack.DefinePlugin({'process.env.NODE_ENV':JSON.stringify(process.env.NODE_ENV||'production')}),newwebpack.optimize.CommonsChunkPlugin({name:'vendor',filename:'vendor.js'}),newwebpack.LoaderOptionsPlugin(loadersConf),newwebpack.optimize.UglifyJsPlugin({compress:{warnings:false}})]})开发环境,使用ex按和webpack-dev-中间件构建开发服务器:constexpress=rerequire('express')constwebpackDevMiddleware=require('webpack-dev-middleware')constwebpack=require('webpack')constconf=require('./webpack.dev.conf')constapp=express()constport=process.env.PORT||8080conf.entry.app=['webpack-hot-middleware/client',conf.entry.app]constcompiler=webpack(conf)app.use(webpackDevMiddleware(编译器,{publicPath:conf.output.publicPath,stats:{colors:true,chunks:false}}))app.use(require('webpack-hot-middleware')(compiler))app.listen(port,()=>{console.log(`serverstartedatlocalhost:${port}`)})路由一个路由子项如下:{name:'index',path:'/index',meta:{title:'companionspace',pv:50,profiles:true,visitor:true,verify(){returntrue}},components:{default:Index2,navbar:Navbar}}其中,configuration中的meta包含页面(view)的配置信息:title:页面的标题pv:用于记录页面PVprofiles:用于判断是否需要孩子进入该页面visitor:是否支持访客访问verify:如果支持游客访问,可选额外发布验证问题:在ios中,单页应用切换视图时不能使用页面title更新解决:切换路由时,用iframe加载空页面触发title更新,如下图constiframeLoad=(src)=>{letiframe=document.createElement('iframe')iframe.style.display='none'iframe.src=srcdocument.body.appendChild(iframe)iframe.addEventListener('load',function(){setTimeout(function(){iframe.remove()},0)})路由中需要处理的东西比较多。在router.beforeEach中处理页面传入的参数,请求登录状态、文件数据等基本接口,上报PV,在router.afterEach中处理一些比较小的东西。下面介绍项目中的单文件组件。以下是经过特殊编辑的单文件组件代码:

{{btnText}}
/template>@import"common";.test{background-image:url(~@img/test/bg.png);}slot对于可重用的组件来说意义重大,因为在实际应用中,组件往往是类似模块能做成组件的总会有或多或少的差异,通过参数来控制这些差异也是可行的,但是不利于组件的扩展,所以这些地方就留给槽来处理,槽的意思就是它是一个slot,也就是说我们可以在父组件中需要的时候给组件填充自定义的内容,父组件通过props给子组件传值,或者父组件也可以通过子组件实例的方法(如代码中的open方法)。子组件可以通过发射事件向上通信,也可以通过直接调用作为prop传入的父组件方法(如代码中的changeNickname)。Mixins一般是,它不推荐使用globalmixin,但总会有特殊需求。比如在这个项目中,由于埋点等需求,几乎每个组件都需要用到几个公共的全局数据,所以全局mixin是***而是Vue.mixin(mixins)。在使用全局mixin的时候要注意不要把逻辑放在mixin里面,因为每个组件都会执行mixin的内容,组件太多会很恐怖。常量管理为了以后更好的维护代码,需要对常量进行收集和管理。这里的常量主要是链接和数据字段。//链接常量统一管理exportconstREBUY_LIST=`${NIGHT}/re_purchase_detail`exportconstREBUY_SWITCH=`${NIGHT}/re_purchase_switch_good`exportconstREBUY_REMIND=`${NIGHT}/re_purchase_remind`//...//数据字段统一管理exportconstID='id'exportconstSKU='sku'exportconstLINK='link'exportconstNAME='name'exportconstIMAGE='image'exportconstJD_PRICE='jdPrice'exportconstPRICE='price'//...统一常量管理也有利于标准化,比如数据字段,接口给的数据可能字段不一致,或者没有表达意思,或者有很多脏数据等,需要在获取到后端数据后进行“修整”。还有规范统一的字段名称。有利于组件化。数据中心项目使用vuex统一管理数据。在视图组件中,使用vuex提供的mapActions和mapGetters来获取数据,如下代码所示。computed:{...mapGetters({cate1st:'cate1st',cate2nd:'cate2nd'})},methods:{...mapActions(['getCate1st','getCate2nd'])}在“数据中心”时,getter从state中获取值,调用action请求后端接口,主动触发mutation,对mutation中的数据进行“剪枝”,得到我们真正想要的数据。大致流程如下图所示:环境兼容项目需要兼容多种环境,包括购物车相关、店铺页面链接、优惠券链接、搜索链接等方式,根据不同的环境而有所不同。因此,必须为不同的环境定义它们。然后根据ua选择://...letconfigs={[uaTypes.APP]:App,[uaTypes.WECHAT]:Wechat,[uaTypes.QQ]:QQ,[uaTypes.MOBILE]:Mobile}exportdefaultconfigs[UA.type]的滚动行为对于SPA应用程序来说是相当令人头疼的。毕竟它的本质只是一个页面,而且是异步渲染的,所以很难保证每个view的滚动行为都能和多页面应用一样。为此,探索了以下步骤。结合vuex在视图的beforeDestory中存储滚动,主动记录视图的滚动值,下次挂载时延迟滚动到该位置。该解决方案需要为每个需要记录滚动的视图添加state、mutation和action,并向视图添加额外的代码。实际操作比较繁琐,跳转到外部链接返回时,记录的值已经被销毁。使用浏览器存储定位跳转外链返回后的滚动位置,使用localStorage记录滚动值,使用mixin,这样需要操作滚动行为的view就可以插入到这个mixin中,不需要额外添加到查看代码。但是问题来了,我们无法区分访问是第一次打开还是刚从外部链接返回,所以第一次访问也会被定位,于是我们想到了cookies,把cookies保存了30分钟。显然,这不是一个好的解决方案。考虑sessionStorage,可以保留当前session中的数据,跳转外链后数据依然可以保留(之前以为跳转外链后sessionStorage数据也会被清空),打开一个新的tab为一个新的会话,并且彼此之间不共享数据。这些功能刚好满足我们的要求。另一个需要考虑的问题是页面是异步渲染的,我们不知道它的界面什么时候被请求过,所以除了默认的延迟滚动外,还增加了主动触发滚动的特性,供开发者考虑什么时候触发页面加载(通常是观察一个或多个异步请求的状态),然后主动调用滚动方法。***需要指出的是,滚动行为的解决方案并不完美,例如,该解决方案不适用于具有延迟加载模块的页面。最终mixin代码如下:/***如需手动触动:*manualTriggerLivescroll:true*this._livescroll()*/importToolsfrom'@/utils/tools'constss=window.sessionStorageexportdefault{data(){return{routeName:this.$route.name,liveScrollFlag:false,liveScrollFn:null,liveScrollTimer:null}},computed:{liveScrollTop(){returnss?ss.getItem(`view-${this.routeName}`):Tools.getCookie(`view-${this.routeName}`)}},methods:{_livescroll(){if(this.liveScrollFlag||!this.liveScrollTop){return}this.liveScrollFlag=true//$nextTick发送不稳定this.liveScrollTimer=window.setTimeout(()=>{document.body.scrollTop=document.documentElement.scrollTop=this.liveScrollTop},500)}},mounted(){document.body.scrollTop=document.documentElement.scrollTop=0!this.manualTriggerLivescroll&&this._livescroll()this.liveScrollFn=()=>{ss?ss.setItem(`view-${this.routeName}`,this.getScrollTop()):Tools.setCookie(`view-${this.routeName}`,this.getScrollTop(),0.2083)}window.addEventListener('touchend',this.liveScrollFn,false)},beforeDestroy(){window.removeEventListener('touchend',this.liveScrollFn,false)this.liveScrollTimer&&window.clearTimeout(this.liveScrollTimer)}}下面是一些小问题,也有项目开发过程中踩过的坑接口延迟为了尽量减少请求数据为空的情况,在基于vue的请求方式上加了一层,对超时的接口重新发起请求。支持restspread在babel中添加“plugins”:[“transform-object-rest-spread”]支持restspread,或者直接使用babel-preset-env,在eslint配置中添加“parserOptions”:{“ecmaFeatures”:{"experimentalObjectRestSpread":true}}如何切换Webpack的publicPath开发环境和生产环境的配置一开始是手动改的,然后根据当前环境自动选择constpublicPath={development:'/',实验室:'http://xx.xxx.xx/mtd/h5/accompany/3.0.0-alpha/',生产:'//xx.xxx.xx/mtd/h5/accompany/3.2.2/'}[env]alias在mixin资源路径的应用中,因为页面的路径和includePath的路径不同,比如有一个@mixiniconAddcart{background:url(../addcart.png);},在组件样式中包含的时候,会提示找不到图片,如果改成别名~@img/addcart.png的路径,就可以很好的解决这个问题。CSSMasking的应用可以参考Leeenx的CSS3Mask安利报告。在这个项目中,掩码被广泛使用。主要优点是:减小背景图尺寸,自定义遮罩,适应多种背景颜色相同形状的情况。需要注意的是,如果要使用drop-shadow,则必须在外面再添加一层来添加drop-shadow。这就是本文的全部内容。原文链接:https://aotu.io/notes/2017/07/17/The-Exploration-and-Practice-of-Vue/作者:游镇【本文为专栏作者“傲途实验室”原创稿件,转载请联系原作者获得授权】点此阅读作者更多好文