Как создать программу дел в реальном времени с помощью React Native

1656668541 kak sozdat programmu del v realnom vremeni s pomoshhyu react

Приложение todo касается всех важных частей создания любой программы, управляемой данными, включая Cесть, Рead, Update и Доперации elete (CRUD) В этой истории я буду создавать приложение todo с одним из самых популярных мобильных фреймворков, React Native.

Я буду использовать ReactiveSearch Nativeбиблиотека с открытым исходным кодом, предоставляющая компоненты React Native UI и упрощающая создание приложений на основе данных.

Вот что я буду строить в этой истории:

1*bbDAbPL1rYl2k5fPFDtFHg
Приложение Todo

Ознакомьтесь с приложением на перекусе или выставке.

Что такое React Native?

Вот что говорят документы:

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

Даже если вы только начинаете работать с React или React Native, вы сможете следить за этой историей и создать свое собственное приложение для дел в реальном времени.

Зачем использовать ReactiveSearch? ⚛

ReactiveSearch — это библиотека компонентов React и React Native UI с открытым кодом для Elasticsearch, которую я создал в соавторстве с несколькими замечательными людьми. Он предоставляет различные компоненты React Native, которые можно подключать к любому кластеру Elasticsearch.

Я написал еще одну историю о создании GitHub Repo Explorer с помощью React и Elasticsearch, которую вы можете просмотреть, чтобы получить краткий обзор Elasticsearch. Даже если у вас не было опыта работы с Elasticsearch, вы можете хорошо следить за этой историей.

Настраиваем вещи ⚒

Здесь мы будем использовать версию библиотеки React Native.

Прежде чем мы начнем создавать UI, нам потребуется создать хранилище данных в Elasticsearch. ReactiveSearch работает с любым индексом Elasticsearch и вы можете легко использовать его с собственным набором данных.

1*7be2L3leZOfV6hwRIcB9Mg
Просмотрите мой набор данных программы здесь. Вы также можете клонировать это в свое собственное приложение

Для краткости вы можете использовать мой набор данных непосредственно или создать его для себя с помощью appbase.io, который позволяет создать размещенный индекс Elasticsearch (он же приложение).

Все задачи структурированы в следующем формате:

{
  "title": "react-native",
  "completed": true,
  "createdAt": 1518449005768
}

Первоначальный проект

Прежде чем начать, я бы порекомендовал установить пряжу. В Linux это можно сделать, просто добавив репозиторий yarn и запустив команду установки через менеджер пакетов. На Mac вам нужно сначала установить Homebrew, чтобы упростить работу. Вот документы по установке пряжи для более подробной информации. Следующее, что вы можете установить это сторож. Это служба просмотра файлов, которая поможет бесперебойной работе упаковщика react-native.

Я настроил стартовый проект с create-react-native-app в ветке GitHub здесь. Вы можете скачать zip или клонировать базовую ветвь, выполнив следующую команду: ?

git clone -b base https://github.com/appbaseio-apps/todos-native
  • Далее установите зависимости и запустите упаковщик:
cd todos-native && yarn && yarn start
  • После запуска упаковщика вы можете запустить приложение на своем телефоне с помощью приложения Expo или с помощью эмулятора Android или IOS:
1*vTzfrdAPwha5GKpkzxaOeQ
Базовая настройка со всеми вкладками. Клон отсюда.

Погружение в код?

После того, как вы клонировали код из базовой ветви, вы должны увидеть структуру каталогов, как показано ниже:

navigation
├── RootComponent.js         // Root component for our app
├── MainTabNavigator.js      // Tab navigation component
screens
├── TodosScreen.js           // Renders the TodosContainer
components        
├── Header.js                // Header component         
├── AddTodo.js               // Add todo input        
├── AddTodoButton.js         // Add todo floating button
├── TodoItem.js              // The todo item         
├── TodosContainer.js        // Todos main container api
├── todos.js                 // APIs for performing writes
constants                    // All types of constants used in app
types                        // Todo type to be used with prop-types
utils                        // Streaming logic goes here

Давайте разберем, что включает базовую настройку:

