对小孩子反应本地。移动开发经验

曾经,在一个产品团队中,他们想要开发一种移动应用程序,以测试对产品需求及其对用户的便利性的假设。团队没有移动开发人员这一事实并没有受到伤害。两名前端开发人员使用React Native并在三个月内编写了一个应用程序。假设测试成功,应用程序不断发展,并引起了公司其他团队的兴趣。


图片来自工具说明:www.semrush.com/news/position-tracking-on-the-go

这是对Artyom Lashevsky团队历史的简短描述,距离前端开发人员三个月的时间已经变成了移动设备。有关如何发生的更多详细信息,请阅读Artyom在FrontendConf 2019上的报告的解码:什么是React Native,为什么要这么做,这是创建应用程序以及选择正确的库和组件的分步说明。

Artyom LashevskySEMrush的领先前端开发人员,也是自动化系统信息安全领域的专家。


为什么我们需要申请


SEMrush是一家国际IT公司,为营销人员开发大型在线平台。包括在用于营销和SEO的TOP 3平台中。该公司拥有500万用户,在两个大洲设有7个办事处,拥有800名员工和30个开发团队。

每个开发团队都在平台内处理自己的工具。我是由8人组成的团队之一:前端两个,后端三个,QA,DevOps工程师和PO。我们小组正在使用的工具允许用户检查其站点页面上特定Google搜索的位置。该工具不仅可以帮助您检查站点关键字,还可以检查竞争对手的关键字,并有助于分析和比较。

该平台可在浏览器中使用。运行移动应用程序将使您获得竞争优势,因为该应用程序有助于保留旧用户的时间更长,并增加了从免费到付费的转换。

对于用户而言,该应用程序改善了平台的可用性。通过电话,客户可以在以前需要计算机的地方使用SEMrush。进入该应用程序,看看他的网站是否属于Google的SERP或上升了- 关键指标始终在手边。如果他们蘸了水,那么在网络版本中,您可以看到详细信息并了解如何解决这种情况。

我们的团队看到了移动应用程序的潜力,并决定尝试开发它,但是我们中间没有移动开发人员。我们开始自己研究用于应用程序开发的工具和技术。

本机应用-您注意到的第一件事。经过研究,我们意识到对我们而言这是一个漫长,昂贵且不便的事情:您需要研究两个技术堆栈,重复的功能并对其进行两次测试。



我们从本机开发转向WebView(Apache Cordova)的研究。一位前端同事具有在Cordova上进行开发的经验,因此在研究技术和解决方案的第一个冲刺中,其中一个原型是在Cordova上开发的。



但是我们想要更现代的东西-“时尚青年”,因此我们转向跨平台解决方案。

跨平台开发。考虑了几个选项:Flutter,Xamarin,Native Script和React Native。对于我们来说,每个选项都有其自己的缺点,因为我们希望尽快开始:

  • Flutter需要Dart知识(JavaScript的替代方法)。没时间研究相关技术-我想快速“飞起来”以检验假设。
  • Xamarin需要C#的知识。我们有后端开发人员,但iOS和Android需要不同的界面。无论如何,您必须复制代码和实现。
  • 本机脚本旨在与Angular捆绑在一起。我们的项目使用ReactJS和Redux,所以没有。
  • React Native看起来很酷,他们选择了他。此外,尽管有其他解决方案的弊端和优势,我们只是想尝试一下。

在选择过程中,我们没有遇到离子。也许他们也会考虑,但是他们仍然会选择React Native。

反应本机


这是用于开发跨平台本机应用程序的Facebook框架。基于ReactJS构建的,在后台不使用WebView,因此没有DOM API。React Native没有HTML和CSS,但是JSX和类似CSS的多文件中有一些平台组件。

React Native在本地组件上包含一些JavaScript API。这意味着本机组件对ReactJS上的JavaScript组件具有某种“绑定”。本机包和JavaScript包之间的关系是通过使用JavaScript API的桥完成的。从表面上看,这就是所有架构。



React Native Pros:

  • 跨平台我们最初希望在iOS下编写该应用程序,但在Android下额外启动将是一个巨大的优势。
  • . , . React Native , 80% .
  • . Telegram React Native 3000 . , , , .
  • . — , . , ReactJS React Native.


使用React Native,有两种方法来开发应用程序:使用Expo或React Native CLI。

世博会这是一个抽象层-一组用于快速启动的工具,库和服务。这是一些开箱即用的API,可用于访问设备的功能:摄像头,地理位置,推送消息。

