Статьи

Как создать программу чтения новостей с React Native: компонент веб-страницы

В первой части этой серии вы узнали, как настроить React Native на своем компьютере, создавать и использовать пользовательские компоненты, а также использовать сторонние библиотеки, такие как moment.js . В этом руководстве вы узнаете, как выполнять сетевые запросы с использованием fetch , отображать веб-страницу с помощью встроенного компонента WebView и запускать приложение на физическом устройстве.

В первой части этой серии мы использовали функцию api , но еще не определили ее. Начните с создания каталога src и добавьте в него файл api.js. Откройте файл и добавьте к нему следующее:

1
2
3
4
5
6
7
8
module.exports = function(url){
     
    return fetch(url).then(function(response){
        return response.json();
    }).then(function(json){
        return json;
    });
}

Этот файл использует функцию fetch , которая по умолчанию доступна в React Native. Эта функция позволяет приложению выполнять сетевые запросы. Если вы использовали jQuery , он очень похож на функцию $.ajax . Вы указываете URL и некоторые дополнительные данные, и вы получаете ответ обратно.

Разница лишь в том, что вам нужно проделать дополнительную работу. Функция для захвата первого обещания возвращает необработанный ответ, что означает, что вам нужно вызвать метод json для response чтобы получить обещание, которое возвращает строку JSON. Таким образом, вы должны вернуть результат и захватить обещание, снова вызвав функцию then и передать функцию, которая будет вызвана после разрешения обещания.

Затем строка JSON будет передана в качестве аргумента этой функции, поэтому мы просто ее возвращаем. Метод fetch возвращает обещание, поэтому, когда мы вызываем метод api , нам все равно приходится вызывать метод then чтобы получить реальный ответ, как мы это делали в первой части этой серии.

1
2
3
4
5
api(story_url).then(
    (story) => {
       …
    }
);

Компонент WebPage отвечает за отображение веб-страницы. Для этого он использует компонент WebView .

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
var React = require(‘react-native’);
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  WebView
} = React;
 
var Button = require(‘react-native-button’);
var GiftedSpinner = require(‘react-native-gifted-spinner’);
 
var _ = require(‘lodash’);
 
var WebPage = React.createClass({
    getInitialState: function() {
        return {
            isLoading: true
        };
    },
 
    render: function(){
             
        return (<View style={styles.container}>
         
            <View style={styles.webview_header}>
              <View style={styles.header_item}>
                <Button style={styles.button} onPress={this.back}>Back</Button>
              </View>
              <View style={styles.header_item}>
                <Text style={styles.page_title}>{this.truncate(this.state.pageTitle)}</Text>
              </View>
              <View style={[styles.header_item, styles.spinner]}>
                { this.state.isLoading && <GiftedSpinner /> }
              </View>
            </View>
 
            <View style={styles.webview_body}>
                <WebView
                    url={this.props.url}
                    onNavigationStateChange={this.onNavigationStateChange}
                     
                />
            </View>
        </View>);
 
    },
 
    truncate: function(str){
        return _.truncate(str, 20);
    },
 
    onNavigationStateChange: function(navState) {
         
        if(!navState.loading){
            this.setState({
                isLoading: false,
                pageTitle: navState.title
            });
        }
    },
     
    back: function(){
       this.props.navigator.pop();
    }
});
 
 
var styles = StyleSheet.create({
    container: {
        flex: 1
    },
    webview_header: {
        paddingLeft: 10,
        backgroundColor: ‘#FF6600’,
        flex: 1,
        justifyContent: ‘space-between’,
        flexDirection: ‘row’
    },
    header_item: {
        paddingLeft: 10,
        paddingRight: 10,
        justifyContent: ‘center’
    },
    webview_body: {
        flex: 9
    },
    button: {
        textAlign: ‘left’,
        color: ‘#FFF’
    },
    page_title: {
        color: ‘#FFF’
    },
    spinner: {
 
        alignItems: ‘flex-end’
    }
});
 
module.exports = WebPage;

Во-первых, мы делаем некоторую домашнюю работу, создавая нужные нам переменные и требуя библиотеки, которые мы будем использовать.

01
02
03
04
05
06
07
08
09
10
11
12
13
var React = require(‘react-native’);
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  WebView
} = React;
 
var Button = require(‘react-native-button’);
var GiftedSpinner = require(‘react-native-gifted-spinner’);
 
var _ = require(‘lodash’);

Далее мы создаем компонент WebPage .

1
2
3
var WebPage = React.createClass({
    …
});

Мы устанавливаем isLoading в true как состояние по умолчанию. Это свойство отвечает за определение, показывать или нет счетчик. По умолчанию счетчик должен быть виден, чтобы указать, что страница загружается.

1
2
3
4
5
getInitialState: function() {
    return {
        isLoading: true
    };
},

Далее мы визуализируем компонент. Как и у компонента новостей, у этого также есть заголовок и тело. Заголовок содержит кнопку «Назад», заголовок страницы и спиннер.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
render: function(){
         
    return (<View style={styles.container}>
     
        <View style={styles.webview_header}>
          <View style={styles.header_item}>
            <Button style={styles.button} onPress={this.back}>Back</Button>
          </View>
          <View style={styles.header_item}>
            <Text style={styles.page_title}>{this.truncate(this.state.pageTitle)}</Text>
          </View>
          <View style={[styles.header_item, styles.spinner]}>
            { this.state.isLoading && <GiftedSpinner /> }
          </View>
        </View>
 
        <View style={styles.webview_body}>
            <WebView
                url={this.props.url}
                onNavigationStateChange={this.onNavigationStateChange}
            />
        </View>
    </View>);
 
},

Тело содержит компонент WebView . Компонент WebView имеет атрибуты url и onNavigationStateChange . url — это URL-адрес, который ранее был передан из функции NewsItems компоненте NewsItems . Поэтому, когда следующий код выполняется:

1
this.props.navigator.push({name: ‘web_page’, url: url});

Метод renderScene в index.android.js также выполняется, и ему передается URL:

1
2
3
4
5
6
7
renderScene: function(route, navigator) {
 
    var Component = ROUTES[route.name];
    return (
        <Component route={route} navigator={navigator} url={route.url} />
    );
},

Вот как у нас есть доступ к URL, извлекая его из реквизита: this.props.url .

Давайте вернемся к атрибутам, добавленным в компонент WebView . У нас onNavigationStateChange атрибут onNavigationStateChange , который используется для указания функции, которая будет выполняться при каждом onNavigationStateChange веб-представления на новую страницу. Вот как выглядит эта функция:

1
2
3
4
5
6
7
8
9
onNavigationStateChange: function(navState) {
     
    if(!navState.loading){
        this.setState({
            isLoading: false,
            pageTitle: navState.title
        });
    }
},

Когда вышеупомянутая функция вызывается, navState передается в качестве аргумента. Он содержит информацию о текущем состоянии веб-представления, например, название страницы и загружается ли она в данный момент. Это идеальное место для обновления штата. Когда страница больше не загружается, мы устанавливаем для isLoading значение false и устанавливаем значение для pageTitle .

Далее у нас есть функция back , которая заставляет навигатор вернуться на одну страницу назад. Он вызывается всякий раз, когда пользователь нажимает кнопку «Назад» в заголовке.

1
2
3
back: function(){
   this.props.navigator.pop();
}

Функция truncate ограничивает длину всего, что передается в функцию. Мы используем эту функцию, чтобы ограничить текст заголовка страницы веб-страницы.

1
2
3
truncate: function(str){
    return _.truncate(str, 20);
},