1. Навигация

  • Все необходимые конфигурации для подключения к Elasticsearch находятся по адресу constants/Config.js.
  • Мы используем TabNavigator с react-navigation для показа все, Активный и Выполнено экран задач. Это отображается с помощью navigation/RootComponent.js. Вы заметите RootComponent заворачивает все внутрь ReactiveBase компонент от ReactiveSearch Этот компонент предоставляет все необходимые данные для дочерних компонентов ReactiveSearch. Вы можете подключить свой собственный индекс Elasticsearch здесь, просто обновив конфигурации в constants/Config.js.

Логика навигации присутствует в navigation/MainNavigator.js. Рассмотрим, как это работает. Вот документы для навигации вкладками, если вы хотите ссылаться на что-нибудь.

import React from 'react';
import { MaterialIcons } from '@expo/vector-icons';
import { TabNavigator, TabBarBottom } from 'react-navigation';

import Colors from '../constants/Colors';
import CONSTANTS from '../constants';
import TodosScreen from '../screens/TodosScreen';

const commonNavigationOptions = ({ navigation }) => ({
    header: null,
    title: navigation.state.routeName,
});

// we just pass these to render different routes
const routeOptions = {
    screen: TodosScreen,
    navigationOptions: commonNavigationOptions,
};

// different routes for all, active and completed todos
const TabNav = TabNavigator(
    {
        [CONSTANTS.ALL]: routeOptions,
        [CONSTANTS.ACTIVE]: routeOptions,
        [CONSTANTS.COMPLETED]: routeOptions,
    },
    {
        navigationOptions: ({ navigation }) => ({
            // this tells us which icon to render on the tabs
            tabBarIcon: ({ focused }) => {
                const { routeName } = navigation.state;
                let iconName;
                switch (routeName) {
                    case CONSTANTS.ALL:
                        iconName="format-list-bulleted";
                        break;
                    case CONSTANTS.ACTIVE:
                        iconName="filter-center-focus";
                        break;
                    case CONSTANTS.COMPLETED:
                        iconName="playlist-add-check";
                }
                return (
                    <MaterialIcons
                        name={iconName}
                        size={28}
                        style={{ marginBottom: -3 }}
                        color={focused ? Colors.tabIconSelected : Colors.tabIconDefault}
                    />
                );
            },
        }),
        // for rendering the tabs at bottom
        tabBarComponent: TabBarBottom,
        tabBarPosition: 'bottom',
        animationEnabled: true,
        swipeEnabled: true,
    },
);

export default TabNav;
  • The TabNavigator функция принимает два аргумента, первый – конфигурации маршрута, а второй – TabNavigator конфигурации. В приведенном выше фрагменте мы передаем конфигурации для отображения панели навигации вкладок в нижней части и установки разных значков для каждой вкладки.

2. TodosScreen и TodosContainer

The TodosScreen компонент в screens/TodosScreen.js оборачивает наш главный TodosContainer компонент в components/TodosContainer.js где мы будем добавлять разные компоненты для программы. The TodosContainer покажет отфильтрованные данные в зависимости от того, находимся ли мы на все, активный, или Выполнено вкладка.

3. API для создания, обновления и удаления задач

API для операций CUD на Elasticsearch присутствуют в api/todos.js . Он содержит три простых метода add, update и destroy работающих с любым индексом Elasticsearch, как указано в constants/Config.js. Важно помнить, что каждый созданный нами элемент дел будет уникален. _id поле. Мы можем этим воспользоваться _id поле для обновления или удаления существующей задачи.

Для нашей программы нам понадобятся всего три метода добавления, создания или удаления задач. Однако можно найти подробное объяснение методов API в документах.

Создание компонентов и пользовательского интерфейса?

Давайте начнем добавлять некоторые компоненты для завершения функциональности программы.

1. Добавление Todos

Мы будем использовать Fab от native-base чтобы отобразить плавающую кнопку для добавления задач.

1*C1-bdZSvCajaJ-dtSsWcjg
const AddTodoButton = ({ onPress }) => (
  <Fab
      direction="up"
      containerStyle={{}}
      style={{ backgroundColor: COLORS.primary }}
      position="bottomRight"
      onPress={onPress}
  >
      <Icon name="add" />
  </Fab>
);

Теперь вы можете использовать этот компонент в components/TodosContainer.js.

import AddTodoButton from './AddTodoButton';
...
export default class TodosContainer extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        ...
        <AddTodoButton />
      </View>
    );
  }
}

Когда мы добавим кнопку, мы увидим что-то вроде этого:

1*vWdtqKsk0gZzMC4UO35IGg
После добавления кнопки AddTodoButton