博览会拥有应用程序调试和测试工具。该算法很简单:在电话上安装Expo Client应用程序,将电话与计算机连接到相同的本地网络,在电话上转到Expo,打开正在开发的应用程序。代码中的所有更改都会立即显示。这有助于测试并与团队共享。

在世博会上起飞迅速而迅速。借助Expo,不需要Xcode和Android Studio。我们在开发环境中编写代码,使用Expo服务收集应用程序,实时进行签名和更新。

但是也有缺点。

  • 您无法添加自定义本机模块。
  • Expo是React Native之上的抽象层。在Expo进行更新之前,您无法升级到该框架的新版本。

反应本机CLI它的优点是可以进行定制,添加本机模块。

但是要签名和构建应用程序(并发布),您需要Xcode或Android Studio。要测试该应用程序,您需要将其生成并下载到手机中,例如,使用iOS的TestFlight或直接从PC安装到手机。标准方案,但不方便且时间长。



通常,选择很简单:我们想开发一个原型-我们选择Expo,我们希望有前景的应用程序-React Native CLI。
我们选择了React Native CLI。在SEMrush中,我们可能要编写自定义的本机部件,因此本机模块和带模块的第三方库的重要性成为决定性因素。使用React Native CLI,由于需要大量的培训,因此启动速度较慢,但​​是将来会有更多的机会。

使用React Native的许多同事都面临着自定义的需求,他们有专门的团队在React Native CLI上通过Expo进行Eject。简单应用程序文件的目录结构从通常的JavaScript文件集转移到Android,iOS和JavaScript的文件夹。但是,对于复杂的应用程序,将有困难。

我们正在编写一个应用程序


在没有通常的阵营本地CSS,DOM和网页元素:divspanlibutton但是React Native中有两种类型的平台组件。

  • 跨平台ViewTextImage等。
  • 例如,特定于每个平台DatePickerIOSDatePickerAndroid

从反应阵营本土的过渡是简单的:它阻止divViewspanText,并将其列liul- FlatList每个元素都有对应关系。

注意。要将现成的Web组件传输到非Web组件,可以编写一个传输器,但是通常更容易再次重写它。

您可以很快开始编写React Native。

import React, {Component} from 'react';
import {Text, View, StyleSheet} from 'react-native';

export default class App extends Component {
    render() {
        return (
            <View style={styles.container}>
                <Image style={styles.image} source={require('./img/main.png')}/>
                <Text style={styles.welcome}>Hello, SEMrush!</Text>
            </View>
        );
    }
}

有一些类App(应用程序容器)。它包含一种render绘制可视部分的方法它包含容器View内肚里ImageText

例如,每个元素都View包含一个属性该属性style的值是通过object指定的styles使用React Native 方法的模块创建

一个对象该对象是CSS对象表示法,足够了。Flexbox还用于简化界面的布局,但是足以将其水平和垂直对齐。stylesStyleSheetcreate

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
    welcome: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
    },
    image: {
        marginBottom: 20,
    },
});

完成-我们为iOS编写了一个应用程序。展望未来,我将说明它也适用于Android。


第一个应用程序是“ Hello,World!”。

跨平台


我们希望我们的应用程序可同时在Android和iOS上运行。同时,我们希望能够根据平台进行各种实现。在React Native中有两种方法可以做到这一点。

第一种方法是使用平台模块。例如,我们创建一个应用程序并根据平台设置高度。对于iOS,高度为200;对于Android,高度为100。使用该方法,我们Platform.OS可以确定用户使用的平台。

import {Platform, StyleSheet} from 'react-native';

const styles = StyleSheet.create({
    height: Platform.OS === 'ios' & 200 : 100,
});

如果我们有40个这样的属性,则无需重复代码。Platform.select同一模块中提供了一种方法它通过平台键返回一组属性:在iOS返回backgroundColor = 'red'下,在Android-下backgroundColor = 'blue'结果,我们可以返回iOS的属性集(至少有20个,其中至少有40个),对于Android,这是完全不同的一组。

import {Platform, StyleSheet} from 'react-native';

const styles = StyleSheet.create({
    container: {
        flex: 1,
        ...Platform.select({
            ios: {
                backgroundColor: 'red',
            },
            android: {
                backgroundColor: 'blue',
            },
        }),
    },
});

该方法Platform.select允许您编写跨平台组件。

const Component = Platform.select({
    ios: () => require('ComponentIOS'),
    android: () => require('ComponentAndroid'),
})();

