Часть 2 из 2.

Посмотрите Часть 1, если вы еще этого не сделали.

Теперь, когда у нас есть базовая настройка приложения, мы позволим пользователям перемещаться между страницей «Все сообщения» и конкретным сообщением. Кроме того, мы хотим, чтобы пользователи могли комментировать и ставить лайки другим сообщениям.

Шаг 1. Настройте навигацию

Чтобы добавить навигацию, мы сначала создадим новый файл в нашей папке «MinYikYak», назовем его PostPage.js и вырежем весь код из index.ios.js в новый файл. Index.ios.js не должен быть пустым, и нам нужно будет удалить последнюю строку PostPage.js и изменить имя класса с MinYikYak на PostPage.

/* change */
...
export default class PostPage extends Component {
...
/* delete: AppRegistry.registerComponent('MinYikYak', () => MinYikYak); */

Важно, что мы по-прежнему используем файл index.ios.js, поэтому мы все равно должны создать класс MinYikYak. Чтобы создать класс, нам нужно импортировать элементы React и React Native, которые мы будем использовать в дополнение к использованию AppRegistry для отображения приложения. Кроме того, мы хотим импортировать «новый» класс, который мы только что создали, и отображать его в файле index.ios.js. Код должен выглядеть так:

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

Самое главное, чтобы новая страница работала с навигацией, мы должны использовать компонент NavigatorIOS. ** Различия между Android и iOS начинаются здесь, пользователи Android захотят использовать обычный компонент Navigator**. Нам нужно добавить компонент NavigatorIOS и соответствующие стили.

const styles = StyleSheet.create({
  container: {
    flex: 1
  }
});
class MinYikYak extends Component {
  render() {
    return (
      <NavigatorIOS 
        style={styles.container}
        initialRoute={{
          title: 'Mini Yik Yak',
          component: PostPage
        }}/>
    );
  }
}

Свойство initialRoute просто указывает, каким будет первый визуализируемый компонент. После обновления вы должны увидеть этикетку сверху с надписью Mini Yik Yak. Теперь у нас есть приложение, которое может перемещаться на другие страницы, к которым мы вернемся позже.

Шаг 2: Настройка лайков и прокрутки

Теперь давайте добавим пользователям возможность лайкать другие сообщения. Всякий раз, когда пользователь щелкает значок, мы хотим переключать лайки (нравится, если не нравится, в отличие от, если нравится). Перейдите в папку PostPage и добавьте функцию toggleLike(key), которая использует ключ компонента для увеличения/уменьшения количества отметок «Нравится» у публикации.

  toggleLike(key) {
    var post = this.state.posts[key];
    post.likes == 0 ? post.likes += 1 : post.likes -= 1;
    this.setState({ posts: this.state.posts });
  }
  renderPost(key) {
    return (
      ...
      <Icon ... onPress={this.toggleLike.bind(this, key)}
      ...
    );
  }

Если вы не знакомы с ? : обозначение, это по сути то же самое, что и запись:

if (post.likes == 0) {
    post.likes += 1;
} else {
    post.likes -= 1;
}

Функция устанавливает количество лайков и устанавливает состояние. Значок запускает функцию при нажатии и передает ключевой параметр toggleLike. Помните, что .bind(this) используется для того, чтобы вызываемая функция использовала this в контексте класса. Вы должны заметить, что теперь вы можете поставить лайк и отклонить публикацию, а также значок меняет цвет.

Кроме того, мы хотим, чтобы пользователь мог прокручивать страницу после добавления нескольких сообщений. Это требует нескольких изменений в методе render(). Импортируйте компонент ScrollView React, измените стиль контейнера, чтобы он включал только отступ: 15, и измените внешний вид в методе рендеринга на ScrollView.

import {
  ...
  ScrollView,
} from 'react-native';
...
  render() {
    var posts = Object.keys(this.state.posts).reverse();
    return (
      <ScrollView style={styles.container}>
        ...
      </ScrollView>
    );
  }
...
const styles = ... {
  container : {
    padding: 15,
  },
  ...
});

Вы должны заметить, что теперь мы можем прокручивать страницу.

Шаг 3. Завершите навигацию.

Теперь мы хотим, чтобы пользователь мог щелкнуть сообщение и увидеть комментарии, а также прокомментировать сообщение. Для начала создайте новый файл с именем PostResult.js, создайте класс и экспортируйте его. Не забудьте импортировать класс PostResult в класс PostPage, Icon, требования React и React-Native и скопировать стили из PostPage в PostResult. PostResult должен отображать сообщение и комментарии ниже. У нас уже есть код для отображения поста, поэтому скопируйте весь код из функции рендеринга PostPage и поместите его в функцию рендеринга PostResult. Ваш код должен выглядеть так:

render() {
  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'}
                onPress={this.toggleLike.bind(this, key)}/>
        </View>
      </View>
    );
 }

