O aplicație todo atinge toate părțile importante ale construirii oricărei aplicații bazate pe date, inclusiv Cculege, Read, Update și Doperații de eliminare (CRUD). În această poveste voi construi o aplicație pentru tot cu una dintre cele mai populare cadre mobile, Reactive native.

Voi folosi ReactiveSearch Native, o bibliotecă open-source care oferă componente React Native UI și simplifică crearea de aplicații bazate pe date.

Iată ce voi construi în această poveste:

Cum sa construiesti o aplicatie de lucru in timp real
Aplicația Todo

Consultați aplicația pe gustare sau pe expo.

Ce este React Native?

Iată ce documente Spune:

React Native vă permite să creați aplicații mobile folosind numai JavaScript. Folosește același design ca React, permițându-vă să compuneți o interfață de utilizare mobilă bogată din componente declarative.

Chiar dacă tocmai începeți cu React sau React Native, ar trebui să puteți urmări această poveste și să vă creați propria aplicație de lucru în timp real.

De ce să folosiți ReactiveSearch? ⚛

Căutare reactivă este o bibliotecă open-source de componente React și React Native UI pentru Elasticsearch cu care am fost coautor niște oameni minunați. Oferă o varietate de componente React Native care pot conectați-vă la orice Elasticsearch grup.

Am scris o altă poveste pe Construirea unui GitHub Repo Explorer cu React și Elasticsearch pe care îl puteți consulta pentru o scurtă prezentare generală a Elasticsearch. Chiar dacă nu ați avut experiență cu Elasticsearch, ar trebui să puteți urmări bine această poveste.

Configurarea lucrurilor ⚒

Vom folosi Versiunea React Native bibliotecii de aici.

Înainte de a începe să construim interfața de utilizare, va trebui să creăm un depozit de date în Elasticsearch. ReactiveSearch funcționează cu orice index Elasticsearch și puteți face cu ușurință folosiți-l cu propriul set de date.

1611207728 788 Cum sa construiesti o aplicatie de lucru in timp real
Vizualizați setul de date al aplicației mele aici. De asemenea, puteți clona acest lucru în propria aplicație

Pentru scurtă durată, puteți utiliza setul meu de date direct sau creați unul pentru dvs. utilizând appbase.io care vă permite să creați un index găzduit Elasticsearch (aka aplicație).

Toate toate sunt structurate în următorul format:

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

Proiectul de pornire

Înainte de a începe, aș recomanda instalarea fire. Pe Linux se poate face pur și simplu prin adăugarea depozitului de fire și executarea comenzii de instalare prin managerul de pachete. Pe Mac, ar trebui să instalați Homebrew mai întâi pentru a simplifica lucrurile. Aici sunt documentele de instalare a firelor pentru mai multe detalii. Următorul lucru pe care îl puteți instala este paznic. Este un serviciu de vizionare a fișierelor care îl va ajuta pe ambalatorul nativ să funcționeze fără probleme.

Am configurat proiectul de pornire cu creați-reacționați-aplicația nativă într-o sucursală GitHub aici. Poți descărcați un fișier zip sau clonați ramura de bază executând următoarea comandă:?

git clone -b base https://github.com/appbaseio-apps/todos-native
  • Apoi instalați dependențele și porniți pachetul:
cd todos-native && yarn && yarn start
  • După începerea pachetului, puteți rula aplicația pe telefon folosind Expo aplicație sau folosind un emulator Android sau IOS:
1611207729 437 Cum sa construiesti o aplicatie de lucru in timp real
Configurare de bază cu toate filele. Clona din aici.

Te scufunzi în cod?

După ce ați clonat codul din ramură de bază, ar trebui să vedeți o structură de directoare ca mai jos:

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

Să prezentăm cu ce vine setarea de bază:

1. Navigare

  • Toate configurațiile necesare pentru conectarea la Elasticsearch sunt la constants/Config.js.
  • Folosim TabNavigator din navigare reactivă pentru arătarea Toate, Activ și Efectuat ecranul todos. Acest lucru este redat de navigation/RootComponent.js. Veți observa RootComponent învelește totul în interiorul ReactiveBase componentă din ReactiveSearch. Această componentă oferă toate datele necesare componentelor copilului ReactiveSearch. Vă puteți conecta propriul index Elasticsearch aici doar actualizând configurațiile din constants/Config.js.