Теперь, когда кто-нибудь нажимает эту кнопку, нам нужно будет показать введенные данные для добавления задания. Давайте добавим код для этого components/AddTodo.js.

class AddTodo extends Component {
  constructor(props) {
    super(props);
    const { title, completed, createdAt } = this.props.todo;
    this.state = {
      title,
      completed,
      createdAt,
    };
  }

  onSubmit = () => {
    if (this.state.title.length > 0) this.props.onAdd(this.state);
    return null;
  };

  setStateUtil = (property, value = undefined) => {
    this.setState({
      [property]: value,
    });
  };

  render() {
    const { title, completed } = this.state;
    const { onBlur } = this.props;
    return (
      <View
        style={{
          flex: 1,
          width: '100%',
          flexDirection: 'row',
          alignItems: 'center',
          paddingRight: 10,
          paddingBottom: 5,
          paddingTop: 5,
        }}
      >
        <CheckBox checked={completed} onPress={() => this.setStateUtil('completed', !completed)} />
        <Body
          style={{
            flex: 1,
            justifyContent: 'flex-start',
            alignItems: 'flex-start',
            paddingLeft: 25,
          }}
        >
          <TextInput
            style={{ width: '90%' }}
            placeholder="What needs to be done?"
            autoFocus
            underLineColorAndroid="transparent"
            underlineColor="transparent"
            blurOnSubmit
            onSubmitEditing={this.onSubmit}
            onChangeText={changedTitle => this.setStateUtil('title', changedTitle)}
            value={title}
            autoCorrect={false}
            autoCapitalize="none"
            onBlur={onBlur}
          />
        </Body>
        <TouchableOpacity
          onPress={() => this.props.onCancelDelete}
          style={{ paddingLeft: 25, paddingRight: 15 }}
        >
          <Ionicons
            name="ios-trash-outline"
            color={`${title.length > 0 ? 'black' : 'grey'}`}
            size={23}
          />
        </TouchableOpacity>
      </View>
    );
  }
}

Основными компонентами, которые здесь используются, являются TextInput, Checkbox и Ionicons с простыми сопротивлениями. Мы используем title и completed от state. Мы передадим реквизит todo, onAdd, onCancelDelete и onBlur от components/TodosContainer.js. Это поможет нам добавить новые задачи или сбросить просмотр, если вы хотите отменить добавление задач.

Теперь мы можем обновить components/TodosContainer.js с необходимыми изменениями для визуализации AddTodo компонент:

...
import AddTodoButton from './AddTodoButton';
import AddTodo from './AddTodo';
import TodoModel from '../api/todos';
...

// will render todos based on the active screen: all, active or completed
export default class TodosContainer extends React.Component {
  state = {
    addingTodo: false,
  };

  componentDidMount() {
    // includes the methods for creation, updation and deletion
    this.api = new TodoModel('react-todos');
  }

  render() {
    return (
      <View style={styles.container}>
        <Header />
        <StatusBar backgroundColor={COLORS.primary} barStyle="light-content" />
        <ScrollView>
          {this.state.addingTodo ? (
            <View style={styles.row}>
              <AddTodo
                onAdd={(todo) => {
                  this.setState({ addingTodo: false });
                  this.api.add(todo);
                }}
                onCancelDelete={() => this.setState({ addingTodo: false })}
                onBlur={() => this.setState({ addingTodo: false })}
              />
            </View>
          ) : null}
        </ScrollView>
        <AddTodoButton onPress={() => this.setState({ addingTodo: true })} />
      </View>
    );
  }
}

The AddTodo компонент отображается внутри a ScrollView компонент. Мы также проходим an onPress опора на AddTodoButton для переключения состояния и условного отображения AddTodo компонент на основе this.state.addingTodo. The onAdd опора передана AddTodo также создает новое задание с помощью add API на api/todos.js.

После нажатия кнопки «Добавить» мы увидим входные данные для добавления задания, как это:

1*VrlfuWW4tdj0TTrGjSfDSw
Добавление задачи

2. Отображение Todos

После завершения добавления задачи оно добавляется к Elasticsearch (который мы настроили в constants/Config.js). Все эти данные можно просматривать в реальном времени с помощью компонентов ReactiveSearch Native.

