大家好,又见面了,我是你们的朋友全栈君。
by Charlie Jeppsson
查理·杰普森(Charlie Jeppsson)
我如何为我的第一个自由客户构建第一个React Native应用程序 (How I built my first React Native app for my first freelance client)
I recently launched my first native mobile app built with React Native. As it happens, it was also the first app I’ve built for a client as a freelancing developer. Here’s the bumpy ride, all the way from react-native init to app store release.
我最近启动了我的第一个使用React Native构建的本地移动应用程序。 碰巧的是,它也是我作为自由职业开发人员为客户开发的第一个应用程序。 从React式本地初始化到应用商店发布,这一直是坎store的旅程。
目录 (Table of contents)
- Why freelance?
为什么是自由职业者?
- Why React Native?
为什么要使用Native?
- App specs
应用规格
- Learning from the best
向最好的学习
- Dev environment
开发环境
- Navigation
导航
- Splash screen
开机画面
- State management
国家管理
- Sessions
届会
- Lists
清单
- Images
图片
- Time
时间
- Custom fonts and icons
自定义字体和图标
- CI/CD and monitoring
CI / CD和监控
- Adding support for Android
添加对Android的支持
- Because Apple
因为苹果
- Summary
摘要
为什么是自由职业者? (Why freelance?)
Last May, I stumbled upon this exciting freelance opportunity. At the time, I was working as a full stack web developer for a Stockholm-based startup. It was my first dev job, and I’d landed it barely a year earlier (which you can read more about in this article).
去年五月,我偶然发现了这个令人兴奋的自由职业机会。 当时,我是一家总部位于斯德哥尔摩的初创公司的全职Web开发人员。 这是我的第一份开发工作,而就在一年前就找到了(您可以在本文中了解更多信息)。
Summer was approaching rapidly, and the otherwise fairly high work pace was getting slower by the day. During one week, when the product team’s rotating tech support duty was mine, I was feeling a bit bored and frustrated with some of the bugs I was assigned to.
夏天快到了,原本相当高的工作节奏一天比一天慢。 在一周的时间里,当产品团队的轮换技术支持职责归我所有时,我对分配给我的一些错误感到有些无聊和沮丧。
It was in this gloomy mood that my dad reached out to me about his intentions to build a mobile app for his company’s customers. Although he knew my job kept me busy and didn’t expect a full-time commitment, he asked if I wanted to be a part of the project in a more advisory type of role. Somewhat intellectually starved I said yes. Although it wasn’t my original intention, this advisory role eventually resulted in me taking on the development of the app as lead developer.
正是在这种沮丧的情绪中,我父亲向我伸出了援助之手,向他介绍了为公司客户构建移动应用程序的意图。 尽管他知道我的工作使我很忙,并且不希望有全职工作,但他问我是否想以更具咨询性的角色参与该项目。 我有点精神上的饿,我说了。 尽管这并不是我的初衷,但最终还是因为担任顾问的角色,使我成为首席开发人员来负责该应用程序的开发。
Now, you might be asking yourself — why would one even attempt to get into the mobile app space after just shy of a year of professional web development experience? Wouldn’t it make more sense to keep specializing in that area while adding some years of experience to your resume?
现在,您可能会问自己-在不到一年的专业Web开发经验之后,为什么还要尝试进入移动应用程序领域? 在您的履历中增加几年经验的同时继续专注于该领域是否更有意义?
Absolutely it would. But, being the hopeless generalist that I am, I committed several years ago to making career decisions not based on career strategy, but rather on what makes me happy. In other words: my resume is already a trainwreck that could probably not get more scattered and incoherent.
绝对会的。 但是,由于我是一名绝望的通才,几年前,我致力于做出职业决策,而不是基于职业战略,而是基于使我感到高兴的事物。 换句话说:我的履历已经是一场沉船事故,可能无法变得更加分散和不连贯。
Of course, career strategy and work life happiness aren’t necessarily mutually exclusive. In fact, I was very happy with my former job and employer. I just happened to find another project that I felt even more passionately about.
当然,职业策略和工作生活幸福并不一定是相互排斥的。 实际上,我对以前的工作和雇主感到非常满意。 我只是偶然发现了另一个令我充满激情的项目。
So what was it that made this particular project that exciting? Even more exciting than working on a hyper-growth product used by thousands of companies in a team with some of the most awesome people I’ve met? In three words: freedom, challenge and self-development.
那么,是什么让这个特别的项目如此令人兴奋? 甚至比与我遇到的一些最出色的人一起在团队中成千上万的公司使用超增长产品开发更令人兴奋吗? 用三个词来说: 自由,挑战和自我发展。
为什么要使用Native? (Why React Native?)
When I joined the project, my client had already received a few offers from some local digital agencies. Before I was even considering building the app on my own, I was asked to review them as a friendly favor. And I was just amazed by the low quality of the propositions.
当我加入该项目时,我的客户已经收到了一些当地数字代理商的一些报价。 在我甚至考虑自己构建应用程序之前,我被要求对其进行友好审核。 我对这些命题的低质量感到惊讶。
One of them had sent some design sketches that were really sloppy and not at all in line with the brand presented on the client’s website. Another agency proposed a ridiculous price with some even more ridiculous recurring fees. And a third didn’t even seem to have done any pitch prep work whatsoever. And they all shared one thing: that they wanted to build the app with the hybrid framework Cordova.
其中一位发送了一些设计草图,这些草图确实草率,根本不符合客户网站上展示的品牌。 另一家代理商提出了一个荒谬的价格,其中包含一些甚至更荒谬的重复费用。 而且三分之一的人似乎都没有做过任何音高准备工作。 他们都共享一件事:他们想使用混合框架Cordova来构建应用程序。
And that wasn’t all. Although Cordova is completely free and open-source, one of them had even tried to hide the fact that this was the technology they used. Instead, they promoted their “own” internal mobile app platform — seemingly just some thin layer around Cordova — to justify a lock-in giving them exclusive app maintenance rights and making an eventual future handover complicated and expensive. Low-quality propositions.
不仅如此。 尽管Cordova是完全免费和开源的,但其中的一个甚至试图掩盖一个事实,那就是这就是他们使用的技术。 相反,他们推广了自己的“内部”内部移动应用程序平台-似乎只是Cordova的薄薄一层-证明了锁定的合理性,赋予他们专有的应用程序维护权,并最终使将来的交接变得复杂而昂贵。 低质量的主张。
Now, I don’t hold any grudge against hybrid frameworks. I use apps built with them all the time. Gmail, Slack, Atom and Figma to name a few. But at the time, I’d been hearing about React Native for quite some time. How it allowed building cross-platform mobile apps using Javascript — that weren’t hybrid!
现在,我对混合框架没有任何怨恨。 我一直都在使用由它们内置的应用程序。 Gmail,Slack,Atom和Figma等。 但是当时,我已经听说过React Native很长时间了。 它如何允许使用Javascript构建跨平台的移动应用程序—不能混合使用!
What now? Had iOS and Android somehow stealthily been sneaking in support for writing native apps in Javascript? Because last I checked, iOS apps had to be built with Objective-C or Swift, and Android apps with Java or Kotlin.
现在怎么办? iOS和Android是否以某种方式悄悄潜入了对使用Java编写本机应用程序的支持? 因为上次我检查过,所以iOS应用必须使用Objective-C或Swift构建,而Android应用必须使用Java或Kotlin构建。
Of course not. So how could React Native apps be called actual native apps? Short answer: APIs. It took me longer to get this than I dare to admit, but the way React Native apps can run natively on your smart phone is not by running Javascript, and not by compiling your Javascript to native code, but by making requests to APIs that render native components in Objective-C on iPhone and in Java on Android.
当然不是。 那么如何将React Native应用程序称为实际的本机应用程序呢? 简短答案:API。 我花了更长的时间才敢做到这一点,但React Native应用程序可以在智能手机上本地运行的方式不是通过运行Javascript,也不是通过将Javascript编译为本机代码,而是通过请求渲染API来完成的。 iPhone上的Objective-C和Android上的Java中的本地组件。
If you want to know more about React Native’s fundamentals I’d really recommend this super-pedagogical Quora answer, this React Conf talk by the amazing Parashuram N and the original unveiling of RN to the world.
如果您想了解更多有关React Native的基础知识,我真的会推荐这个超级教学法的Quora答案 ,这个令人惊叹的Parashuram N的React Conf演讲 ,以及RN最初在世界范围内的揭晓 。
Although I didn’t know this secret behind React Native’s magic trick at the time, I knew that it was in fact running native code — which was also my main argument for not going with any of the Cordova solutions suggested by the agencies. I reckoned that if they wanted a mobile app, they should build a native app. And if they wanted an HTML/CSS/JS app, their money would be better spent simply improving the mobile experience of their web app.
尽管当时我还不知道React Native魔术背后的秘密,但我知道它实际上是在运行本机代码-这也是我的主要观点,即不使用代理商建议的任何Cordova解决方案。 我认为如果他们想要移动应用程序,则应该构建本机应用程序。 而且,如果他们想要HTML / CSS / JS应用程序,则只需改善他们的Web应用程序的移动体验,他们的钱就更好了。
When I shared this with the client, they asked me if I knew someone who could build such an app. I told them I didn’t. They asked me if I could do it. I told them I couldn’t. Still, the seed had been planted, and I just couldn’t keep myself from dabbling around with React Native based on their app specs.
当我与客户共享此内容时,他们问我是否知道有人可以构建这样的应用程序。 我告诉他们我没有。 他们问我是否可以做到。 我告诉他们我不能。 尽管如此,种子还是已经播种了,根据他们的应用规范,我无法阻止自己涉足React Native。
And before I knew it, a foundation for their app was already in place. So somehow, just a few weeks after that conversation we’d agreed that I would build the app for them.
在我不知道这一点之前,他们的应用程序的基础已经到位。 所以以某种方式,在那次对话之后仅仅几周,我们就同意为他们构建应用程序。
应用规格 (App specs)
Before we dive into the more techy details, a brief description of what type of app we’re dealing with here seems to be in place.
在深入探讨更多技术细节之前,似乎已经对这里要处理的应用类型进行了简短描述。
The client is a Stockholm-based company that operates coworking spaces. In other words, workspace hotels for companies. They currently have some 10 active spaces where about 400 companies with about 1,400 employees rent office space. These tenants are the target group of the app.
客户是一家总部位于斯德哥尔摩的公司,该公司经营联合办公空间。 换句话说,公司的工作区酒店。 他们目前有大约10个活动空间,约有1400名员工的400家公司租用办公空间。 这些租户是应用程序的目标组。
After some discussions back and forth with the project manager, a few app specs crystallized:
经过与项目经理的反复讨论,一些应用程序规范逐渐形成:
- Login/logout authentication and password resetting. Note: all user accounts are created by admins, so sign-ups are not possible in the app. Hence, if you decide to download the app, you will basically not be able to do anything with it ?
登录/注销身份验证和密码重置。 注意:所有用户帐户都是由管理员创建的,因此无法在应用中注册。 因此,如果您决定下载该应用程序,那么您将基本上无法执行任何操作?
- Viewing and editing of a user profile, including name, email, password and avatar image.
查看和编辑用户个人资料,包括姓名,电子邮件,密码和头像图片。
- Push notifications.
推送通知。
- A Home destination where users can read about recent events around the company in general and their home coworking space in particular.
一个家庭目的地,用户可以在其中总体上了解公司附近的最新事件,尤其是他们的家庭合作空间。
- A Community destination where users can browse through the different coworking spaces, get in touch with each space’s site manager and see other resident companies.
一个社区目的地,用户可以在其中浏览不同的协同工作空间,与每个空间的站点管理员联系并查看其他居民公司。
- A Conference destination where users can book meeting rooms and manage their bookings.
会议目的地,用户可以在其中预定会议室并管理其预订。
- A Selection destination where users can access exclusive member discounts and offers.
用户可以在其中访问专属会员折扣和优惠的选择目的地。
- First build the iOS version, then add support for Android.
首先构建iOS版本,然后添加对Android的支持。
- A backend admin web app to manage the content of the RN app. Although I will focus on the frontend stuff in this text, it might be relevant to know that it was built on Ruby on Rails, Postgres and Heroku.
后端管理Web应用程序,用于管理RN应用程序的内容。 尽管我将在本文中重点介绍前端内容,但可能知道它是基于Ruby on Rails,Postgres和Heroku构建的。
As you can tell, it’s a pretty slim set of features. Which is exactly what you want for your first app with a new technology. If you want to know how the end result turned out (and whether the rest of this text is worth your time or not), here’s an overview of the 1.0 version:
如您所知,这是一组非常苗条的功能。 正是您对使用新技术的第一个应用程序的需求。 如果您想知道最终结果如何(以及本文的其余部分是否值得您花费时间),那么这里是1.0版本的概述:
You’re still here? Great, then let’s move on.
你还在这里? 太好了,那就继续吧。
向最好的学习 (Learning from the best)
So imagine you’ve promised a friend to build them a house. But you have no idea how to build a house. Barely even where to start. What’s the first thing you’d do?
想象一下,您已经答应了一位朋友来为他们盖房子。 但是你不知道如何盖房子。 甚至从哪里开始。 您要做的第一件事是什么?
You find yourself a carpenter.
您会发现自己是木匠。
So that’s what I tried to do. And did I hit the jackpot. After just a few hours of researching the React Native learning resources out there, I found a 13-part video course from Harvard on Youtube (completely free). Each lecture deep-diving into its own topic for between 90–120 minutes each. So about 23 hours of high quality material in total.
这就是我试图做的。 我是否中了大奖。 在研究了React Native学习资源仅几个小时之后,我在哈佛大学 YouTube网站上找到了由13个部分组成的视频课程 (完全免费)。 每堂课深入探讨自己的主题,每节课持续90-120分钟。 因此,总共大约需要23个小时的高质量材料。
Immediately, I started consuming the video lectures as if possessed. And after just a few weeks of coding along during nights and weekends, I’d finished the course and set myself up with a pretty decent app base.
随即,我开始喜欢观看视频讲座。 在夜间和周末进行了短短几周的编码后,我完成了该课程,并为自己建立了一个不错的应用程序基础。
In hindsight it’s without a doubt one of the best learning resources I’ve found, all categories. The packed and always relevant curriculum absolutely played a big part, but the teacher Jordan Hayashi was definitely the big win here. I’d describe his teaching style as fast, hyper-practical and straight to the point. No time wasted on bad jokes and distracting personal anecdotes. Unlike yours truly…
事后看来,毫无疑问,它是我发现的所有类别中最好的学习资源之一。 内容丰富且始终相关的课程绝对起了很大的作用,但是老师Jordan Hayashi绝对是这里的大赢家。 我将他的教学风格描述为快速,务实且直截了当。 不要把时间浪费在坏笑话和分散个人趣闻上。 真正不同于您的…
Anyhow, somehow each lecture always seemed to compress an amount of information that would take most other teachers at least twice the time. In other words, a style very similar to Harvard CS50 teacher David J Malan.
无论如何,以某种方式,每次讲座似乎总是压缩大量的信息,这将使大多数其他老师至少花费两倍的时间。 换句话说,这种风格与哈佛CS50老师David J Malan非常相似。
So if you’re looking for a starting point for your first RN app, this would be my #1 recommendation. One caveat though: in the course, Jordan uses the Expo toolchain, which is a great tool for most simple apps as it does a lot of the nitty gritty work for you. But if you, like me, are building the foundation for what might become a quite big and complex app sooner rather than later, where you value total configuration freedom, react-native init might be a more appropriate solution.
因此,如果您正在寻找第一个RN应用程序的起点,这将是我的第一建议。 需要注意的是:在课程中,Jordan使用了Expo工具链 ,对于大多数简单的应用程序来说,它是一个很好的工具,因为它为您完成了很多棘手的工作。 但是,如果您像我一样,为早日(而不是后来)发展成为一个大型而复杂的应用程序奠定基础,而在此您重视总体配置的自由度,那么本机初始化可能是一个更合适的解决方案。
The second best learning resource I had access to was actually my colleagues. By a lucky coincidence, we were just getting started with a React Native project at the company where I worked until just a few months ago. Although I wasn’t on the project myself, I learned a ton from just talking to the guys on the project and reviewing their PRs.
我获得的第二好的学习资源实际上是我的同事。 碰巧的是,直到我几个月前,我们才刚在我工作的公司开始一个React Native项目。 尽管我自己并不参与该项目,但通过与项目中的人员交谈并查看其PR,我学到了很多东西。
Now that we’ve got all the contextual things sorted out, we can finally move on to the more technical stuff!?
既然我们已经整理了所有上下文相关的内容,那么我们终于可以继续进行更多技术性工作了!
开发环境 (Dev environment)
After getting the app foundation set up using react-native init, one of the first challenges was to get comfortable with a new development environment.
使用react-native init设置好应用程序基础后,首要挑战之一就是适应新的开发环境。
If you’re coming from the average web development environment, many things will stay the same. For me that included keeping Atom as my text editor, iTerm as my terminal and GitUp as my git interface (to the groaning Vim users out there: haters gonna hate). But other than that React Native required a few additions to my usual work flow.
如果您来自普通的Web开发环境,那么许多事情将保持不变。 对我来说,包括让Atom成为我的文本编辑器,让iTerm成为我的终端,并让GitUp成为我的git界面(对于不断增长的Vim用户:讨厌的人会讨厌)。 但是除此之外,React Native还需要对我通常的工作流程进行一些补充。
Getting cozy with the iOS simulator, for instance. While running “react-native run-ios” from your command line sounds deceptively simple, in the beginning it was rarely enough to get the simulator up and running. As new npm packages were added to the project almost daily (and later on also quite a few native CocoaPod modules), I had to get more familiar then I’d preferred with the painful ritual of clearing watchman, removing the haste cache, deleting the node_modules directory, reinstalling all the node modules again and resetting the Metro Bundler cache. The following command will do all this for you:
例如,熟悉iOS模拟器。 从命令行运行“ react-native run-ios”听起来很简单,但一开始它很少能启动并运行模拟器。 随着几乎每天都在项目中添加新的npm软件包(后来又添加了许多本地CocoaPod模块),我不得不变得更加熟悉,然后我更愿意清除痛苦的仪式,包括清理守卫,删除急速缓存,删除node_modules目录,再次重新安装所有节点模块并重置Metro Bundler缓存。 以下命令将为您完成所有这些操作:
watchman watch-del-all && rm -rf tmp/haste-map-react-native-packager && rm -rf node_modules && yarn && npm start --reset-cache
9 times out of 10 that dance would be enough to get the simulator going again. And sometimes it required delving deep into various GitHub issues and Stackoverflow threads.
十分之九的舞蹈足以使模拟器再次运行。 有时,它需要深入研究各种GitHub问题和Stackoverflow线程。
The root of some other pains was that I for a long time thought that opening Xcode was required to achieve certain things. And believe me, you want to spend as little time as possible in that horror house of an IDE (more on that later).
其他麻烦的根源是我很长一段时间以来一直认为打开Xcode是实现某些事情所必需的。 并相信我,您想花尽可能少的时间在IDE的恐怖屋子里(稍后再介绍)。
Like telling the simulator to run a certain iPhone version. If someone would have told me that the line below did exactly that for me, straight from the command line, I would probably have been a slightly happier person during those first months.
就像告诉模拟器运行某个iPhone版本。 如果有人告诉我,下面的命令行直接从命令行为我做了,那么在最初的几个月中,我可能会变得更加幸福。
react-native run-ios --simulator=’iPhone X’
Another example would be the 3 stage rocket required when going from Release mode (for deploying the app to App Store or some CI destination like Visual Studio App Center or Firebase) to Debug mode (dev mode). Perhaps obvious to many, these changes could also be made directly from your text editor of choice. Anyhow, just two small things that had a surprisingly big impact on my work flow when working in dev mode.
另一个示例是从发布模式(用于将应用程序部署到App Store或某些CI目标,如Visual Studio App Center或Firebase)到调试模式(开发模式)时需要的3级火箭 。 对于许多人来说也许显而易见,这些更改也可以直接从您选择的文本编辑器中进行。 无论如何,在开发模式下工作时,只有两件事对我的工作流程产生了惊人的巨大影响。
Lastly, it took some time to get used to constantly having to jump between different macOS apps to do things I would normally do in Chrome when working with web apps.
最后,花了一些时间习惯于不断地在不同的macOS应用程序之间切换来完成我在使用Web应用程序时通常在Chrome中执行的操作。
To inspect my Javascript console logs and HTML/CSS output for style debugging, I turned to React Native Debugger. And to keep track of app state, actions dispatched and API requests/responses I used Reactotron. While I found both these apps immensely useful, I couldn’t help but miss my corresponding Ember.js workflow, where I could do all of these things in the same place that my app was actually running (with the help of the Ember Inspector Chrome plugin).
为了检查我的Javascript控制台日志和HTML / CSS输出是否进行样式调试,我转向了React Native Debugger 。 为了跟踪应用程序状态,调度的动作以及API请求/响应,我使用了Reactotron 。 虽然我发现这两个应用程序都非常有用,但我不禁错过了相应的Ember.js工作流程,在这里我可以在应用程序实际运行的同一位置进行所有这些操作(借助Ember Inspector Chrome)插入)。
导航 (Navigation)
Navigation/routing has apparently been a pretty hard problem to solve in React Native. Four years in, there’s plenty of different solutions out there, but still no obvious consensus on which one is the best. I decided to go with react-navigation, but mostly due to that being the solution used in both the Harvard course and in the project my colleagues worked on.
在React Native中,导航/路由显然是一个很难解决的问题。 四年来,有很多不同的解决方案,但是对于哪种方案最好,仍然没有明显的共识。 我决定采用React导航,但是主要是因为这是哈佛课程和我的同事从事的项目中使用的解决方案。
However, if I would have taken the time to do some proper research, I might have made the following findings:
但是,如果我花时间做一些适当的研究,我可能会得出以下发现:
-
The react-navigation repo has ~15 000 stars and 86 open issues. It’s fully Javascript-based and also has the most thorough documentation of all the navigation solutions I’ve seen.
react-navigation仓库拥有约1.5万颗星和86个未解决的问题。 它完全基于Javascript,并且具有我所见过的所有导航解决方案中最详尽的文档。
-
The react-native-navigation repo has ~10 000 stars and 162 open issues. Also worth taking into account is that it is not fully Javascript-based (i.e. requires editing native files).
react-native-navigation仓库拥有约10000个星星和162个未解决的问题。 还值得考虑的是它不是完全基于Javascript的(即,需要编辑本机文件)。
-
The react-router repo has ~35 000 stars and 36 open issues. However, these figures are not really comparable to the others since the repo includes routing packages for React.js as well.
React-Router存储库拥有约3.5万颗星和36个未解决的问题。 但是,这些数字实际上不能与其他数字相提并论,因为该仓库还包括React.js的路由包。
-
The native-navigation repo has ~3 000 stars and 55 open issues. However, the fact that this solution is still in beta, not fully Javascript-based and maintained by Airbnb should be seriously considered before investing a lot of time into it (Airbnb decided to sunset React Native).
本地导航回购协议拥有约3000颗星和55个未解决的问题。 但是,在投入大量时间之前,应认真考虑该解决方案仍处于beta版本,而不是完全由Airbnb维护并完全基于Javascript的事实( Airbnb决定停用React Native )。
Considering the above, I would probably still have chosen react-navigation, since I would not have had the time to test them all, as for instance Kurtis Kemple at MLS did. Lastly, as he explains in his talk, picking a navigation solution is not really a question about which one is the best as much as a question about which one best suits your particular needs.
考虑到上述情况,我可能仍会选择React导航,因为我没有时间测试它们,例如MLS的Kurtis Kemple所做的 。 最后,正如他在演讲中所解释的那样,选择导航解决方案并不是一个关于哪一个最适合的问题,而是一个关于哪个最适合您的特定需求的问题。
After working with react-navigation for about 9 months, I have to say I don’t really have much to complain about. Seeing as my main point of reference was the router.js library used in Ember.js, it was an entirely new routing experience.
在使用React导航大约9个月后,我不得不说我真的没有什么可抱怨的。 看到作为我的主要参考点是在router.js中使用库Ember.js ,它是一个完全新的路由经验。
Getting to know react-navigation’s three main types of navigators was the easy part (StackNavigator, TabNavigator and DrawerNavigator). The hard part was understanding how the navigators should be nested with one another to get the intended user flow.
了解React导航的三种主要导航器是最简单的部分(StackNavigator,TabNavigator和DrawerNavigator)。 困难的部分是了解如何将导航器彼此嵌套以获取预期的用户流。
For instance, that my DrawerNavigator was supposed to be at the navigation root (one step above my main TabNavigation) was not at all obvious to me. If this is hard to picture, here’s the DrawerNavigator in action (much smoother in reality than in the gif):
例如,我的DrawerNavigator应该位于导航根目录(比我的主TabNavigation高一级)对我来说一点都不明显。 如果难以想象这是实际操作中的DrawerNavigator(实际上比gif中的平滑得多):
As you can see, I wanted a sidebar that could be opened with a swipe of the thumb from anywhere in the app.
如您所见,我想要一个侧边栏,可以从应用程序中的任何位置通过滑动拇指来打开它。
Seeing as a sidebar is more of a secondary component in an app compared to the main bottom tab bar, my first intuition here was that the DrawerNavigator should be placed underneath or in parallel with the central BottomTabNavigator position in the route tree (see image below).
与侧底部的主标签栏相比,侧边栏是应用程序中的次要组件,我的第一个直觉是应该将DrawerNavigator放置在路线树中的中心BottomTabNavigator下方或与之平行(请参见下图) 。
However, after banging my head against the wall trying to force-squeeze the sidebar in there, I found that the react-navigation way would actually be to put the DrawerNavigator one step above the BottomTabNavigator, i.e. at the root of the route tree. Hopefully this heads up will save someone out there the fair amount of hours I spent in the docs and GitHub issue threads to get to this insight.
但是,在将我的头撞到墙上试图强行挤压其中的侧边栏后,我发现React导航的方法实际上是将DrawerNavigator放在BottomTabNavigator上方一步,即在路径树的根部。 希望这会为我节省一些人,我花了大量的时间在文档和GitHub问题线程上,以获取这种见解。
Here’s another illustration with the DrawerNavigator at the root:
这是另一个以DrawerNavigator为根的插图:
One question you might ask yourself is: why both a StackNavigator and a TabNavigator for both Community and Conference? Why not just skip the stack layer and go straight to the tabs?
您可能会问自己一个问题:为什么同时为社区和会议使用StackNavigator和TabNavigator? 为什么不跳过堆栈层直接进入选项卡呢?
Well, because I wanted a header on top of each of the two TabNavigators. These guys:
好吧,因为我想在两个TabNavigator的每个顶部都放置一个标题。 这些家伙:
Again, my intuition and the react-navigation way of doing things diverged. Seeing as the createMaterialTopTabNavigator must be a pretty standard navigation component, I figured it should have a simple built-in header config in it’s navigationOptions. Turns out it doesn’t, which is why I was forced to use a StackNavigator in between, thus adding another layer of complexity to the infrastructure for a purely superficial purpose.
再次,我的直觉和React导航的方式有所不同。 鉴于createMaterialTopTabNavigator必须是一个非常标准的导航组件,我认为它的navigationOptions应该具有一个简单的内置标头配置。 事实并非如此,这就是为什么我不得不在两者之间使用StackNavigator,从而为纯粹的肤浅目的在基础架构中增加了另一层复杂性。
This flaw in react-navigation also caused me some more serious problems. Namely, getting the header images to collapse/disappear when a user scrolls down in any of the two FlatLists. Since the headers of Home and Selection are rendered within the same StackNavigator as their lists, here this could easily be solved by simply letting the header scroll up together with the rest of the list.
React导航中的这种缺陷也给我带来了一些更严重的问题。 即,当用户在两个FlatList中的任意一个向下滚动时,使标题图像折叠/消失。 由于Home和Selection的标题与它们的列表在同一StackNavigator中呈现,因此在此可以通过简单地使标题与列表的其余部分一起向上滚动来轻松解决。
But with Community and Conference — since the headers are rendered in StackNavigators, and the lists in TabNavigators one step beneath them in the tree — I found no way to apply the same solution to them. Hence I’m left with this painful asymmetry:
但是对于社区和会议,由于标题是在StackNavigators中呈现的,而TabNavigators中的列表在树中位于其下方一步,因此,我发现无法对它们应用相同的解决方案。 因此,我留下了这种痛苦的不对称:
Now this may not appear as an issue on the iPhone X running in the simulator above, but on smaller screens that header might take up some 20% of valuable screen area. If anyone has an idea how to get around this, I’m all ears!
现在,在上面的模拟器中运行的iPhone X上,这可能不会成为问题,但是在较小的屏幕上,标题可能会占据宝贵的屏幕区域的20%。 如果有人对如何解决这个问题有想法,我会很高兴!
The same TabNavigator issue also caused a problem in the Community destination. As demonstrated below, I wanted to put another TabNavigator inside the Coworking Spaces tab, to get the three top tabs Info, Members and Contact visible on the right side of the gif.
相同的TabNavigator问题也导致社区目标出现问题。 如下所示,我想在Coworking Spaces选项卡中放置另一个TabNavigator,以在gif的右侧显示三个顶部的选项卡Info,Members和Contact。
However, since TabNavigator made it really hard to put an image slideshow on top of it without adding a ton of complexity causing all sorts of other navigation headaches (mainly related to navigation params), I had to resort to a JS package called react-native-swiper to deal with those three tabs instead. And I would actually have been totally fine with that, if it wasn’t for the quite unsmooth slide animations of the tab underlines. Anyway, I deemed it a fair price to avoid the alternative navigation headaches.
但是,由于TabNavigator使得很难在其上放置图像幻灯片而不增加大量复杂性而导致其他各种导航麻烦(主要与导航参数有关),因此我不得不求助于JS包,称为react-native -swiper处理这三个选项卡。 如果不是因为选项卡下划线的幻灯片动画不是很平滑,那我实际上会很好。 无论如何,我认为这是避免其他导航麻烦的合理价格。
To sum up my experience with navigation in React Native:
总结一下我在React Native中导航的经验:
- There are plenty of well-documented solutions out there, of which I found react-navigation to best suit my needs.
有很多记录良好的解决方案,我发现其中的React导航最适合我的需求。
- React-navigation made it really easy to get started without knowing much about how purely native navigation works.
使用React-navigation可以非常轻松地开始上手,而无需了解很多纯本机导航的工作原理。
- React-navigation has a few non-intuitive dimensions (for a web developer), but none that can’t be conquered with some clunky work-arounds.
对于Web开发人员而言,React-navigation具有一些非直觉的维度,但是没有一些笨拙的解决方法可以解决的。
开机画面 (Splash screen)
When running a new react-native init app on your simulator, reloading the app over and over again every time you make a change, you will quickly become aware of the need for a pretty launch screen (also called splash screen).
在模拟器上运行新的react-native初始化应用程序时,每次进行更改时都要一次又一次地重新加载应用程序,您会很快意识到需要漂亮的启动屏幕(也称为启动屏幕)。
And since there’s already a really great guide for how to achieve this, I will not waste any of our time repeating the author’s words. I only ran into one problem here that the guide did not cover:
而且,由于已经有一个非常好的实现此目标的指南,因此 ,我不会浪费我们的时间重复作者的话。 我在这里只遇到了一个问题,该指南没有涵盖:
It’s pretty much an iOS edge case, but still something that will likely bother the few users exposed to it. I discovered it first when I was working someplace where I could not access the wifi, and thus was sharing the 4G from my phone with my laptop. As iPhone users will know, the status bar on the device will turn blue and get an increased height while internet sharing. And it totally broke my splash screen image when running on device. The same problem appeared when in-call.
这几乎是一个iOS的优势案例,但仍然有可能使少数接触它的用户感到困扰。 我在无法访问wifi的地方工作时首先发现了它,因此与笔记本电脑共享手机中的4G。 如iPhone用户所知,在共享Internet时,设备上的状态栏将变为蓝色并增加高度。 在设备上运行时,它完全破坏了我的初始屏幕图像。 通话时出现相同的问题。
So after a while of digging around in the react-native-splash-screen repo and not finding anything helpful, I decided to work around the problem by hiding the status bar completely while the splash screen was visible.
因此,在React本机启动屏幕回购中搜索了一段时间之后,发现没有任何帮助,我决定通过在启动屏幕可见时完全隐藏状态栏来解决此问题。
It’s super easy, all you need to do is add a UIStatusBarHidden key with the boolean value of true to your info.plist file, and then set the React Native StatusBar component’s “hidden” prop to false after SplashScreen.hide() has been called.
这非常容易,您要做的就是将一个布尔值为true的UIStatusBarHidden键添加到info.plist文件中,然后在调用SplashScreen.hide()之后将React Native StatusBar组件的“ hidden”属性设置为false。 。
国家管理 (State management)
“Convention over configuration” is a mantra I feel like I’ve been hearing every single day for the past two years. Not least at my former employer. Not very surprisingly, since we used Ruby on Rails on the server side and Ember.js one the client side — two frameworks basically synonymous with that saying. I thought I knew what those words meant, but the process of learning how to handle state in React Native gave them a whole new meaning.
我觉得过去两年里每天都在听到“配置之上的约定”。 尤其是在我的前雇主那里。 毫不奇怪,因为我们在服务器端使用了Ruby on Rails,在客户端使用了Ember.js,所以这两个框架基本上就是这种说法的代名词。 我以为我知道这些词的含义,但是在React Native中学习如何处理状态的过程赋予了它们全新的含义。
Although I’d played around with the “configuration over convention” React library for the web in a few very simple demo apps, I’d never built anything big enough to justify bringing in a state container like Redux or MobX. Instead, most of my JS state management experience came from Ember Data (Ember’s built-in state management and data persistence library).
尽管我在一些非常简单的演示应用程序中使用了Web上的“约定配置” React库,但是我从来没有构建过足够大的东西来证明引入Redux或MobX这样的状态容器是合理的 。 相反,我的大部分JS状态管理经验来自Ember Data (Ember的内置状态管理和数据持久性库)。
Since Redux was the go-to solution I’d heard people talk about for years on podcasts, blogs and in videos (including Jordan in the RN Youtube course), I never really considered any of it’s contenders. I just wanted to set up the best possible state management infrastructure with the least possible effort.
由于Redux是我多年来一直在播客,博客和视频中谈论的首选解决方案(包括RN Youtube课程中的Jordan),所以我从未真正考虑过其中的任何竞争者。 我只是想以最少的努力来建立最佳的状态管理基础架构。
In Ember you are basically given 90% of this infrastructure for free. Little did I know that I would have to accept the opposite ratio in my current project. Not only won’t React provide you with anything useful to handle global state, but Redux — the most popular solution on the market — is so light-weight that you’ll basically have to pull 90% of the weight yourself the get an equal state solution.
在Ember中,您基本上可以免费获得此基础架构的90%。 我几乎不知道我必须在当前项目中接受相反的比率。 React不仅不会为您提供处理全局状态的任何有用方法,而且Redux(市场上最受欢迎的解决方案)如此轻巧,以至于您基本上必须自己拉90%的重量才能获得平等状态解决方案。
Now that the slightly younger me got that out of his system, the hard part was actually just getting a hang of this new functional and immutable work flow. Once I’d accepted the surprising amount of new complexity needed to simply fetch or post some data from/to my server, it all boiled down to 7 pretty straightforward steps:
现在,我才刚刚年轻,就从他的系统中解脱出来,最困难的部分实际上只是掌握了这种新的功能和不变的工作流程。 一旦我接受了从服务器上获取或发布一些数据所需的惊人数量的新复杂性,所有这些都归结为7个非常简单的步骤:
- Add the three SOME_ACTION_REQUEST, SOME_ACTION_FAILED, SOME_ACTION_SUCCEEDED to your constants file.
将三个SOME_ACTION_REQUEST,SOME_ACTION_FAILED,SOME_ACTION_SUCCEEDED添加到常量文件中。
- Add the action creator to your actions file.
将动作创建者添加到您的动作文件中。
- Handle the three actions in the appropriate reducer, and if necessary add a new reducer and include that reducer in your root reducer.
在适当的减速器中处理这三个动作,并在必要时添加新的减速器并将该减速器包括在根减速器中。
Add workers to the appropriate saga, and if necessary add a new saga and include that saga in your root saga (I’m using redux-saga for async actions).
将工人添加到适当的传奇中,如有必要,添加一个新的传奇,并将该传奇包含在您的根传奇中(我将redux-saga用于异步操作)。
- Add function to handle any eventual API request.
添加函数以处理任何最终的API请求。
- Map the necessary state to props in the appropriate React components.
将必要的状态映射到适当的React组件中的props。
- Dispatch the SOME_ACTION_REQUEST action from the appropriate React components.
从适当的React组件调度SOME_ACTION_REQUEST操作。
Redux and redux-saga surely have so much more to offer, but as far as I’m currently concerned, the above 7 steps are essentially what Redux is for me.
Redux和redux-saga当然可以提供更多的功能,但是就我目前而言,以上7个步骤实质上是Redux对我而言的。
届会 (Sessions)
So we’ve got our React Native dev environment set up, a navigation tree mapped out, and a state management infrastructure in place. What would be a good next step? Well, for me the natural choice was user authentication, thus getting into sessions.
因此,我们已经建立了React Native开发环境,映射了导航树,并建立了状态管理基础架构。 下一步将是什么好? 好吧,对我而言,自然的选择是用户身份验证,从而进入会话。
If you’re coming to React Native from a web background, dealing with sessions will not require much brain compute power. If you’re in any way familiar with the concept of LocalStorage, you simply need to replace it with AsyncStorage: an abstraction layer that will let you persist key-value pairs across sessions. In other words perfect for storing an authentication token generated from your server when a user logs in.
如果您是从网络背景接触React Native的,那么处理会话将不需要太多的大脑计算能力。 如果您以某种方式熟悉LocalStorage的概念,则只需将其替换为AsyncStorage :一个抽象层,可让您在会话之间持久保存键值对。 换句话说,非常适合存储用户登录时从服务器生成的身份验证令牌。
清单 (Lists)
Overall, my impression is that lists is a fairly well-solved problem in React Native. Basically, you have three options at hand: If you’re dealing with a static list whose data doesn’t change, ScrollView will likely suffice. If you’re dealing with a list that’s bigger and dynamic, FlatList is what you want. And if you want a bigger and dynamic list that’s also divided into different sections, SectionList is your answer.
总的来说,我的印象是列表是React Native中一个相当好的解决问题。 基本上,您有三个选择:如果您要处理的静态列表的数据不变,则ScrollView可能就足够了。 如果您要处理更大且动态的列表,则需要FlatList 。 而且,如果您希望将更大且动态的列表也分为不同的部分,则可以使用SectionList 。
I exclusively used FlatList for my dynamic lists. And while I intuitively like it and its massive set of configuration options, I experienced a few quite painful situations. Below I’ll go through them one by one.
我专门将FlatList用于动态列表。 虽然我凭直觉喜欢它及其庞大的配置选项集,但我遇到了一些非常痛苦的情况。 在下面,我将一一介绍。
Pull to refreshFlatList has a prop called refreshControl, to which you can pass a component you want to use for refreshing the list content, triggered when the user pulls downward from the top of the list. Lucky for us, React Native has a component just for that purpose — RefreshControl . All very intuitive and easy to set up.
拉动刷新 FlatList有一个名为refreshControl的道具,您可以将要用于刷新列表内容的组件传递给该控件,当用户从列表顶部向下拉动时触发。 对我们来说幸运的是,React Native具有一个专门用于此目的的组件-RefreshControl 。 所有这些都非常直观且易于设置。
However, I ran into a weird situation, where the refreshControl prop and/or the RefreshControl component seemed to be the ones to blame. Some background:
但是,我遇到了一种怪异的情况,其中refreshControl道具和/或RefreshControl组件似乎应该受到指责。 一些背景:
So in my lists I want users to be able to a) scroll up at the top to refresh the list, triggering a function I named handleRefresh() and b) scroll down to load more items into the list, aka. “infinite scrolling” (more on that further down). Pretty standard stuff.
因此,在我的列表中,我希望用户能够a)在顶部向上滚动以刷新列表,从而触发一个名为handleRefresh()的函数,并b)向下滚动以将更多项加载到列表中。 “无限滚动”(进一步介绍)。 很标准的东西。
However, after a while I started getting these situations where the refresh spinner would just freeze and keep spinning forever, not displaying the new items fetched from the server. After quite some time of researching, I found the reason for my problem in this GitHub issue response.
但是,过了一会儿,我开始遇到这种情况,刷新微调器将冻结并永远旋转,而不显示从服务器获取的新项目。 经过一段时间的研究,我在此GitHub问题响应中找到了导致问题的原因。
The problem was that both the refreshControl and onEndReached (for infinite scrolling) props were making use of the same boolean prop: “fetching”. And for some weird reason, when this fetching prop would change from false to true and then back to true again, within a time interval of less than 250ms, RefreshControl would break and the loading spinner freeze.
问题在于refreshControl和onEndReached(用于无限滚动)道具都使用相同的布尔道具:“获取”。 出于某些奇怪的原因,当该获取道具从false更改为true,然后在不到250ms的时间间隔内再次恢复为true时,RefreshControl将中断并且加载微调器冻结。
So to try this theory out, I tried adding a setTimeout(), setting a minimum time interval of 350ms between changing the value of the fetching boolean. And it fixed the problem. But since using setTimeout felt a bit too hacky for my taste, I ultimately landed on the solution of simply using two different props for the handleRefresh() and handleLoadMore() functions: “refreshing” and “loadingMore”. Not sure how common this problem is, but hopefully my workaround can save someone some time and frustration.
因此,为了尝试该理论,我尝试添加一个setTimeout(),设置更改获取布尔值之间的最小时间间隔为350ms。 它解决了这个问题。 但是由于我觉得使用setTimeout感觉有点过于棘手,因此我最终着手解决方案,即为handleRefresh()和handleLoadMore()函数简单地使用两个不同的道具:“ refreshing”和“ loadingMore”。 不知道这个问题有多普遍,但是希望我的解决方法可以节省一些时间和沮丧。
Note that the official documentation recommends using onRefresh and refreshing instead of the refreshControl prop. The reason I went with refreshControl was that I didn’t see any other way to be able to customize the style of the spinner.
请注意,官方文档建议使用onRefresh和刷新而不是refreshControl属性。 我使用refreshControl的原因是,我看不到任何其他方式可以自定义微调器的样式。
Infinity scrollingAs mentioned above, I also wanted to give my users the feeling as if the list was completely seamless. Meaning not having to press any “Load more” button at the bottom to load more items, and not having to get some blocking loading spinner or loading placeholders covering also the already loaded list item’s while fetching more items.
无限滚动如上所述,我还想给用户一种感觉,好像列表是完全无缝的。 这意味着不必按底部的任何“更多加载”按钮即可加载更多项目,也不必在获取更多项目时获得一些阻止加载微调器或加载占位符,这些占位符也覆盖了已加载列表项。
For this purpose, onEndReached had basically everything I needed. I had two problems while implementing it though.
为此,onEndReached基本上满足了我的所有需求。 我在实施它时遇到了两个问题。
The first was wrapping my head around the onEndReachedThreshold prop, which will tell your FlatList when to trigger the function passed to onEndReached. After some trial and error, my explanation would be this:
首先是将我的头缠在onEndReachedThreshold道具上,这将告诉FlatList何时触发传递给onEndReached的函数。 经过反复试验,我的解释是这样的:
If you have 100 items loaded into your list and the screen fits 10 items at a time, a onEndReachedThreshold value of 1 would mean that the onEndReached function will be called when you scroll past the 90th item in your list. If the value is 2, the function will be called already when you’re within 2 screen heights from the end, i.e. at the 80th item, and so on.
如果将100个项目加载到列表中,并且屏幕一次可容纳10个项目,则onEndReachedThreshold值为1表示在滚动到列表中的第90个项目时将调用onEndReached函数。 如果值为2,则当您距离末尾2个屏幕高度(即第80个项目)在2个屏幕高度之内时,该函数已经被调用。
The second problem I ran into with infinite scrolling was what I can only assume is a FlatList bug. Namely, that every single time I scrolled down passed the threshold, my handleLoadMore() function passed to the onEndReached prop would be called repeatedly, often more than 10 times in a row.
我无限滚动遇到的第二个问题是我只能假定是FlatList错误。 也就是说,每次向下滚动超过阈值时,传递给onEndReached道具的handleLoadMore()函数都会被重复调用,通常连续调用十次以上。
Coincidentally, once again the solution could be found in making use of the loadingMore prop, adding an if statement in the handleLoadMore() function making sure the fetch action was only dispatched if !loadingMore. Naturally, you’d also want to check in that same if statement that you’re not on the last page in your server pagination.
巧合的是,再次可以通过使用loadingMore道具找到解决方案,在handleLoadMore()函数中添加一条if语句,确保仅在!loadingMore时才分派fetch动作。 自然地,您还想以同样的if语句检入不在服务器分页中的最后一页。
Loading placeholdersSomething that wouldn’t necessarily have any effect on the user experience, but most definitely would have made me happier as a developer, would be the presence of a ListLoadingComponent prop in FlatList, just like there’s a ListHeaderComponent, a ListEmptyComponent and a ListFooterComponent.
加载占位符不一定会对用户体验产生任何影响的东西,但是最肯定的是,使我作为开发人员更加高兴的是FlatList中存在ListLoadingComponent道具,就像存在ListHeaderComponent,ListEmptyComponent和ListFooterComponent一样。
Since there is not, I was forced to rely on clumsy if statements to handle the placeholder rendering in plenty of render() functions.
由于没有,我不得不依靠笨拙的if语句来处理大量render()函数中的占位符渲染。
Scroll to topThe final list topic I’d like to touch upon is scrolling to the top of the list with the press of a button. In my app, I currently have these buttons in the headers, but another common location for them is in the bottom tab buttons.
滚动到顶部我要触摸的最终列表主题是通过按一个按钮滚动到列表的顶部。 在我的应用程序中,我目前在标题中具有这些按钮,但它们的另一个常见位置是底部的标签按钮。
To achieve this I used the FlatList scrollToOffset method, which is simple enough to understand from the docs. However, a crucial detail which I could not find in the docs was that you also need to make use of the ref prop in the FlatList component, like so:
为此,我使用了FlatList scrollToOffset方法,该方法很简单,可以从文档中了解。 但是,我在文档中找不到的一个关键细节是,您还需要在FlatList组件中使用ref prop,如下所示:
<FlatList ref={(ref) => { this.newsListRef = ref; }} .../>
What this does is basically giving your FlatList an identifier, so that it can be called from a function elsewhere. So in my case it allowed me to call the ScrollToOffset function from my handleScrollToTop()
function, and for instance pass it as a parameter to my react-navigation navigation object, allowing it to be called from any route the param is passed to.
这基本上是为FlatList提供一个标识符,以便可以从其他函数中调用它。 因此,在我的情况下,它允许我从handleScrollToTop()
函数调用ScrollToOffset函数,例如,将其作为参数传递给我的handleScrollToTop()
-Navigation导航对象,从而可以从传递参数的任何路径中调用它。
componentDidMount() { this.props.navigation.setParams({ scrollToTop: this.handleScrollToTop, });}
handleScrollToTop = () => { this.newsListRef.ScrollToOffset({ x: 0, y: 0, animated: true, });};
Note that after upgrading to react-navigation 3, the ref thingy was no longer needed since the createBottomTabNavigator buttons will now handle scroll to top by default.
请注意,升级到react-navigation 3之后,不再需要ref东西,因为createBottomTabNavigator按钮现在默认将滚动到顶部。
图片 (Images)
Images, I’ve come to learn, runs the biggest risk of becoming the one thing that makes your mobile app suck. Naturally, efficient image handling is important also on the web, but since phones will run on 4G (or 3G, god forbid) to a much larger extent, a lower average download speed must be assumed, which in turn could make your app seem slow.
我已经开始学习图像,这成为使您的移动应用吸引人的最大风险。 自然,有效的图像处理在网络上也很重要,但是由于手机将在更大的程度上运行4G(或3G,禁止使用),因此必须假定较低的平均下载速度,这可能会使您的应用看起来运行缓慢。
Images will also likely take up a bigger share of the phone screen compared to the computer screen, why they should also be given a higher priority from a cosmetic perspective. So although it might not be the most fun part of it all, investing some time into it will probably be worth it.
与计算机屏幕相比,图像也可能会在电话屏幕中占据更大的份额,为什么从装饰性的角度来看,图像也应被赋予更高的优先级。 因此,尽管这可能不是全部中最有趣的部分,但花一些时间在其中可能是值得的。
My app turned out to be quite heavy on image content. It totals 7 lists with list items with image props that are not only displayed in the actual list items, but also on each item’s own “details” (the screen a user get’s redirected to when pressing a list item).
事实证明,我的应用程序在图像内容上非常繁琐。 它总共有7个列表,其中带有图像道具的列表项目不仅显示在实际列表项目中,而且还显示在每个项目自己的“详细信息”(按列表项目时,用户将重定向到的屏幕)。
Uploading imagesOn the user account edit screen, the app would also allow users to upload an avatar image. For this I used the react-native-image-picker library, along with Cloudinary and Carrierwave on my Rails backend.
上传图像在用户帐户编辑屏幕上,该应用程序还将允许用户上传头像图像。 为此,我在Rails后端使用了react-native-image- picker库以及Cloudinary和Carrierwave。
At first I put all the uploading logic on the client side, using Cloudinary’s Node API and the react-native-fetch-blob module. But after a while, since I wanted a bit more flexibility in my uploading logic and didn’t want to put too much complex logic on the React Native side, I moved it all to the Rails backend.
首先,我使用Cloudinary的Node API和react-native-fetch-blob模块将所有上传逻辑放在客户端。 但是过了一会儿,由于我希望在上传逻辑上有更多的灵活性,并且不想在React Native方面添加太多复杂的逻辑,所以我将所有这些都移到了Rails后端。
However, I ran into some troubles while trying to post the images to my server using react-native-fetch-blob. Hence, the additional complexity and the at the time very uncertain maintenance status of the repo made me choose the built-in JS FormData API instead. Note however that the no longer maintained react-native-fetch-blob repo has since been moved to rn-fetch-blob, where it is being actively maintained.
However, I ran into some troubles while trying to post the images to my server using react-native-fetch-blob. Hence, the additional complexity and the at the time very uncertain maintenance status of the repo made me choose the built-in JS FormData API instead. Note however that the no longer maintained react-native-fetch-blob repo has since been moved to rn-fetch-blob , where it is being actively maintained.
Displaying imagesTruth is, the standard React Native Image tag, with its style, source and resizeMode props will take you a long way. If you don’t care about caching, displaying multiple images or some other fancy special case you probably won’t need to bring in other dependencies.
Displaying images Truth is, the standard React Native Image tag , with its style, source and resizeMode props will take you a long way. If you don’t care about caching, displaying multiple images or some other fancy special case you probably won’t need to bring in other dependencies.
However, I found two cases where I actually found it worth the effort of adding to my list of dependencies. The first was the circular avatar-formatted images shown in some of the list cards and the user profile screens. For that, the react-native-elements Avatar came in handy.
However, I found two cases where I actually found it worth the effort of adding to my list of dependencies. The first was the circular avatar-formatted images shown in some of the list cards and the user profile screens. For that, the react-native-elements Avatar came in handy.
However, this component does nothing that you can’t achieve yourself with some extra styling to the default Image component. So unless you’ve already brought in the library for some other purposes, I wouldn’t recommend adding this dependency for the single purpose of avatar-formatting.
However, this component does nothing that you can’t achieve yourself with some extra styling to the default Image component. So unless you’ve already brought in the library for some other purposes, I wouldn’t recommend adding this dependency for the single purpose of avatar-formatting.
The other case where I decided to outsource was when displaying multiple images in a slideshow (see gif). For this I used the react-native-slideshow library, which did exactly what I wanted.
The other case where I decided to outsource was when displaying multiple images in a slideshow (see gif). For this I used the react-native-slideshow library, which did exactly what I wanted.
But beware, since it’s poorly maintained I’d strongly recommend forking it and trimming the code a bit rather than use as is from your node_modules.
But beware, since it’s poorly maintained I’d strongly recommend forking it and trimming the code a bit rather than use as is from your node_modules.
Loading placeholdersSo with 7 infinite scroll lists displaying images, the user is bound to do some waiting while all this data is being fetched from the server. As we all know, waiting is probably the single most frustrating experience in modern technology. So naturally we want to make it as endurable as possible.
Loading placeholders So with 7 infinite scroll lists displaying images, the user is bound to do some waiting while all this data is being fetched from the server. As we all know, waiting is probably the single most frustrating experience in modern technology. So naturally we want to make it as endurable as possible.
Enter placeholders.
Enter placeholders.
I’m not really sure why, but any time I wait for some content to load, I get a billion times more frustrated if all I get is a loading spinner (or even worse — nothing at all), than if I see some shiny, dynamic placeholders á Facebook news feed style. So that’s what I was going for.
I’m not really sure why, but any time I wait for some content to load, I get a billion times more frustrated if all I get is a loading spinner (or even worse — nothing at all), than if I see some shiny, dynamic placeholders á Facebook news feed style. So that’s what I was going for.
Thankfully, I wasn’t the first one to have that idea in React Native. It didn’t take much research before I could confidently settle on two libraries: react-native-loading-placeholder (for the actual placeholders) and react-native-linear-gradient (for the shiny animations). I was really happy with the result, even if I may have taken it a bit too far with the one to the right…
Thankfully, I wasn’t the first one to have that idea in React Native. It didn’t take much research before I could confidently settle on two libraries: react-native-loading-placeholder (for the actual placeholders) and react-native-linear-gradient (for the shiny animations). I was really happy with the result, even if I may have taken it a bit too far with the one to the right…
CachingYes, caching is a thing also in the native world. Strangely enough though, there’s still no built-in support for it in the default RN Image tag. Instead, you’ll have use the CachedImage tag from this great lib: react-native-cached-image.
Caching Yes, caching is a thing also in the native world. Strangely enough though, there’s still no built-in support for it in the default RN Image tag. Instead, you’ll have use the CachedImage tag from this great lib: react-native-cached-image .
Basically, all you need to do is install the npm package and exchange all the default Image tags you want to cache with CachedImage. You can then check your Reactotron timeline to confirm that the images are actually being stored.
Basically, all you need to do is install the npm package and exchange all the default Image tags you want to cache with CachedImage. You can then check your Reactotron timeline to confirm that the images are actually being stored.
Compared to the minimal effort required to set up image caching, the payoff is huge. Seeing my Cloudinary bandwidth drop from a hefty 95% of the free monthly quota to about 4% was sooo satisfying.
Compared to the minimal effort required to set up image caching, the payoff is huge. Seeing my Cloudinary bandwidth drop from a hefty 95% of the free monthly quota to about 4% was sooo satisfying.
Pro tip: add the prop activityIndicatorProps={
{ animating: false }} and roll your own loading placeholder rather than the standard loading spinner while loading images.
Pro tip: add the prop activityIndicatorProps={
{ animating: false }} and roll your own loading placeholder rather than the standard loading spinner while loading images.
时间 (Time)
Time pickerReact Native actually has a cross-platform Picker component. However, due to it’s very configurable nature (and my impatience), I looked around for a JS library that had already done some of the work for me. Luckily, I found react-native-picker-select, which emulates the native <select> interfaces for iOS and Android for my almost exactly my purposes.
Time picker React Native actually has a cross-platform Picker component . However, due to it’s very configurable nature (and my impatience), I looked around for a JS library that had already done some of the work for me. Luckily, I found react-native-picker-select , which emulates the native <select> interfaces for iOS and Android for my almost exactly my purposes.
Since it’s basically just a single Javascript file using built-in React Native components (and some lodash, which was already a dependency of mine), I decided to simply steal the code — with some small adjustments— and put it in my own picker component. From then on, I use it not only for my time pickers for all input lists except for the date picker.
Since it’s basically just a single Javascript file using built-in React Native components (and some lodash , which was already a dependency of mine), I decided to simply steal the code — with some small adjustments— and put it in my own picker component. From then on, I use it not only for my time pickers for all input lists except for the date picker.
Date pickerI decided to go with Wix’s react-native-calendars library for a few reasons:
Date picker I decided to go with Wix’s react-native-calendars library for a few reasons:
- I don’t like the native iOS date picker, since it gives a poor overview of month and year. Maybe I’ve just been broken by web development, but that’s my opinion.
I don’t like the native iOS date picker, since it gives a poor overview of month and year. Maybe I’ve just been broken by web development, but that’s my opinion.
- React Native currently requires two separate implementations for the two platforms; DatePickerIOS and DatePickerAndroid, which would have required plenty of code duplication doing the same thing.
React Native currently requires two separate implementations for the two platforms; DatePickerIOS and DatePickerAndroid, which would have required plenty of code duplication doing the same thing.
- I wanted the picker to have more personality and reflect the client company’s brand rather than Apple’s and Google’s.
I wanted the picker to have more personality and reflect the client company’s brand rather than Apple’s and Google’s.
Hate it or love it, this was the result:
Hate it or love it, this was the result:
Time zonesTime zones. So simple in theory, yet so hard in reality.
Time zones Time zones. So simple in theory, yet so hard in reality.
Towards the end of the project, I was integrating the app’s backend with a third-party SaaS that the client use for their room bookings. I’d just had the pleasure of getting intimate with the good old SOAP protocol to set up the necessary API requests for the Conference section of the app. And when I finally had all the pieces in place, I started noticing some strange time behaviors on the React Native side.
Towards the end of the project, I was integrating the app’s backend with a third-party SaaS that the client use for their room bookings. I’d just had the pleasure of getting intimate with the good old SOAP protocol to set up the necessary API requests for the Conference section of the app. And when I finally had all the pieces in place, I started noticing some strange time behaviors on the React Native side.
The client company had made it explicit that they didn’t want users to be able to make new bookings on today’s date after 5pm that day, for reasons. But due to the Javascript Date object’s strict UTC time zone default, generating this max value for the time picker proved to be pretty tricky. In fact, so tricky that the logic bloated my component with too much complexity for my taste. Please let there be a library for this, I thought to myself.
The client company had made it explicit that they didn’t want users to be able to make new bookings on today’s date after 5pm that day, for reasons. But due to the Javascript Date object’s strict UTC time zone default, generating this max value for the time picker proved to be pretty tricky. In fact, so tricky that the logic bloated my component with too much complexity for my taste. Please let there be a library for this, I thought to myself.
My prayers were answered by moment-js, which not only was totally compatible with React Native, but also had a specific time zone module that generated the perfect boolean for me in a single line:
My prayers were answered by moment-js , which not only was totally compatible with React Native, but also had a specific time zone module that generated the perfect boolean for me in a single line:
const timeSthlmAfterFive = moment().isAfter(moment.tz('17:00:00', 'HH:mm:ss', 'Europe/Stockholm'), 'second');
Custom fonts and icons (Custom fonts and icons)
Custom fonts and icons — two tiny details with huge impact on the UI and branding of your app. Coming from a web background, I expected this to be a headache in proportion with the file conversion and font-face css file assembly dance I was used to.
Custom fonts and icons — two tiny details with huge impact on the UI and branding of your app. Coming from a web background, I expected this to be a headache in proportion with the file conversion and font-face css file assembly dance I was used to.
But the work of other people before me made this a lot more painless than I’d expected. Following this tutorial, it took me about 10 minutes to import the client company’s custom fonts. And the vast icon library of react-native-vector-icons, together with some custom imports, has so far provided me with all I need in terms of icons.
But the work of other people before me made this a lot more painless than I’d expected. Following this tutorial , it took me about 10 minutes to import the client company’s custom fonts. And the vast icon library of react-native-vector-icons , together with some custom imports, has so far provided me with all I need in terms of icons.
Continuous integration, deployment and monitoring (Continuous integration, deployment and monitoring)
Moving on to CI/CD — the livelihood of devops people, and the #1 configuration nightmare of all lone-ranging developers looking to make a quick buck.
Moving on to CI/CD — the livelihood of devops people, and the #1 configuration nightmare of all lone-ranging developers looking to make a quick buck.
Since I was (and still am) the only one working on this app, it might seem a bit overkill for some. Since there’s no code collaboration, all new deployments will come from the same computer, and I could just as easily build and test the app locally before pushing to the GitHub repo and submitting a new release to the app stores. However, for a few simple reasons I still considered a CI solution necessary:
Since I was (and still am) the only one working on this app, it might seem a bit overkill for some. Since there’s no code collaboration, all new deployments will come from the same computer, and I could just as easily build and test the app locally before pushing to the GitHub repo and submitting a new release to the app stores. However, for a few simple reasons I still considered a CI solution necessary:
- The client company is about to establish an in-house team of developers. And when they do, they’ll want the infrastructure to make it as easy as possible to add new people to the team.
The client company is about to establish an in-house team of developers. And when they do, they’ll want the infrastructure to make it as easy as possible to add new people to the team.
- Although running your tests locally only takes a single line on the command line, it’s always desirable to automate everything that can be automated.
Although running your tests locally only takes a single line on the command line, it’s always desirable to automate everything that can be automated.
So I was determined to implement a CI solution. But up until this point, I had assumed that this would be isolated to building and testing, and that I would have to find separate solutions for e.g. error reporting, analytics and push notifications. Not to mention continuous deployments, which didn’t even seem to exists in the world of native.
So I was determined to implement a CI solution. But up until this point, I had assumed that this would be isolated to building and testing, and that I would have to find separate solutions for eg error reporting, analytics and push notifications. Not to mention continuous deployments, which didn’t even seem to exists in the world of native.
And then I found Visual Studio App Center. This Chain React 2017 talk by Parashuram N (again) completely blew my mind. What he presented seemed to include all the different devops services I’d considered adding one by one, in one single solution: building, testing, diagnostics (error reporting), analytics, push notifications AND continuous deployment with Codepush. Not to mention distribution to the app stores and beta testers. And best of all, it would enable managing all these things for both my iOS and Android app in the same place. And the best of the best of all, it would likely be free until the app grew bigger, about a year or so later.
And then I found Visual Studio App Center . This Chain React 2017 talk by Parashuram N (again) completely blew my mind. What he presented seemed to include all the different devops services I’d considered adding one by one, in one single solution: building, testing, diagnostics (error reporting), analytics, push notifications AND continuous deployment with Codepush . Not to mention distribution to the app stores and beta testers. And best of all, it would enable managing all these things for both my iOS and Android app in the same place. And the best of the best of all, it would likely be free until the app grew bigger, about a year or so later.
“This is too good to be true”, I thought to myself with teary eyes, panting from excitement. It was just so beautiful. So seamless. So developer-friendly (API-first). And yet with such a user-friendly UI, to the extent that also non-technical employees at my client could make sense of it (some of it).
“This is too good to be true”, I thought to myself with teary eyes, panting from excitement. It was just so beautiful. So seamless. So developer-friendly (API-first). And yet with such a user-friendly UI, to the extent that also non-technical employees at my client could make sense of it (some of it).
How could all this be possible, you ask? Well, turns out Microsoft has been on a shopping spree lately. To assemble the goodie bag that VSAC is, they acquired a bunch of existing independent solutions like Codepush (continuous RN deployments) and HockeyApp (test distribution and crash reporting), as well as built and extended existing Microsoft products. The famous “developers, developers, developers, developers” ethos signed Steve Balmer really seems to run truer than ever in the company’s bloodstream.
How could all this be possible, you ask? Well, turns out Microsoft has been on a shopping spree lately. To assemble the goodie bag that VSAC is, they acquired a bunch of existing independent solutions like Codepush (continuous RN deployments) and HockeyApp (test distribution and crash reporting), as well as built and extended existing Microsoft products. The famous “developers, developers, developers, developers” ethos signed Steve Balmer really seems to run truer than ever in the company’s bloodstream.
So, had I heard enough to make an informed bet on this fairly new technology, discarding competing services like Fastlane, BuddyBuild and Firebase? Well, if it was truly as good as Parashuram claimed, it would save me weeks of installations and configurations, and probably countless hours of future maintenance of all the scattered services required to put together a similar result. Either way, it was definitely worth a shot.
So, had I heard enough to make an informed bet on this fairly new technology, discarding competing services like Fastlane , BuddyBuild and Firebase ? Well, if it was truly as good as Parashuram claimed, it would save me weeks of installations and configurations, and probably countless hours of future maintenance of all the scattered services required to put together a similar result. Either way, it was definitely worth a shot.
And about a week later the app was all set up with all of the VSAC features. Except for a few child diseases, the docs together with the support chat supplied me with all the answers I needed.
And about a week later the app was all set up with all of the VSAC features. Except for a few child diseases, the docs together with the support chat supplied me with all the answers I needed.
One such problem was the fact that they did not yet support integrating with Apple Developer accounts using two-factor authentication (which Apple started enforcing just in time for me to get my account set up…). This was incredibly frustrating to me at the time, but just a few weeks after I’d reported it, they added official support for it.
One such problem was the fact that they did not yet support integrating with Apple Developer accounts using two-factor authentication (which Apple started enforcing just in time for me to get my account set up…). This was incredibly frustrating to me at the time, but just a few weeks after I’d reported it, they added official support for it.
If you find my praise of VSAC a bit one-sided, and would like to hear the opinion of someone with a big app perspective, I’d recommend this more skeptical CI/CD review.
If you find my praise of VSAC a bit one-sided, and would like to hear the opinion of someone with a big app perspective, I’d recommend this more skeptical CI/CD review .
Adding support for Android (Adding support for Android)
With all the iOS groundwork in place, I found adding support for Android very painless. After setting up the Android Studio dev environment and getting the app running on an Android emulator, most of the issues could be solved with the React Native Platform module. For platform-specific styling, it offers the Platform.select() method. And for all other platform-specific code Platform.OS will do the trick.
With all the iOS groundwork in place, I found adding support for Android very painless. After setting up the Android Studio dev environment and getting the app running on an Android emulator, most of the issues could be solved with the React Native Platform module . For platform-specific styling, it offers the Platform.select() method. And for all other platform-specific code Platform.OS will do the trick.
Also, getting the app submitted and approved on the Google Play Store was SO much easier than for App Store. Why?
Also, getting the app submitted and approved on the Google Play Store was SO much easier than for App Store. 为什么?
Because Apple (Because Apple)
Evidently, approaching React Native as a web developer is bound to cause a few headaches. But for me, the worst headache by far was the development process imposed by Apple. I honestly can’t recall ever having experienced that many bottlenecks and schedule postponements caused by one single source. Technical project or otherwise.
Evidently, approaching React Native as a web developer is bound to cause a few headaches. But for me, the worst headache by far was the development process imposed by Apple. I honestly can’t recall ever having experienced that many bottlenecks and schedule postponements caused by one single source. Technical project or otherwise.
Mainly, I’d say the inexplicably high degree of bureaucracy is to blame. If there’s one thing you can count on when developing your first iOS app, it’s that you’ll make a ton of new friends along the way.
Mainly, I’d say the inexplicably high degree of bureaucracy is to blame. If there’s one thing you can count on when developing your first iOS app, it’s that you’ll make a ton of new friends along the way.
The guys and gals at the Apple Support, for instance. You might have to convince them that the company behind your app actually exists. You might also make a few friends at Dun & Bradstreet, their company identification partner. And while you’re at it, you might even make some friends over at your local governmental company registry, in order to update your company address to a format that Apple supports (they don’t support companies registered on a post office box, which is a very common practice at least here in Scandinavia).
The guys and gals at the Apple Support, for instance. You might have to convince them that the company behind your app actually exists. You might also make a few friends at Dun & Bradstreet, their company identification partner. And while you’re at it, you might even make some friends over at your local governmental company registry, in order to update your company address to a format that Apple supports (they don’t support companies registered on a post office box, which is a very common practice at least here in Scandinavia).
And then you might get to spend even more time with the Apple Support people, since they still won’t be able to approve your Apple Developer registration seeing as you’re just a consultant, and not an actual employee of your client company. The whole process might take more than a month. But who cares when you’re making new friends, right?
And then you might get to spend even more time with the Apple Support people, since they still won’t be able to approve your Apple Developer registration seeing as you’re just a consultant, and not an actual employee of your client company. The whole process might take more than a month. But who cares when you’re making new friends, right?
And the fun doesn’t end there.
And the fun doesn’t end there.
You’ve got your Apple Developer company account all set up. You’ve finished the 1.0 version of your app. Now you’re dying to get it out into the wild.
You’ve got your Apple Developer company account all set up. You’ve finished the 1.0 version of your app. Now you’re dying to get it out into the wild.
Well, you can’t just yet. First, you‘re up for some digital paperwork. You’ll have to generate a provisioning profile, an iOS certificate, an identifier, a .p12 Apple push notification certificate and the good old dSym file. And once you’ve generated, configured and uploaded all those files to the right places, you can start the actual app review process.
Well, you can’t just yet. First, you’re up for some digital paperwork. You’ll have to generate a provisioning profile, an iOS certificate, an identifier, a .p12 Apple push notification certificate and the good old dSym file. And once you’ve generated, configured and uploaded all those files to the right places, you can start the actual app review process.
According to themselves, 50% of all apps are approved/rejected within 24 hours, and 90% within 48 hours. But prepare for the worst, since rejection is apparently just a normal part of life as an Apple Developer.
According to themselves, 50% of all apps are approved/rejected within 24 hours, and 90% within 48 hours. But prepare for the worst, since rejection is apparently just a normal part of life as an Apple Developer.
Luckily, my app only got rejected one time. It was due to “Metadata Rejected” reasons. And I would totally have been okay with that if I’d simply forgotten to fill in some required information. But seeing as the missing meta data apparently was 5 very specific questions (non of them included in the App Store Review Guidelines), it honestly just made me sad.
Luckily, my app only got rejected one time. It was due to “Metadata Rejected” reasons. And I would totally have been okay with that if I’d simply forgotten to fill in some required information. But seeing as the missing meta data apparently was 5 very specific questions (non of them included in the App Store Review Guidelines ), it honestly just made me sad.
Sad to live in a world where just two companies are gatekeeping the entire native mobile app distribution pipeline. Sad that at least one of them cares so little about the customer that they allow themselves to arbitrarily take up other people’s time, causing months of expensive app launch delays. And so so happy that the same does not apply for the web (yet).
Sad to live in a world where just two companies are gatekeeping the entire native mobile app distribution pipeline. Sad that at least one of them cares so little about the customer that they allow themselves to arbitrarily take up other people’s time, causing months of expensive app launch delays. And so so happy that the same does not apply for the web (yet).
Evidently, developing my first React Native app for iOS included layers upon layers of bureaucratic screening processes. If there’s an equivalent of a dementor in the world of mobile development, this is most definitely it. It will literally suck the soul along with any eventual developer happiness right out of your body.
Evidently, developing my first React Native app for iOS included layers upon layers of bureaucratic screening processes. If there’s an equivalent of a dementor in the world of mobile development, this is most definitely it. It will literally suck the soul along with any eventual developer happiness right out of your body.
There. Rant over. That felt good.
那里。 乱跑。 感觉很好。
摘要 (Summary)
As noted earlier, this project was started in the early summer. As such, the slower summer work pace at my job made it possible to juggle the two for a few months. But eventually the Oct/Nov deadline would just be coming at me way too fast, and I would realize I’d have to make a choice whether to stay in the job or finish the app on time. After a few weeks of contemplation, I chose the latter.
As noted earlier, this project was started in the early summer. As such, the slower summer work pace at my job made it possible to juggle the two for a few months. But eventually the Oct/Nov deadline would just be coming at me way too fast, and I would realize I’d have to make a choice whether to stay in the job or finish the app on time. After a few weeks of contemplation, I chose the latter.
Although it was a really tough decision, in hindsight I think it was the right one. The freedom, challenge and self-development I was looking for were most definitely found, and then some.
Although it was a really tough decision, in hindsight I think it was the right one. The freedom, challenge and self-development I was looking for were most definitely found, and then some.
Regarding the freedom, the loose leash of the client has basically allowed me to work from anywhere I want, anytime I want. Which has had a positive impact on many aspects of my life. It’s enabled me to get my 8 hours of sleep more or less every night. It’s enabled me to find a more consistent workout routine. It’s enabled me to find more time for the people I care about. And working while traveling.
Regarding the freedom, the loose leash of the client has basically allowed me to work from anywhere I want, anytime I want. Which has had a positive impact on many aspects of my life. It’s enabled me to get my 8 hours of sleep more or less every night. It’s enabled me to find a more consistent workout routine. It’s enabled me to find more time for the people I care about. And working while traveling.
On the negative side though, this freedom has also often made the process quite lonely. Even when working among people in cafés and coworking spaces, the absence of actual teammates to share the ups and downs with has been crystal clear.
On the negative side though, this freedom has also often made the process quite lonely. Even when working among people in cafés and coworking spaces, the absence of actual teammates to share the ups and downs with has been crystal clear.
In terms of challenge and self-development, I find the project taught me in just 6–7 months what probably would have taken me at least a few years in any normal job. It’s basically made me a better developer in every area, including:
In terms of challenge and self-development, I find the project taught me in just 6–7 months what probably would have taken me at least a few years in any normal job. It’s basically made me a better developer in every area, including:
- That it’s opened up a door to not only one, but two new digital platforms (iOS and Android), translating to a 3x increase in the platforms of my developer tool belt.
That it’s opened up a door to not only one, but two new digital platforms (iOS and Android), translating to a 3x increase in the platforms of my developer tool belt.
- A deepened Javascript knowledge. Many concepts I took for granted in the convention-driven confines of Ember.js needed to be retaught.
A deepened Javascript knowledge. Many concepts I took for granted in the convention-driven confines of Ember.js needed to be retaught.
- With little experience in plain React.js prior to this, I now feel that I have most of the puzzle pieces in place for building bigger React apps targeting the DOM. Which would mean a 2x increase in the JS frameworks/libraries of my developer tool belt.
With little experience in plain React.js prior to this, I now feel that I have most of the puzzle pieces in place for building bigger React apps targeting the DOM. Which would mean a 2x increase in the JS frameworks/libraries of my developer tool belt.
- An introduction to functional programming and the immutability philosophy associated to state management with Redux.
An introduction to functional programming and the immutability philosophy associated to state management with Redux.
- Massive devops and project management insights.
Massive devops and project management insights.
- Better repo investigation skills when trying to work out technologies with poor documentation.
Better repo investigation skills when trying to work out technologies with poor documentation.
- Better UI/UX design skills.
Better UI/UX design skills.
- Best of all, a stronger confidence that I can independently teach myself any technology I want, and find ways around any obstacles that may appear in that pursuit.
Best of all, a stronger confidence that I can independently teach myself any technology I want, and find ways around any obstacles that may appear in that pursuit.
Since I don’t have any experience neither from any other mobile native JS tools like Flutter or NativeScript, nor from Objective-C, Swift, Java or Kotlin, I will not attempt to make any claim whether React Native is better or worse than the competition.
Since I don’t have any experience neither from any other mobile native JS tools like Flutter or NativeScript , nor from Objective-C, Swift, Java or Kotlin, I will not attempt to make any claim whether React Native is better or worse than the competition.
But what I can say is that as a web developer, React Native made the transition to mobile a very stimulating, developing and an overall smooth experience. Admittedly, the technology is young and far from perfect. But I for one would not hesitate to use it again.
But what I can say is that as a web developer, React Native made the transition to mobile a very stimulating, developing and an overall smooth experience. Admittedly, the technology is young and far from perfect. But I for one would not hesitate to use it again.
Thanks for reading! As always, if you have any questions or feedback, you’re welcome to reach out to me in the comments, at charlie.jeppsson1[at]gmail[dot] com or on LinkedIn. And if you’re an experienced Rails/React developer and interested in working in a Stockholm-based coworking startup, Convendum is hiring!
谢谢阅读! As always, if you have any questions or feedback, you’re welcome to reach out to me in the comments, at charlie.jeppsson1[at]gmail[dot] com or on LinkedIn . And if you’re an experienced Rails/React developer and interested in working in a Stockholm-based coworking startup, Convendum is hiring !
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159167.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...