Logica de navigație este prezentă în navigation/MainNavigator.js. Să trecem în revistă cum funcționează. Aici sunt documentele pentru navigarea în file dacă doriți să faceți referire la ceva.

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;
  • TabNavigator funcția acceptă două argumente, primul fiind configurațiile rutei și al doilea fiind TabNavigator configurații. În fragmentul de mai sus, trecem configurațiile pentru afișarea unei bare de navigare a filei în partea de jos și setarea pictogramelor diferite pentru fiecare filă.

2. TodosScreen și TodosContainer

TodosScreen componentă în screens/TodosScreen.js ne înfășoară principalul TodosContainer componentă în components/TodosContainer.js unde vom adăuga diverse componente pentru aplicație. TodosContainer va afișa date filtrate, în funcție de faptul dacă suntem pe Toate, Activ, sau Efectuat filă.

3. API-uri pentru crearea, actualizarea și ștergerea tuturor

API-urile pentru operațiunile CUD pe Elasticsearch sunt prezente în api/todos.js . Conține trei metode simple add, update și destroy care funcționează cu orice index Elasticsearch conform specificațiilor din constants/Config.js. Un punct important de reținut este că fiecare articol pe care îl creăm va avea un element unic _id camp. Putem folosi acest lucru _id câmp pentru actualizarea sau ștergerea unui lucru existent.

Pentru aplicația noastră, vom avea nevoie doar de trei metode pentru adăugarea, crearea sau ștergerea tuturor. Cu toate acestea, puteți găsi o explicație detaliată despre metodele API la documente.

Construiți componentele și interfața de utilizare?

Să începem să adăugăm câteva componente pentru a finaliza funcționalitatea aplicației.

1. Adăugarea Todos

Vom folosi Fab din native-base pentru a reda un buton flotant pentru adăugarea de todos.

1611207729 346 Cum sa construiesti o aplicatie de lucru in timp real
const AddTodoButton = ({ onPress }) => (
  <Fab
      direction="up"
      containerStyle={{}}
      style={{ backgroundColor: COLORS.primary }}
      position="bottomRight"
      onPress={onPress}
  >
      <Icon name="add" />
  </Fab>
);

Acum puteți utiliza această componentă în components/TodosContainer.js.

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

După ce am adăugat butonul, vom vedea ceva de genul acesta:

1611207729 929 Cum sa construiesti o aplicatie de lucru in timp real
După adăugarea AddTodoButton

Acum, când cineva face clic pe acest buton, va trebui să afișăm intrarea pentru adăugarea unui lucru. Permiteți adăugarea codului pentru aceasta în 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>
    );
  }
}

Principalele componente utilizate aici sunt TextInput, Checkbox și Ionicons cu recuzită directă. Folosim title și completed de la state. Vom trece prin recuzită todo, onAdd, onCancelDelete și onBlur de la components/TodosContainer.js. Acestea ne vor ajuta să adăugăm noi todos sau să resetăm vizualizarea dacă doriți să anulați adăugarea tuturor.

Acum putem actualiza components/TodosContainer.js cu modificările necesare pentru redare AddTodo componentă:

...
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>
    );
  }
}

AddTodo componentă este redată în interiorul unui ScrollView componentă. Trecem și de o onPress prop la AddTodoButton pentru a comuta starea și a afișa condiționat AddTodo componentă bazată pe this.state.addingTodo. onAdd prop a trecut la AddTodo creează, de asemenea, un nou lucru folosind add API la api/todos.js.

După ce faceți clic pe butonul Adăugați, vom vedea intrarea pentru adăugarea unei lucrări de genul acesta:

1611207730 721 Cum sa construiesti o aplicatie de lucru in timp real
Adăugarea unui totul

2. Se afișează Todos

După ce ați terminat de adăugat un lucru, acesta este adăugat în Elasticsearch (pe care l-am configurat în constants/Config.js). Toate aceste date pot fi vizualizate în timp real folosind ReactiveSearch Native componente.

Sunt peste 10 nativi Componentele UI pe care biblioteca o oferă. Pentru aplicația noastră todo, vom utiliza în primul rând ReactiveList componentă pentru a arăta starea de todos.

Să adăugăm ReactiveList componentă și obțineți afișarea tuturor. Vom adăuga această componentă în components/TodosContainer.js și metodele necesare pentru ca acesta să funcționeze. Iată cum ReactiveList va fi folosit:


...
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>
    );
  }
}

