
能力说明:
了解变量作用域、Java类的结构,能够创建带main方法可执行的java应用,从命令行运行java程序;能够使用Java基本数据类型、运算符和控制结构、数组、循环结构书写和运行简单的Java程序。
暂时未有相关云产品技术能力~
阿里云技能认证
详细说明react-native-easy-app 是一款为React Native App快速开发提供基础服务的纯JS库(支持 IOS & Android),特别是在从0到1的项目搭建初期,至少可以为开发者减少30%的工作量。 react-native-easy-app 主要做了这些工作:1. 对AsyncStorage进行封装,开发者只需几行代码即可实现一个持久化数据管理器。2. 对fetch进行封装,使得开发者只需关注当前App的前后台交互逻辑和协议,定义好参数设置及解析逻辑即可。3. 重新封装了RN的View、Text、Image、FlatList 使用得这些控件在适当的时候支持事件或支持icon与文本,能有效减少布局中的嵌套逻辑。4. 通过设置一个屏幕参考尺寸,重置XView、XText、XImage的尺寸,实现自动多屏适配 可能有人觉得,不同的App对Http请求的要求各异,第三方库怎么可能做到全面的封装,就算做到了,那也必定会 封装过度。 一千个人心中,有一千个哈姆雷特,也许我的思路能给你带来不一样的启发也未可知呢? 网络请求(fetch) 我们先来看下React native中文网给出的fetch使用示例: 异步请求(核心代码) fetch('https://facebook.github.io/react-native/movies.json') .then((response) => response.json()) .then((responseJson) => { return responseJson.movies; }) .catch((error) => { console.error(error); }); 同步请求(核心代码) try { // 注意这里的await语句,其所在的函数必须有async关键字声明 let response = await fetch('https://facebook.github.io/react-native/movies.json'); let responseJson = await response.json(); return responseJson.movies; } catch (error) { console.error(error); } RN平台的fetch请求很简洁,那我们再看看react-native-easy-app的请求 XHttp是不是也可以方便快捷的发送请求呢? 异步请求(核心代码) 示例 1 import { XHttp } from 'react-native-easy-app'; XHttp().url('https://facebook.github.io/react-native/movies.json').execute('GET') .then(({success, json, message, status}) => { console.log(json.movies) }) .catch(({message}) => { showToast(message); }) 同步请求(核心代码)示例 2 import { XHttp } from 'react-native-easy-app'; const response = await XHttp().url('https://facebook.github.io/react-native/movies.json').execute('GET'); const {success, json, message, status} = response; console.log(json.movies) 异步请求2(核心代码)示例 3 import { XHttp } from 'react-native-easy-app'; XHttp().url('https://facebook.github.io/react-native/movies.json').get((success, json, message, status)=>{ console.log(json.movies) }); 通过执行上面三段示例代码,发现输出了一致的结果(电影列表数组): 通过对比发现 XHttp 的使用与React Native平台提供的fetch很相似,其execute('get')方法返回的是一个promise对象,故也可以像fetch一样,发送同步或异步请求。另外还可以通过[method]+回调的形式发送请求。 相比原生fetch请求,XHttp 却返回了多个参数,我们打印一下示例2中的response看看里面都有啥?输出结果,格式化后如下: success => [true | false] 请求成功或失败的标识(默认以Http的请求状态码:[ status >= 200 && status < 400 ] 作为判断依据)。 json => [Json Object | originText] 默认为请求返回的json对象,必要时可以指定返回纯文本字符串(若请求结果为非标准Json,如XML结构或其它)或通过自定义配置指定请求返回的数据结构。 message 默认情况下,请求成功时:为[code+url],失败时:则为错误信息[错误信息+code+url],若开发者指定了特定的解析方式,则由开发者制定。 status 默认情况下为Http请求的status code,可由开发者制定,返回自定义的业务逻辑请求状态码 通过上面的示例, react-native-easy-app 的 XHttp 可以像使用fetch一样方便快捷的发送Http请求,而且还包含请求码,错误信息,结果也被转化为了json对象,使用我们发送请求更加方便了。 但在实际的App开发中,我们Http请求框架的要求不只是能发送简单的Http请求就可以了,比如说,需要打印请求日志、设置header参数、统一处理解析逻辑,甚至可能处理返回的结构不是标准的json数据等各种需求。 我们来看看 react-native-easy-app 的 XHttp 能满足我们哪些需求:注:上面三个示例的请求方式各有所长,下文发送请求示例的地方我都选择使用请求 示例 3 的方式举例。 需求 1 :能支持get、post、put、delete等基本常用类型的请求 : 框架会自动根据输入的请求类型,自动会处理请求的body有无问题 1、通过XHttp 的execute('method')方式发送请求自然是没有问题 2、通过method + 回调的形式(满足90%的情况),我问下的情况怎么办?不用担心框架提供了另一种方式实现,即: XHttp().url('https://facebook.github.io/react-native/movies.json').request('HEAD', (success, json, message, status) => { console.log(json.movies); }) 需求 2:能支持常用的contentType设置,如 application/json、multipart/form-data、application/x-www-form-urlencoded等 当然并不只是简单的传个参数而已,必须能根据请求contentType按正常的方式处理body,如果contentType若为multipart/form-data,则使用FormData去接收拼接开发者传入的参数 1、 XHttp 有三种方式设置contentType,三种常用的方式被提取了出来,如下分别是:直接设置;通过header设置;通过方法直接指定。开发者设置了相应的方式之后,就可以放心的发送Http请求了,剩下的框架会处理(下面示例为:上传图片设置): 需求 3:能支持超时设置;支持日志打印;支持返回非标准Json以及baseUrl的拼接 请求超的原理是通过 Promise.race 实现; 1.由于超时请求并不完全属于某个特定的请求,故引入了一个公共配置对象:XHttpConfig,开发者可以通过两种试设置请求超时配置,如下: import { XHttpConfig } from 'react-native-easy-app'; XHttpConfig().initTimeout(300000); //全局配置,设置所有Http请求的超时时间为30秒 XHttp().url('https://facebook.github.io/react-native/movies.json').timeout(15000) //设置当前请求超时间为15秒 .get((success, json, message, status) => { }) 2、日志打印也是通过 XHttpConfig().initHttpLogOn(true) 设置为 true 即可,设置完成后,我们发送请求,看看控制台的输出日志: XHttpConfig().initHttpLogOn(true); XHttp().url('https://facebook.github.io/react-native/movies.json').get((success, json, message, status) => { }) 可以看出控制台打印出了详细的日志,是不是很方便? 3、现在的移动开发99%的情况下前后台交互都是使用的json格式数据,但很难保证一些特殊情况下,App不使用非标准json数据格式的Http请求。比如需要请求一些老网站或者使用一些第三方开放的老接口。这时候只需要指定返回纯文件数据即可,下面找一个返回xml格式的接口,请求看看结果: let url = 'http://www.webxml.com.cn/WebServices/MobileCodeWS.asmx/getDatabaseInfo' XHttp().url(url).pureText().get((success, text, message, status) => { console.log('XML data', text) }) 控制台输出结果如下(通过XHttp的 pureText() 指定返回的数据以纯文本返回): 4、至于baseUrl的拼接,则是为了在App开发中,减少不必要的baseUrl的重复使用(程序通过判断传入的url是否是完整按需拼接BaseUrl),使用方法如下: import { XHttpConfig, XHttp } from 'react-native-easy-app'; XHttpConfig().initBaseUrl('http://www.webxml.com.cn/WebServices/'); XHttp().url('MobileCodeWS.asmx/getDatabaseInfo').get((success, text, message, status) => { console.log('XML data', text) }) 需求 4:能自由设置公共的params、headers;发送Http请求的时候,也能自由设定当前请求的header及param数据。 import { XHttpConfig, XHttp } from 'react-native-easy-app'; XHttpConfig().initHttpLogOn(true) .initBaseUrl('https://facebook.github.io/') .initContentType('multipart/form-data') .initHeaderSetFunc((headers, request) => { headers.headers_customerId = 'headers_CustomerId001'; headers.headers_refreshToken = 'headers_RefreshToken002'; }) .initParamSetFunc((params, request) => { params.params_version = 'params_version003'; params.params_channel_code = 'params_channel_code004'; params.testChannel = 'testChannel005'; }); XHttp().url('react-native/movies.json') .header({'Content-Type': 'application/json', header_type: 'header_type006'}) .param({paramUserName: 'paramUserName007', testChannel: 'testChannel008'}) .post((success, text, message, status) => { }) 从代码中可以看出通过XHttpConfig配置,我们设置了公共的heders、params,然后在通过XHttp发送请求时,又设置了特定的header和param的值,同时了修改了contentType的类型,并改为post请求,执行代码我们看看控制台日志内容: 通过控制台打印的日志,我们可以很清晰的看到,参数从001~008所有的参数(除了005)都能有效设置到请求当中。但为什么公共参数 params.testChannel = 'testChannel005'; 的设置没有生效呢,其实是因为,XHttp中的接口请求的私有参数中也设置了一个:testChannel: 'testChannel008' 的参数,两者的Key相同,所以被接口私有参数给覆盖了(细心的同学也可以发现,日志中'Content-Type': 'application/json',contentType的类型也被覆盖了),这说明了接口的私有参数具有更高的优先级,这是合理的同时也使接口的请求更灵活方便。 需求 5:能支持自定义数据解析,这也是最重要的。 每个app都有一套前后台数据交互方式,对于返回的数据都有统一固定的格式:方便前端解析处理,如 cryptonator.com 网站提供的比特币查询接口,接口url:https://api.cryptonator.com/api/ticker/btc-usd。我们先通过postman请求一下: 返回的数据格式如下: { "ticker": { "base": "BTC", "target": "USD", "price": "5301.78924881", "volume": "179358.70555921", "change": "-21.18183054" }, "timestamp": 1584291183, "success": true, "error": "" } 可以看出,接口返回的数据结构中,有三个主要字段: success 接口逻辑成功与失败的判断依据。 error 接口若失败时,包含错误信息。 ticker 接口返回的主要数据的主体。 以前面XHttp发送请求,接口的成功与否的判断依然是http的status来判断,显示达不到要求,请求cryptonator.com网站api数据统一解析的基本要求,那怎么自定义数据解析呢?我们试试看。 import { XHttpConfig, XHttp } from 'react-native-easy-app'; XHttpConfig().initHttpLogOn(true) .initBaseUrl('https://www.cryptonator.com/api/') .initParseDataFunc((result, request, callback) => { let {success, json, message, status} = result; callback(success && json.success, json.ticker || {}, json.error || message, status); }); XHttp().url('ticker/btc-usd').get((success, json, message, status) => { console.log('success = ' + success); console.log('json = ' + JSON.stringify(json)); console.log('message = ' + message); console.log('status = ' + status); }); 我们再看下控制台输出的请求日志与Http请求打印的4个标准参数的内容: 发现没有,json对应的值就是返回的数据结构中:ticker对应的数据。其它字段并不能反映出来,因为数据刚好与默认判断条件吻合或为空。这是怎么实现的呢? 因为通过XHttpConfig的initParseDataFunc方法,我们重新定义了,接口请求返回的标准字段的值: success => success && json.success 只有当接口请求与返回的成功标记同时为true的时候才认为是成功 json => json.ticker 直接读取json.ticker的值(若为空,则返回一个没有任何属性对象) message => json.error || message 优先获取接口返回的错误信息(若为空,则读取Http请求的错误信息) status => status 由于些api并没有code判断标记,故依然使用Http的status 这样Http请求返回的参数自定义问题就解决了,这时候可能有人会说:我的app不只是请求一个后台或者还要请求第三方接口,不同的后台返回的数据结构也完全不一样,这种情况下么处理?不用担心,这种情况也是有解的: 办法一(非标准接口较少的情况):比如说,我的请求以cryptonator.com网站的api为主,偶尔要请求域名查询接口: https://api.domainsdb.info/v1/domains/search?domain=zhangsan&zone=com,这个时候,我可以依然保持前面的自定义解析方式不变,在请求域名查询的时候,增加一个标记: FHttp().url('https://api.domainsdb.info/v1/domains/search') .param({domain: 'zhangsan', zone: 'com'}) .contentType('text/plain') .rawData() .get((success, json, message, status) => { if (success) { console.log('rawData', JSON.stringify(json)) } else { console.log(message) } }) 接口请求打印的日志为: 请求依然成功,各参数也没有问题,因为在发送Http请求的时候增加了一个标记rawData(),这个标记就是用于特殊处理的,标记当前Http请求需要返回原始的,不做任何解析的数据(设置此标记,会自动忽略用户自定义的数据解析方式) 办法二(也有可能一个App要请求多个不同的平台或者新老版本过渡,而且不同风格的接口数量还不在少数),同时在这种情况下可能请求的参数风格,公共参数也有不同的要求,这就更复杂了,这种情况能否处理?答案是肯定的: 假设当前App要请求三个平台:分别为SA,SB,SC,这三个平台要求不同的公共参数(包括header),且返回的数据结构也完全不一致,这时候我们可以这样处理,配置与请求都可以完全独立的实现: import { XHttpConfig, XHttp } from 'react-native-easy-app'; XHttpConfig('SA').initHttpLogOn(true) ... XHttpConfig('SB').initHttpLogOn(true) ... XHttpConfig('SC').initHttpLogOn(true) ... const url = 'https://facebook.github.io/react-native/movies.json'; XHttp('SA').url(url) .get((success, json, message, status) =>{ }); XHttp('SB').url(url) .get((success, json, message, status) =>{ }); XHttp('SC').url(url) .get((success, json, message, status) =>{ }); 就是这么简单,配置与请求可以通过serverTag来区别,默认情况下使用同一个配置,但若指定了新的serverTag,发送Http请求时就可以通过serverTag来指定使用哪个Http请求的配置,这样同一个app里面,请求不同的服务器,以及处理不同服务器返回的数据也完全没有压力。 通过上面的例子,我们可以看出,XHttpConfig的三个公共配置方法:initHeaderSetFunc、initParamSetFunc、initParseDataFunc 是一个 面向切面的编程模式 ,这些方法还有一个共同的参数request(第二个参数)里面包含了请求的所有原始信息,因此可以有更多的想象空间,就等你去探索。 可能部分同学觉得,框架的参数设置挺方便,但数据的解析我想完全自己实现可以么?当然可以,通过fetch方法,返回的是原fetch请求的promise,框架不做任何处理: 也有同学想,框架的解析很方便,我想完全使用框架的解析,但有些参数是放在header里面,我怎么才能在解析数据的时候取到response的header数据呢?这个问题也不用担心,在所有示例中,我列表的解析回调的参数都是4个:(success, json, message, status),但实际上有5个参数,第5就是response,它就是fetch返回的reponse,你可以从里取到任何想要的数据,包括headers const url = 'https://facebook.github.io/react-native/movies.json'; XHttp().url(url).get((success, json, message, status, response) => { console.log(JSON.stringify(response.headers)) }); const {success, json, message, status, response} = await XHttp().url(url).execute('GET'); console.log(JSON.stringify(response.headers)) 也有同学可能想到有一种应用场景oauth2需要特别处理: 发送请求req1,因为accessToken失效而请求失败 程序通过refreshToken重新获取到了新的accessToken 拿着新的accessToken重新请求req1 这种应用场景怎么处理呢? XHttpConfig() .initHttpLogOn(true) .initBaseUrl(ApiCredit.baseUrl) .initContentType(XHttpConst.CONTENT_TYPE_URLENCODED) .initHeaderSetFunc((headers, request) => { if (request.internal) { Object.assign(headers, AuthToken.baseHeaders());//添加基础参数 headers.customerId = RNStorage.customerId; if (RNStorage.refreshToken) {//若refreshToken不为空,则拼接 headers['access-token'] = RNStorage.accessToken; headers['refresh-token'] = RNStorage.refreshToken; } } }) .initParamSetFunc((params, request) => { if (request.internal && RNStorage.customerId) { params.CUSTOMER_ID = RNStorage.customerId; } }).initParseDataFunc((result, request, callback) => { let {success, json, response, message, status} = result; AuthToken.parseTokenRes(response);//解析token if (status === 503) {//指定的Token过期标记 this.refreshToken(request, callback) } else { let {successful, msg, code} = json; callback(success && successful === 1, selfOr(json.data, {}), selfOr(msg, message), code); } }); static refreshToken(request, callback) { if (global.hasQueryToken) { global.tokenExpiredList.push({request, callback}); } else { global.hasQueryToken = true; global.tokenExpiredList = [{request, callback}]; const refreshUrl = `${RNStorage.baseUrl}api/refreshToken?refreshToken=${RNStorage.refreshToken}`; fetch(refreshUrl).then(resp => { resp.json().then(({successful, data: {accessToken}}) => { if (successful === 1) {// 获取到新的accessToken RNStorage.accessToken = accessToken; global.tokenExpiredList.map(({request, callback}) => { request.resendRequest(request, callback); }); global.tokenExpiredList = []; } else { console.log('Token 过期,退出登录'); } }); }).catch(err => { console.log('Token 过期,退出登录'); }).finally(() => { global.hasQueryToken = false; }); } }; 在这里我就不做详细说明了直接贴代码,详细的请大家可以直接阅读源码或者参考 react-native-easy-app 库对应的 示例项目,至于原理是:在请求的时候,将初请求的方法引用保存到了request中,并命名为resendRequest,若获取到新的token之后,重新请求一遍resendRequest方法,传入原来的参数即可。 可能有同学觉得react-native-easy-app封装XHttp与XHttpConfig的方法与参数太多了,根本没办法记住,框架虽好却不便于使用,这个目前可能需要大家参考示例项目来写了(后面我会完善说明文档)。 当然大家有没有发现,在使用这些库方法的时候,代码有提示呢?那就对了。因为我为主要的方法增加了dts描述文档,所以在写代码过程中,如果不记得方法名参数直接通过代码自动提示来写就行了(自动提示在webStorm上的体验更好): react-native-easy-app 详解与使用之(三) View,Text,Image,Flatlist 想进一步了解,请移步至 npm 或github查看 react-native-easy-app,有源码及使用示例,待大家一探究竟,欢迎朋友们 Star!
在React Native开发过程中,做为开发人员,我们经常做着费力不“讨好”的事情,这样占用我们不少时间: 定位bug,通常流程是: 按测试同学的的bug描述,登录指定的账号走一遍验证一下问题是否存在。 若bug存在则,在app的调试模式下再验证是否存在,并查看日志或者断点调试查看内存数据的来源及处理是否正确 最后很有可能恼火的证明这个bug只是后台数据问题,而不是App的bug 多环境打包 服务器有多个环境,测试步骤一般是从测试线 => 预生产 => 正式线,这就意味着同一套代码需要打3个包(或者至少需要发3次热更新),为啥不做成连平服务器环境可以动态切换呢? 最后定位bug是后台数据问题:出现这种问题的根本原因是因为测试同学做的是“黑盒测试”,我们拿过来重现的步骤也是“黑盒验证”,这种测试方式不解决,上面那种尴尬的问题永远会存在。 怎么解决“黑盒测试”、“黑盒验证”问题呢?答案是:开放日志。如果我们把app的数据流都展示在开发同学面前(至少把http请求数据流和基本日志输出开放),这样无论是测试同学还我们自己就能更容易的定位很大一部分bug的问题所在。 怎么“开放”这些日志?通过react-native-debug-tool 库,只需要几行代码就能实现一个开发调试工具,通过它,我们可以把我们想要“开放”的日志都以UI的形式随时随地展示出来。下面我们看看这个开发调试工具库能做什么: 提供一个全局悬浮点入口,永远在最顶层,不受页面切换的影响 支持记录http请求,并解析数据并展示(app主动调用记录) 支持记录webView加url请求(app主动调用记录) 支持环境列表展示与切换回调(依赖于app的实现) 任何一项记录的数据都可以(通过点击每一项进行)复制、粘贴 先来看几张效果图: 可以看出普通日志,Http请求,webView的加载等都有详细的记录,因为数据一目了然,就不容易出现因为数据问题给app的bug的情况了,那我们集成这个开发工具会不会很复杂呢? 安装 npm install react-native-debug-tool --save or yarn add react-native-debug-tool 安装 react-native-root-siblings 【若当前项目没有则需要安装些,目前只支持 3.x 版本】 显示调试工具浮点 RootSibling from 'react-native-root-siblings';DebugManager.showFloat(RootSibling) 记录Http请求日志:通过 DebugManager.appendHttpLogs() 记录 fetch(url, params).then((response) => { DebugManager.appendHttpLogs({url, ...params}, response) }) 记录webView日志:通过 DebugManager.appendWebViewLogs() 记录 <WebView source={{uri: url}} onNavigationStateChange={params => { DebugManagerDebugManager.appendWebViewLogs(params.url); }} /> 记录普通日志:通过 DebugManager.appendLogs() 记录 static log(...args) { DebugManager.appendLogs(args.join('')) } 就这样通过以上6步就能实现上面效果图中的所有功能了,是不是很简单,几分钟的时间就能搞定。以后,无论是测试同学还是开发人员都可以通过查看日志更方便的定位问题的bug在哪儿。 当然,前面有提到app的连接服务器环境切换的问题,这个实际上还得依赖于app的Http请求封装的实现,当前调试工具只提供一个服务器环境展示与选择功能,如图所示: 实现代码: DebugManager.initDeviceInfo(DeviceInfo) .initServerUrlMap(serverUrlMap, RNStorage.baseUrl, (baseUrl) => { XHttpConfig().initBaseUrl(baseUrl); // 重置Http请求baseUrl 根据实际情况调用 RNStorage.baseUrl = baseUrl; setTimeout(() => Alert.alert('环境切换', '服务器环境已经切换至' + baseUrl), 1000) }); 注:当前设备信息的显示依赖于 'react-native-device-info' 库需在调用页面引入依赖:import DeviceInfo from 'react-native-device-info';并把DeviceInfo传入到DebugManager的初始化参数中。 至此调试开发工具的所有功能就完全实现了,详细用例大家可以运行 示例 程序,一切就明了,欢迎朋友们 Star! 另外我还有一个开源项目,通过它可以极大的提高RN项目开发速度,大家有兴趣可以了解下: 项目名称:react-native-easy-app 第一篇介绍:数据存储 如果有任何疑问,欢迎扫码加入RN技术QQ交流群
react-native-easy-app 是一款为React Native App快速开发提供基础服务的纯JS库(支持 IOS & Android),特别是在从0到1的项目搭建初期,至少可以为开发者减少30%的工作量。 react-native-easy-app 主要做了这些工作:1. 对AsyncStorage进行封装,开发者只需几行代码即可实现一个持久化数据管理器。2. 对fetch进行封装,使得开发者只需关注当前App的前后台交互逻辑和协议,定义好参数设置及解析逻辑即可。3. 重新封装了RN的View、Text、Image、FlatList 使用得这些控件在适当的时候支持事件或支持icon与文本,能有效减少布局中的嵌套逻辑。4. 通过设置一个屏幕参考尺寸,重置XView、XText、XImage的尺寸,实现自动多屏适配 可能有人觉得,RN的适配一般不都是根据目标屏幕的尺寸对当前UI尺寸进行一定比例的缩放么,直接定义一个获取缩放比例的方法不就可以了么? 一千个人心中,有一千个哈姆雷特,也许我的封装思路能给你带来不一样的启发也未可知呢? UI多屏幕适配 RN平台默认的尺寸就是不带单位的,使用的是设备独立像素,但由于手机的尺寸也越来越多,比如说相同尺寸,像素密不同结果导致UI的物理尺寸也不同,所以通常来说,我们们需要根据屏幕的尺寸对UI的尺寸进行一定比例的缩放。 但UI的尺寸属性太多了,每次都调用相应的尺寸比例方法去计算,不仅看着代码不雅观而且代码量也明显增多,那有没有更好的办法呢? 当前开发库的实现思路是,通过XView、XText、XImage的重定义,将传入组件的style属性做一次重置(将所有涉及尺寸的属性值重置为乘以缩放比例后的值)。至于UI的尺寸属性,在React Native源代码中就可以找到(目前以白名单的形式定义)。这样,在使用以上X系列UI组件的时候不需要做任何特别处理,即可自动实现多屏幕适配。 当然,要想X系列组件支持的前提还是:得设置一个参考屏幕尺寸(设计同学做UI的时候使用的参考屏幕尺寸),代码如下: XWidget.initReferenceScreen(375, 667); //iphone 6 屏幕 我们找个UI看看适配前后的区别(sample项目的列表页面,适配前后): 可以看到,适配后在5S屏幕上UI比较明显(相应的UI尺寸缩小了一些)。UI布局页面适配如下(sample项目的UI组件页面,适配前后): 依然在5S上表现比较明显,适配前UI组件高、宽不太协调,特别是Image组件相对比较拥挤,但适配后就自然多了。在Xs Max上,UI组件由之前偏小,后适配后,相对应的都放大了也显得自然了(大屏、高分辨率屏幕上UI尺寸太小看着比较吃力) 可能有同学想,X系列组件都做了屏幕适配但一些特别情况下,可能不需要作适配怎么办,比如说外层容器组件(有的时候会设置一个定值),这时候有两种办法: 改用系统原生组件 设置X系列组件属性 raw = {true},设置了此属性,当前UI尺寸就不会做任何缩放 所以当大家需要对app的用UI做多屏适配的情况下,尽可能多的使用X系列组件就行了(记得设置参考屏幕哦) 想进一步了解,请移步至 npm 或github查看 react-native-easy-app,有源码及使用示例,待大家一探究竟,欢迎朋友们 Star!
react-native-easy-app 是一款为React Native App快速开发提供基础服务的纯JS库(支持 IOS & Android),特别是在从0到1的项目搭建初期,至少可以为开发者减少30%的工作量。 react-native-easy-app 主要做了这些工作:1. 对AsyncStorage进行封装,开发者只需几行代码即可实现一个持久化数据管理器。2. 对fetch进行封装,使得开发者只需关注当前App的前后台交互逻辑和协议,定义好参数设置及解析逻辑即可。3. 重新封装了RN的View、Text、Image、FlatList 使用得这些控件在适当的时候支持事件或支持icon与文本,能有效减少布局中的嵌套逻辑。4. 通过设置一个屏幕参考尺寸,重置XView、XText、XImage的尺寸,实现自动多屏适配 可能有人觉得,RN的AsyncStorage本身就很简单,自己封装也就几十行代码的工作量,为什么还要使用第三方库? 一千个人心中,有一千个哈姆雷特,也许我的封装思路能给你带来不一样的启发也未可知呢? 数据存储(AsyncStorage) RN平台提供的AsyncStorage有一些基础方法:setItem,getItem,removeItem,getAllKeys,这些是promise模式的并且AsyncStorage只支持对纯字符串的存取,因此我们不便于直接在代码中去直接调用这些方法,我们得对AsyncStorage做一次封装,怎样封装能使我们更方便快捷的访问本地存取呢? 我们来看下通过 react-native-easy-app 库的XStorage,我们可以怎样访问AsyncStorage: 1、核心代码实现 import { XStorage } from 'react-native-easy-app'; import { AsyncStorage } from 'react-native'; let RNStorage = { // 自定义对象 hasLogin: undefined, customerId: undefined, userInfo: undefined }; XStorage.initStorage(RNStorage,AsyncStorage, () => { // 初始化自定义数据管理器 RNStorage.customerId = '123456'; RNStorage.hasLogin = true; RNStorage.userInfo = {name: 'zhangsan', age: 30}; console.log(JSON.stringify(RNStorage)) // 打印数据管理器的内容 }) 执行完上面的代码后我们看看控制台输出: 再通过Root Explorer 查看一下当前App的data/data/{package}/database 下数据表的内容: 什么?上面的代码中并没有做任何数据库的存储操作啊,为什么赋值给RNStorage的数据却被存到了本地数据库中呢?我们先看上面的代码中做了什么: 定义了一个自定义对象RNStorage 将自定义对象传给XStorage.initStorage 进行初始化 在初始化完成后对RNStorage的属性进行了赋值 打印RNStorage的内容 由此可见,数据的存储操作必定是上面的第2、3步引起的。我们进入XStorage的源码看看,里面做了什么: 源代码 1 Object.keys(targetObj).map(key => { const keyStr = newKey(Tag, key); Object.defineProperty(targetObj, key, { get: () => { return this[keyStr] }, set: (value) => { try { this[keyStr] = value; const valueStr = (typeof value === 'object') ? JSON.stringify(value) : String(value); keyValuesPairs.push([keyStr, valueStr]) } catch (exception) { console.log(exception && exception.message); } }, }) }); setInterval(() => { if (!isEmpty(keyValuesPairs)) { let saveDataArray = [...keyValuesPairs]; keyValuesPairs = []; //清空原键值对数组 AsyncStorage.multiSet(saveDataArray, () => { dataChangedCallback && dataChangedCallback(saveDataArray) }); } }, 2500) 源代码 2 const Keys = Object.keys(storageObj); const StorageKeys = Keys.map(key => newKey(Tag, key)); // 初始化时,将AsyncStorage中的数据一次性读取到内存中 AsyncStorage.multiGet(StorageKeys).then(keyValuePairs => { keyValuePairs.map(([keyStr, value]) => { let [, key] = keyStr.split(splitTag); if (persistTag !== key && !isEmpty(value)) { storageObj[key] = convertData(value) } }); setTimeout(() => initializedCallback(), 100) }).catch(error => { console.log(error) }) 哦,原来 XStorage 通过getter、setter生成器,将用户自定义的 RNStorage 的各属与 AsyncStorage 的数据表各字段的值进行了关联形成了一个绑定关系,在当用户对 RNStorage 的各属性进行赋值、取值操作的时候,实际上会触发getter、setter生成器,相应的会对 AsyncStorage 中的数据表进行读写操作。 效率与性能的平衡 <读> 在初始化XStorage的时候就将AsyncStorage中的所有字段一次性读取到 RNStorage 对象中,以后续读取属性时,并不需要经过AsyncStorage,而是直接返回 RNStorage的属性。 <写> 在开发者修改XStorage的属性值时,会先将目标数据赋值给XStorage的属性,然后再异步通过AsyncStorage将目标数据写入到数据库中(考虑到数据写入的效率与性能问题,目前的处理方式为:每次数值的变更都会记录下来,定时程序每隔2.5秒进行一次数据批量写入操作),但这个丝毫不会影响App对数据的操作,因为RNStorage中的数据是实时且同步的。 至此就完全清楚了,是不是很简单?开发者通过 react-native-easy-app 只需定义一个全局可导出的 RNStorage对象(命名随意,并定义好App所需的各属性字段),然后在App启动的时候通过XStorage初始化一次RNStorage即可,以后直接访问RNStorage中的属性值就行了(所有对RNStorage属性的修改都会被自动同步到AsyncStorage中),完全是一劳永逸啊。。。 react-native-easy-app 详解与使用之(二) fetch 想进一步了解,请移步至 npm 或github查看 react-native-easy-app,有源码及使用示例,待大家一探究竟,欢迎朋友们 Star!
2020年10月