Часть 1 из 2.

Во-первых, вам нужно установить React и все необходимые зависимости, которые вы можете сделать здесь. Наиболее важные элементы для установки - это node и react-native-cli. Чтобы начать работу, откройте приложение терминала и введите следующие команды:

react-native init MinYikYak
cd MinYikYak
react-native run-ios

Вы должны увидеть страницу «Добро пожаловать в React Native» в симуляторе. Перейдите в папку MinYikYak на вашем компьютере и откройте файл index.ios.js. Мы создаем этот проект для устройств ios, но большая часть проекта должна быть совместима с Android, просто используйте специальные команды для Android.

Шаг 1: TextInput

Наиболее важные свойства React Native - это свойства и состояние. Свойства указывают, какие свойства родительский элемент может передавать дочернему элементу, а состояние обозначает элементы в конкретном компоненте, которые изменяются. Хорошие разработчики React понимают, как «разбить» свое приложение на компоненты. Какие компоненты необходимы для такого приложения, как Yik Yak?

Для компонента Search Box мы можем использовать встроенный компонент React TextInput. Это первая часть проекта, который мы будем строить. В вашем index.ios.js вы должны увидеть компоненты просмотра и текстовые компоненты в функции рендеринга. Удалите все это так, чтобы было просто читать:

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  TextInput,
  TouchableHighlight,
  View
} from 'react-native';
export default class MinYikYak extends Component {
  render() {
    return (
      <View>
      </View>
    );
  }
}
AppRegistry.registerComponent('MinYikYak', () => MinYikYak);

Внутри этого представления мы создадим новый компонент TextInput. Для этого импортируйте TextInput вверху и добавьте компонент TextInput в свое приложение.

import {
  AppRegistry,
  StyleSheet,
  Text,
  TextInput,
  View
} from 'react-native';
const styles = Stylesheet.create({
  container: {
    padding: 30,
    marginTop: 65,
    alignItems: 'center'
  },
  searchInput: {
    height: 36,
    padding: 4,
    marginRight: 5,
    flex: 4,
    fontSize: 18,
    borderWidth: 1,
    borderColor: '#32cd32',
    borderRadius: 8,
    color: '#32cd32'
  },
});
...
    <View style={styles.container}>
       <TextInput 
       placeholder="Yak"
       style={styles.searchInput}/>
    </View>
...

Как видите, мы добавили компонент styles.container в компонент View в дополнение к стилям. Стили React очень похожи на стили CSS, более подробную статью о стилях с помощью гибких контейнеров можно найти здесь. Поскольку состояние отслеживает, какие изменения в приложении, мы должны понимать, что может измениться внутри TextInput. Значение текста внутри TextInput изменится, поэтому мы должны добавить переменную состояния для этого текста. Кроме того, мы должны установить значение TextInput для переменной, которую мы определяем. Для этого нужно добавить в класс функцию-конструктор следующим образом:

export default class MinYikYak extends Component {
  constructor(props) {
    super(props);
    this.state = {
      postString: '',
    }
  }
  render() {
     /* Not included to save space */
       <TextInput 
       placeholder="Yak"
       style={styles.searchInput}
       value={this.state.postString}/>
     ...

Если вы перезагрузите это в симуляторе, вы заметите, что ввод текста внутри TextInput ничего не меняет. Это потому, что postString всегда является пустой строкой, и всякий раз, когда мы вводим текст в TextInput, мы снова устанавливаем значение этой пустой строки. Поэтому мы должны изменять значение переменной состояния по мере ввода. Для этого мы должны добавить функцию в свойство onChange TextInput. Мы вызовем функцию postTextChange () и добавим ее в наш класс.

export default class MinYikYak extends Component {
  constructor(props) {
    super(props);
    this.state = {
      postString: '',
    }
  }
  postTextChange(event) {
    this.setState({ postString : event.nativeEvent.text )};
  render() {
     /* Not included to save space */
       <TextInput 
       placeholder="Yak"
       style={styles.searchInput}
       value={this.state.postString}
       onChange={this.postTextChange.bind(this)}/>
     ...

Если вы работали с html-формами, вы можете увидеть, что обработка событий ничем не отличается. Мы вызываем this.postTextChange.bind (this) каждый раз, когда TextInput изменяется, и обновляем переменную состояния postString во время этого процесса. Мы должны использовать .bind (this), потому что он гарантирует, что использование переменной this в функции определено как класс MinYikYak. Как видите, у нас есть рабочий TextInput, это было не так уж и плохо!

Шаг 2. Добавление сообщений

Теперь, когда мы добавили textInput, мы можем обновлять сообщения на странице. Напомним, что переменные состояния отслеживают изменения в компоненте, поэтому мы должны отслеживать сообщения, которые меняются. Мы должны добавить новую переменную состояния под названием posts. Чтобы понять, как будут работать сообщения, мы должны сохранить объект (словарь в Python, карту в Java) сообщений. Структура каждого поста будет выглядеть следующим образом:

this.state.posts = {
  (TimePosted) : {
    message : "..."
    likes : 0
    comment : ["...", "..."]
  },
  (TimePosted) : {...}
  (TimePosted) : {...}
}

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

export default class MinYikYak extends Component {
  constructor(props) {
    super(props);
    this.state = {
        postString: '',
        posts : {},
    }
  }
  createPost() {
    this.state.posts[Date.now()] = {
        message : this.state.postString,
        likes : 0,
        comments : []
    };
    this.setState({
        posts : this.state.posts,
        postString : '',
    )};
  }
  /* Not included to save space */
}

Делая это, мы устанавливаем ключ для каждого сообщения на время его публикации, свойство сообщения на postString, а затем мы обновляем сообщения и очищаем postString. Но когда вызывается функция createPost? Мы должны добавить кнопку, позволяющую пользователям публиковать сообщения. Теперь мы должны обновить стили и функцию рендеринга.

import {
  AppRegistry,
  StyleSheet,
  Text,
  TextInput,
  TouchableHighlight,
  View
} from 'react-native';
const styles = Stylesheet.create({
  /* Not included to save space */
flowRight: {
  flexDirection: 'row',
  alignItems: 'center',
  alignSelf: 'stretch'
},
buttonText: {
  fontSize: 18,
  color: 'white',
  alignSelf: 'center'
},
button: {
  height: 36,
  flex: 1,
  flexDirection: 'row',
  backgroundColor: '#32cd32',
  borderColor: '#32cd32',
  borderWidth: 1,
  borderRadius: 8,
  alignSelf: 'stretch',
  justifyContent: 'center'
},
)};
export default class MinYikYak extends Component {
    /* Not included to save space */
    render() {
       return (
          <View style={styles.container}>
             <View style={styles.flowRight}>
               <TextInput 
               placeholder="Yak"
               style={styles.searchInput}
               value={this.state.searchString}
               onChange={this.updateQuery.bind(this)}
               />
               <TouchableHighlight 
               style={styles.button}
               onPress={this.createPost.bind(this)}
               >
                 <Text style={styles.buttonText}>
                   Yak
                 </Text>
               </TouchableHighlight>
             </View>
          </View>
    );

Ого! Если похоже, что я добавил много, это не должно иметь большого значения. Единственное, что действительно изменилось, это то, что мы добавили новый вид со стилем flowRight. Стиль гарантирует, что компоненты TextInput и TouchableHighlight находятся в одной строке. Компонент TouchableHighlight - это просто кнопка с текстом внутри, и всякий раз, когда она нажимается (onPress), он вызывает функцию createPost, которую мы создали ранее. Это все здорово, но при обновлении симулятора, вводе текста и последующем нажатии кнопки Як сообщения не отображаются. Это потому, что мы должны отображать каждый пост с помощью функции карты. По сути, карта вызывает функцию для каждого значения внутри массива. Глядя на код, вы можете понять, что происходит. Сначала мы создадим массив всех постов.

var posts = Object.keys(this.state.posts);

Затем мы должны вызвать карту и отрендерить каждый пост. Мы вызовем функцию, которая сопоставляет посты с renderPost ()

posts.map(this.renderPost.bind(this)

Эта функция говорит, что для каждой переменной в сообщениях (для каждого ключа в this.state.posts) вызывается renderPost (key). Итак, теперь мы должны создать функцию renderPost, которая возвращает компоненты, составляющие пост.

  renderPost(key) {
    return (
      <View style={styles.rowContainer}>
        <Text style={styles.title}>
          {this.state.posts[key].message}
        </Text>
      </View>
    );
  }

Как вы можете видеть здесь, мы отображали сообщение в каждом посте. Окончательный макет должен выглядеть так:

const styles = Stylesheet.create({
/* Not included to save space */
  title: {
    fontSize: 20,
    color: '#656565'
  },
  rowContainer: {
    flexDirection: 'row',
    padding: 10
  },
)};
/* Not included to save space */
  renderPost(key) {
    return (
      <View style={styles.rowContainer}>
        <Text style={styles.title}>
          {this.state.posts[key].message}
        </Text>
      </View>
    );
  }
  render() {
    var posts = Object.keys(this.state.posts);
    return (
      <View style={styles.container}>
        <View style={styles.flowRight}>
          <TextInput 
          placeholder="Yak"
          style={styles.searchInput}
          value={this.state.searchString}
          onChange={this.updateQuery.bind(this)}
          />
          <TouchableHighlight 
            style={styles.button}
            onPress={this.createPost.bind(this)}
            >
            <Text style={styles.buttonText}>
              Yak
            </Text>
          </TouchableHighlight>
        </View>
        {posts.map(this.renderPost.bind(this))}
      </View>
    );
  }
/* Not included to save space */

Вы могли заметить, что я поместил posts.map ... в фигурные скобки. Это указывает React на то, что код внутри фигурных скобок является Javascript, а не компонентом. Каждый раз, когда вы включаете Javascript в оператор return функции React, вы должны заключить его в {}. Ввод сообщений в TextInput и их отправка должны работать. Вы могли заметить, что каждое новое сообщение добавляется внизу, а не вверху. Чтобы изменить это (если хотите), все, что вам нужно сделать, это изменить var posts на:

var posts = Object.keys(this.state.posts).reverse();

Теперь у нас работают посты!

Шаг 3: укладка

Последний пост, который мы хотим показать, выглядит так:

Как мы можем стилизовать пост, чтобы он выглядел немного более похожим на этот? Опять же, стили, которые я добавляю, основаны на гибких стилях, которые использует React Native. Обязательно проверьте ссылку, которую я разместил ранее, чтобы получить более подробную информацию о стилях гибкости. Во-первых, мы должны загрузить функцию, которая использует Javascript, чтобы вычислить, сколько секунд / минут / часов… назад было Date. Я использовал ответ на StackOverflow.

function timeSince(date) {

    var seconds = Math.floor((new Date() - date) / 1000);

    var interval = Math.floor(seconds / 31536000);

    if (interval > 1) {
        return interval + " years";
    }
    interval = Math.floor(seconds / 2592000);
    if (interval > 1) {
        return interval + " months";
    }
    interval = Math.floor(seconds / 86400);
    if (interval > 1) {
        return interval + " days";
    }
    interval = Math.floor(seconds / 3600);
    if (interval > 1) {
        return interval + " hours";
    }
    interval = Math.floor(seconds / 60);
    if (interval > 1) {
        return interval + " minutes";
    }
    return seconds == 0 ? "Just now" Math.floor(seconds) + " seconds";
}

Поместите эту функцию прямо перед объявлением класса MinYikYak, она должна быть снаружи, а не внутри класса. В нашей функции renderPost мы добавим новый компонент Text, который будет определять время и правильно его стилизовать.

const styles = ... {
... 
  time: {
    fontSize: 15,
    fontWeight: 'bold',
    color: '#32cd32'
  },
  textContainer: {
    flex: 1
  },
}
...
renderPost(key) {
    return (
      <View key={key} style={styles.rowContainer}>
        <View style={styles.textContainer}>
          <Text style={styles.time}>{timeSince(key)}</Text>
          <Text style={styles.title}>
            {this.state.posts[key].message}
          </Text>
        </View>
      </View>
    );
  }

Добавление ключа в rowContainer позволяет избежать предупреждения, которое вы, возможно, получали. TextContainer правильно стилизует отображаемые текстовые компоненты. Сообщения теперь выглядят довольно хорошо, но нам по-прежнему нужен значок Нравится со счетчиком рядом с ним. Чтобы использовать значки, необходимо установить пакет npm. Мы будем использовать React Native Vector Icons. Это может показаться сложным, но для этого потребуется всего пара строк кода. Перейдите в папку проекта в Терминале.

$ npm install rnpm -g # downloads the react native package manager
$ npm install react-native-vector-icons --save
$ rnpm link

После этого остановите приложение, мы перезапустим его позже. Этот шаг важен. Теперь мы импортируем значок в наш проект. Сразу под операторами импорта из response и response-native добавьте:

import Icon from 'react-native-vector-icons/FontAwesome';

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

renderPost(key) {
    return (
      <View key={key} style={styles.rowContainer}>
        <View style={styles.textContainer}>
          <Text style={styles.time}>{timeSince(key)}</Text>
          <Text style={styles.title}>
            {this.state.posts[key].message}
          </Text>
        </View>
        <View>
          <Text>{this.state.posts[key].likes}</Text>
          <Icon name="thumbs-up" 
                size={30} 
                color={this.state.posts[key].likes == 0 ? '#dddddd' : '#32cd32'}
                />
        </View>
      </View>
    );
  }

Это добавляет еще одно представление с текстовым компонентом, чтобы указать количество лайков посту, и значок с надписью «большой палец вверх», чтобы указать тип значка и цвет, который зависит от количества лайков посту. Перезагрузите приложение (run-ios с поддержкой реакции). Вы должны увидеть, что мы завершили стилизацию каждого поста.

Вы можете заметить, что приложение все еще имеет некоторые недостатки: 1) после добавления большого количества сообщений нельзя прокручивать и видеть другие сообщения, 2) вы не видите комментарии к сообщению, 3) вам не нравится сообщение, 4) вы не можете добавить комментарий к сообщению. Мы добавим этот функционал в Часть 2 проекта. Сначала попробуйте сами!