taro多端实践之h5端

taro是什么

引用官方的一句话来说明:

Taro 是一套遵循 React 语法规范的 多端开发 解决方案。现如今市面上端的形态多种多样,Web、React-Native、微信小程序等各种端大行其道,当业务同时在不同的端都要求有所表现的时候,针对不同的端编写多套代码的成本显然非常高,这时只编写一套代码就能适配到多端的能力就显得极为重要。使用 Taro,我们只需书写一套代码,再通过 Taro 的编译工具,即可将源代码分别编译出在不同端(微信/百度/支付宝/字节跳动小程序、快应用、H5、React-Native 等)运行的代码。

由于各小程序在api设计层面大体上还是一致的,所以这些端的兼容问题会稍微少点。h5和RN还有快应用的兼容,相对来说应该是比较难处理的。

taro兼容h5的实践

从兼容h5端的实践,总的来说,大问题没有,小问题还是不少的。下面我就来一一说说这些小问题。

  1. map组件暂未支持编译到h5端(当前taro最新版本2.0.1)。map组件的缺失对于需要的业务来说,还是比较麻烦的,解决办法有2种。第一,可以自己通过js的开源地图封装,对于能力较强的,可以去官方提个PR;第二,看看能不能从产品上进行阉割;
  2. iconfont使用alicdn,在本地dev无法显示。这个部署到服务器就没有问题了。后来查看taro的github上的issue发现,跟dev的机制有关系,具体还说不太清楚。需要在iconfont生成的代码font-face的url,指向alicdn的域名前添加完整的http或https协议(默认的域名前是双斜线(//)开头)。
  3. swiper组件在h5端,autoplay到最后一个slide,会停止下来(使用taro版本1.3.25)。查看官方changelog,该问题已经在taro版本2.0.0解决。
  4. 写样式需要注意2个问题。一个是单位,前期写css,用了rpx单位的情况。解决办法就是写回px。对于行内写了rpx的,使用Taro.pxTransform方法兼容,行内样式写了rpx,不会生效。另外一个需要注意的是高度使用vh。在部分浏览器,100vh是整个屏幕的高度,而视窗高度是100vh减去地址栏的高度,因此最好使用window.innerHeight来替代100vh的使用。
  5. onPageScroll函数的定义如果使用函数表达式定义,在h5端不会执行。解决办法是,使用具名函数定义而非函数表达式。

  6. 页面栈没有route属性。

const pages = Taro.getCurrentPages()
pages[0].route // route属性取到的值是undefined

解决办法:查询issue发现已有解决办法,在h5端,route获取方法pages[0][‘vnode’][‘_owner’][‘vnode’][‘props’][‘path’](注:pages[0]泛指页面栈实例)。

  1. picker-view组件不支持h5端编译。可以使用picker或者第三方组件来代替。其中如果要使用picker组件来代替,样式需要通过css覆写来实现。然后picker组件的触发,业务中如果不是picker组件点击显示,兼容的办法是获取到picker组件下的子节点,将这个子节点的虚拟dom获取到,调用click,就可以触发了。对于picker的字节点,最好做css隐藏处理。

  2. h5端,navigateBack的delta不能设置超过已有的页面栈数量,设置数值过大,跳转会失败,且不会有任何提示信息。

  3. setState传入一个方法的时候,返回的对象不能是传入的prevState对象本身,即有的开发者会在prevState对象上修改属性,然后return,在h5端不会生效。

  4. $router的参数跟各小程序端的不一样,可以大致这样兼容,即小程序端的this.$router.params等价于h5端的this.$router。

  5. taro的绑定事件阻止冒泡,需要在第一个事件处理函数里处理,如果通过第一个事件处理函数传递event,在下一个函数中处理会不起作用。(实验结果taro版本v1.3.25,支付宝端)。

  6. image图片,在h5不支持mode属性,需要自己用background-size兼容实现。

  7. webview组件的使用,webview内的h5跟壳之间的通信兼容。webview组件编译到h5端,是使用iframe来实现webview的效果的,通信可以使用js原生的方法postMessage来做,在壳这一侧,没有支持组件属性onMessage来监听内嵌h5的数据传递,需要通过addEventListener对message事件进行监听,同时需要注意的是,message事件会默认触发,因此,在传递信息的时候需要通过一些自定义的字段,来过滤掉不相干的message事件的处理。

小结

taro处理的端很多,虽然具备了一定的成熟度,能够满足一些业务需求,但离完全成熟还有点距离,所以难免在使用过程中会遇到坑,踩坑之旅,需要多点耐心,多调试想办法,祝入坑的朋友,不掉发,斜眼笑:)。

小程序技术概述及支付宝小程序开发实践小结

前言

小程序底层是基于webview来实现的,虽然不是什么革命性的全新技术,但是却是影响广泛的技术新应用。它的出现不是凭空出现的,肯定为了解决一些问题。那么没有小程序前的客户端有哪些比较明显的痛点呢?

  1. 纯客户端原生渲染的缺点:在动态打包,动态下发,存在一些问题,比如缺乏灵活性。
  2. 纯web技术渲染的缺点:在一些有复杂交互的页面上可能会面临一些性能问题,这是因为在 Web 技术中,UI渲染跟 JavaScript 的脚本执行都在一个单线程中执行,这就容易导致一些逻辑任务抢占UI渲染的资源。另外一个问题,是web太灵活,用完即走,用户留存度低。

针对这两个缺点,客户端技术、web技术都有一些新的技术方案出现。而在web技术这个方向,大的方向是对web容器进行增强,赋予web更多性能和能力。目前主流有三类技术比较火:

  • 小程序技术
  • PWA技术
  • phoneGap为代表的套壳并对webview增强的技术

这里主要说说小程序技术,其他两类技术在国外较火,但在国内不温不火,由于主题不是它们,这里不多做介绍了。小程序实现的基本思路是通过分离渲染和逻辑的处理,即将小程序的运行环境分为渲染层和逻辑层,每个页面有单独的webview来渲染,而逻辑是在一个独立的线程运行,避免了web的UI渲染跟javaScript在同一单线程中执行会出现的性能瓶颈的问题,因此他们互不干扰,两个线程只会有一些数据通信。同时,提供大量客户端原生能力的接口,从而充分结合客户端原生技术和web技术的能力,给客户端动态化提供了一个新的技术方案。

小程序通信模型

一门新技术的诞生,在初期,对开发者而言,都会有那么一段黑暗时期,各家小程序都不例外。进入正题,那些年踩过的支付宝小程序的坑。

支付宝小程序开发的一些痛点

小程序自身不稳定

据不完全统计,我们在开发我司在各家超级APP中的小程序的时候,给官方提过的bug,总和不下于百余枚。其中支付宝小程序占比20%左右,分布主要在以下几个方面:

  1. input、text-area等表单组件
  2. 覆盖原生组件的cover-view组件
  3. 支付宝授权
  4. 开发工具

当然,我们遇到这几个方面问题都一一向官方反馈过,也得到了官方的确认,在面临这些问题时,由于修复有个过程,我们通过修改产品交互设计和程序hack等方法,也还算顺利地绕开了一些问题。由于大部分问题官方已经修复,所以具体将bug罗列出来没什么意义。重要的是在面对问题的时候,有化繁为简、追根究底的能力。

调试及测试

调试的问题也是比较棘手的,影响业务开发效率,主要的痛点在以下几个方面:

  1. 代理抓包只支持ios,不支持android代理抓包,官方解释:其底层网络库有做限制导致的
  2. 开发工具与真机在某些特性上表现不一致
  3. 体验版不像其他家小程序那样可以使用调试器

目前这些问题还部分存在,虽然不是致命的,但是作为开发者,还是很希望这方面的能力能更快完善和优化。如果你看到本文已经没有这些问题了,那么恭喜你,如果还存在,也恭喜你,顺利入坑。

发版本

每次发体验版,需要重新设置新发版本为体验版,相比于微信小程序的开发体验(同一个人上传体验版,自动更新,无须重新设置体验版)来说,真的不是一般的苦逼,虽然可以通过尽量降低发布频次,集合更多问题统一发版这个解决方案,但是特别是在一些特定的场景下,需要频繁在体验版测试的时候,每次都需要设置体验版,是极其琐碎的事情。希望以后支付宝小程序能改善这一开发体验。

社区反馈

社区论坛第一版的时候,我觉得反馈问题非常方便,是非常好的。但是在今年(2019)5月份开始将社区改版了,满怀期待的新版本社区,在8月份左右上线了,但是却非常不好用,反馈问题、查找问题现在反而变得不容易了,以前提过的问题,也找不到了。这个实在让人费解。期间还在各种渠道,比如在线客服、钉钉官方技术群等反馈过问题,但是这些渠道反馈的问题毕竟没有可追溯性,不是一种理想的反馈bug的渠道。还是希望继续推进社区论坛的建设,以前反馈的问题,也能搜索到,而不是将其抹去。所以总结下,目前相对比较有效率的反馈方式是支付宝小官方技术钉钉群,群号:23362130,反馈问题前,可以先在社区搜索下问题。如果出现了更便捷的反馈形式,欢迎补充。

资源位跳转,与运营及市场人员的沟通之痛

支付宝小程序提供多种跳转能力,主要分为四类:

  • 小程序跳转小程序
  • url schema 跳转小程序(h5跳转小程序)
  • 小程序跳转支付宝官方页面或小程序
  • 小程序跳转小程序插件

运营与市场人员为了给小程序导流,时常会给出触达我们小程序的方法,以及收到别人可触达他们家小程序的方法。而由于支付宝的跳转能力是多种多样的,同时我们自己还有一套配置规则,运营和市场人员常常面对的问题就是,收到各种不一样的跳转方法,但是他们不明白这些方法之间的转换规则,自然当需要把资源位上到我们家小程序时,不知道应该怎么转换。同时,当别人要我们家小程序触达方法时,无法按照对方的规则给出方法。

当我们运营市场人员遇到这个问题,目前都是通过直接跟开发沟通,开发帮忙手动转换来实现这个需求。后续,我们还是会尝试一些解决方法,比如开发一些转换工具,给他们使用。

结语

总的说来,小程序的能力是显而易见的,身在中国应该大家都有深刻的印象。但是小程序目前在各家超级APP的具体应用都有不小的差异,虽然底层技术类似,但是由于各个生态存在的差异性,还是对开发者造成了不小的困扰。目前,业界虽有Uni-app, Taro等这种多端开发框架,来抹平多端的底层差异,但是实践来看,仍然不是那么一帆风顺,坑也不少,同时各家的运营策略是不同的,提供的扩展服务不一而同。同时,引入第三方框架来开发,在获取收益的同时,又引入了一个可能出问题的一环,如此,要同时排查第三方框架、支付宝小程序本身和自身业务写法的问题,增大了开发及调试的难度系数。幸运的是,随着时间的推移,小程序底层会逐渐稳定,同时,目前w3c也在积极朝着制定小程序规范的方向努力,小程序的发展会绽放更多光彩。

margin在flex布局中的妙用

直奔主题,我们来看一个情况:

<div>
    <div></div>
    <div></div>
</div>

上面就是描述问题的最简单的结果,一个div中有两个子div,要求是第一个div居左,第二个div居右,位置不能变化,第一个div有可能隐藏。

那么问题来了,如何使用flex来实现这种布局呢?

分析尝试

大家很容易想到,父容器flex, justify-content:space-between不就好了吗? 如果第一个元素不隐藏,那毋庸置疑,但是如果第一个元素隐藏,那么第二个元素会自动跑到左边,这样行不通的哟。我也是想了好一段时间,都没想到最好的方案。

margin在flex布局中的力量

最终在知乎上找到了同类型的问题,如何解决前端的flex流动布局中的单个子元素位置?。margin在flex布局中,子元素margin为auto的时候,伸缩包含块剩余的空间将会分配到flex-item的外边距margin上,那这个问题的答案就可以对第二个flex-item添加margin-left: auto来完美解决。同时,这个也很完美的解决了当flex容器下有三个子元素时,要求两个元素居左一个元素居右的这种情况。就不用对两个元素这边再加一个flex容器包裹了。简直不要太完美。

结语

在网上看到的绝大多数flex教程都没有提到过关于margin在flex布局中的应用,这也是此文的意义,希望能帮助到大家,同时希望大家更深入或者找到第一手资料学习。不过大家也许和我一样懒,那我们就多百度咯,毕竟第一手资料读起来不是一般的难受。苦笑。

web前端交互反馈思考

目前的人类发展阶段,人机交互主要集中在电脑,而与电脑的交互主要有两种,一种命令行,另一种就是UI(user interface)可视化界面。可视化界面具有交互自然的属性,所以更为受大众欢迎,但是不得不说,它受欢迎程度以后会逐渐被语音交互甚至更高级别的人工智能交互所替代,因为以后的交互的发展趋势是趋近于更为自然。貌似扯远了,回到当下,在可视化界面里,点击的交互占了绝大部分,不管是pc的鼠标还是移动端的手机点击。不难想象,这种点击交互的反馈,对我们对这款设备或应用的评价有直接作用。好吧,我发现貌似起高了,我主要想思考的领域没这么广,其实通俗来说就是你点击了,让你知道你的点击的确发生了的反馈, 而且我思考的领域是web前端技术。

在css里,对于点击有active伪类可以响应点击,响应点击后,据我总结,我们主要有5种方式来反馈。

  1. 使用背景色变化来响应。目前应用比较广泛,比如按钮、列表等模块;
  2. 字体颜色变化。
  3. 区域透明度变化。
  4. 设置filter滤镜,来改变区域明暗度,色值等。
  5. 动效,不多说。

有冲动总结这个是因为自己长期使用macOS和IOS系统,发现在这些系统下的应用,在点击打开时的反馈是使图标变暗,而我在web中,没有实现过这种效果,于是很好奇。在以往,我们的web交互反馈类似的效果,是改变某一区域的背景色来使用户感知。但这样有一个缺陷,就是你不得不对每种颜色去设置点击响应色彩,比如红色,点击反馈你需要设置深红色。很幸运,思考进入状态了,一不小心就抓住了本质,就是一个滤镜功能,才想起css拥有强大的filter样式。但是很不巧,经过www.caniuse.com查询,filter兼容性,IE完全不支持,不管是IE的PC版还是Mobile版。如果你是在移动端,且不考虑IE mobile,那么使用这个属性没有太大问题的,PC如果兼容需要纳入IE, 那么很不幸,这么fancy的样式,你是用不了。

快应用开发之初体验

1. 环境搭建及开发调试

  • 开发需要安装一个npm包hap-toolkit来生成初始化项目,类似vue-cli;
  • 移动端调试器安装,需要andorid手机,去官网资料中心下载apk;
  • 项目初始化后,使用npm run watch以及npm run server,通过快应用调试器扫码进入,就可以hot reload了,开发体验不会太差;
  • 项目初始化出的模板,从package.json依赖来看,webpack需要升级,太老旧,是1.x的版本;
  • 使用vConsole来作为调试控制台,无法安装到快应用中;调试使用快应用调试器提供的调试吧(需要在manifest开启日志,默认off),它是直接打开默认浏览器,然后接着玩儿;

2. 项目配置信息

  • 路由在这里配置;
  • 页面级别的信息如页面标题以及标题颜色、标题背景灯,也在这里配置;
  • 应用基本信息; 3.框架

大体上

  • 写法跟vue类似;
  • 生命周期跟微信小程序类似;
  • 页面级别的数据在private对象中,类似小程序的data对象;但是调用又跟vue类似,private的数据都挂载在this实例上,不像小程序的data数据是挂载在this实例的data对象上;
  • 路由参数传递,两种方式分别是a链接的query和router模块params参数传递,参数接受有两种方式,一种是protected对象,一种是public对象,他们的区别是protected是应用内部参数传递,public负责接收应用外部的参数传递;
  • 数据量大的时候,可以用$app实例上的$data来传递参数;
  • 数据请求可以使用快应用提供的@system.fetch模块
  • 现在app.ux中还不支持公用样式(官方:加了的话,担心性能会变差) 模板
  • 文字一定要放在text中,否则不显示(坑);
  • 不支持单位vw,vh;

逻辑

  • dom事件模型在快应用中不适用;

css样式( 由于快应用最终效果是native不是h5,用的是css的子集,坑较多)

  • css样式不需要写兼容,已经默认集成, 写兼容还可能会报错,默认border-box盒模型,不支持content-box和box-sizing;
  • 没有块元素、行内元素的模型;
  • css样式中的颜色值只严格支持16进制,不能使用缩写,不能使用语义颜色(还有这种操作,无语);
  • css样式中background只设置颜色的话,需要使用background-color,否则报错;
  • 不支持相对定位和绝对定位,支持固定定位;
  • 不支持float布局;
  • 不支持overflow属性;
  • 不支持伪元素选择器,如::before, ::after;
  • flex属性不支持缩写,如flex: 1 0 auto;是不支持的,只能单独写flex的属性;
  • 不支持样式中使用!important;
  • 不支持vertical-align属性;
  • 框架目前仅支持长度单位px和%, 当然不支持单位vw,vh;

4.组件

  • 自定义组件的引入方式是在模板使用import标签引入;

5.接口

  • 接口模块使用,需要在manifest.json中声明(特别是使用web组件时,需要声明,单无需引入模块);
  • 推送模块暂时还未支持(截止20180629);
  • 快应用限制唤起其他app的能力(https://bbs.quickapp.cn/forum.php?mod=viewthread&tid=282&highlight=webview);

6.发布部署

  • 需要在项目中的sign文件夹下添加release签名,然后npm run release,会生成有release签名的工程包,这个包就可以用于部署;
  • 部署是需要申请一个快应用的账号,通过后台上传相关项目资料;
  • 上传包的时候命名规则与app一致,版本管理要注意,快应用后台会检测versionCode,每次发布需要比上次发布的versionCode大;

后续会持续补充…

对PWA(Progressive Web Application)的认识

前言

时间大概是在2016年,google推出了PWA技术,旨在提升web app的用户体验, 总体来说,这个技术将会对web应用的普及及市场占有率推向新的高潮,它的意义是非凡的,可以说是下一代的web应用模型。下面简要罗列一下我对web app的劣势和优势的认识,来一窥PWA技术是如何弥补web app的不足的。

web app的优势

  • 轻量,不用下载
  • 开发成本低
  • 跨平台能力强
  • 用户获取成本低
  • 迭代快

web app的劣势

  • 体验较native app弱(离线体验,动效,消息推送等)
  • 无一级入口(native级别的入口)

google的PWA技术

针对web应用比较大的两个劣势,google推出了自己的解决方案——PWA技术。PWA技术主要分两个方面,分别针对以上分析的web app的劣势。

  • 用service-worker技术来弥补离线体验和消息推送方面的劣势
  • 用manifest.json的实现,使得web app可以添加到系统桌面并以native app一样有全局窗口,而不是打开浏览器的体验

最后

这里对于PWA应用的实现,我就不做介绍了,因为官网文档实在是太细致了,跟着官网的教程,你绝对能实现一个PWA应用,并对其有一定的认识,比各种百度google要强很多(亲身体验…)。

google PWA 教程

我翻译的google PWA教程

webpack2及以上的tree-shaking功能探索

从很容易就会思考的一个问题开始

以下代码是es6语法关于模块导入的,当我们使用webpack打包的时候,很自然会想到这个打包过程是将整个模块打包进来还是我要用的即引入的部分打包进来呢?

import { a } from './module'

a()

在webpack1的时候,这个打包过程是将整个模块打包进来,不管你这个文件使用了模块多少的代码。显然,这是不合理的,理想情况,我们更愿意见到使用的部分打包进来,其余部分最好给我们清理掉,打包一堆没用的东西进来,占用了bundle的体积。据说最开始,rollup.js(前端打包工具,跟webpack是同类型产品),实现了这个功能,并起了一个非常形象的名词,叫tree-shaking。webpack2开始,也加入了这个功能。

那么webpack的tree-shaking要如何开启呢?

其实很简单,需要有几个前提条件:

  • webpack2.0及以上
  • 使用了es6的模块语法导出导入
  • 使用了uglifyJS压等压缩工具压缩代码

发现的问题

以上是网上说的比较多的,但是本人在使用过程中发现,还需要注意一个细节,那就是在配置babelrc(指所有配置babel的配置,其他可配置babel的地方如webpack配置babel-loader的options,还有package.json)时虽然可以归并到第一个前提条件,但是容易被忽略。

这个细节就是,配置presets时,一定要将modules设置为false,这个如果没设置,就没卵用,作者亲身体会。现在看看,这个设置理所当然,因为即使你使用了es6的语法来处理模块,在没设置这个参数时,默认是true,也就是默认将es6等的语法转换成common.js。

下面用实验代码来证实一下吧。(本次使用webpack3)

没有配置modules为false即modules默认值为true

index.js文件代码

import { a } from './module'

a()

module.js文件代码

export function a() {
  console.log('a');
}

export function b() {
  console.log('b');
}

webpack.config.js文件代码

const path = require('path')
const webpack = require('webpack')

module.exports = {
  entry: path.resolve(__dirname, 'index'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['env']
        }
      }
    }]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin()
  ]
}