Библиотека предоставляет более 10 собственных компонентов пользовательского интерфейса. Для нашего приложения todo мы будем в основном использовать компонент ReactiveList, чтобы показать состояние todos.

Давайте добавим ReactiveList компонент и получить отображение наших задач. Мы добавим этот компонент components/TodosContainer.js и необходимые методы для его работы. Во как ReactiveList будет использовано:


...
import { ReactiveList } from '@appbaseio/reactivesearch-native';
...

export default class TodosContainer extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Header />
        <StatusBar backgroundColor={COLORS.primary} barStyle="light-content" />
        <ScrollView>
          <ReactiveList
            componentId="ReactiveList"
            defaultQuery={() => ({
              query: {
                match_all: {},
              },
            })}
            stream
            onAllData={this.onAllData}
            dataField="title"
            showResultStats={false}
            pagination={false}
          />
          ...
        </ScrollView>
        <AddTodoButton onPress={() => this.setState({ addingTodo: true })} />
      </View>
    );
  }
}

Мы не добавили onAllData метода, но давайте немного разберемся с атрибутами, которые мы здесь использовали:

  • componentId – уникальный идентификатор компонента.
  • defaultQuery: запрос, который будет первоначально использован для списка. Мы будем использовать match_all чтобы показать все задачи в регистре по умолчанию.
  • stream: транслировать новые обновления результатов или просто показывать исторические результаты. Установив для этого значение trueТеперь мы также слушаем текущие обновления Todo. Позже мы добавим связанную с потоковой передачей логику.
  • onAllData — функция обратного вызова, которая получает список текущих задач и потоковую передачу (новые задачи и любые обновления) и возвращает компонент React или JSX для рендеринга. Вот как выглядит синтаксис:
<ReactiveList
  onAllData(todos, streamData) {
    // return the list to render
  }
  ...
/>

Вы можете прочитать больше обо всех этих свойствах на странице документов ReactiveList.

Чтобы увидеть что-то, нам нужно будет вернуть компонент JSX или React из onAllData обратный звонок. Для этого мы будем использовать FlatList React Native, состоящий из компонентов Text. На следующем шаге мы добавим наш собственный TodoItem компонент.

...
import { ScrollView, StyleSheet, StatusBar, FlatList, Text } from 'react-native';
import CONSTANTS from '../constants';
...

export default class TodosContainer extends React.Component {
  ...
  onAllData = (todos, streamData) => {
    // filter data based on "screen": [All | Active | Completed]
    const filteredData = this.filterTodosData(todos);

    return (
      <FlatList
        style={{ width: '100%', top: 15 }}
        data={filteredData}
        keyExtractor={item => item._id}
        renderItem={({ item: todo }) => (
            <Text>{todo.title}</Text>
        )}
      />
    );
  };

  filterTodosData = (todosData) => {
    const { screen } = this.props;

    switch (screen) {
      case CONSTANTS.ALL:
        return todosData;
      case CONSTANTS.ACTIVE:
        return todosData.filter(todo => !todo.completed);
      case CONSTANTS.COMPLETED:
        return todosData.filter(todo => todo.completed);
    }

    return todosData;
  };

  render() {
    ...
  }
}
1*kobdkvtn9oZY7qvF9pzK0Q
Интеграция ReactiveList с onAllData

3. Добавление TodoItem(s)

Далее мы создадим отдельный компонент TodoItem для показа каждого задания, которое будет содержать все необходимые разметки для элемента задания, например флажок, текст и значок удаления. Это входит components/TodoItem.js:

class TodoItem extends Component {
  onTodoItemToggle = (todo, propAction) => {
    propAction({
      ...todo,
      completed: !todo.completed,
    });
  };

  render() {
    const { todo, onUpdate, onDelete } = this.props;

    return (
      <View style={styles.row}>
        <View
          style={{
            flex: 1,
            width: '100%',
            flexDirection: 'row',
            alignItems: 'center',
            paddingRight: 10,
            paddingVertical: 5,
          }}
        >
          <TouchableOpacity
            onPress={() => this.onTodoItemToggle(todo, onUpdate)}
            style={{
              flex: 1,
              width: '100%',
              flexDirection: 'row',
            }}
          >
            <CheckBox
              checked={todo.completed}
              onPress={() => this.onTodoItemToggle(todo, onUpdate)}
            />
            <Body
              style={{
                flex: 1,
                justifyContent: 'flex-start',
                alignItems: 'flex-start',
                paddingLeft: 25,
              }}
            >
              <Text
                style={{
                  color: todo.completed ? 'grey' : 'black',
                  textDecorationLine: todo.completed ? 'line-through' : 'none',
                }}
              >
                {todo.title}
              </Text>
            </Body>
          </TouchableOpacity>
          <TouchableOpacity
            onPress={() => onDelete(todo)}
            style={{ paddingLeft: 25, paddingRight: 15 }}
          >
            <Ionicons
              name="ios-trash-outline"
              color={`${todo.title.length > 0 ? 'black' : 'grey'}`}
              size={23}
            />
          </TouchableOpacity>
        </View>
      </View>
    );
  }
}

