Vue Mounted没有强制执行没有钱转刑事,请问为什么

&img src=&/v2-0c5c1adacf6b34ed3848bdb1cc69be31_b.png& data-rawwidth=&930& data-rawheight=&385& class=&origin_image zh-lightbox-thumb& width=&930& data-original=&/v2-0c5c1adacf6b34ed3848bdb1cc69be31_r.png&&&p&前段时间阅读了&a href=&/?target=https%3A//huangxuan.me//upgrading-eleme-to-pwa/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&饿了么的 PWA 升级实践&i class=&icon-external&&&/i&&/a&一文,受益匪浅。其中&a href=&/?target=https%3A//huangxuan.me//upgrading-eleme-to-pwa/%23%25E5%259C%25A8%25E6%259E%%25BB%25BA%25E6%%25E4%25BD%25BF%25E7%-vue-%25E9%25A2%%25B8%25B2%25E6%259F%%25AA%25A8%25E6%259E%25B6%25E5%25B1%258F& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&构建时使用 Vue 预渲染骨架屏&i class=&icon-external&&&/i&&/a&一节,为开发者提供了减少白屏时间,提升用户感知体验的新思路。本文将借鉴这一思路,尝试为 Vue 项目添加骨架屏。&/p&&h2&骨架屏是什么?&/h2&&p&在 Google 提出的&a href=&/?target=https%3A///web/updates/2017/06/user-centric-performance-metrics& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&以用户为中心&i class=&icon-external&&&/i&&/a&的四个页面性能衡量指标中,FP/FCP(首屏渲染)可能是开发者最熟悉的了。下图来自&a href=&/?target=https%3A///web/updates/2017/06/user-centric-performance-metrics& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&原文&i class=&icon-external&&&/i&&/a&:&/p&&img src=&/v2-931d2ed7eaaa14d8aefbc8_b.png& data-rawwidth=&1400& data-rawheight=&488& class=&origin_image zh-lightbox-thumb& width=&1400& data-original=&/v2-931d2ed7eaaa14d8aefbc8_r.png&&&p&关于尽快渲染出首屏,减少白屏时间,常见的优化方式大致有以下几种:&/p&&ul&&li&优化 &a href=&/?target=https%3A///web/fundamentals/performance/critical-rendering-path/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Critical Rendering Path(关键渲染路径)&i class=&icon-external&&&/i&&/a&,尽可能减少阻塞渲染的 JavaScript 和 CSS。常见做法包括使用 async/defer 让浏览器下载 JavaScript 的同时不阻塞 HTML 解析,内联页面关键部分的样式到 HTML 中等。&/li&&li&使用 Service Worker 缓存&a href=&/?target=https%3A///web/updates/2015/11/app-shell& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AppShell&i class=&icon-external&&&/i&&/a&,加快后续访问速度。&/li&&li&使用 HTTP/2 Server Push,帮助浏览器尽早发现静态资源,减少请求数。&a href=&/p/& class=&internal&&浅谈 HTTP/2 Server Push&/a& 一文介绍了 Ele.me 在这方面的实践,推送 API 请求而非静态资源。&/li&&/ul&&p&骨架屏充分利用了前两点。下图来自原文&a href=&/?target=https%3A//huangxuan.me//upgrading-eleme-to-pwa/%23%25E4%25B8%25BA%25E6%E7%259F%25A5%25E4%25BD%%25AA%258C%25E5%25A5%258B%25E6%& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&为感知体验奋斗&i class=&icon-external&&&/i&&/a&一节。从图中的 Skeleton Screen (骨架屏)中可以看出,在页面完全渲染完成之前,用户会看到一个样式简单,描绘了当前页面的大致框架,感知到页面正在逐步加载,最终骨架屏中各个占位部分被完全替换,体验良好。&/p&&img src=&/v2-8dd8f8ddce_b.jpg& data-rawwidth=&2560& data-rawheight=&1280& class=&origin_image zh-lightbox-thumb& width=&2560& data-original=&/v2-8dd8f8ddce_r.jpg&&&p&骨架屏可以看成一个简单的关键渲染路径,由于只是页面的大致框架,样式不会太复杂,内联在 HTML 中体积很小。使用 Service Worker 缓存包含骨架屏的 HTML 页面之后,从缓存中取出展示速度更快。&/p&&h2&实现思路&/h2&&p&参考原文中&a href=&/?target=https%3A//huangxuan.me//upgrading-eleme-to-pwa/%23%25E5%259C%25A8%25E6%259E%%25BB%25BA%25E6%%25E4%25BD%25BF%25E7%-vue-%25E9%25A2%%25B8%25B2%25E6%259F%%25AA%25A8%25E6%259E%25B6%25E5%25B1%258F& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&在构建时使用 Vue 预渲染骨架屏&i class=&icon-external&&&/i&&/a&一节介绍的思路,我将骨架屏也看成路由组件,在构建时使用 Vue 预渲染功能,将骨架屏组件的渲染结果 HTML 片段插入 HTML 页面模版的挂载点中,将样式内联到 head 标签中。这样在前端渲染时,Vue 将使用&a href=&/?target=https%3A//ssr.vuejs.org/zh/hydration.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&客户端混合&i class=&icon-external&&&/i&&/a&,把挂载点中的骨架屏内容替换成真正的页面内容。&/p&&p&有了以上思路,让我们看看如何为一个简单的 Vue 应用添加骨架屏。&/p&&h2&具体实现&/h2&&p&为此我开发了一个 webpack 插件:&a href=&/?target=https%3A///lavas-project/vue-skeleton-webpack-plugin& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&vue-skeleton-webpack-plugin&i class=&icon-external&&&/i&&/a&。下面将从以下三方面介绍部分实现细节:&/p&&ul&&li&使用 Vue 预渲染骨架屏&/li&&li&将骨架屏渲染结果插入 HTML 模版中&/li&&li&开发模式下插入各个骨架屏路由&/li&&/ul&&h2&使用 Vue 预渲染骨架屏&/h2&&p&我们使用 Vue 的&a href=&/?target=https%3A//ssr.vuejs.org/zh/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&预渲染&i class=&icon-external&&&/i&&/a&功能渲染骨架屏组件,不熟悉的同学可以先阅读官方文档中的&a href=&/?target=https%3A//ssr.vuejs.org/zh/basic.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&基本用法&i class=&icon-external&&&/i&&/a&一节。&/p&&p&首先需要创建一个仅使用骨架屏组件的入口文件:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// src/entry-skeleton.js&/span&
&span class=&kr&&import&/span& &span class=&nx&&Skeleton&/span& &span class=&nx&&from&/span& &span class=&s1&&'./Skeleton.vue'&/span&&span class=&p&&;&/span&
&span class=&c1&&// 创建一个骨架屏 Vue 实例&/span&
&span class=&kr&&export&/span& &span class=&k&&default&/span& &span class=&k&&new&/span& &span class=&nx&&Vue&/span&&span class=&p&&({&/span&
&span class=&nx&&components&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&Skeleton&/span&
&span class=&p&&},&/span&
&span class=&nx&&template&/span&&span class=&o&&:&/span& &span class=&s1&&''&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&接下来创建一个用于服务端渲染的 webpack 配置对象,将刚创建的入口文件指定为 entry 依赖入口:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// webpack.skeleton.conf.js&/span&
&span class=&p&&{&/span&
&span class=&nx&&target&/span&&span class=&o&&:&/span& &span class=&s1&&'node'&/span&&span class=&p&&,&/span& &span class=&c1&&// 区别默认的 'web'&/span&
&span class=&nx&&entry&/span&&span class=&o&&:&/span& &span class=&nx&&resolve&/span&&span class=&p&&(&/span&&span class=&s1&&'./src/entry-skeleton.js'&/span&&span class=&p&&),&/span& &span class=&c1&&// 多页传入对象&/span&
&span class=&nx&&output&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&libraryTarget&/span&&span class=&o&&:&/span& &span class=&s1&&'commonjs2'&/span&
&span class=&p&&},&/span&
&span class=&nx&&externals&/span&&span class=&o&&:&/span& &span class=&nx&&nodeExternals&/span&&span class=&p&&({&/span&
&span class=&nx&&whitelist&/span&&span class=&o&&:&/span& &span class=&sr&&/.css$/&/span&
&span class=&p&&}),&/span&
&span class=&nx&&plugins&/span&&span class=&o&&:&/span& &span class=&p&&[]&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&blockquote&这里只展示单页应用的情况,在多页应用中,指定 entry 为包含各个页面入口的对象即可。关于多页中的 webpack 配置对象示例,可参考&a href=&/?target=https%3A///lavas-project/vue-skeleton-webpack-plugin/tree/master/examples/multipage& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&插件的多页测试用例&i class=&icon-external&&&/i&&/a&或者&a href=&/?target=https%3A///lavas-project/lavas-template-vue-mpa& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Lavas MPA 模版&i class=&icon-external&&&/i&&/a&。&/blockquote&&p&然后我们将这个 webpack 配置对象通过参数传入骨架屏插件中。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// webpack.dev.conf.js&/span&
&span class=&nx&&plugins&/span&&span class=&o&&:&/span& &span class=&p&&[&/span&
&span class=&k&&new&/span& &span class=&nx&&SkeletonWebpackPlugin&/span&&span class=&p&&({&/span& &span class=&c1&&// 我们编写的插件&/span&
&span class=&nx&&webpackConfig&/span&&span class=&o&&:&/span& &span class=&nx&&require&/span&&span class=&p&&(&/span&&span class=&s1&&'./webpack.skeleton.conf'&/span&&span class=&p&&)&/span&
&span class=&p&&})&/span&
&span class=&p&&]&/span&
&/code&&/pre&&/div&&p&骨架屏插件运行时会使用 webpack 编译这个传入的配置对象,得到骨架屏的 bundle 文件。接下来只需要使用这个 bundle 文件内容创建一个 renderer,调用renderToString()方法就可以得到字符串形式的 HTML 渲染结果了。由于我们不需要将过程中的文件产物保存在硬盘中,使用内存文件系统 &a href=&/?target=https%3A///webpack/memory-fs& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&memory-fs&i class=&icon-external&&&/i&&/a& 即可。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// vue-skeleton-webpack-plugin/src/ssr.js&/span&
&span class=&kr&&const&/span& &span class=&nx&&createBundleRenderer&/span& &span class=&o&&=&/span& &span class=&nx&&require&/span&&span class=&p&&(&/span&&span class=&s1&&'vue-server-renderer'&/span&&span class=&p&&).&/span&&span class=&nx&&createBundleRenderer&/span&&span class=&p&&;&/span&
&span class=&c1&&// 从内存文件系统中读取 bundle 文件&/span&
&span class=&kd&&let&/span& &span class=&nx&&bundle&/span& &span class=&o&&=&/span& &span class=&nx&&mfs&/span&&span class=&p&&.&/span&&span class=&nx&&readFileSync&/span&&span class=&p&&(&/span&&span class=&nx&&outputPath&/span&&span class=&p&&,&/span& &span class=&s1&&'utf-8'&/span&&span class=&p&&);&/span&
&span class=&c1&&// 创建 renderer&/span&
&span class=&kd&&let&/span& &span class=&nx&&renderer&/span& &span class=&o&&=&/span& &span class=&nx&&createBundleRenderer&/span&&span class=&p&&(&/span&&span class=&nx&&bundle&/span&&span class=&p&&);&/span&
&span class=&c1&&// 渲染得到 HTML&/span&
&span class=&nx&&renderer&/span&&span class=&p&&.&/span&&span class=&nx&&renderToString&/span&&span class=&p&&({},&/span& &span class=&p&&(&/span&&span class=&nx&&err&/span&&span class=&p&&,&/span& &span class=&nx&&skeletonHtml&/span&&span class=&p&&)&/span& &span class=&o&&=&&/span& &span class=&p&&{});&/span&
&/code&&/pre&&/div&&p&默认情况下,webpack 模块引用的样式内容是内嵌在 JavaScript bundle 中的。官方插件 &a href=&/?target=https%3A///webpack-contrib/extract-text-webpack-plugin& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ExtractTextPlugin&i class=&icon-external&&&/i&&/a& 可以进行样式分离。我们也使用这个插件,将骨架屏样式内容输出到单独的 CSS 文件中。关于插件更多用法,可参考&a href=&/?target=https%3A//doc.webpack-china.org/plugins/extract-text-webpack-plugin/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&官方文档&i class=&icon-external&&&/i&&/a&或者 &a href=&/?target=https%3A///vuejs-templates/webpack/blob/master/template/build/utils.js& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Vue 基于 webpack 的模版&i class=&icon-external&&&/i&&/a&。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// vue-skeleton-webpack-plugin/src/ssr.js&/span&
&span class=&c1&&// 加入 ExtractTextPlugin 插件到 webpack 配置对象插件列表中&/span&
&span class=&nx&&serverWebpackConfig&/span&&span class=&p&&.&/span&&span class=&nx&&plugins&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&k&&new&/span& &span class=&nx&&ExtractTextPlugin&/span&&span class=&p&&({&/span&
&span class=&nx&&filename&/span&&span class=&o&&:&/span& &span class=&nx&&outputCssBasename&/span&
&span class=&p&&}));&/span&
&/code&&/pre&&/div&&p&至此,我们已经得到了骨架屏的渲染结果 HTML 和样式内容,接下来需要关心如何将结果注入 HTML 页面模版中。&/p&&h2&注入渲染结果&/h2&&p&Vue webpack 模版项目使用了 &a href=&/?target=https%3A///jantimon/html-webpack-plugin& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&HTML Webpack Plugin&i class=&icon-external&&&/i&&/a& 生成 HTML 文件。参考该插件的&a href=&/?target=https%3A///jantimon/html-webpack-plugin%23events& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&事件说明&i class=&icon-external&&&/i&&/a&,我们选择监听 &i&html-webpack-plugin-before-html-processing&/i& 事件,在事件的回调函数中,插件会传入当前待处理的 HTML 内容供我们进一步修改。&/p&&p&我们知道骨架屏组件最终的渲染结果包含 HTML 和样式两部分,样式部分可以直接插入 head 标签內,而 HTML 需要插入挂载点中。插件使用者可以通过参数设置这个挂载点位置,默认将使用 &div id=&app&&。&/p&&p&看起来一切都很顺利,但是在多页应用中,情况会变的稍稍复杂。多页项目中通常会引入多个 HTML Webpack Plugin,例如我们在 &a href=&/?target=https%3A///lavas-project/lavas-template-vue-mpa& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Lavas MPA 模版&i class=&icon-external&&&/i&&/a&中使用的 &a href=&/?target=https%3A///mutualofomaha/multipage-webpack-plugin& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Multipage Webpack 插件&i class=&icon-external&&&/i&&/a&就是如此,这就会导致 &i&html-webpack-plugin-before-html-processing&/i& 事件被多次触发。&/p&&p&在多页应用中,我们传给骨架屏插件的 webpack 配置对象是包含多个入口的:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// webpack.skeleton.conf.js&/span&
&span class=&nx&&entry&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&page1&/span&&span class=&o&&:&/span& &span class=&nx&&resolve&/span&&span class=&p&&(&/span&&span class=&s1&&'./src/pages/page1/entry-skeleton.js'&/span&&span class=&p&&),&/span&
&span class=&nx&&page2&/span&&span class=&o&&:&/span& &span class=&nx&&resolve&/span&&span class=&p&&(&/span&&span class=&s1&&'./src/pages/page2/entry-skeleton.js'&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&这就意味着每次事件触发时,骨架屏插件都需要识别出当前正在处理的入口文件,执行 webpack 编译当前页面对应的骨架屏入口文件,渲染对应的骨架屏组件。查找当前处理的入口文件过程如下:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// vue-skeleton-webpack-plugin/src/index.js&/span&
&span class=&c1&&// 当前页面使用的所有 chunks&/span&
&span class=&kd&&let&/span& &span class=&nx&&usedChunks&/span& &span class=&o&&=&/span& &span class=&nx&&htmlPluginData&/span&&span class=&p&&.&/span&&span class=&nx&&plugin&/span&&span class=&p&&.&/span&&span class=&nx&&options&/span&&span class=&p&&.&/span&&span class=&nx&&chunks&/span&&span class=&p&&;&/span&
&span class=&kd&&let&/span& &span class=&nx&&entryKey&/span&&span class=&p&&;&/span&
&span class=&c1&&// chunks 和所有入口文件的交集就是当前待处理的入口文件&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nb&&Array&/span&&span class=&p&&.&/span&&span class=&nx&&isArray&/span&&span class=&p&&(&/span&&span class=&nx&&usedChunks&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&nx&&entryKey&/span& &span class=&o&&=&/span& &span class=&nb&&Object&/span&&span class=&p&&.&/span&&span class=&nx&&keys&/span&&span class=&p&&(&/span&&span class=&nx&&skeletonEntries&/span&&span class=&p&&).&/span&&span class=&nx&&find&/span&&span class=&p&&(&/span&&span class=&nx&&v&/span& &span class=&o&&=&&/span& &span class=&nx&&usedChunks&/span&&span class=&p&&.&/span&&span class=&nx&&indexOf&/span&&span class=&p&&(&/span&&span class=&nx&&v&/span&&span class=&p&&)&/span& &span class=&o&&&&/span& &span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&c1&&// 设置当前的 webpack 配置对象的入口文件和结果输出文件&/span&
&span class=&nx&&webpackConfig&/span&&span class=&p&&.&/span&&span class=&nx&&entry&/span& &span class=&o&&=&/span& &span class=&nx&&skeletonEntries&/span&&span class=&p&&[&/span&&span class=&nx&&entryKey&/span&&span class=&p&&];&/span&
&span class=&nx&&webpackConfig&/span&&span class=&p&&.&/span&&span class=&nx&&output&/span&&span class=&p&&.&/span&&span class=&nx&&filename&/span& &span class=&o&&=&/span& &span class=&sb&&`skeleton-&/span&&span class=&si&&${&/span&&span class=&nx&&entryKey&/span&&span class=&si&&}&/span&&span class=&sb&&.js`&/span&&span class=&p&&;&/span&
&span class=&c1&&// 使用配置对象进行服务端渲染&/span&
&span class=&nx&&ssr&/span&&span class=&p&&(&/span&&span class=&nx&&webpackConfig&/span&&span class=&p&&).&/span&&span class=&nx&&then&/span&&span class=&p&&(({&/span&&span class=&nx&&skeletonHtml&/span&&span class=&p&&,&/span& &span class=&nx&&skeletonCss&/span&&span class=&p&&})&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&c1&&// 注入骨架屏 HTML 和 CSS 到页面 HTML 中&/span&
&span class=&p&&});&/span&
&/code&&/pre&&/div&&p&至此,我们已经完成了骨架屏的渲染和注入工作,接下来有一个开发中的小问题需要关注。&/p&&h2&开发模式下插入路由&/h2&&p&前面说过,由于 Vue 会使用&a href=&/?target=https%3A//ssr.vuejs.org/zh/hydration.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&客户端混合&i class=&icon-external&&&/i&&/a&,骨架屏内容在前端渲染完成后就会被替换,那么如何在开发时方便的查看调试呢?&/p&&p&使用浏览器开发工具设置断点是一个办法,但如果能在开发模式中向路由文件插入骨架屏组件对应的路由规则,使各个页面的骨架屏能像其他路由组件一样被访问,将使开发调试变得更加方便。向路由文件插入规则代码的工作将在&a href=&/?target=https%3A///lavas-project/vue-skeleton-webpack-plugin/blob/master/src/loader.js& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&插件的 loader&i class=&icon-external&&&/i&&/a& 中完成。如果您对 webpack loader 还不了解,可以参阅&a href=&/?target=https%3A//doc.webpack-china.org/concepts/loaders/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&官方文档&i class=&icon-external&&&/i&&/a&。&/p&&p&我们需要向路由文件插入两类代码:引入骨架屏组件的代码和对应的路由规则对象。关于代码插入点,引入组件代码相对简单,放在文件顶部就行了,而路由规则需要插入路由对象数组中,目前我使用的是简单的字符串匹配来查找这个数组的起始位置。例如下面的例子中,需要向 loader 传入 &&i&routes: [& &/i&来确定插入路由的位置。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// router.js&/span&
&span class=&kr&&import&/span& &span class=&nx&&Skeleton&/span& &span class=&nx&&from&/span& &span class=&s1&&'@/pages/Skeleton.vue'&/span&
&span class=&nx&&routes&/span&&span class=&o&&:&/span& &span class=&p&&[&/span&
&span class=&p&&{&/span& &span class=&c1&&// 插入骨架屏路由&/span&
&span class=&nx&&path&/span&&span class=&o&&:&/span& &span class=&s1&&'/skeleton'&/span&&span class=&p&&,&/span&
&span class=&nx&&name&/span&&span class=&o&&:&/span& &span class=&s1&&'skeleton'&/span&&span class=&p&&,&/span&
&span class=&nx&&component&/span&&span class=&o&&:&/span& &span class=&nx&&Skeleton&/span&
&span class=&p&&}&/span&
&span class=&c1&&// ...其余路由规则&/span&
&span class=&p&&]&/span&
&/code&&/pre&&/div&&p&在多页应用中,每个页面对应的骨架屏都需要插入代码,使用者可以通过占位符设置引入骨架屏组件语句和路由规则的模版。loader 在运行时会使用这些模版,用真实的骨架屏名称替换掉占位符。在下面的例子中,假设我们有 &i&Page1.skeleton.vue &/i&和 &i&Page2.skeleton.vue &/i&这两个骨架屏,开发模式下可以通过 &i&/skeleton-page1 &/i&和 &i&/skeleton-page2&/i& 访问这两个骨架屏路由。更多参数说明可以参考&a href=&/?target=https%3A///lavas-project/vue-skeleton-webpack-plugin%23%25E5%258F%%%25E8%25AF%25B4%25E6%E& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这里&i class=&icon-external&&&/i&&/a&。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// webpack.dev.conf.js&/span&
&span class=&nx&&module&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&rules&/span&&span class=&o&&:&/span& &span class=&p&&[]&/span& &span class=&c1&&// 其他规则&/span&
&span class=&p&&.&/span&&span class=&nx&&concat&/span&&span class=&p&&(&/span&&span class=&nx&&SkeletonWebpackPlugin&/span&&span class=&p&&.&/span&&span class=&nx&&loader&/span&&span class=&p&&({&/span&
&span class=&nx&&resource&/span&&span class=&o&&:&/span& &span class=&nx&&resolve&/span&&span class=&p&&(&/span&&span class=&s1&&'src/router.js'&/span&&span class=&p&&),&/span& &span class=&c1&&// 目标路由文件&/span&
&span class=&nx&&options&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&entry&/span&&span class=&o&&:&/span& &span class=&p&&[&/span&&span class=&s1&&'page1'&/span&&span class=&p&&,&/span& &span class=&s1&&'page2'&/span&&span class=&p&&],&/span&
&span class=&nx&&importTemplate&/span&&span class=&o&&:&/span& &span class=&s1&&'import [nameCap] from '&/span&&span class=&err&&@&/span&&span class=&o&&/&/span&&span class=&nx&&pages&/span&&span class=&o&&/&/span&&span class=&p&&[&/span&&span class=&nx&&name&/span&&span class=&p&&]&/span&&span class=&o&&/&/span&&span class=&p&&[&/span&&span class=&nx&&nameCap&/span&&span class=&p&&].&/span&&span class=&nx&&skeleton&/span&&span class=&p&&.&/span&&span class=&nx&&vue&/span&&span class=&s1&&';'&/span&&span class=&p&&,&/span&
&span class=&nx&&routePathTemplate&/span&&span class=&o&&:&/span& &span class=&s1&&'/skeleton-[name]'&/span&
&span class=&p&&}&/span&
&span class=&p&&}))&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&blockquote&多页中的具体应用示例,可参考&a href=&/?target=https%3A///lavas-project/vue-skeleton-webpack-plugin/tree/master/examples/multipage& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&多页测试用例&i class=&icon-external&&&/i&&/a&或者&a href=&/?target=https%3A///lavas-project/lavas-template-vue-mpa& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Lavas MPA 模版&i class=&icon-external&&&/i&&/a&。&/blockquote&&h2&总结&/h2&&p&感谢&a href=&/?target=https%3A//huangxuan.me//upgrading-eleme-to-pwa/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&饿了么的 PWA 升级实践&i class=&icon-external&&&/i&&/a&一文,提供了解决这个问题的思路。当然文章中包含的远不止骨架屏这方面,相信每个读者都会受益匪浅。&/p&&p&&a href=&/?target=https%3A///lavas-project/vue-skeleton-webpack-plugin& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&插件&i class=&icon-external&&&/i&&/a&使用中遇到任何问题,都欢迎提出&a href=&/?target=https%3A///lavas-project/vue-skeleton-webpack-plugin/issues& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ISSUE&i class=&icon-external&&&/i&&/a&。&/p&&p&百度&a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Lavas&i class=&icon-external&&&/i&&/a&是一个基于 Vue 的 PWA 解决方案,帮助开发者轻松搭建 PWA 站点。其中多个模版也使用了这个插件,欢迎大家试用并提出意见。&/p&
前段时间阅读了一文,受益匪浅。其中一节,为开发者提供了减少白屏时间,提升用户感知体验的新思路。本文将借鉴这一思路,尝试为 Vue 项目添加骨架屏。骨架屏是什么?在 Google 提出的的四个…
&img src=&/v2-ebbf7828dae440f2e9cdf93_b.png& data-rawwidth=&1000& data-rawheight=&400& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-ebbf7828dae440f2e9cdf93_r.png&&&p&用vue+elementUI重构了一个后台管理系统项目,我们也根据自己的业务场景,往vue官方脚手架中加了一些自己的东西,现在记录下整个系统的构建的一些经验和使用vue实战过程中的一些小技巧&/p&&h2&首先当然是从零开始构建整个项目&/h2&&ul&&li&用vue-cli官方模板(叫“webpack”的那个模板)初始化整个项目,然后引入一些必要的dependence(element,axios)&/li&&/ul&&blockquote&A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.&/blockquote&&ul&&li&用yarn构建。 之前用npm构建,经常因为一些插件的更新导致项目跑不起来(插件升级了,与插件配套的vue或者其他一些依赖没有升级)。采用yarn 主要是因为它能通过yarn.lock锁定依赖的版本号,用起来和npm基本一样。(现在最新版的npm也有lock功能)&/li&&li&路由以及目录结构。 路由采用官方文档的多级嵌套路由形式,用起来非常的灵活。页面文件,一般可公用的组件我都会放在components文件夹下,其他的.vue文件按功能模块分别放在views下的不同文件夹下。&/li&&/ul&&img src=&/v2-cc5c3bddba05284cbecd18_b.png& data-rawwidth=&354& data-rawheight=&776& class=&content_image& width=&354&&&p&&br&&/p&&ul&&li&http模块axios封装,API层。 首先我自己写了一个HttpUtil.js文件对axios的get和post方法进行封装,这样的好处在于可以在请求的底层写一些共用函数,如发现接口返回没有权限,则跳转到登录页让用户重新登录,而且也让业务层代码更加简洁。&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//HttpUtil.js
import router from '../router'
import axios from 'axios'
import Vue from 'vue'
var qs = require('qs')
const Utils = {
get (url, data = {}) {
return new Promise((resolve, reject) =& {
axios.get(url, {
params: data,
withCredentials: true
.then(function (response) {
if (response.data.code === 1010) {
console.log('没有权限')
router.push({ path: '/login' })
} else if (response.data.code === 0) {
resolve(response)
new Vue().$message({
message: response.data.message,
type: 'error'
.catch(function (error) {
console.log(error)
post (url, data = {}, upload = false) {
return new Promise((resolve, reject) =& {
axios.post(url, upload ? data : qs.stringify(data), {
withCredentials: true,
headers: {'Content-Type': 'application/x-www-form- charset=UTF-8'}
.then(function (response) {
if (response.data.code === 1010) {
console.log('没有权限')
} else if (response.data.code === 0) {
// console.log(response)
resolve(response)
console.log('接口返回状态码:' + response.data.code)
new Vue().$message({
message: response.data.message,
type: 'error'
.catch(function (error) {
console.log(error)
export default Utils
&/code&&/pre&&/div&&ul&&li&iconfont 的全局引入,全局过滤器的引入。 在main.js引入filter,在App.vue引入定义icon-font, 这样在项目的任何地方都可以使用iconfont和过滤器。(字体库推荐用阿里的iconfont,&a href=&/?target=http%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Iconfont-阿里巴巴矢量图标库&i class=&icon-external&&&/i&&/a&)&/li&&/ul&&img src=&/v2-9acc_b.png& data-rawwidth=&1646& data-rawheight=&1392& class=&origin_image zh-lightbox-thumb& width=&1646& data-original=&/v2-9acc_r.png&&&p&&br&&/p&&ul&&li&mock数据方案。API文件我采用了这样一种写法,所以我只需要改变config下的mock字段,整个 项目的请求就变成请求mock文件夹下的json文件&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&/**
获取加密RSA PublicKey
* @return {[type]} [description]
getRSAPublicKey (params) {
let postUrl = Config.mock ? 'static/mock/getKey.json' : process.env.DOMAIN + '/bshop/login/key/get'
return HttpUtil.get(postUrl, params)
&/code&&/pre&&/div&&h2&实战小技巧&/h2&&ul&&li&项目性能体积优化。首先用脚手架自带的analyze去分析,在package.json的scripts下配置如下 &/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&analyz&: &NODE_ENV=production npm_config_report=true npm run build&
&/code&&/pre&&/div&&p&然后跑npm run analyz ,就可以看到项目的一个体积分析,类似这个图,你就可以针对性的去进行优化。&/p&&img src=&/v2-7017017dddc7bc4f86a469d9805110eb_b.png& data-rawwidth=&1442& data-rawheight=&1170& class=&origin_image zh-lightbox-thumb& width=&1442& data-original=&/v2-7017017dddc7bc4f86a469d9805110eb_r.png&&&p&这里介绍两个体积优化的点儿:1.element组件按需引入,2.路由懒加载。对于第一点,饿了么官方文档里有很详尽的介绍,不提了。第二点路由懒加载,你可以在上图中发现项目打包后会变成vendor.js,0.xxxx.js,1.xxxx.js,2.xxxx.js ,每一个路由都会变成一个文件,项目在打开对应路由对应组件时候,才会加载对应的js(恩,这就是我想要的滑板鞋)。实现方式其实很简单,在路由文件中用这样的方式定义&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const UserIndex = resolve =& require(['@/components/UserIndex'], resolve)
&/code&&/pre&&/div&&p&以上就是vue-router官方建议的,结合 Vue 的 异步组件 和 Webpack 的 code splitting feature来实现路由组件的懒加载的简单方法。&/p&&ul&&li&前端权限实现。 整个项目的权限分两种:按钮权限和Tab权限。实现思路很简单:1.和后台约定好每种权限对应的code,如'AB'。2.登录成功的时候在sessionStroage存储权限信息。(如果用localStorage【能多标签通信】,一台电脑登录两个账号的时候,后一个账号会覆盖前一个账号的权限)。3.写一个公用函数,传入一个参数code,返回该权限是否在权限数组里。4.按钮权限直接用v-if实现,tab权限在mouted的时候进行初始化。 综上四点,基本实现思想是这个样子,具体的实现根据每个项目结构的不同都会不一样(一级路由权限,二级路由权限),我自己具体实现起来还是蛮复杂的,不具有普适性,暂不赘述,但有几个场景大家也是都需要考虑到的:1.刷新页面获取最新权限。2.当前页面没有权限,则从当前页面跳走。3.若二级菜单下第一个二级tab选项被删除,则点击该项对应的一级菜单,则应该跳转到该一级菜单下面的第一个可用二级tab(原第二个二级tab)。4.每次刷新页面时候,菜单不能有很明显的抖动。(先根据sessionStroage存储的权限初始化一遍页面,再通过最新请求到的权限初始化页面)&/li&&li&巧用vuex进行数据提前加载。 有一个详情页面,一堆选择框是需要从后台读取数据的(而且这个接口返回速度较慢),这个时候可以借助vuex在这个页面之前的一个必经页面提前进行数据加载。由于vuex computed 取到的数据还是响应式的,你不好去改变它,这个时候可以通过JSON.stringify 和 JSON.parse 把 响应式数据变换成为正常数组。&/li&&li&默认头像存放位置。 我们都知道,webpack的url-loader中,会处理把asset目录下小于10kb的图片都转换成base64编码。&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&c&&&!-- 报错
--&&/span&
&span class=&p&&&&/span&&span class=&nt&&img&/span& &span class=&na&&:src&/span&&span class=&o&&=&/span&&span class=&s&&&headPic | 'asset/img/default.jpg'&&/span&&span class=&p&&&&/span&
&/code&&/pre&&/div&&p&上面这段代码会报错,动态图或会默认头像 我都会放在 static目录下&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&img&/span& &span class=&na&&:src&/span&&span class=&o&&=&/span&&span class=&s&&&headPic | '/static/img/default.jpg'&&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&img&/span& &span class=&na&&:src&/span&&span class=&o&&=&/span&&span class=&s&&&'/static/img/month'+ month +'.jpg'&&/span&&span class=&p&&&&/span&
&span class=&c&&&!-- 当然这样强行放在asset下也没有任何问题,就代码编程了两行
--&&/span&
&span class=&p&&&&/span&&span class=&nt&&img&/span& &span class=&na&&v-if&/span&&span class=&o&&=&/span&&span class=&s&&&headPic&&/span& &span class=&na&&:src&/span&&span class=&o&&=&/span&&span class=&s&&&headPic&&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&img&/span& &span class=&na&&v-else&/span&&span class=&o&&=&/span&&span class=&s&&&headPic&&/span& &span class=&na&&src&/span&&span class=&o&&=&/span&&span class=&s&&&asset/img/default.jpg&&/span&&span class=&p&&&&/span&
&/code&&/pre&&/div&&ul&&li&通过 import 'es6-promise/auto'
来解决在360兼容模式 ie10 不兼容bug&/li&&li&this.$route.query.xxxx 路由取值记得用 parseInt处理下 .(路由跳转过来 ,取到的是int,刷新页面取到的是 字符串)&/li&&li&许多要在页面渲染后出发的函数记得用nexTick,比如el-checkbox-group的viewModel,你在数据初始化好后(页面初始化好之前)去设置它的值,它会报错。&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&Vue&/span&&span class=&p&&.&/span&&span class=&nx&&nextTick&/span&&span class=&p&&(()&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&selectCountries&/span&&span class=&p&&()&/span&
&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&disableCountries&/span&&span class=&p&&()&/span&
&span class=&p&&})&/span&
&/code&&/pre&&/div&&ul&&li&在标签中要用到对象下的属性时候,记得要写v-if判断。(不然初始化products为空的时候,就会报一个错)&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&div v-if=&products& class=&len-tip&&{{products.length}}/500&/div&
&/code&&/pre&&/div&&ul&&li&用watch 实现搜索框 延时实时搜索效果(最后一个输入500毫秒内如果没有按键,则发出请求),vue2.x 取消了debounce,建议我们使用lodash之类的库,我看了下团队同学用下面这个蛮简洁的方法实现了它。&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span& &span class=&nx&&watch&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&searchInfo&/span& &span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kd&&let&/span& &span class=&nx&&oldSearchInfo&/span& &span class=&o&&=&/span& &span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&searchInfo&/span&
&span class=&nx&&setTimeout&/span&&span class=&p&&(()&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&searchInfo&/span& &span class=&o&&===&/span& &span class=&nx&&oldSearchInfo&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// 请求数据&/span&
&span class=&p&&}&/span&
&span class=&p&&},&/span& &span class=&mi&&500&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&ul&&li&像素级页面开发插件 PerfectPixel。 由于公司对页面UI要求非常严格,找到了这个插件,能把设计图贴在网页上,切页面的时候,调节贴图的透明度,让页面和设计图完整的贴合,这样就完全没有问题了。&/li&&li&父子路由mounted顺序。当前vue===& 一级路由===》二级路由 &/li&&li&递归组件 实现树状结构。两个关键点:1.父组件通过props向子组件传递整个树状结构数据。2.子组件遍历本层数据,并用name的方式递归调用自己(向props中传入children数据)&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&!-- 子组件 --&
&template&
&div v-for=&item in arr&&
&h1&{{item.name}}&/h1&
&son :arr=&item.children&&&/son&
&/template&
export default {
name: 'son',
arr: Array
&!-- 父组件 --&
&Son :arr=&allDate& &&/Son&
allDate数据为: [{name: '第一层1', children: [{name: '第二层1', children: [{name: '第san层1', children: []}, {name: '第san层2', children: []}]}, {name: '第二层1', children: []}, {name: '第二层1', children: []}] }, {name: '第一层2', children: [] }]
&/code&&/pre&&/div&&ul&&li&css 注释切记 不能用 //
。下图的代码会导致iconfont引入失败。(在less中 用//注释是ok的)这个坑要命的地方就是1. eslint不会报错 2.sublime Text 中打开css文件中 打注释内容 然后按注释快捷键(cmd+ /)进行注释,出的就是下面的//(而不是正确的 /**
xxxx */) 。&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// icon-font
@font-face {
font-family: 'icomoon';
src: url('../fonts/iconfont.eot');
src: url('../fonts/iconfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/iconfont.woff') format('woff'),
url('../fonts/iconfont.ttf') format('truetype'),
url('../fonts/iconfont.svg#iconmoon') format('svg');
&/code&&/pre&&/div&&h2&最后&/h2&&p&双休日应女朋友的要求来咖啡厅陪她,就趁机写个文章总结下之前一段时间的工作。本文所有的实现方式并不是解决问题的最佳实现,如果你有更好的方式,欢迎在下方评论区给我留言,我们大家一起讨论,一起进步~&/p&&p&&br&&/p&&img src=&/v2-bdc78da2d8e636d8ba677_b.jpg& data-rawwidth=&1536& data-rawheight=&2048& class=&origin_image zh-lightbox-thumb& width=&1536& data-original=&/v2-bdc78da2d8e636d8ba677_r.jpg&&&p&&/p&
用vue+elementUI重构了一个后台管理系统项目,我们也根据自己的业务场景,往vue官方脚手架中加了一些自己的东西,现在记录下整个系统的构建的一些经验和使用vue实战过程中的一些小技巧首先当然是从零开始构建整个项目用vue-cli官方模板(叫“webpack”的那个…
&p&如何管理 vue 项目的数据?这个问题似乎早已经有答案了,无非就是使用 vuex ,全局 store,整个应用维护一个超大的 Object,界面的显示情况随着超大 Object 的变化而变化。&/p&&p&看起来很简单,不就维护一个 Object 嘛,实际上,要想组织好数据这块代码,必须事先对项目的数据结构理解得非常透彻,然后像设计数据库表一样把各个 module 的样子设计出来。实际上,个人觉得设计 vuex 的 module 比设计数据库表复杂得多:&/p&&ul&&li&1、像数据库一样设计各个业务实体的外貌,这部分设计难度应该和数据库表设计差不多;&/li&&li&2、维护一堆 ajax 请求状态;&/li&&li&3、如何优雅地复用 module。比如有一个 PersonListModule,在一个页面上有两处要用到 PersonListModule 中的列表数据:一个是要在表格控件里面展示,一个是要在下拉控件里面展示,每个控件中展示的列表数据筛选条件不一样;&/li&&li&4、如何同步 vuex 中的数据和服务器端数据。vuex 的超大 Object 可以看做服务器端数据在客户端内存中的一个缓存,怎么设计这个缓存的同步策略?&/li&&/ul&&p&对于3、4两个问题,结合起来更恐怖:同步服务器端数据到 PersonListModule 的同时,还要考虑如何从 PersonListModule 中筛选出分页数据到页面展示,还要筛选出多个列表,还要考虑在什么时机重新更新“缓存”,想想就头大。&/p&&p&假设我们能力很强大,设计出了能完美应对上述问题的 store 方案,还有一个大问题拦着我们呢:如何保证这套设计的可扩展性?因为业务系统变化多端,不知道什么时候产品经理又有新想法了,我们得设计能很好地应对变化多端的需求吗?&/p&&h2&为什么这么难?问题究竟出现在哪里?&/h2&&p&vuex 的思维模式主要是从数据着手,由数据推导出界面的样子,这就需要先设计好 store 结构了。要设计好 store 结构,目测必须具备如下特质的工程师才能做好:&/p&&ul&&li&1、对项目业务了解非常深入;&/li&&li&2、具备超强的抽象思维能力;&/li&&li&3、经验丰富,能尽量想到设计出的 store 结构能应付哪些情况、不能应付哪些情况。&/li&&/ul&&p&第2、3条的门槛实在是太高了,能做到的前端工程师估计没多少。&/p&&h2&怎么办?&/h2&&p&我们不应该从数据推导出界面,而应该从界面推导出数据,逐层抽象。&/p&&p&比如现在要仿一个新浪微博首页,页面上主要包含的数据有:分组信息、微博列表、个人信息、一些推荐信息等,那么就设计一个只针对该页面的 module ,大致结构为:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const homePageModule = {
groupList: [{
name: '名人明星',
name: '同事',
groupListExtraInfo: {
// 初始显示多少个小组
initShowCount: 5,
loading: true
weiboList: [{
content: '&p&震惊部&/p&',
author: 'yibuyisheng',
createTime: '22'
weiboListPageInfo: {
loadingStatus: 'QUIET', // 三种取值:QUIET -& 没有加载;UP -& 向上加载;DOWN -& 向下加载
// weiboList 的开始时间,可用这个时间做下一次的向上加载
startTime: '22',
// weiboList 的结束时间,可用这个时间做下一次的向下加载
endTime: '22'
nickname: 'yibuyisheng',
email: '',
avatar: '//profile?rightmod=1&wvr=6&mod=personinfo',
followedCount: 405,
followerCount: 235,
weiboCount: 1321
recommendMovies: [
recommendTopics: [
mutations: {
updateWeiboList(state, list) {
actions: {
appendWeiboList() {
prependWeiboList() {
&/code&&/pre&&/div&&p&针对这个页面,这个结构,各个处理逻辑就具体化、特殊化了,代码写起来非常轻松。&/p&&h2&代码复用?&/h2&&p&假设现在有个小组页面,点进去后可以看到该小组所有成员发的微博,因为是一个新的页面,所以需要新起一个 module ,这也意味着要重复写一遍 weiboList 相关的代码,岂不蛋疼!&/p&&p&此时可以考虑写一个 createWeiboListModule() 函数,用于创建这种通用 module ,然后再写一个 mergeModules() 函数,把 createWeiboListModule() 函数创建出来的 module 对象和各页面特殊的 module 合并起来,样子看起来大致是这样:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&mergeModules(createWeiboListModule(), {
mutations: {
actions: {
&/code&&/pre&&/div&&p&遇到需要复用的才抽取通用逻辑,很自然,很简单。&/p&&h2&怎么结合 vue 组件?&/h2&&p&上面的结构有一个很大的问题,就是不能很好地和 vue 组件结合。比如,要让微博首页和分组页面中的微博列表能复用 weiboList 相关代码,那么 weiboList 涉及到的 state、action、mutation、getter 的命名都要尽量保持一致,不然就要传一个 nameMap(命名映射)给两个页面通用的 WeiboListComponent 组件,看起来就像这样:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&weibo-list-component :name-map=&{weiboList: 'homePageWeiboList'}&&&/weibo-list-component&
&/code&&/pre&&/div&&p&简直蛋疼!&/p&&p&好吧,那就严格约束这两个页面的 state、action、mutation、getter 命名都保持一致吧!&/p&&p&简直超级蛋疼!&/p&&p&此时可以考虑用 namespace 来解决这个问题,比如上面的 homePageModule 可以把 weiboList 拆分出来:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const store = new vuex.Store({
modules: {
'page:home': {
groupList: [{
name: '名人明星',
name: '同事',
groupListExtraInfo: {
// 初始显示多少个小组
initShowCount: 5,
loading: true
nickname: 'yibuyisheng',
email: '',
avatar: '//profile?rightmod=1&wvr=6&mod=personinfo',
followedCount: 405,
followerCount: 235,
weiboCount: 1321
recommendMovies: [
recommendTopics: [
'page:home:weiboList': createWeiboListModule(...)
&/code&&/pre&&/div&&p&这样一来,只要给 vue 组件传一个 namespace 参数就行了:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&weibo-list-component namespace=&page:home:weiboList&&&/weibo-list-component&
&/code&&/pre&&/div&&p&嗯,看起来挺好的!&/p&&h2&如何处理“store 缓存”?&/h2&&p&可以在上一个问题解决的基础上,加上缓存功能,目测有大把现成的缓存策略可以参考(服务器端都玩儿烂了),由于绝大部分系统并不需要这层缓存功能,所以此处不赘述。&/p&&h2&就这样了吗?&/h2&&p&上述方案,转变了思维方式,从具体到抽象,从简单到复杂,很自然,符合思考习惯。但是最终的代码还是会很容易搞得很乱的:&/p&&ul&&li&1、mergeModules() 要照顾各种合并策略;&/li&&li&2、createXXXModule() 方法会抽出很多层。比如可以从 createWeiboListModule() 抽出来 createContinuousListModule() ,用于构造通用的具备“向前向后”加载能力的列表 Module,最终可能会形成一条常常的“继承链”,需要自己去定义维护这套继承逻辑,心累。&/li&&/ul&&p&其实上面两条一看,就知道有现成的解决方案了: class。&/p&&p&参考此处实现(代码还在持续完善中):&a href=&/?target=https%3A///yibuyisheng/vuex-model/blob/master/src/store/BaseModule.js%25EF%25BC%%25BB%25A3%25E7%25A0%%25BF%%259C%25A8%25E5%25AE%258C%25E5%E4%25B8%25AD%25EF%25BC%%& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&/yibuyisheng/vuex-model/blob/master/src/store/BaseModule.js 。&i class=&icon-external&&&/i&&/a&&/p&&p&具体业务代码写起来就像是这样了:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&class&/span& &span class=&nx&&ContinuousList&/span& &span class=&kr&&extends&/span& &span class=&nx&&BaseModule&/span& &span class=&p&&{&/span&
&span class=&nx&&state&/span& &span class=&o&&=&/span& &span class=&p&&{&/span&
&span class=&nx&&list&/span&&span class=&o&&:&/span& &span class=&p&&[],&/span&
&span class=&nx&&pageInfo&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&loadingStatus&/span&&span class=&o&&:&/span& &span class=&s1&&'QUIET'&/span&&span class=&p&&,&/span&
&span class=&nx&&startTime&/span&&span class=&o&&:&/span& &span class=&s1&&'39'&/span&&span class=&p&&,&/span&
&span class=&nx&&endTime&/span&&span class=&o&&:&/span& &span class=&s1&&'39'&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&err&&@&/span&&span class=&nx&&action&/span&
&span class=&nx&&async&/span& &span class=&nx&&appendList&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&
&span class=&kr&&const&/span& &span class=&nx&&result&/span& &span class=&o&&=&/span& &span class=&nx&&await&/span& &span class=&nx&&request&/span&&span class=&p&&(&/span&&span class=&s1&&'some url'&/span&&span class=&p&&,&/span& &span class=&nx&&params&/span&&span class=&p&&);&/span&
&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&updateList&/span&&span class=&p&&(&/span&&span class=&nx&&result&/span&&span class=&p&&.&/span&&span class=&nx&&list&/span&&span class=&p&&);&/span&
&span class=&p&&...&/span&
&span class=&p&&}&/span&
&span class=&err&&@&/span&&span class=&nx&&action&/span&
&span class=&nx&&prependList&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span& &span class=&p&&...&/span& &span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&kr&&class&/span& &span class=&nx&&WeiboList&/span& &span class=&kr&&extends&/span& &span class=&nx&&ContinuousList&/span& &span class=&p&&{&/span&
&span class=&err&&@&/span&&span class=&nx&&action&/span&
&span class=&nx&&async&/span& &span class=&nx&&voteUp&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&
&span class=&nx&&await&/span& &span class=&nx&&request&/span&&span class=&p&&(&/span&&span class=&s1&&'some url'&/span&&span class=&p&&,&/span& &span class=&nx&&params&/span&&span class=&p&&);&/span&
&span class=&kr&&const&/span& &span class=&nx&&weiboDetail&/span& &span class=&o&&=&/span& &span class=&nx&&await&/span& &span class=&nx&&updateWeibo&/span&&span class=&p&&(&/span&&span class=&s1&&'some url'&/span&&span class=&p&&,&/span& &span class=&nx&&params&/span&&span class=&p&&.&/span&&span class=&nx&&weiboId&/span&&span class=&p&&);&/span&
&span class=&kr&&const&/span& &span class=&nx&&newList&/span& &span class=&o&&=&/span& &span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&state&/span&&span class=&p&&.&/span&&span class=&nx&&list&/span&&span class=&p&&.&/span&&span class=&nx&&map&/span&&span class=&p&&((&/span&&span class=&nx&&wb&/span&&span class=&p&&)&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&wb&/span&&span class=&p&&.&/span&&span class=&nx&&id&/span& &span class=&o&&===&/span& &span class=&nx&&weiboDetail&/span&&span class=&p&&.&/span&&span class=&nx&&id&/span& &span class=&o&&?&/span& &span class=&nx&&weiboDetail&/span& &span class=&o&&:&/span& &span class=&nx&&wb&/span&&span class=&p&&;&/span&
&span class=&p&&});&/span&
&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&updateList&/span&&span class=&p&&(&/span&&span class=&nx&&newList&/span&&span class=&p&&);&/span&
&span class=&p&&...&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&err&&@&/span&&span class=&nx&&composition&/span&&span class=&p&&(&/span&&span class=&nx&&WeiboList&/span&&span class=&p&&)&/span&
&span class=&kr&&class&/span& &span class=&nx&&HomePage&/span& &span class=&kr&&extends&/span& &span class=&nx&&BaseModule&/span& &span class=&p&&{&/span&
&span class=&nx&&$namespace&/span& &span class=&o&&=&/span& &span class=&s1&&'page:home:'&/span&&span class=&p&&;&/span&
&span class=&p&&...&/span&
&span class=&err&&@&/span&&span class=&nx&&action&/span&
&span class=&nx&&requestRecommendInfo&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&
&span class=&p&&}&/span&
&span class=&p&&...&/span&
&span class=&p&&}&/span&
&span class=&nx&&HomePage&/span&&span class=&p&&.&/span&&span class=&nx&&register&/span&&span class=&p&&();&/span&
&/code&&/pre&&/div&&p&在对应的 HomePage.vue 里面,大致是这样:&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&template&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&div&/span& &span class=&na&&class&/span&&span class=&o&&=&/span&&span class=&s&&&home-page-view&&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&weibo-list-component&/span& &span class=&na&&namespace&/span&&span class=&o&&=&/span&&span class=&s&&&page:home:weiboList&&/span&&span class=&p&&&&/&/span&&span class=&nt&&weibo-list-component&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&div&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&template&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&script&/span&&span class=&p&&&&/span&
&span class=&kr&&export&/span& &span class=&k&&default&/span& &span class=&p&&{&/span&
&span class=&nx&&created&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&
&span class=&kr&&const&/span& &span class=&nx&&constants&/span& &span class=&o&&=&/span& &span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&getConstants&/span&&span class=&p&&(&/span&&span class=&s1&&'page:home'&/span&&span class=&p&&);&/span&
&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&$store&/span&&span class=&p&&.&/span&&span class=&nx&&dispatch&/span&&span class=&p&&(&/span&&span class=&nx&&constants&/span&&span class=&p&&.&/span&&span class=&nx&&REQUEST_RECOMMEND_INFO&/span&&span class=&p&&,&/span& &span class=&nx&&params&/span&&span class=&p&&);&/span&
&span class=&p&&...&/span&
&span class=&p&&}&/span&
&span class=&p&&};&/span&
&span class=&p&&&/&/span&&span class=&nt&&script&/span&&span class=&p&&&&/span&
&/code&&/pre&&/div&&p&而 WeiboListComponent 组件大致是这样:&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&template&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&div&/span& &span class=&na&&class&/span&&span class=&o&&=&/span&&span class=&s&&&weibo-list-component&&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&div&/span&&span class=&p&&&&/span&
&span class=&p&&&/&/span&&span class=&nt&&template&/span&&span class=&p&&&&/span&
&span class=&p&&&&/span&&span class=&nt&&script&/span&&span class=&p&&&&/span&
&span class=&kr&&export&/span& &span class=&k&&default&/span& &span class=&p&&{&/span&
&span class=&nx&&props&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&namespace&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&nb&&String&/span&&span class=&p&&,&/span&
&span class=&nx&&required&/span&&span class=&o&&:&/span& &span class=&kc&&true&/span&
&span class=&p&&}&/span&
&span class=&p&&},&/span&
&span class=&nx&&computed&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&weiboList&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kr&&const&/span& &span class=&nx&&constants&/span& &span class=&o&&=&/span& &span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&getConstants&/span&&span class=&p&&(&/span&&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&namespace&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&$store&/span&&span class=&p&&.&/span&&span class=&nx&&getters&/span&&span class=&p&&[&/span&&span class=&nx&&constants&/span&&span class=&p&&.&/span&&span class=&nx&&LIST&/span&&span class=&p&&];&/span&
&span class=&p&&}&/span&
&span class=&p&&},&/span&
&span class=&nx&&created&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&
&span class=&kr&&const&/span& &span class=&nx&&constants&/span& &span class=&o&&=&/span& &span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&getConstants&/span&&span class=&p&&(&/span&&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&namespace&/span&&span class=&p&&);&/span&
&span class=&k&&this&/span&&span class=&p&&.&/span&&span class=&nx&&$store&/span&&span class=&p&&.&/span&&span class=&nx&&dispatch&/span&&span class=&p&&(&/span&&span class=&nx&&constants&/span&&span class=&p&&.&/span&&span class=&nx&&APPEND_LIST&/span&&span class=&p&&,&/span& &span class=&nx&&params&/span&&span class=&p&&);&/span&
&span class=&p&&...&/span&
&span class=&p&&}&/span&
&span class=&p&&};&/span&
&span class=&p&&&/&/span&&span class=&nt&&script&/span&&span class=&p&&&&/span&
&/code&&/pre&&/div&&h2&总结&/h2&&p&其实就是换一种思路:从界面推导数据,从具体到抽象。&/p&
如何管理 vue 项目的数据?这个问题似乎早已经有答案了,无非就是使用 vuex ,全局 store,整个应用维护一个超大的 Object,界面的显示情况随着超大 Object 的变化而变化。看起来很简单,不就维护一个 Object 嘛,实际上,要想组织好数据这块代码,必须事先…
&p&我现在做的项目正好与题主类似,主要是不同页面间的一些状态共享。先说结论,我们最终选择了vuex。&/p&&p&其他回答中提到了event bus,全局对象等解决方案在页面状态共享这个场景上还是有些局限的。&/p&&p&event bus:event bus很好地解决了多层嵌套,兄弟组件的通信问题。但他没法解决一个场景:现在与未来,event bus是即时性的,只有当前存在的实例可以订阅到响应,那未来创建的实例呢?而题主面临的应该就是这种场景,多页面之间,之后的页面创建了,如何能监听到上个页面的事件呢。&/p&&p&全局对象:全局对象的确能够做到页面状态的共享,但是这样做的健壮性跟局限就比较大了。&br&1. 全局对象谁都可以读,谁都可以写,这里的谁不仅包含了我们的代码还包含了第三方代码。&br&2. 缺乏响应式的设计。&br&3. 没有对写操作的必要处理,比如一个状态要求是date对象,因为一个地方写成了string,会不会对所有页面产生副作用呢?&/p&&p&vuex恰恰能够解决以上两个方案不足的地方。&/p&&p&题主疑惑的应该还有什么样的数据应该放在vuex中,页面状态、页面数据等。&/p&&blockquote&local state is ok&/blockquote&&p&在我们现在的实践中,我们将“共享”的状态与数据放在vuex中,比如某全局的列表及选择态。&/p&&p&在评论中,题主将所有ajax放在vuex,将vuex作为一个model层,我还是持保留意见的。无需缓存的一些列表数据,分页数据,校验请求等放在vuex中不太合适。vuex虽然可以将不同业务封装成module,但是mutation,action名却是全局维护的,项目越来越大,维护是个比较棘手的问题。&/p&&p&再引申一下,可能跟题主的项目并没有关系。我理解中的model层处理的数据不仅仅只有ajax请求,还有从localstorege取到的同步数据,web socket取到的实时数据等等,可能为了性能问题,先轮询,再socket,再将其他ajax请求改走socket。&/p&
我现在做的项目正好与题主类似,主要是不同页面间的一些状态共享。先说结论,我们最终选择了vuex。其他回答中提到了event bus,全局对象等解决方案在页面状态共享这个场景上还是有些局限的。event bus:event bus很好地解决了多层嵌套,兄弟组件的通信问题…
&p&element VS
iview&/p&&p&(最近项目UI框架在选型 ,做了个分析, 不带有任何利益相关)&/p&&p&主要从以下几个方面来做对比&/p&&p&使用率(npm 平均下载频率,组件数量,star, issue…)&/p&&p&API风格&/p&&p&打包优化&/p&&p&与设计师友好性&/p&&br&&p&&b&1,使用率(npm 平均下载频率,组件数量,star, issue…)&/b&&/p&&p&&b&element-ui&/b&&/p&&br&&img data-rawheight=&954& src=&/v2-01ce33ff9f8d2d1e98dbc565_b.png& data-rawwidth=&1794& class=&origin_image zh-lightbox-thumb& width=&1794& data-original=&/v2-01ce33ff9f8d2d1e98dbc565_r.png&&&br&&br&&img data-rawheight=&1108& src=&/v2-6a41ef0be0befccc67b8a3_b.png& data-rawwidth=&2052& class=&origin_image zh-lightbox-thumb& width=&2052& data-original=&/v2-6a41ef0be0befccc67b8a3_r.png&&&br&&img data-rawheight=&1096& src=&/v2-be799d3c30fbb6c96f5d7f586f70c9bc_b.png& data-rawwidth=&2030& class=&origin_image zh-lightbox-thumb& width=&2030& data-original=&/v2-be799d3c30fbb6c96f5d7f586f70c9bc_r.png&&&br&&img data-rawheight=&1074& src=&/v2-6c516bbdbff44e68852c22d_b.png& data-rawwidth=&1940& class=&origin_image zh-lightbox-thumb& width=&1940& data-original=&/v2-6c516bbdbff44e68852c22d_r.png&&&br&&img data-rawheight=&1108& src=&/v2-d1cb79eecfe1cb611c54d8d472f33692_b.png& data-rawwidth=&728& class=&origin_image zh-lightbox-thumb& width=&728& data-original=&/v2-d1cb79eecfe1cb611c54d8d472f33692_r.png&&&br&&img data-rawheight=&1028& src=&/v2-b6e067c29df1feadc17c0a_b.png& data-rawwidth=&642& class=&origin_image zh-lightbox-thumb& width=&642& data-original=&/v2-b6e067c29df1feadc17c0a_r.png&&&br&&img data-rawheight=&812& src=&/v2-0ed43cc40ba5a72d67da_b.png& data-rawwidth=&1162& class=&origin_image zh-lightbox-thumb& width=&1162& data-original=&/v2-0ed43cc40ba5a72d67da_r.png&&&br&&br&&p&结论 ,element 生态更好,使用频率远超过iview ,element开发团队实力&/p&&p&感谢评论区 &/p&&p&纠错&/p&&p&1.Element 有级联选择。&/p&&p&2.Element 的 Popover 即 iView 的 Poptip 组件。&/p&&p&3.Tooltip iView 和 Element 都有。&br&&/p&&br&&p&一些小众组件上各有所长 整体iview 更丰富(时间轴,加载进度条,气泡卡片 ,BackTop,图钉)&/p&&p&&b&API风格&/b&&/p&&p&&b&通过使用平率最高的 form
table 日历
等比较两者&/b&&/p&&br&&img data-rawheight=&934& src=&/v2-78e85270daba159bb61af31c_b.png& data-rawwidth=&2006& class=&origin_image zh-lightbox-thumb& width=&2006& data-original=&/v2-78e85270daba159bb61af31c_r.png&&&p&对应代码&/p&&img data-rawheight=&828& src=&/v2-11d9c9e3ea51b05e4c6cb619cc1d1837_b.png& data-rawwidth=&1996& class=&origin_image zh-lightbox-thumb& width=&1996& data-original=&/v2-11d9c9e3ea51b05e4c6cb619cc1d1837_r.png&&&p&明显感觉 iview 的api 更加简洁,在生成类似表格
下拉框这些较复杂的组件时 , iview 的方式类似于antdesign , 好处是直接传数据进去,在内部实现了模板生成,高效 快捷。 而element 则是用到到v-for vue指令结合的方式去生成,批量生成元素。&/p&&br&&img data-rawheight=&580& src=&/v2-c550ab309d935c83e3f006ca633ca3ce_b.png& data-rawwidth=&2000& class=&origin_image zh-lightbox-thumb& width=&2000& data-original=&/v2-c550ab309d935c83e3f006ca633ca3ce_r.png&&&p&表格
操作列 自定义渲染的时 ,iview 使用的是 vue的 render 函数, element 直接在template 中插入对应模板 &/p&&p&表格分页都需要 引入分页组件 配合使用&/p&&br&&img data-rawheight=&422& src=&/v2-c718aed03a61d2d1d8db1fe_b.png& data-rawwidth=&2006& class=&origin_image zh-lightbox-thumb& width=&2006& data-original=&/v2-c718aed03a61d2d1d8db1fe_r.png&&&br&&img data-rawheight=&538& src=&/v2-5c366bca7de47a0d963332_b.png& data-rawwidth=&1992& class=&origin_image zh-lightbox-thumb& width=&1992& data-original=&/v2-5c366bca7de47a0d963332_r.png&&&br&&img data-rawheight=&728& src=&/v2-aff4ae069a00c93ce3dcb628d577cbeb_b.png& data-rawwidth=&1824& class=&origin_image zh-lightbox-thumb& width=&1824& data-original=&/v2-aff4ae069a00c93ce3dcb628d577cbeb_r.png&&&br&&br&&p&两者api 总体比较 ,iview 要比element 简洁许多。 饿了么更侧重于在template里直接去渲染模板&/p&&p&思想上 个人觉得iview偏向react,
element 更vue&/p&&br&&p&表单校验 两者都使用同一款插件 async-validator
校验方式一样&/p&&br&&p&&b&项目优化角度&/b&&/p&&p&首屏优化,第三方组件库依赖过大 会给首屏加载带来很大的压力,一般解决方式是 按需求引入组件&/p&&p&element-ui
根据官方说明 现需要引入 &a href=&///?target=https%3A///QingWei-Li/babel-plugin-component& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&babel-plugin-component&i class=&icon-external&&&/i&&/a& 插件 做相关配置 然后直接在组件目录 注册全局组件&/p&&br&&br&&img data-rawheight=&428& src=&/v2-080e46faf3bf93a5bff2ad8894cfb3fa_b.png& data-rawwidth=&714& class=&origin_image zh-lightbox-thumb& width=&714& data-original=&/v2-080e46faf3bf93a5bff2ad8894cfb3fa_r.png&&&p&iview
按需求加载
这里感觉官方给的文档不是很详细 &/p&&br&&img data-rawheight=&762& src=&/v2-7cf13dff07aef_b.png& data-rawwidth=&1662& class=&origin_image zh-lightbox-thumb& width=&1662& data-original=&/v2-7cf13dff07aef_r.png&&&br&&p&&b&主题&/b&&/p&&p&&b&iview&/b&&/p&&br&&p&本身提供了一套主题可供选择,除此之外 自定义主题&/p&&br&&p&方法一(官方推荐,前提条件是使用webpack):&/p&&p&新建一个.less 文件 , 先在less文件中引入官方样式文件 然后在此基础上复写&/p&&br&&img data-rawheight=&218& src=&/v2-21cb00fea46ce6480aed6c6f38f3ff6e_b.png& data-rawwidth=&792& class=&origin_image zh-lightbox-thumb& width=&792& data-original=&/v2-21cb00fea46ce6480aed6c6f38f3ff6e_r.png&&&br&&p&方法二 : &/p&&p&官方提供了 自动编译工具iview-them 来编译。干的事情就是 把自定义的样式和 github仓库最新的样式 通过工具生成一个新的样式文件。&/p&&br&&p&&b&element-ui&/b&&/p&&p&如果只替换颜色 ,可以使用 &a href=&///?target=https%3A//elementui.github.io/theme-preview& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&在线主题生成工具&i class=&icon-external&&&/i&&/a& 在线编辑颜色, 生成element-ui 主题 直接下载 再引入&/p&&br&&p&深度定制主题&/p&&p&官方提供了 主题生成工具
element-them &/p&&p&执行命令 初始化得到一个配置文件 ,修改相关配置 经过编译得到 得到相关主题文件
再通过babel 插件引入&/p&&br&&img data-rawheight=&300& src=&/v2-cd9b175df89ef98d0a0a8beeaef69e05_b.png& data-rawwidth=&440& class=&origin_image zh-lightbox-thumb& width=&440& data-original=&/v2-cd9b175df89ef98d0a0a8beeaef69e05_r.png&&&br&&p&双方都提供了专门的工具用于深度定制主题,综合比较 iview 更加简单,element 主题定制需要配合 babel做一些预编译 ,以及步骤更多 显得更加复杂&/p&&br&&p&&b&过渡动画&/b&&/p&&p&element 有内置过渡动画
使得组件的切换变化 更具动感&/p&&p&iview 更为中规中矩&/p&&br&&p&&b&对设计人员&/b&&/p&&p&&b&element 提供了 Sketch 和 Axure 工具 对设计人员友好&/b&&/p&&p&&b&iview 没有提供&/b&&/p&&br&&p&以上 ...&/p&
element VS iview(最近项目UI框架在选型 ,做了个分析, 不带有任何利益相关)主要从以下几个方面来做对比使用率(npm 平均下载频率,组件数量,star, issue…)API风格打包优化与设计师友好性 1,使用率(npm 平均下载频率,组件数量,star, issue…)elemen…
&img src=&/v2-bfc9c0f0b8668f3ecf84ac5da71d9aea_b.png& data-rawwidth=&912& data-rawheight=&364& class=&origin_image zh-lightbox-thumb& width=&912& data-original=&/v2-bfc9c0f0b8668f3ecf84ac5da71d9aea_r.png&&&p&&strong&技术栈:&/strong&&/p&&p&服务端:Node.js&/p&&p&前端框架:Vue (v2.x) &br&&/p&&p&前端构建工具:webpack &br&&/p&&p&代码检查:eslint &br&&/p&&p&状态管理:Vuex &br&&/p&&p&服务端通信:axios &br&&/p&&p&进程管理:pm2 &br&&/p&&p&项目自动化部署:jenkins &br&&/p&&p&首先先说一下单页面的优缺点:&/p&&br&&p&&strong&单页面开发的优点:&/strong&&/p&&ol&&li&&p&良好的用户体验&/p&&p&用户不需要重新刷新页面,减少TTFB的请求耗时,获取数据也是通过Ajax异步获取,页面显示流畅。&/p&&/li&&li&&p&前后端分离&/p&&p&前端负责界面显示,后端负责数据存储和计算,各司其职,不会把前后端的逻辑混杂在一起。&/p&&/li&&li&&p&减轻服务端压力&/p&&p&减轻服务器压力,服务器只需要提供API接口,不用管页面逻辑和页面的拼接,吞吐能力会提高几倍。&/p&&/li&&li&&p&共用一套后端程序代码,适配多端&/p&&p&同一套后端程序代码,不用修改就可以适用于Web、手机、平板。&/p&&/li&&/ol&&p&&strong&单页面开发的缺点:&/strong&&/p&&ol&&li&&p&首屏加载过慢&/p&&p&单页面首次加载,需要将所有页面所依赖的css和js 合并后统一加载,所 以css和js文件会较大,影响页面首次打开时间。&/p&&/li&&li&&p&SEO&/p&&p&因为页面数据都是前端异步加载的方式,不利于搜索引擎的抓取。&/p&&/li&&/ol&&p&基于以上的原因,所以我们引入了Vue SSR技术&/p&&br&&p&&strong&引入Vue SSR&/strong&&/p&&ol&&li&&p&服务端预渲染&/p&&p&vue2.0引入了虚拟DOM,实现原理就是vue的编译器在编译模板之后, 会将这些模板编译成一个渲染函数,也就是render方法, 函数被调用的时 候就会渲染并且返回一个虚拟DOM的树,在交给一个patch函数,把虚拟 DOM施加到真实的DOM上。这样做的主要原因是因为js的运算是非常快 的,而在浏览器中直接操作DOM会对性能有一定损耗。&/p&&/li&&li&&p&流式渲染&/p&&p&服务端渲染支持流式渲染,因为http请求也是流式的,在渲染组件时返回一个可读的 stream 流,然后直接写入 到 HTTP 响应中。流式渲染 能够确保服务端的响应度,也能让用户更快地获得渲染内容。&/p&&/li&&li&&p&对组件进行缓存&/p&&/li&&li&&p&前后端复用一套代码&/p&&p&前端和服务端可以复用一套代码,提升开发效率和维护性&/p&&/li&&/ol&&p&&strong&Vue SSR流程图&/strong&&br&&/p&&img src=&/v2-0e50e752acfbd44333fbf_b.png& data-rawwidth=&980& data-rawheight=&450& class=&origin_image zh-lightbox-thumb& width=&980& data-original=&/v2-0e50e752acfbd44333fbf_r.png&&&blockquote&从图中可以看出,ssr有两个入口文件,client-entry 和 server-entry.js 也就是客户端和服务端入口文件,都包含了同一套应用代码,webpack 通过两个入口文件分别打包成给服务端用的 server bundle 和给客户端用的 client bundle. 当服务器接收到了来自客户端的请求之后,会创建一个渲染器 bundleRenderer,这个 bundleRenderer 会读取上面生成的 server bundle 文件,并且执行它的代码, 然后发送一个生成好的 html 到浏览器,等到客户端加载了 client bundle 之后,会和服务端生成的DOM 进行对比,也就是判断这个DOM 和自己即将生成的DOM 是否相同,如果相同就将客户端的vue实例挂载到这个DOM上, 否则会提示警告。&br&&/blockquote&&br&&p&&strong&项目目录结构:&/strong&&/p&&p&|— api
# api 接口文件&/p&&p&|— assets
# 静态资源目录&/p&&p&|— components
# vue组件&/p&&p&|— filters
# 过滤器文件&/p&&p&|— router
# 路由文件&/p&&p&|— views
# 页面模板文件&/p&&p&|— utils
# 工具方法目录&/p&&p&|— store
# vuex相关文件&/p&&p&|— app.js
# vue入口文件&/p&&p&|— client-entry.js
# 客户端入口文件&/p&&p&|— server.entry.js
# 服务端入口文件&/p&&p&|— index.html
# HTML入口文件&/p&&br&&p&&strong&开启Vue SSR&/strong&&/p&&ol&&li&&p&使用服务端框架 express 或 koa&/p&&p&服务端框架,我们使用的是express,之前也使用过了koa,有了一些未知的坑,koa是不错,但是生态圈不如express,express有不少比较有特色的模块并不支持koa,并且官方的例子使用的就是express,所以我们为了方便就选择了express作为我们的ssr服务器。&/p&&/li&&li&&p&安装配置webpack 和 vue&/p&&/li&&li&&p&拆分入口文件,服务端和浏览器要分开渲染&/p&&p&vue2使用了虚拟DOM, 因此对浏览器环境和服务端环境要分开渲染, 要创建两个对应的入口文件。&/p&&p&server-entry: 使用 vue ssr 功能将虚拟DOM渲染成网页&/p&&p&client-entry: 使用 $mount 直接将应用挂载到DOM上&br&&/p&&/li&&/ol&&br&&p&&strong&server-entry 服务端入口文件&/strong&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&import&/span& &span class=&p&&{&/span& &span class=&nx&&app&/span&&span class=&p&&,&/span& &span class=&nx&&router&/span&&span class=&p&&,&/span& &span class=&nx&&store&/span& &span class=&p&&}&/span& &span class=&nx&&from&/span& &span class=&s1&&'./app'&/span&&span class=&p&&;&/span&
&span class=&kr&&const&/span& &span class=&nx&&isDev&/span& &span class=&o&&=&/span& &span class=&nx&&process&/span&&span class=&p&&.&/span&&span class=&nx&&env&/span&&span class=&p&&.&/span&&span class=&nx&&NODE_ENV&/span& &span class=&o&&!==&/span& &span class=&s1&&'production'&/span&&span class=&p&&;&/span&
&span class=&kr&&export&/span& &span class=&k&&default&/span& &span class=&nx&&context&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&kr&&const&/span& &span class=&nx&&s&/span& &span class=&o&&=&/span& &span class=&nx&&isDev&/span& &span class=&o&&&&&/span& &span class=&nb&&Date&/span&&span class=&p&&.&/span&&span class=&nx&&now&/span&&span class=&p&&();&/span&
&span class=&c1&&// 调用router切换到对应的路由&/span&
&span class=&nx&&router&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&context&/span&&span class=&p&&.&/span&&span class=&nx&&url&/span&&span class=&p&&);&/span&
&span class=&kr&&const&/span& &span class=&nx&&matchedComponents&/span& &span class=&o&&=&/span& &span class=&nx&&router&/span&&span class=&p&&.&/span&&span class=&nx&&getMatchedComponents&/span&&span class=&p&&();&/span&
&span class=&c1&&// 返回对应要渲染的组件,检查组件是否有 prefetch 方法&/span&
&span class=&k&&return&/span& &span class=&nb&&Promise&/span&&span class=&p&&.&/span&&span class=&nx&&all&/span&&span class=&p&&(&/span&&span class=&nx&&router&/span&&span class=&p&&.&/span&&span class=&nx&&getMatchedComponents&/span&&span class=&p&&().&/span&&span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&nx&&component&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&component&/span&&span class=&p&&.&/span&&span class=&nx&&preFetch&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&component&/span&&span class=&p&&.&/span&&span class=&nx&&preFetch&/span&&span class=&p&&(&/span&&span class=&nx&&store&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&})).&/span&&span class=&nx&&then&/span&&span c

我要回帖

更多关于 没有可供执行财产 的文章

 

随机推荐