Nu am adăugat onAllData metodă încă, dar să înțelegem puțin despre recuzita pe care am folosit-o aici:

  • componentId – identificator unic pentru componentă.
  • defaultQuery: interogarea care trebuie aplicată inițial pentru listă. Vom folosi match_all pentru a afișa toate toate în majuscule.
  • stream: dacă doriți să transmiteți actualizări de rezultate noi sau să afișați doar rezultate istorice. Setând acest lucru la true, acum ascultăm și actualizările live ale Todo. Vom adăuga logica legată de streaming mai târziu.
  • onAllData – o funcție de apel invers care primește lista cu toate elementele curente și fluxul (toate noi și orice actualizări) și returnează o componentă React sau JSX de redat. Iată cum arată sintaxa:
<ReactiveList
  onAllData(todos, streamData) {
    // return the list to render
  }
  ...
/>

Puteți citi mai multe despre toate aceste recuzită în detaliu pe ReactiveList pagina docs.

Pentru a vedea ceva, va trebui să returnăm o componentă JSX sau React onAllData suna inapoi. Pentru aceasta, vom folosi React Native FlatList care este compus din Text componente. În pasul următor vom adăuga personalizarea noastră TodoItem componentă.

...
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() {
    ...
  }
}
1611207730 760 Cum sa construiesti o aplicatie de lucru in timp real
Integrarea ReactiveList cu onAllData

3. Adăugarea TodoItem (s)

Apoi, vom crea o componentă separată TodoItem pentru afișarea fiecărui todo, care va conține toate marcajele necesare pentru un element todo, cum ar fi Caseta de bifat, Textși o ștergere Pictogramă. Aceasta intră 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>
    );
  }
}

Această componentă primește todo din recuzită împreună cu onDelete și onUpdate care sunt folosite pentru a actualiza și șterge respectiv elementul todo. Le folosim în locurile necesare folosind onPress elemente componente ale pe care le folosim.

Apoi, putem import și utilizați TodoItem componentă a noastră onAllData în components/TodosContainer.js. Vom trece todo ca un suport împreună cu metodele API pentru update și destroy care va fi folosit de TodoItem componentă.

class TodosContainer extends Component {
  ...
  onAllData = (todos, streamData) => {
    ...
    return (
      <FlatList
        ...
        renderItem={({ item: todo }) => (
          <TodoItem 
            todo={todo}
            onUpdate={this.api.update} 
            onDelete={this.api.destroy}
          />
        )}
      />
    );
  }
}
1611207730 198 Cum sa construiesti o aplicatie de lucru in timp real
După adăugarea TodoItem în TodosContainer

4. Actualizări de date în flux

Este posibil să fi observat că toate afișează bine, cu excepția faptului că nu puteți vizualiza toate actualizate fără a actualiza aplicația. În acest ultim pas, ne vom potrivi cu acea parte lipsă a puzzle-ului.

În secțiunea anterioară, am adăugat un onAllData metoda pentru ReactiveList componentă. Al doilea parametru al onAllData primește actualizări de streaming pe care le vom folosi pentru a menține întotdeauna actualizările tuturor. Iată cum se actualizează onAllData metoda va arăta ca în 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} />
        )}
      />
    );
  };
  ...
}

mergeTodos metoda este prezentă în utils/index.js. Iată cum funcționează:

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;

streamData primește o serie de obiecte todo când sunt create, șterse sau actualizate. Dacă un obiect este actualizat, acesta conține un _updated tasta setată la true. În mod similar, dacă un obiect este șters, acesta conține un _deleted tasta setată la true. Dacă este creat un obiect, acesta nu conține niciunul dintre cele două. Folosind aceste puncte, am adăugat mergeTodos funcţie.

Cu aceasta, ar trebui să puteți vedea modificările aduse articolelor de lucru în timp real! Dacă aveți un dispozitiv / emulator suplimentar care rulează aceeași aplicație, ambele vor transmite și noi actualizări. ?

  1. Aplicația Todos demo, link expo, proiect de start și codul sursă final
  2. ReactiveSearch GitHub repo ⭐️
  3. Căutare reactivă documente

Sper că ți-a plăcut această poveste. Dacă aveți gânduri sau sugestii, vă rog să-mi spuneți și să vă distrați!


Poți să mă urmărești mai departe stare de nervozitate pentru ultimele actualizări. De asemenea, am început să postez postări mai recente pe pagina mea personală blog.

Multumiri speciale către Dhruvdutt Jadhav pentru că m-a ajutat cu această poveste și cu aplicația Todos.