Этот компонент получает todo от своего реквизита вместе с onDelete и onUpdate которые используются для обновления и удаления пункта Todo соответственно. Мы используем их в необходимых местах с помощью onPress prop компоненты, которые мы используем.

Дальше мы можем import и используйте TodoItem компонент в нашем onAllData в components/TodosContainer.js. Мы передадим todo в качестве сопротивления вместе с методами API для update и destroy который будет использоваться TodoItem компонент.

class TodosContainer extends Component {
  ...
  onAllData = (todos, streamData) => {
    ...
    return (
      <FlatList
        ...
        renderItem={({ item: todo }) => (
          <TodoItem 
            todo={todo}
            onUpdate={this.api.update} 
            onDelete={this.api.destroy}
          />
        )}
      />
    );
  }
}
1*46QMtTpPsof09oOBwvrELA
После добавления TodoItem в TodosContainer

4. Потоковое обновление данных

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

В предыдущем разделе мы добавили onAllData метод для ReactiveList компонент. Второй параметр onAllData получает потоковые обновления, которые мы будем использовать, чтобы всегда обновлять задачи. Вот как обновлено onAllData метод будет выглядеть как в components/TodosContainer.js.

import Utils from '../utils';
...

export default class TodosContainer extends React.Component {
  ...
  onAllData = (todos, streamData) => {
    // merge streaming todos data along with current todos
    const todosData = Utils.mergeTodos(todos, streamData);

    // filter data based on "screen": [All | Active | Completed]
    const filteredData = this.filterTodosData(todosData);

    return (
      <FlatList
        style={{ width: '100%', top: 15 }}
        data={filteredData}
        keyExtractor={item => item._id}
        renderItem={({ item: todo }) => (
            <TodoItem todo={todo} onUpdate={this.api.update} onDelete={this.api.destroy} />
        )}
      />
    );
  };
  ...
}

The mergeTodos метод присутствует в utils/index.js. Вот как это работает:

class Utils {
  static mergeTodos(todos, streamData) {
    // generate an array of ids of streamData
    const streamDataIds = streamData.map(todo => todo._id);

    return (
      todos
        // consider streamData as the source of truth
        // first take existing todos which are not present in stream data
        .filter(({ _id }) => !streamDataIds.includes(_id))
        // then add todos from stream data
        .concat(streamData)
        // remove todos which are deleted in stream data
        .filter(todo => !todo._deleted)
        // finally sort on the basis of creation timestamp
        .sort((a, b) => a.createdAt - b.createdAt)
    );
  }
}

export default Utils;

The streamData получает массив объектов todo при их создании, удалении или обновлении. Если объект обновлен, он содержит a _updated установлен ключ true. Так же, если объект удален, он содержит a _deleted установлен ключ true. Если объект создан, он не содержит ни одного из двух. Используя эти точки, мы добавили mergeTodos функция.

Благодаря этому вы сможете видеть изменения в элементах дела в реальном времени! Если у вас есть дополнительное устройство/эмулятор, на котором работает та же программа, оба будут также транслировать новые обновления. ?

  1. Демонстрация программы Todos, ссылка на экспозицию, стартовый проект и конечный исходный код
  2. Репо ReactiveSearch GitHub ⭐️
  3. Документы ReactiveSearch

Надеюсь, вам понравилась эта история. Если у вас есть какие-либо мнения или предложения, пожалуйста, дайте мне знать и получайте удовольствие!

Вы можете следить за мной в Twitter, чтобы получать последние обновления. Я тоже начал публиковать последние публикации в своем личном блоге.

Особая благодарность Дхрувдутту Джадхаву за помощь мне с этой историей и приложением Todos.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *