- 小程序webview使用
一、使用原因 由于小程序包大小8M的限制 (之前版本)我们组做的是公共活动、工具,业务方会拉取我们的代码, 新活动会不断添加H5小程序都做,维护成本会比较高二、webview引入思路 webview 安全域名申请访问原先小程序活动重定向到webview容器页面并且在url后面追加一些参数(h5页面需要)实现公共的webview页面,需要功能:分享回调、h5路径转发、身份token透传、刷新H5页面H5页面实现功能兼容, 一些活动配置项、分享标题、分享图片、打点(跟小程序和h5环境相关)三、代码实现 原小程序页面重定向webview页面方法包装 [图片] 小程序webview代码实现 Page({ /** * 页面的初始数据 */ data: { url: '', // webview 地址 shareData: {}, // 分享参数 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { // TODO 登录信息获取 }, /** * 用户点击右上角分享 */ onShareAppMessage: function (options) { // 这里把网址编码了下,用户分享的卡片进去的时候会在onload中得到webview的网址,解码后赋值给src属性就行了 let that = this const shareInfo = { title: that.data.shareData.title, path: `/pages/hudong/common/pages/hdview/index?url=${encodeURIComponent(that.data.shareData.url)}`, imageUrl: that.data.shareData.imgUrl } return shareInfo }, /** * 分享回调 */ onShareback() { let that = this // 分享成功后 通知h5页面(h5有配置xxxback的话), 调用之后需要手动删除 xxxback that.refreshH5(that.data.shareData.xxxback) delete that.data.shareData.xxxback }, /** * 初始化信息 */ initData(options, userInfo = {}) { let url = decodeURIComponent(options.url); const { openid, appid, token } = userInfo; let statType = '' // 记录当前options的链接 this.data.shareData.url = url // h5链接xxxxxxx=xxxxxxxxx 标识 const navUrl = `${url}${url.indexOf('?') > -1 ? '&' : '?'}xxxxxxx=${token}&dotStatType=${statType}` this.setData({ url: navUrl }) }, // 接收h5发送的数据 getMessage(e) { const that = this const data = e.detail.data let action = '' // 注意:每次发送数据后数据会添加到e.detail.data中的数组, // 并不会清除掉上一次传递的数据,所以我们取数组的最后一位 if(data.length > 0) { let item = data[data.length - 1] action = item.action switch(action) { case 'share': that.dealH5Share(item) break case 'saveFormId': // TODO that.dealSaveFormId(item) break } } }, // 处理接收到h5数据 分享操作 dealH5Share(data) { const that = this if(data) { that.data.shareData = data } }, // 处理接收到h5数据 保存formId操作 (待拓展) dealSaveFormId() { // TODO debugger }, // 刷新H5页面当前 refreshH5(xxxback) { if(typeof xxxback != 'object') { return false } const oldUrl = this.data.url let newUrl = oldUrl const oldParams = ParseURL(oldUrl) // 删除历史 oldParams.xxxback && (newUrl = newUrl.replace(`xxxback=${oldParams.xxxback}`, ``)) newUrl += `&xxxback=${(JSON.stringify(xxxback))}` this.setData({ url: newUrl }) } H5分享相关改造 /** * 获取指定的URL参数值 * 参数:paramName URL参数 * 调用方法:getQueryParam("name") * 返回值:tyler */ function _getQueryParam(paramName) { paramValue = "", isFound = !1; if (window.location.search.indexOf("?") == 0 && window.location.search.indexOf("=") > 1) { arrSource = decodeURIComponent(window.location.search).substring(1, window.location.search.length).split("&"), i = 0; while (i < arrSource.length && !isFound) arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++ } return paramValue == "" && (paramValue = null), paramValue } // 判断是否是小程序环境 function IsMiniProgramEnv(fun) { // 通过navigator.userAgent 判断 var bl = navigator.userAgent && navigator.userAgent.indexOf('miniProgram') > -1 if (typeof fun == 'function') { fun(bl) } } // 初始化执行 IsMiniProgramEnv(function (bl) { if (bl) { IsInMiniProgram = true // 判断是否是小程序环境 _initMiniData() } }) // 设置小程序分享 function SetMiniProgramShare(url, title, imgUrl, fun) { if (title && url && imgUrl) { IsMiniProgramEnv(function (bl) { if (bl) { // 向小程序发送消息 var data = { action: 'share', // 触发修改分享参数动作 url: url, title: title, imgUrl: imgUrl } if(typeof fun === 'function') { data.xxxback = fun() || {} } wx.miniProgram.postMessage({ data: data }) } }) } } // H5跳转小程序路径 function _NavigateToMiniProgram(url) { var newUrl = '/pages/hudong/common/pages/bridge/index?url=' + encodeURIComponent(url) wx.miniProgram.navigateTo({ url: newUrl }) } function _initMiniData() { } // 重新包装链接跳转, 用于跳转业务方的小程序地址或者h5页面地址 function HrefToBusiness(url) { if(!url) { return false; } // 是小程序环境, 并且不是h5链接 if(IsInMiniProgram && url.split("?")[0].indexOf('http') == -1) { _NavigateToMiniProgram(url) } else { window.location.href = url } } webview改造原有小程序项目利弊 优点 减少小程序包大小提升开发、迭代效率大多数需求可省去审核流程,h5最新的功能就是小程序功能缺点 用户体验不如原生小程序不能用复杂的动画交互,简单页面还可以权限限制比较多,例如不能 埋点弹起授权手机号授权等h5、小程序通讯限制太多,具体可参考微信文档
2020-01-25 - Webpack学习《接入Typescript》
TypeScript | 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。 传统调试TypeScript Typescript 代码需要通过构建将它转换成 JavaScript 代码后才能运行。 // show .ts 为 show 函数增加类型检查 export function show (content : string) { console.log('Hello ,' + content) } // main.ts 通过 ES6 模块规范导如 show 函数 import {show) from './show '; // 执行 show 函数 show ('Webpack '); 我们需要在当前项目的根目录下新建一个用于配置编译选项的 tsconfig.json 文件,编译器默认会读取和使用这个文件,配置文件的内容大致如下: [图片] 通过 npm install -g typescript 安装编译器到全局后,可以通过 tsc main. ts 命令编译 器到全局后,可以通过 tsc main. ts 命令编译 集成 Webpack 通过 Loader TypeScript 转换成 JavaScriptWebpack 在寻找模块对应的文件时需要尝试 ts 后缀。在运行构建前 npm i -D typescript awesome-typescript-loader 配置如下: const path = require('path') module.exports = { entry: './main', output: { filename: 'bundle.js', path: path.resolve(_dirname,'./dist') }, resolve: { // 先尝试以 ts 为后缀的 Typescript 源码文件 extensions : [ '.ts', '.js'] }, module: { rules: [ { test : /\.ts$/, loader: 'awesome-typescript-loader' } ] }, devtool:'so urce-map', // 输出 ur ce Map 以方便在浏览器里调试 Types cript 代码 } 总结由于对webpack比较感兴趣, 所以看了一些资料 《深入浅出Webpack.pdf--吴浩麟》后,决定打包配置引入TypeScript,小试一把。
2020-01-25 - react源码解析--React组件
说明:目前react发展到现在,其应用场景已经包含web页面、native页面、node服务端,它所对应的渲染方式也是各不相同的,所以React不能局限固定渲染UI的方式,那么在React的核心代码中,我们只会看到相关的组件定义,而并不涉及具体的组件渲染,组件的渲染会在之后的React-dom源码解析中说明。所以本篇文章以定义说明和使用为主。 本文接上篇文章:react源码解析–开篇 一、定义 1、首先是react引入ReactBaseClasses文件中定义了两个构造函数(或者类):Component,和PureComponent。 [图片] 2、Component的定义如下 [图片] 这里仅仅是组件的构造函数的定义,有三个参数,props、context、updater,props为传入组件的数据、context为上下文,updater为render(例如ReactDOM.render)注入,后续文章会讲,而它的默认值ReactNoopUpdateQueue只是一个对象,定义了enqueueForceUpdate(强制更新state,不会调用shouldComponentUpdate)、enqueueReplaceState(异步、替换state的数据)、enqueueSetState(即setState)方法。 Component的原型上挂了一些方法和属性,isReactComponent属性,setState方法,forceUpdate方法,主要还是调用构造函数中传入的updater的方法,代码如下: [图片] 3、PureComponent的定义如下 [图片] PureComponent继承自Component,这两个类基本相同,唯一的区别是PureComponent的原型上多了一个标识:isPureReactComponent。首先是创建了一个ComponentDummy构造函数,他的原型指到Component的原型;然后创建了一个PureComponent, 加上了和Component一样的属性。PureComponent的原型指向ComponentDummy的实例;修改PureComponent原型的constructor属性使其正确指向PureComponent的构造函数,并挂一个isPureReactComponent的属性。为了减少向上去查找原型链次数,用了一个assign直接将Component原型的东西拷贝到PureComponent的原型上。 二、Component使用 [图片] Hello 继承自 React.Component,在构造函数中,通过 super() 来调用父类的构造函数,state是在构造函数中通过 this.state 来进行赋值的,组件的props是在类Greeting上创建的。用这种方式创建组件的时候,React 并没有对内部函数进行 this 绑定,如果在回调函数中保持正确的 this ,需要手动对函数进行 this 绑定,如上面的 handleClick 函数,在构造函数中对 this 进行了绑定。 当组件的 props 或者 state 发生变化的时候,React会对组件当前的 Props 和 State 分别与nextProps 和 nextState 进行比较,当发现变化时,就会对当前组件以及子组件进行重新渲染,否则就不渲染。有时候为了避免组件进行不必要的重新渲染,我们可以通过定义 shouldComponentUpdate 来优化性能。 [图片] 当然这里也只是对state和props的个别属性进行了浅比较,其实如果只是对state和props下的所有属性进行浅比较的话,可以直接使用PureComponent。 三、PureComponent的使用 [图片] 大多数情况下, 我们使用 PureComponent 能够简化我们的代码,并且提高性能,但是 PureComponent 的自动为我们添加的shouldComponentUpate 函数,只是对 props 和 state 进行浅比较(shadow comparison),当 props 或者 state 本身是嵌套对象或数组等时,浅比较并不能得到预期的结果,这会导致实际的 props 和 state 发生了变化,但组件却没有更新的问题。 四、无状态功能性组件 上面创建组件的方式,都是用来创建包含状态和用户交互的复杂组件,当组件本身只是用来展示,所有数据都是通过 props 传入的时候,我们可以使用 Stateless Functional Component(无状态功能性组件) 来快速创建组件。 [图片] 这种组件,没有自身的状态,相同的 props 输入,必然会获得完全相同的组件展示。因为不需要关心组件的一些生命周期函数和渲染的钩子,所以不用继承自 Component 显得更简洁。 五、小结 本篇文章主要介绍了Component和PureComponent的定义以及使用方式,最后也介绍了无状态功能性组件的定义方式,Component相对来说使用场景会相对复杂,自由度也更高、PureComponent则是对组件状态有变化时自动浅比较来决定是否要渲染更新、无状态功能性组件则是只对传入数据进行展示无内部状态。 ps:下一篇预告《react源码解析–ReactElement》
2020-01-23 - 小程序毫秒级计时优化
需求:在进行复杂程序操作的同时,毫秒级计时。 方案流程: 1. 使用setData进行毫秒级计时,以10s为例,代码很简单: [图片] Ok,搞定了,测试一下,ios正常,android性能欠佳的机型、在没有执行复杂运算的情况居然差了几秒。。。?(有的甚至更多) 我们再稍微加点东西: [图片] 结果:10s的计时跑到了20多秒 原因:主线程中有其他任务在执行导致setInterval延迟执行。 2. 既然这样,我们减去延迟带来的时间差 [图片] 结果:达到效果,只存在了毫秒级的误差了。 完美解决?我们来搞点事情 [图片] 页面渲染出现了卡顿,会看到计时不会连续的变化,出现跳跃减少。 原因分析:数据的setData通信,造成的延迟。 3. 既然setData会有延迟,那么还有什么办法可以更新页面?——canvas 马上动手写个: [图片] 完美决绝!!,requestAnimationFrame小程序之前不支持,若支持可替代,实现方案有多种。 效果对比: 普通方式:http://video.static.logan4846.com/public/1.mp4 canvas:http://video.static.logan4846.com/public/2.mp4 代码片段: https://developers.weixin.qq.com/s/CV8mctmn7AeE
2020-01-19 - 编程大赛--用小程序云开发做《拍卖》项目
[图片] 引子:微盟第三届编程比赛今年的日期定于12月19号,我们组了一个叫“NAZA”的小队,然后参赛项目定了《拍卖》,开发周期一周半,测试时间半周。团队成员全部为前端组的,所以产品的原型、数据库、接口、小程序、测试全部自己完成(ps:UI实在没人会所以找了场外私人支持),技术选型为了图快速和方便就选择了“云开发”(开赛前2天才学的文档😂)。这篇文章就以叙事的风格纪录一下我们完整的开发流程,内容主要涉及产品介绍、数据库设计、接口设计、接口开发、小程序页面开发,以及实时数据推送和定时触发器。 一、产品设计 这一期编程比赛的主题是:小技术、大营销,主要聚焦于商业创新、赋能商家,所以在综合考虑了类似公益项目、众筹项目、活动项目、游戏项目等之后选择了“拍卖”这个主题,主要是为了赋予商家针对其“爆款商品”或者“活动商品”去吸引用户,让其停留在该小程序中,并在各种激励、引导下让客户去购买商家真正“热销”“高利润”的商品。所以主要的设计模块如下: 1、后台管理,包括:拍卖商品列表、拍卖商品编辑页、商品列表、商品编辑页 2、拍卖中心,包括:首页、拍卖详情页、拍卖纪录页 3、商品展示,包括:商品列表页、商品详情页、订单页 4、个人中心,包括:个人中心页、收支明细页、竞拍商品&订单商品展示 5、其他页面,包括:拍卖币获取引导页、大转盘页、刮刮卡页、其他说明页等 二、开发准备 1、创建云开发模版,新建项目选择一个空目录,填入 AppID(使用云开发能力必须填写 AppID),勾选创建 “云开发 QuickStart 项目”,点击创建即可得到一个展示云开发基础能力的示例小程序。 2、小程序准备,这里使用了私人的一个小程序,然后开通云开发,在小程序开发者工具中点击“云开发”按钮,打开云控制台、根据提示开通云开发、创建云环境。 [图片] 3、微信公众平台,小程序后台中加入小伙伴的微信账号到“项目成员”中。 三、产品原型 从网上搜索了一下好的原型开发工具,然后选择了“墨刀”,坑的是我只有2天的原型设计时间,因为我用的是试用版(本屌丝比较qiong~),试用期过了之后不能再导出html文件了😂。原型导出来之后放到了同事的云服务器上:《拍卖》项目原型。大家感兴趣的可以看看。 四、UI设计 团队成员只会照着UI开发页面,自己作图短时间内学不会啊啊啊啊啊!!!!所以找了我的表妹一个超萌的妹子帮忙设计,篇幅以及本文章的重点内容不在此,所以只展示我们的首页让大家看一下吧~ [图片] 五、数据库设计 哈哈哈哈,为了大家看的更加方便,我直接罗列了一个表格放下面了 [图片] 在云开发操作界面的“数据库”中,先增加好这些“集合” [图片] 六、接口设计 嗯。。。。罗列如下 [图片] [图片] 七、开发 开发主要分为两个部分:云函数(接口开发)、小程序页面开发,这里罗列几个技术点吧供大家参考~ 1、我们开发的时候云开发的“事务”还不支持😢,在12月24号的时候看官方人员回复已经支持。事务官方文档 [图片] 在12月13号的那一周他们还在灰度,所以我们找了替代方案,代码找一个简单的罗列如下吧~ [图片] 2、数据实时推送 针对拍卖的这种玩法,对数据实时更新要求很高,做好的解决方案其实是用websocket来做,我们用云开发,所以只能使用云开发提供的数据实时推送。该功能使用场景为:1)、拍卖详情页,多人打开这个页面时,A用户出价后,其他的B用户、C用户、D用户......等看到的页面上的当前拍卖加个实时更新成最新数据、拍卖纪录列表更新最新的数据;2)、拍卖商品的最后5分钟内如果有人出价,那么该拍卖的最终结束时间顺延5分钟,所有其他用户都更新这个拍卖的结束时间。 小程序端的拍卖详情页的代码实现如下: [图片] 此处有两个坑,1):第一个是从监听开始的那一刻,之后每增加一条都会推送一条消息,第一次一条数据,第二次有两条数据且最新数据是在最后,即:当数据库跟你监听的状态对比下来所有不一样的数据都会推送到用户端;2):第二个坑是,需要设置该数据表(集合)的权限为“所有用户可读,仅创建者可写”,不然获取不到数据的实时推送 [图片] 3、定时触发器,可以查看官方文档 我们的业务使用场景为保证金的返还。每天的中午12点,会把前一天结束的需要先缴纳保证金的拍卖统计出来,然后把保证金返还给每个参与了这些拍卖且未最后竞价未成功的用户。这里主要是给大家看一下这个云函数的定时器的设置 [图片] 4、云函数的本地调试,官方文档 一开始看完文档自己本地调试去勾选“开启本地调试”的时候,每次都说未安装node modules [图片] 这里需要先本地安装npm包 [图片] 5、拍卖详情页的一个效果展示,可以更直观的看到数据的实时更新,本视频在本篇文章的最后~ 八、测试 当时项目比较紧张,抽了半下午加一晚上的时间测试,解决了80%的bug,比赛当天还在边展示边该代码,哈哈哈哈😂 九、参赛ppt & 演讲 这个过程主要靠我们的演讲打动评委,就不详细记载了~ 十、后记 两周的时间,我们在不影响项目排期的情况下,每天加班来做,大约有8000行代码吧,从一开始没有选题的茫然和不自信、到开发过程中的边学边做、到项目完成后满满的成就感,挺辛苦的,但是也学到了很多东西。最后,我们获得了二等奖,我们公司是一个卧虎藏龙的地方。哈哈哈哈,欢迎各路大神大牛加入我们公司、加入我们团队~ [视频]
2020-01-10 - react源码解析--开篇
引子:react开源很久了,使用它也有5年的时间了,从最开始用的还是0.13.1版本吧,到现在的16.12,版本经过了历次迭代,也变得越来越优秀了,一直没有学习过它的源码,深感惭愧。这次也趁着这次机会吧,一边读一边写,先从react开始,然后是react-dom、react-server、react插件(ReactEventsTap、ReactEventsSwipe、ReactEventsSwipe…) 等等吧,写成一个系列的文章,希望我能写的完,哈哈哈哈😂~ ps:文中若有写的不对的地方,欢迎大家指出来 一、代码 直接GitHub上拉代码吧~https://github.com/facebook/react,我拉的时候react版本是16.12.0,文章也是基于这个版本。 在真正的开始之前吧,先说一下我个人理解的学习过程,我把它分为三个阶段:基础学习、应用实践、原理研究。所谓基础学习是通过其官方文档,学习他的入门、使用方法、API等等;应用实践是指真正使用它去做一些东西,这个过程很重要,通过这个阶段才能了解我们所学的东西能转换成什么样的生产力,也有助于我们的逐步深入;最后一个过程是原理研究,也就是所谓的实现方式的研究,只有经过了这最后一个步骤,才能在我们的个人介绍里写上“精通xxx”了吧😄(个人意见~) 二、项目解析 1、首先,让我们看一下项目的目录解析~ ├── build 构建后的输出目录 │ ├── dist react相关 │ ├── facebook-www 插件相关 │ └── react-native react-native相关 ├── fixtures React 开发的测试用例 │ ├── art │ ├── attribute-behavior │ ├── concurrent │ ├── devtools │ ├── dom │ ├── eslint │ ├── expiration │ ├── fiber-debugger │ ├── fiber-triangle │ ├── fizz-ssr-browser │ ├── flight │ ├── flight-browser │ ├── packaging │ ├── scheduler │ ├── ssr │ └── tracing ├── packages 源码目录 │ ├── babel-plugin-react-jsx 该软件包旨在最终替代当前的@babel/plugin-transform-react-jsx,将JSX转换从目标React.createElement(type, props, children)更改为React.jsx(types, props, key) │ ├── create-subscription 在组件里订阅额外数据的工具 │ ├── dom-event-testing-library dom事件测试库 │ ├── eslint-plugin-react-hooks eslint插件钩子 │ ├── jest-mock-scheduler │ ├── jest-react │ ├── legacy-events 事件系统 │ ├── react react核心包 │ ├── react-art 矢量图形库 │ ├── react-cache 为 React 应用提供缓存 │ ├── react-debug-tools │ ├── react-devtools │ ├── react-devtools-core │ ├── react-devtools-extensions │ ├── react-devtools-inline │ ├── react-devtools-shared │ ├── react-devtools-shell │ ├── react-dom DOM 渲染相关 │ ├── react-flight │ ├── react-flight-dom-webpack │ ├── react-interactions │ ├── react-is React 元素类型相关 │ ├── react-native-renderer react-native 渲染相关 │ ├── react-noop-renderer Fiber 测试相关 │ ├── react-reconciler React 调制器 │ ├── react-refresh │ ├── react-server 服务端渲染 │ ├── react-test-renderer │ ├── scheduler 调度:异步渲染等 │ ├── shared 通用代码 │ └── use-subscription └── scripts 公共的babel、eslintflow和release等相关的文件 ├── babel ├── bench ├── circleci ├── error-codes ├── eslint ├── eslint-rules ├── flow ├── git ├── jest ├── perf-counters ├── prettier ├── print-warnings ├── release ├── rollup ├── shared ├── tasks └── yarn 我们主要解析的就是packages下的相关包了。 2、源码构建 这个我们看一下package.json文件里的代码: { … “devDependencies”: { … }, … “scripts”: { “build”: “node ./scripts/rollup/build.js”,// 构建命令 … } } 打包的文件都在./scripts/rollup下了,build文件有740行,是构建所有项目的命令,核心代码如下~ [图片] 启动打包的方法就是这句了:createBundle(bundle, bundleType)。 打包的项目配置,全部在bundles.js中了 [图片] 构建的过程会循环包配置文件的配置构建出不同用途的包,依次确定文件名称、打包入口文件、fb文件名称替换、确认依赖包、输出包等过程。那么我们看下createBundle方法里的构建的入口的JS文件地址: [图片] 沿着bundle.entry我们发现它的值为react(在scripts/rollup/bundles.js中),require.resolve用于查询文件的完整绝对路径,也就说react对应的真实入口路径是~/work/react/packages/react/index.js(前两级为项目目录),由此不难看出所有源码都在packages中。 3、react入口文件 根据第二步的构建过程我们可以发现,真正的react入口文件为:…/packages/react/src/React.js [图片] 三、react源码文件 React.js文件内容如下~ [图片] 通过这个文件也可以看的出来,其实React相关部分主要就是定义了React对象,他的属性有组件、元素、自元素、上下文、懒加载方法、ref、memo、React Hooks、ReactElementValidator等,至于他们的实现其实主要是在react-dom中,那么我们要解析React的话就从React各个部分的使用方法介绍(着重)以及他们的定义(源码查看)这两个方面来进行吧。 四、小结 本篇文章首先了解了整个项目的结构目录,然后通过查看整个项目的构建文件,研究其构建方式,找到了真正的源码文件入口,也正是开启了react部分的源码的解析系列文章,先预告一下下一篇文章react源码解析–React组件。 欢迎收藏+转发~ ps:再次请求大家如果发现有错误,请帮忙指正~
2020-01-23