Как сделать так, чтобы страница PostResult отображалась? Нам нужно добавить возможность перехода к новому компоненту при нажатии на пост. Сначала мы должны создать функцию, которая может переходить от компонента PostPage к компоненту PostResult. Компонент NavigatorIOS, который мы использовали в index.ios.js, передает свойство компоненту PostPage, называемому navigator. Когда мы нажимаем на эту функцию, мы перемещаем данный компонент поверх текущего компонента, и отображается новый. Создайте функцию nextPage(key) в PostPage:

  nextPage(key) {
    this.props.navigator.push({
      title: 'Post',
      component: PostResult
    });
  }

Теперь нам нужно вызвать функцию nextPage. Для этого нам нужно сделать каждый пост в PostPage кнопкой, окружив его компонентом TouchableOpacity внутри функции renderPost. Сначала импортируйте компонент TouchableOpacity и добавьте его в метод renderPost с его методом onPress, вызывающим nextPage:

import {
  ...
  TouchableOpacity,
} from 'react-native';
  renderPost(key) {
    return (
      <TouchableOpacity key={key} onPress={this.nextPage.bind(this, key)}>
      <View key={key} style={styles.rowContainer}>
         ...
      </View>
      </TouchableOpacity>
    );
  }

Теперь, когда вы создаете новый пост и нажимаете на пост, он должен… ошибка? Но… когда мы смотрим на первую строку ошибки, она исходит от функции рендеринга PostResult, что означает, что мы на один шаг ближе к рендерингу функции! Проблему с PostResults должно быть довольно легко найти, там нет переменной с именем key. Итак, как мы можем получить переменную из одного родительского компонента в дочерний компонент? Используйте реквизит! Если вы знакомы с React, передача реквизита другому компоненту может выглядеть так:

/* Passing prop from PostPage.js */
<PostResult key={key} />
/* Receiving prop in PostResult.js */
this.props.key

Однако, поскольку мы используем навигатор, получение реквизита выглядит так же, но передача их немного отличается:

  nextPage(key) {
    this.props.navigator.push({
      title: 'Post',
      component: PostResult,
      passProps: {time: key, 
                  post: this.state.posts[key],
                  timeSince: timeSince,
                  toggleLike: this.toggleLike.bind(this)}  
    });
  }

Здесь мы говорим, что в компоненте PostResult вы можете получить доступ к ключу через this.props.time, к сообщению через this.props.post и к функции timeSince через this.props.timeSince. Обратите внимание, как мы используем .bind(this) для toggleLike, а не для timeSince, потому что timeSince не находится внутри класса и не использует переменные класса, использующие «this». Мы должны вернуться к файлу PostResult.js, чтобы изменить код, который мы добавили ранее.

render() {
   return (
    <View style={styles.wrapper} >
      <ScrollView contentContainerStyle={styles.wrapper}>
      <View style={styles.rowContainer}>
         <View style={styles.textContainer}>
           <Text style={styles.time}>{this.props.timeSince(this.props.time)}</Text>
           <Text style={styles.title}>
             {this.props.post.message}
           </Text>
         </View>
         <View>
           <Text>{this.props.post.likes}</Text>
           <Icon name="thumbs-up" 
                 size={30} 
                 color={this.props.post.likes == 0 ? '#dddddd' : '#32cd32'}
                 onPress={this.props.toggleLike.bind(this, this.props.time)}/>
         </View>
       </View>
       </ScrollView>
     </View>
    );
 }
...
const styles = ... {
  ...
  wrapper: {
    flex: 1
  },
  ...
});

Мы добавили ScrollView в этот код, потому что комментарии должны прокручиваться по мере их добавления. Другие проблемы со стилем и реквизитом также исправлены в этом процессе. Вы могли заметить, что нажатие кнопки «Нравится» на странице PostResult не обновляет лайк на самой странице. Это проблема с NavigatorIOS, и она исправлена ​​при использовании обычного компонента Navigator, который не так удобен в использовании. Простое решение этой проблемы — добавить следующее:

/* In PostPage.js */
 toggleLike(key, result) {
    var post = this.state.posts[key];
    post.likes == 0 ? post.likes += 1 : post.likes -= 1;
    this.setState({ posts: this.state.posts });
    if (result) {
      this.props.navigator.replace({
        title: 'Post',
        component: PostResult,
        passProps: {time: key, 
                    post: this.state.posts[key], 
                    timeSince: timeSince,
                    toggleLike: this.toggleLike.bind(this)}
      });
    }
  }
  renderPost(key) {
    ...
          <Icon ...
                onPress={this.toggleLike.bind(this, key, false)}/>
    ...
  }
/* In PostResult.js */
 render() {
  ...
           <Icon ...
                 onPress={this.props.toggleLike.bind(this, this.props.time, true)}/>
  ...
 }

Этот код проверяет, была ли нажата кнопка Like в компоненте PostResult или PostPage. Если он был в PostResult, он перерендерит страницу, иначе нет. Это короткое решение проблемы, но использование Навигатора обеспечивает лучшее решение.

Шаг 4: Завершите комментарии

Теперь мы добавим вводимый текст, привязанный к нижней части экрана, что позволит пользователям оставлять комментарии. Используя тот же процесс, что и для postInput, мы должны создать конструктор в PostResult с переменной состояния, которая содержит строку комментария. Кроме того, мы должны добавить компонент TextInput вне компонента ScrollView. Это потому, что мы не хотим, чтобы ввод прокручивался за пределы поля зрения, мы хотим, чтобы он оставался фиксированным внизу.