Таблица стилей выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var styles = StyleSheet.create({
    container: {
        flex: 1
    },
    webview_header: {
        paddingLeft: 10,
        backgroundColor: ‘#FF6600’,
        flex: 1,
        justifyContent: ‘space-between’,
        flexDirection: ‘row’
    },
    header_item: {
        paddingLeft: 10,
        paddingRight: 10,
        justifyContent: ‘center’
    },
    webview_body: {
        flex: 9
    },
    button: {
        textAlign: ‘left’,
        color: ‘#FFF’
    },
    page_title: {
        color: ‘#FFF’
    },
    spinner: {
        alignItems: ‘flex-end’
    }
});

Наконец, представьте компонент внешнему миру:

1
module.exports = WebPage;

Для запуска приложения вам понадобится устройство Android или эмулятор. Если вы хотите использовать эмулятор, я рекомендую использовать Genymotion . Вы можете запустить приложение, выполнив следующую команду:

1
react-native run-android

Эта команда устанавливает и запускает приложение. Но вы получите следующую ошибку, если попытаетесь это сделать:

Запуск приложения

Это потому, что React Native ожидает, что сервер React будет работать на вашем компьютере. Сервер React компилирует приложение каждый раз, когда вы сохраняете изменения в текстовом редакторе. react-native run-android используется только для запуска приложения с целью тестирования и отладки приложения. Вот почему он зависит от сервера React для фактической компиляции приложения.

Чтобы избавиться от ошибки, вам нужно запустить react-native start команду запуска, чтобы запустить сервер. Это занимает некоторое время при первом запуске, но когда он достигает той части, где говорится следующее:

1
<END> Building Dependency Graph (35135ms)

Вы можете открыть новое окно терминала в каталоге вашего проекта и выполнить adb shell input keyevent 82 . Это открывает меню разработчика в устройстве или эмуляторе. После того, как меню откроется, выберите dev settings, затем выберите хост и порт отладочного сервера .

Откроется окно с просьбой ввести IP-адрес и порт вашего компьютера. Узнайте внутренний IP-адрес вашего компьютера и введите его в командной строке вместе с портом 8081 , который является портом по умолчанию, на котором работает сервер React. Другими словами, если ваш IP-адрес 192.168.254.254 , введите 192.168.254.254:8081 .

После этого вернитесь в меню разработчика и выберите перезагрузить JS . Это перезагрузит приложение, чтобы оно обнаружило работающий экземпляр сервера React. Приложение должно работать без проблем.

Если вы хотите провести тестирование на устройстве iOS, следуйте инструкциям на веб-сайте React Native .

Мы создали довольно удобное приложение для чтения новостей с React Native. Что дальше? Вот несколько идей, если вы хотите узнать больше о React Native:

  • Улучшите код, разбив приложение на несколько компонентов, которые можно использовать повторно. Начните с просмотра дублированного кода. Например, в приложении, которое мы создали, мы продублировали заголовок и компоненты внутри него. Что вы можете сделать, это создать компонент заголовка, который принимает заголовок в качестве свойства, а затем требует его на каждой странице, где вам нужен заголовок.
  • Увеличьте время отклика приложения, создав сервер, который кэширует элементы из API Hacker News. Это позволяет вам выполнять только один сетевой запрос, содержащий все новости, вместо того, чтобы выполнять несколько сетевых запросов, как мы делали в этом руководстве.
  • Создайте подписанный APK, чтобы вы могли распространять приложение в Google Play. Для iOS вы можете использовать Xcode для распространения вашего приложения в Apple App Store.
  • Изучите документацию по API-интерфейсам, которые имеют доступ к собственным возможностям устройства, таким как камера .
  • Посмотрите репозиторий Awesome React Native на Github. Этот репозиторий содержит список компонентов, ресурсов и инструментов, которые вы можете использовать с React Native.
  • Если вы хотите быть в курсе новостей о React Native, подпишитесь на новостную рассылку React Native .

Вот и все. Из этого руководства вы узнали, как работать с React Native для создания приложения для чтения новостей, которое взаимодействует с Hacker News API. Если у вас есть какие-либо вопросы, оставьте их в комментариях ниже, и я постараюсь ответить на них. Вы можете найти исходные файлы этого руководства на GitHub . Спасибо за прочтение.