<Component />;

某些组件使用的方法Platform.select根据平台密钥返回其实现。一切都很好,我们采用并编写了一个跨平台的库。

如果操作系统上有任何自定义解决方案或限制,我们可以确定操作系统版本。

import {Platform} from 'react-native';

if (Platform.Version ===25) {
    console.log('Running on Nougat!');
}

import {Platform} from 'react-native';

const majorVersionIOS = parceInt(platform.Version, 10);
if (majorVersionIOS <= 9) {
    console.log('Work around a change in behavior');
}

第二种方法-使用特定于平台的文件扩展名*.android.js*.ios.js)。这使您可以开发跨平台的实现,以获取更大的代码和复杂的逻辑。然后,将所有代码分为两个文件,例如BigButton.ios.jsBigButton.android.js在我们要使用它的代码中,我们使用接口并将其导入BigButtonReact Native将根据平台处理必要的导入。

组件


我们可以实现跨平台的解决方案,但是速度很重要。因此,我们转向现成的解决方案-组件库。经过研究,重点介绍了许多出色的解决方案(UI工具包),并选择了NativeBase这是一个庞大的库,包含十二个组件,在GitHub上有12,000个星,可让您解决大多数问题。

   
左侧的iOS用户界面示例,右侧的Android用户界面示例。

在我们的项目中,我们部分使用此库,自定义元素并设置我们自己的样式。例如,我们具有ButtonHeader要从NativeBase库导入的组件。



我们只编写一次代码,而在每个平台上看起来都不同。

import React, { Component } from 'react';
import { Container, Header, Content, Button, Text } from 'native-base';
export default class ButtonThemeExample extends Component {
    render() {
        return (
            <Container>
                <Header />
                <Content>
                    <Button light><Text> Light </Text></Button>
                    <Button primary><Text> Primary </Text></Button>
                    <Button success><Text> Success </Text></Button>
                    <Button info><Text> Info </Text></Button>
                    <Button warning><Text> Warning </Text></Button>
                    <Button danger><Text> Danger </Text></Button>
                    <Button dark><Text> Dark </Text></Button>
                </Content>
            </Container>
        );
    }
}

导航


对于导航,我们还比较了几种可能的解决方案。

  • react-navigation Facebook React Native.
  • wix/react-native-navigation — Wix. , , -, — .
  • react-router — react-router ReactJS . , .
  • airbnb/native-navigation — - Airbnb. , .

我们选择了第一个解决方案-react-navigation它完全是JavaScript,因此我们不必担心本机文件。但另一方面,性能是有问题的。

我们与React Native社区的同事进行了讨论,研究了这些文章,并意识到react-navigation和wix / react-native-navigation的性能大致相同。因此,他们选择了第一个解决方案,并且使用经验仅证实了选择的正确性。

   

反应导航的优点:

  • 灵活的跨平台解决方案。
  • 每个平台的本机UI以及屏幕之间的本机转换。我们不想在Android上创建类似于iOS的应用程序。
  • 它提供了所有基本的过渡类型:下部选项卡,在屏幕之间滑动,模式窗口SwitchNavigator

使用反应导航的示例应用程序结构。该代码从下至上更方便阅读。

const HomeStack = createStackNavigator({
    Home: { screen: HomeScreen },
    Details: {screen: DetailsScreen },
});

const SettingsStack = createStackNavigator({
    Settings: { screen: SettingsScreen },
    Details: { screen: DetailsScreen },
});

const TabNavigator = createBottomTabNavigator({
    Home: { screen: HomeStack },
    Settings: { screen: SettingsStack },
});

export default createAppContainer(TabNavigator);

有些createAppContainer包含createBottomTabNavigator两个堆栈。每个堆栈都有自己的一组屏幕。


我们有两个按钮- HomeSettings这是两个堆栈- HomeStackSettingsStack每叠里面有两个屏幕:HomeScreenDetailsScreenSettingsScreen和,DetailsScreen分别。两个堆栈都使用一个公共组件DetailsScreen我们编写一个组件(屏幕)并在不同的堆栈上重用它。

这三个屏幕的代码看起来尽可能简单。

class HomeScreen extends React.Component {
    static navigationOptions = { title: 'Home' };

    render() {
        return (
            <View style={styles.container}>
                <Text>Home!</Text>
                <Button title="Go to Details" onPress={() => this.props.navigate('Details')}/>
            </View>
        );
    }
}

class SettingsScreen extends React.Component {
    static navigationOptions = { title: 'Settings' };

    render() {
        return (
            <View style={styles.container}>
                <Text>Settings!</Text>
                <Button title="Go to Details" onPress={() => this.props.navigate('Details')}/>
            </View>
        );
    }
}

class DetailsScreen extends React.Component {
    static navigationOptions = { title: 'Details' };

    render() {
        return (
            <View style={styles.container}>
                <Text>Details!</Text>
                <Button title="Go to Details" onPress={() => this.props.navigate('Details')}/>
            </View>
        );
    }
}

这就是我们开始时所说的“ Hello,World!”,仅此而已。

添加Redux我们将根导航器包装在其中Provider,然后滚动store接下来,写reduceractionsactionTypes其余的-一切都是熟悉的。

const Navigation = createAppContainer(TabNavigator);

//Render the app container component with the provider around it
export default class App extends React.Component {
    render() {
        return (
            <Provider store={store}>
                <Navigation />
            </Provider>
        );
    }
}

需要更多组件


在选择最终的UI套件时,我们考虑了许多可能的解决方案:React Native Elements,UI小猫,React Native Material UI,React Native Material Kit,React Native UI库,React Native Paper,Shoutem UI Toolkit,Nachos UI,Teaset。因此,我们使用了这些库的某些组件。



React Native还具有面向平台的组件。一个明显的例子是DatePickerAndroidDatePickerIOS。它们在外观和界面上都不同,因此您必须复制代码以解决两个平台的一个问题。

  
左边是DatePickerAndroid,右边是DatePickerIOS。

例如,在我们的应用程序中,您可以请求一定时间的报告或图表。用户提供两个日期来选择一个期间。但是在iOS上,本地日期选择不方便,特别是与我们的工具的网络版本相比。



我们将必须做两个字段:开始日期和结束日期。与Android一样,在一个字段中选择日期更方便,但是我们无法设置所有内容,因为自定义设置很少

我们找到了一个解决方案-wix / react-native-calendars
     
右边是我们应用中带有公司颜色的日历的示例。

它具有水平和垂直日历,可以按日期进行详细显示,可以显示公司的样式,可以选择日期范围,可以阻止某些日期的选择并支持Android和iOS。该解决方案具有一个界面非常方便-我们只需编写一次代码,而无需重复和重写。

存储


在某些时候,我们需要存储数据以便离线显示或恢复工作时显示。为此,请使用异步存储库(键值存储)。它与键和多键(写入,读取和删除)一起使用,并提供了一个JavaScript API与本地零件进行交互。

IOS数据以序列化形式存储。在Android下-例如,在SQLite中。作为开发人员,我们不必担心:有一个API,我们存储数据,一切都很好。

动画制作


React Native不使用基于HTML的WebView技术,因此我们没有动画。在这里,动画组件可以帮助我们它支持一些包装基本元素:ViewImageTextScrollView它在一个单独的线程中运行,并且不会降低应用程序的速度,但是有一个缺点-“非人类”类型的代码。

下面是一个带有用户项目列表的应用程序屏幕示例。


上面Header的名称为“ Projects”,SearchBar用于过滤项目。看起来好像SearchBar在下方Header,但实际上,它位于下面带有项目列表的常规块中。多亏了Animated组件,这种动画才得以SearchBar“消失”。

实施的简化版本。

export default class ProjectsScreen extends Component {

    state = {
        scrollY: new Animated.Value(
            Platform.OS === 'ios' ? -HEADER_MAX_HEIGHT : 0,
        ),
    };

    render() {
        const scrollY = Animated.add(
            this.state.scrollY,
            Platform.OS === 'ios' & HEADER_MAX_HEIGHT : 0,
        );
        const headerTranslate = scrollY.interpolate({
            inputRange: [0, HEADER_SCROLL_DISTANCE],
            outputRange: [0, -HEADER_SCROLL_DISTANCE],
            extrapolate: 'clamp',
        });

        return (
            <View>
                <Animated.View style={{ translform: [{ translatY: headerTranslate }] }}>
                    <SearchBar/>
                </Animated.View>
                <Animated.ScrollView
                    onScroll={Animated.event(
                        [{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
                        { useNativeDriver: true },
                    )} contentInset={{
                        top: HEADER_MAX_HEIGHT,
                    }}
                    contentOffset={{
                        y: -HEADER_MAX_HEIGHT,
                    }}
                >
                    {this._renderScrollViewContent()}
                </Animated.ScrollView>
            </View>
        );
    }
}    

它看起来很简单,但您必须“抓紧时间”:在iOS上将所有内容上移或下移。在Android上,这不是必需的。动画模块还提供插值和不同的方法。

第一次启动时屏幕“闪烁”的问题


我们编写了该应用程序,并决定添加应用程序图标和启动屏幕(或启动屏幕)-首次启动时从内存中卸载时出现的应用程序屏幕。我们有一个橙色的屏幕,中间有SEMrush徽标。



在React Native架构中,本机部分与一些JavaScript桥接包交互。我们遇到了一个问题,在启动时,本机部分正在运行,并且脚本捆绑包尚未加载。在橙色的“启动画面”和项目列表的外观之间,屏幕上闪闪发光的白色空隙(毫秒级)。

使用react-native-splash-screen库解决了闪烁问题它提供了JavaScript API,以编程方式显示或隐藏应用程序启动屏幕。通过此API,我们使用方法中的项目列表删除了组件中的本机启动画面componentDidMount()他们在两个屏幕之间放置了一个微调框,作为使它们看起来更漂亮的奖励。

失去连接


我们希望开发一个友好而美观的界面,并注意UX和UI。但是,当用户乘坐地铁,失去联系并更新项目列表时,该应用程序仅显示空白屏幕。用户最好查看已加载的信息以及有关Internet连接问题的消息。Netinfo

库帮助进行了脱机工作现在它在一个单独的程序包中,因为Facebook卸载了React Native以减轻它的负担。



Netinfo允许您:

  • 订阅事件,并在更改Internet连接时向用户显示消息;
  • 检查连接质量;
  • 检查复杂应用的连接类型(LTE,WiFi,3G);
  • 使用请求检查您的互联网连接。

应用调试


为了在添加新的依赖项(尤其是那些使用本机代码的依赖项)后不出现错误,您需要调试器。在React Native中-显示在iPhone模拟器的图像中。


对于iOS,我们使用了Xcode中的仿真器,您可以在其中选择操作系统和设备的版本。 Android模拟器-Android Studio。

仿真器的对话框菜单具有“远程调试JS”和“实时重新加载”模式。允许您查看组件的属性,例如类似CSS的样式和视图嵌套。

在本机开发中,您需要编写代码,将其发送到程序集,然后运行-这很长一段时间。在调试器中,所有操作都与Web上的ReactJS中的操作相同:我们打开实时重新加载功能,无需重新启动即可在模拟器中实时重建。

此外,我们还使用React Developer Tools来检查组件,查看层次结构并更改其属性和样式。



但这还不够,还有一个更好的解决方案-React Native Debugger



它具有一个React Inspector,Redux DevTools,Internet交互,控制台日志记录,内存和应用程序性能分析,以及查看所有操作。与前两个解决方案不同,您可以在React Native Debugger本身中更改样式。

连接本机模块


到现在为止,一切都变得简单而透明:我们采用了某种解决方案,将其连接起来就可以了。但是有细微差别-React Native架构的结果。除了JavaScript部分,所有库都使用本机库,该库必须与React Native关联。为此,有一个react-native链接命令可以自动进行所有必要的更改,并链接到项目中的本机代码。

但是自动连接并不总是有效,因此有时您必须手动修改本机文件。如果该库的说明中没有描述手动安装,请考虑是否值得使用它,并且如果可能的话,请避免手动链接-我们踩了几次这样的耙子。

结果


最初,我们想为iOS编写一个应用程序,但是我们为Android编写了一个应用程序,它们的外观有所不同,每个人都应该在其平台上。屏幕之间的过渡看起来很酷,我们大约有30个屏幕(过渡和状态)。我们设法避免实施自定义本机模块,而使用了第三方解决方案。

时间:应用程序的开发花了两个人三个月的时间。自2019年4月以来,他们已经发布了一些具有新功能的更新,该应用程序正在开发中,其他项目团队希望在其中实现其工具。React Native

的性能对我们来说是完全令人满意的,因为我们不需要复杂的逻辑并且负担不大。

反馈:很高兴收到该应用程序的用户反馈。即使您不是移动开发人员,

React Native也适用于快速开发移动应用程序和测试假设。它在不断发展,出现了新的功能和库。例如,一年前不支持32位和64位体系结构-现在没有问题。

, , -, . , , « », . ++ 2020 — .

, , , , , — - , 5900.

All Articles