打包得到的文件index.bundle.js代码,注意,此时打包的文件里面是可以查到有关b函数的定义的,并没有shaking掉。

!function(n){function e(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return n[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var t={};e.m=n,e.c=t,e.i=function(n){return n},e.d=function(n,t,o){e.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:o})},e.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return e.d(t,"a",t),t},e.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},e.p="",e(e.s=1)}([function(n,e,t){"use strict";function o(){console.log("a")}function r(){console.log("b")}Object.defineProperty(e,"__esModule",{value:!0}),e.a=o,e.b=r},function(n,e,t){"use strict";(0,t(0).a)()}]);

配置modules为false

对webpack.config.js的配置更改部分

options: {
  presets: [
    ['env', {modules: false}]
  ]
}

打包结果—无b函数定义

!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=1)}([function(e,t,n){"use strict";function r(){console.log("a")}t.a=r},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(0);n.i(r.a)()}]);

完!

Vue SSR学习小结

前言

ssr即服务器端渲染,是一个有利于SEO,及首屏加载等优势的一种view层的处理。ssr简单来说就是在保持前后端分离的开发模式下,在前后端中间加一个node中间层,在node中间层对页面数据进行组装,即node层去请求后端数据,然后在html模板进行填充,完了之后就返回给客户端。缺点也有,可以参考官方文档。选取时,最好综合考虑。

为什么会出现这种技术?

当前SPA应用是彻底的将内容变成服务器端的一种服务,后端对于前端来说,就是提供动态数据的服务人员。SPA技术将内容和模板的结合放在浏览器端,内容是通过ajax异步获取的。这样的话,根据搜索引擎原理,必然导致SEO很烂。同时,如果客户端网络环境差,也会延长内容到达时间。SSR就是来解决这个问题的。这项技术已经经过充分实践了,最典型的例子就是手机淘宝app,据淘宝内部团队人员透露,目前手机淘宝app里绝大部分的页面都是SSR技术,即他们口中所说的,在服务器端和客户端之间加一个node中间层来渲染页面,手机淘宝的体验,相信绝大多数中国人都是有体验的,非常完美。

核心思想

在node层进行内容数据预取,然后在html模板中进行填充,返回客户端进行展示。

vue SPA应用的SSR构建

将spa应用处理为ssr的模式,还是有很大的复杂性的。当然,社区繁荣一片的现在,你可以使用开源的NUXT.js,来做ssr处理。要想自己有更多的项目结构控制权的话,就需要知道这些东西是怎么根据构建工具串起来的了。在我经过了几天学习官方文档之后,我试着总结一下这个串接过程。在讲述串接过程前,先需要知道vue作者的ssr构建的设计。

首先顶层上,有1个工具叫vue-server-renderer。这个模块负责支持整个构建过程必要环节的处理。这个工具下,提供了一些模块和api。在一个基本的构建过程中需要用到的有:

api: createBundleRenderer // 创建renderer,renderer的作用是将vue实例渲染成html

模块:client-plugin和server-plugin // webpack插件,分别处理客户端打包和服务器端打包

为了将相同的应用程序提供给客户端,我们使用webpack来打包构建。ssr同样也需要打包构建。因此作者的基本思路就是,对服务器端渲染打包成一个bundle,对客户端程序打包成一个bundle。在客户端发起请求的时候,返回服务器对应的渲染页面和客户端bundle,由于服务器端渲染页面已经留下了静态标记,客户端bundle会识别这个标记,并以混合模式挂载(具体怎么实现的需要去阅读源码)。

最后输出应用程序

有了构建工具构建出来的工件,可以进一步输出程序。基本思路是借助createBundleRenderer根据打包好的bundle创建一个renderer,然后在node服务器的帮助下,根据请求路径来确定一个context上下文对象,renderer再依据这个context,返回必要的静态资源给客户端用于展示。

webpack学习再回顾

在我的心中,webpack是一个变革者,他提供了前端一个全新的开发模式,并将这一职业推向一个新的高点。

追溯webpack的起源

这些年互联网发展迅速,越来越多的网站以及web应用的需求不断增加,一个井喷的web程序井喷的时代,为数不多的程序猿们,如何应对呢?自然,得提高效率嘛,于是大家就想办法提高效率,怎么提高呢?啦啦啦,此处省略一千字,答案是web设计师行业开始变更为前端工程师,前后端概念出现,简单的说,前端负责人机交互、内容的良好呈现、把需要的信息提交给后端,而后端即服务器端,负责处理服务器问题、与前端交互、以及数据的处理并与数据库交互。这样初步解决了团队效率问题。日复一日,程序越来越复杂,前端的代码量开始增多,代码维护性出现了性功能问题,哈哈。于是渐渐的,为了解决前端开发工程化复杂的问题,使其代码更加结构化、维护性更好,出现了grunt、gulp、webpack等工具。

webpack概念

webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成少量的 bundle – 通常只有一个,由浏览器加载。

它是高度可配置的,但是,在开始前你需要先理解四个核心概念:入口(entry)、输出(output)、loader、插件(plugins)。不详细解释,如有需要,见其官网。

webpack能力大致描述

我们都知道,前端从大致的方向上看,主要关注点在四个方面,一个是html模板,一个是css样式,一个是javaScript负责的行为逻辑,最后是其他资源,如图片。

  • js需要模块打包,好的,简单的说,webpack内部使用了browserify来处理commonJS和es6的模块的依赖关系来完成打包。你写的jsx语法、es6语法等,浏览器不支持不兼容怎么办?没关系,提前准备了babel的编译器,实现优雅编译。js想要丑化压缩下,没问题,咱有插件系统。

  • 对于css文件,由于webpack本身只理解javaScript,css文件如何打包呢?简单的说,借助loader来实现,css-loader和style-loader就是很好的任务完成者。css-loader出色的完成了css模块化的问题,style-loader出色的完成了在html中对模块化的css插入问题。你写了less,好的,聪明的开发者搞了个less-loader,负责less的编译问题。

  • 图片想要处理下,base64能做得到吗?没问题,咱有url-loader。资源想要集中化一下,没事咱有file-loader。

感受webpack的架构

nodeJS诞生后,各种前端自动化工具成为了可能。webpack出色的架构,很好的解决了自动化过程中需要处理的问题。在我看来,webpack将资源处理或者说应用程序源代码处理,看做一个文件流,甚至可以形象的比喻为一根有很多处理工厂的管道,从这头到那头的过程,自动识别要处理的资源,进行预先设定的加工处理,然后输出。加工厂可以自定义无限扩展,主要分成两类,一类是loader,一类是plugins。loader主要是做文件转换工作的。而plugins是用来做loader不能做的事儿的,因为loader局限在了文件转换,而在打包的生命周期需要执行的任务,它无法完成,插件就是来做这个工作的,比如打包完成需要对js进行执行uglify,这个时候就需要插件来完成这个任务。

废话说完了,嘻嘻。

纯前端canvas俄罗斯方块小游戏实现

小时候打的第一款游戏,说起来大概就是俄罗斯方块了,这种游戏老少咸宜,设计的非常巧妙,不高冷,不装逼,之前查询过这个游戏的过往,发现那些年火爆经典的游戏中,俄罗斯方块的火爆经典程度简直超乎我的想象。其实仔细想下,其实我们的确可以自己来实现一个类似的游戏。于是乎,经过多天的构思和参考别人写过的demo,我也自己实现了一遍,下面我就来谈谈自己的俄罗斯方块。

我的俄罗斯方块

功能点罗列

  • 地图数据结构
  • 方块数据结构
  • 地图生成
  • 地图绘制
  • 地图更新
  • 方块创建
  • 方块擦除
  • 方块下落
  • 方块的方向键操作
  • 方块变形
  • 方块满行消除
  • 方块触底检测
  • 方块左右碰撞检测

当然还有canvas元素的处理,这里就不说了。

地图数据结构

使用0, 1这样的数字来代表有无方块,地图上无方块时,就都是0. 示例如下:

[
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
]

方块数据结构

跟地图类似,使用0,1来代表形状,总结归纳出方块的几种情况:

[
    [1, 1, 1, 1]], // 一字形
    [[1, 1], [1, 1]], // 田字形
    [[1, 1, 0], [0, 1, 1]], // z字形
    [[0, 1, 1], [1, 1, 0]], // 反z字形
    [[1, 0, 0], [1, 1, 1]], // L字形
    [[0, 0, 1], [1, 1, 1]], // 反L字形
    [[0, 1, 0], [1, 1, 1]] // T字形
]

地图生成

我们将地图控制的变量明确出来,这样就可通过设置控制地图的生成。 这些变量是,多少行,多少列,一个格子多大,格子间距多大,方块生成初始化时离x轴起点的位置,方块下落速度, 加速下落速度等。

var tetrisGame = new Tetris({
    row: 12,
    col: 12,
    grid: 30,
    margin: 10,
    offsetX: 4,
    interval: 400,
    fasterInterval: 100
})
tetrisGame.start()

地图绘制

根据地图的数据,借助canvas提供的api(就两个api就可以搞定,分别是fillStyle和fillRect),给不同的数据点绘制不同的颜色。

// 地图绘制
render: function () {
    var map = this.map
    var mRowLen = map.length
    var mColLen = map[0].length
    var margin
    var grid = this.etting.grid
    margin = this.setting.margin
    for (var i = 0; i < mRowLen; i++) {
        for (var j = 0; j < mColLen; j++) {
            if (!map[i][j]) {
                this.ctx.fillStyle = 'grey'
            } else if (map[i][j] === 1) {
                this.ctx.fillStyle = 'orange'
            }
            this.ctx.fillRect(j * (grid + margin), i * (grid + margin), grid, grid)
        }
    }
}

地图更新

方块的移动需要地图做更新操作,方块就是地图中的一部分,它的移动其实就是每个格子状态根据一定的算法来改变,达到动画的效果的。因此地图或者说游戏的更新,就是根据方块当前的位置和方块本身的数据结构来更新的。

方块创建

方块的生成是随机的,利用js的随机数以及预定义好的方块数据结构,很容易做到。然后根据我们预设的方块的初始化的位置,就可以在地图中生成。

方块擦除

方块是随着时间运动的,直到触底。因此在运动的时候,我们根据时间的特性,下一个状态的方块来临之前,我们将上一个状态的方块清掉,这里注意的是,只清除那些格子有形的部分,否则会出现当与其他已定方块耦合时,将已定方块的某些部分也清除了。

方块下落

方块下落使用定时器来处理,每一个时间点,做一次清除然后增加方块y轴的值,然后再更新地图。同时在下落的时候,需要判断是否落地。

方块的方向键操作

根据keydown事件和keyCode的值,我们给不同的方向键提供不同的处理方法。向上时,我们做方块变形,向下时,我们加速下落,左右键做左右移动操作。

// 方块移动及变形操作
enableKeyControl: function () {
    var _this = this
    document.onkeydown = function (e) {
        switch (e.keyCode) {
            case 37: // 向左
                if (!_this.borderTest(_this.curBlock, -1)) {
                    _this.clearBlock()
                    _this.x--
                    _this.updateMap()
                }
                break
            case 39: // 向右
                if (!_this.borderTest(_this.curBlock, 1)) {
                    _this.clearBlock()
                    _this.x++
                    _this.updateMap()
                }
                break
            case 38: // 向上即变形
                _this.clearBlock()
                _this.transform()
                _this.updateMap()
                break
            case 40: // 向下即加速
                if (!_this.onkeydownFlag) {
                    _this.onkeydownFlag = true
                    clearInterval(_this.timer)
                    _this.fall(_this.setting.fasterInterval)
                }
                break
        }
    }
    document.onkeyup = function (e) {
        if (e.keyCode === 40) {
            _this.onkeydownFlag = false
            clearInterval(_this.timer)
            _this.fall(_this.setting.interval)
        }
    }
}

方块变形

方块变形就是方块的旋转,我们通常喜欢顺时针来旋转,比较舒服,当然你喜欢逆时针,也可以做的。我是用的顺时针。所以根据旋转的规律,就可以根据旋转前方块的数据结构得出旋转后的数据结构。这里也需要注意一点,是否可以旋转,需要判断,因为有时候一旋转出了边界,一旋转和底部实体融合了,等等。判断的方法是,方块的数据暂时不变更,我们将旋转后的数据,去做测试,判断是否触底或超出边界,即去做方块触底检测和左右方向碰撞检测,如果没有碰撞,就更新方块的数据,如果有,就不允许更新方块数据。

// 方块变形
transform: function () {
    var result = []
    var curBlock = this.curBlock
    var blockRowLen = curBlock.length
    var blockColLen = curBlock[0].length
    for (var i = 0; i < blockColLen; i++) {
        result.push([])
        for (var j = 0; j < blockRowLen; j++) {
            result[i][blockRowLen - j - 1] = curBlock[j][i]
        }
    }
    if (
        !this.groundTest(result) &&
        !this.borderTest(result, -1, true) &&
        !this.borderTest(result, 1, true)
    ) this.curBlock = result
}

方块满行消除

满行的条件触发是以方块落地开始的,所以落地判断成功,就应该需要去判断是否满行,然后做相应的处理。满行的判断,是通过遍历整个地图,看是否有那么些行的格子里都填满了,如果是就需要消除,消除就是在地图的数据结构中,干掉这一样,当然,这个时候的同时,我们可以在地图的最前面行unshift一个充满空格子的空行。

方块触底检测

触底分为两种情况,第一种,是最底部,这个很好判断。第二种,是与其他已经落下的方块之间的判断,看是否还需要往下走。第二种判断方法是,先判断方块的最底部那一行,如果有实体的地方对应的正下方的地图的格子也是实体,那么就不应该再下落,也就是说触底了,如果没有,就去对底部没有实体的那些列去做while循环,向上找到方块的实体格子,这个是总能找到的,因为方块的设计缘故,找到后,就也做类似的判断,看这个方块处的格子对应的正下方地图的格子是否是实体,进而判断是否触底。

方块左右碰撞检测

左右碰撞也分为两种,一种是边界,一种是与其他方块。这里碰撞后,下落还是继续的,不是停止。这个地方的判断,归根结底,其实和下落时与其他方块的判断很类似,只是判断的时候换了一个方向而已。

啦啦啦,终于写完啦。这个简易的俄罗斯方块的代码我上传到了github,看源码请参考https://github.com/Andrewuetyang/tetris