constructor(props) {
    super(props);
    this.state = {
      commentString: '',
    }
}
onCommentTextChanged(event) {
    this.setState({ commentString: event.nativeEvent.text });
}
render() {
  ...
  </ScrollView>
  <View style={{flexDirection : 'row', bottom : 0}} >
        <TextInput 
        value={this.state.commentString}
        placeholder="Comment"
        style={styles.searchInput}
        onChange={this.onCommentTextChanged.bind(this)}/>
        <TouchableHighlight style={styles.button}
              underlayColor='green'>
          <Text style={styles.buttonText}>Reply</Text>
        </TouchableHighlight>
     </View>
  ...
}

Как видите, это почти тот же код, который мы использовали в файле PostPage.js. Мы использовали технику встроенного стиля для внешнего вида, и через секунду мы поймем, почему. Перезагрузив страницу, мы видим, что все работает хорошо, но в верхней части, перейдя в «Оборудование» -> «Клавиатура» -> «Переключить программную клавиатуру», мы видим, что ввод фактически закрывается при нажатии на комментарий. Так как эта часть не важна для общего эффекта, я добавил код ниже, и вы сможете понять, что происходит.

import {
  ...
  Dimensions,
  Keyboard,
} from 'react-native';
...
constructor(props) {
    super(props);
    this.state = {
      visibleHeight: Dimensions.get('window').height,
      commentString: '',
    }
    this.height = this.state.visibleHeight;
  }
componentWillMount () {
    Keyboard.addListener('keyboardWillShow',         this.keyboardWillShow.bind(this))
    Keyboard.addListener('keyboardWillHide', this.keyboardWillHide.bind(this))
  }
keyboardWillShow (e) {
    let newSize = Dimensions.get('window').height - e.endCoordinates.height
    this.setState({visibleHeight: newSize})
  }
keyboardWillHide (e) {
   this.setState({visibleHeight: Dimensions.get('window').height})
}
 render() {
   return (
   ...
       </ScrollView>
       <View style={{flexDirection : 'row', bottom : this.height - this.state.visibleHeight}} >
   ...
     );
 }

Теперь ввод в разделе комментариев с переключаемой клавиатурой должен по-прежнему работать отлично. Мы почти закончили! Все, что нам нужно сделать сейчас, это добавить функцию, которая позволяет пользователю добавлять комментарий. Однако обновив комментарий в PostResult.js, выполните следующие действия:

this.props.post.comments.push(this.state.commentString);

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

/* In PostPage.js inside the PostPage class */
  loadComment(key, comment) {
    this.state.posts[key].comments.push(comment);
    this.setState({ posts : this.state.posts });
  }

Как мы можем вызвать эту функцию через дочерний компонент? Пропустите его через реквизит! Отредактируйте реквизит сейчас:

toggleLike(key, result) {
        ...
        passProps: {...
                    loadComment: this.loadComment.bind(this)}
      });
    }
  }
  nextPage(key) {
    ...
      passProps: {...
                  loadComment: this.loadComment.bind(this)}
    });
  }

Теперь перейдите к PostResult.js и добавьте следующий код для загрузки комментариев.

loadComment() {
    this.props.loadComment(this.props.time,           this.state.commentString);
    this.setState({ commentString: '' });
  }
 render() {
   return (...
          <TouchableHighlight style={styles.button}
              underlayColor='green'
              onPress={this.loadComment.bind(this)}>
            <Text style={styles.buttonText}>Reply</Text>
          </TouchableHighlight>
     );
 }

Теперь после добавления коммент исчезает! Ничего не случилось?! Это потому, что, как и сообщения, мы должны сопоставить комментарии с функцией, которая будет отображать их все. Итак, наконец,

renderComments(key) {
    return (
      <View key={key} style={styles.rowContainer}>
        <View style={styles.textContainer}>
          <Text style={styles.title}>{key}</Text>
        </View>
        <View style={styles.separator}/>
      </View>
    )
  }
 render() {
   return (
    <View style={styles.wrapper} >
      <ScrollView contentContainerStyle={styles.wrapper}>
      <View style={styles.rowContainer}>
        ...
       </View>
        <View style={styles.separator}/>
        {this.props.post.comments.map(this.renderComments.bind(this))}
       </ScrollView>
     ...
     );
 }
...
const styles = ... {
  ... 
  separator: {
    height: 1,
    backgroundColor: 'black'
  },
)};

Стиль разделителя можно использовать, чтобы сделать определение между публикацией и комментариями более четким. Мы завершили приложение Mini Yik Yak! Вы можете лайкать, отклонять, комментировать и перемещаться в приложении. К сожалению, после обновления приложения весь экран становится пустым, а сообщения не сохраняются. Это можно исправить, добавив бэкэнд. Популярные серверные части для приложений React включают Firebase, Node и Meteor, поскольку они легкие и легко настраиваются. Получайте удовольствие от яккинга!