Теперь пользователи ожидают, что приложения будут обновляться и реагировать на их действия в режиме реального времени. К счастью, сейчас существует множество языковых разновидностей и библиотек, которые помогут вам создавать эти высокодинамичные приложения. Из этого руководства вы узнаете, как создать приложение для чата в реальном времени с помощью Pusher, React-native и Redux для управления состоянием приложения.
Вы можете найти полный проект на GitHub .
Установить зависимости
толкатель
Pusher — это коммуникационная платформа в реальном времени, используемая для трансляции сообщений слушателям через их подписку на канал. Слушатели подписываются на канал, и сообщения передаются на канал, и все слушатели получают сообщения.
Сначала вам нужно будет создать учетную запись, а затем установить модуль Pusher npm с помощью следующей команды:
npm init npm install pusher -g npm install pusher-js -g
В разделе « Ключи приложения » проекта Pusher запишите значения app_id
, key
и secret
.
React Native
React Native — это платформа для создания многофункциональных, быстрых и собственных мобильных приложений с теми же принципами, которые используются для создания веб-приложений с помощью React.js . React (для меня) представляет собой лучший способ создания пользовательских интерфейсов, и его стоит изучить, чтобы лучше понять этот учебник и сделать вашу интерфейсную жизнь намного проще. Если вы ранее не использовали React Native, у SitePoint есть много учебных пособий , в том числе Быстрый совет для начала работы .
Redux
Redux — это простой контейнер состояний (самый простой, который я когда-либо использовал), который помогает поддерживать состояние в приложениях React.js (и React Native), используя однонаправленный поток состояний для компонентов пользовательского интерфейса и обратно из компонента пользовательского интерфейса в дерево состояний Redux. , Для более подробной информации, посмотрите этот удивительный видеоурок от человека, который создал Redux. Вы узнаете много принципов функционального программирования в Javascript, и это заставит вас увидеть Javascript в другом свете.
Бэкэнд приложения
Во-первых, приложению необходим бэкэнд для отправки сообщений чата и в качестве точки, откуда сообщения чата передаются всем слушателям. Вы создадите этот бэкенд с помощью Express.js, минималистской веб-платформы, работающей на node.js.
Установите Express с помощью следующей команды:
npm install express -g
Создайте папку для проекта под названием ChatServer и внутри нее файл index.js .
В index.js потребуйте необходимые библиотеки и создайте экспресс-приложение, работающее на порту 5000
.
var express = require('express'); var Pusher = require('pusher'); var app = express(); app.set('port', (process.env.PORT || 5000));
Создайте свой собственный экземпляр библиотеки Pusher, передав ему значения app_id
, key
и secret
:
... const pusher = new Pusher({ appId: 'YOUR PUSHER APP_ID HERE', key: 'YOUR PUSHER KEY HERE', secret: 'YOUR PUSHER SECRET HERE' })
Создайте конечную точку, которая будет получать сообщения чата, и отправлять их в пушер, чтобы выполнить действие широковещания для всех слушателей на канале чата. Вам также необходимо настроить прослушиватель для соединений на заданном порту.
... app.get('/chat/:chat', function(req,res){ const chat_data = JSON.parse(req.params.chat); pusher.trigger('chat_channel', 'new-message', {chat:chat_data}); }); app.listen(app.get('port'), function() { console.log('Node app is running on port', app.get('port')); });
Мобильное приложение
Теперь перейдите к мобильному приложению, поднимитесь на уровень и выполните следующую команду, чтобы создать новый проект React Native:
react-native init PusherChat cd PusherChat
Приложению нужны некоторые другие зависимости:
- Axios — для обещаний и асинхронных запросов к бэкэнду.
- AsyncStorage — Для хранения сообщений чата локально.
- Момент — для установки времени отправки каждого сообщения чата и упорядочения сообщений на основе этого времени.
- Pusher-js — для подключения к толкателю.
- Redux — государственный контейнер
- Redux-thunk — простое промежуточное ПО, помогающее в распределении действий.
- React-redux — привязки React для Redux.
Вы уже должны были установить pusher-js
ранее, и AsyncStorage
является частью React native. Установите остальные, запустив:
npm install --save redux redux-thunk moment axios react-redux
Теперь вы готовы создать приложение чата, начав с действий, которые будет выполнять приложение.
В Redux вам нужно создавать типы действий приложения, потому что когда вы отправляете действия редукторам (менеджерам состояний), вы отправляете действие для выполнения (тип действия) и любые данные, необходимые для выполнения действия (полезная нагрузка). Для этого приложения необходимо отправить чат, получить все чаты и получить сообщение.
Создайте новый файл в src / actions / index.js и добавьте следующее:
import axios from 'axios' import { AsyncStorage } from 'react-native' import moment from 'moment' import Pusher from 'pusher-js/react-native'; export const SEND_CHAT = "SEND_CHAT"; export const GET_ALL_CHATS = "GET_ALL_CHATS"; export const RECEIVE_MESSAGE = " RECEIVE_MESSAGE";
Вам также нужны вспомогательные функции, которые инкапсулируют и возвращают соответствующий action_type
при вызове, чтобы при отправке чата вы отправляли функцию sendChat
и ее полезную нагрузку:
const sendChat = (payload) => { return { type: SEND_CHAT, payload: payload }; }; const getChats = (payload) => { return { type: GET_ALL_CHATS, payload: payload }; }; const newMessage = (payload) => { return { type: RECEIVE_MESSAGE, payload: payload }; };
Вам также нужна функция, которая подписывается на pusher и прослушивает новые сообщения. Для каждого нового сообщения, получаемого этой функцией, добавьте его на устройство AsyncStorage
и AsyncStorage
новое действие сообщения, чтобы обновить состояние приложения.
// function for adding messages to AsyncStorage const addToStorage = (data) => { AsyncStorage.setItem(data.convo_id+data.sent_at, JSON.stringify(data), () => {}) } // function that listens to pusher for new messages and dispatches a new // message action export function newMesage(dispatch){ const socket = new Pusher("3c01f41582a45afcd689"); const channel = socket.subscribe('chat_channel'); channel.bind('new-message', (data) => { addToStorage(data.chat); dispatch(newMessage(data.chat)) } ); }
У вас также есть функция для отправки сообщений чата. Эта функция ожидает два параметра, отправитель и сообщение. В идеальном приложении чата вы должны знать отправителя через устройство или логин, но для этого введите отправителя:
export function apiSendChat(sender,message){ const sent_at = moment().format(); const chat = {sender:sender,message:message, sent_at:sent_at}; return dispatch => { return axios.get(`http://localhost:5000/chat/${JSON.stringify(chat)}`).then(response =>{ }).catch(err =>{ console.log("error", err); }); }; };
Наконец, это функция, которая получает все сообщения чата с устройства AysncStorage
. Это необходимо при первом открытии приложения чата, загрузке всех сообщений из хранилища устройства и начале прослушивания новых сообщений.
export function apiGetChats(){ //get from device async storage and not api return dispatch => { dispatch(isFetching()); return AsyncStorage.getAllKeys((err, keys) => { AsyncStorage.multiGet(keys, (err, stores) => { let chats = []; stores.map((result, i, store) => { // get at each store's key/value so you can work with it chats.push(JSON.parse(store[i][1])) }); dispatch(getChats(chats)) }); }); }; }
Следующим шагом является создание редуктора. Самый простой способ понять, что делает редуктор, — это рассматривать его как кассира банка, который выполняет действия на вашем банковском счете в зависимости от того, какой бланк (тип действия) вы им предоставляете. Если вы предоставляете им квитанцию о снятии (Тип действия) с установленной суммой (полезная нагрузка) для вывода (действие), они удаляют сумму (полезную нагрузку) с вашего банковского счета (штат). Вы также можете добавить деньги (действие + полезная нагрузка) со счетом депозита (Тип действия) на свой счет (штат).
Таким образом, редуктор — это функция, которая влияет на состояние приложения на основе отправленного действия, а действие содержит его тип и полезную нагрузку. В зависимости от типа действия редуктор влияет на состояние приложения.
Создайте новый файл с именем src / redurs / index.js и добавьте следующее:
import { combineReducers } from 'redux'; import { SEND_CHAT, GET_ALL_CHATS, RECEIVE_MESSAGE} from './../actions' // THE REDUCER const Chats = (state = {chats:[]}, actions) => { switch(actions.type){ case GET_ALL_CHATS: return Object.assign({}, state, { process_status:"completed", chats:state.chats.concat(actions.payload) }); case SEND_CHAT: case NEW_MESSAGE: return Object.assign({}, state, { process_status:"completed", chats:[...state.chats,actions.payload] }); default: return state; } }; const rootReducer = combineReducers({ Chats }) export default rootReducer;
Далее создайте магазин. Продолжая аналогию с банковскими кассирами, магазин похож на склад, где хранятся все банковские счета (состояния). На данный момент у вас есть одно состояние, Чаты, и есть доступ к нему, когда вам это нужно.
Создайте новый файл src / store / configureStore.js и добавьте следующее:
import { createStore, applyMiddleware } from 'redux' import thunkMiddleware from 'redux-thunk' import createLogger from 'redux-logger' import rootReducer from '../reducers' const createStoreWithMiddleware = applyMiddleware( thunkMiddleware, createLogger() )(createStore) export default function configureStore(initialState) { const store = createStoreWithMiddleware(rootReducer, initialState) return store }
Теперь давайте создадим основной компонент чата, который отображает все сообщения чата и позволяет пользователю отправлять сообщения чата, вводя свое сообщение. Этот компонент использует React Native ListView
.
Создайте новый файл src / Screens / convationscreen.js и добавьте следующее:
import React, { Component, View, Text, StyleSheet, Image, ListView, TextInput, Dimensions} from 'react-native'; import Button from './../components/button/button'; import { Actions } from 'react-native-router-flux'; import KeyboardSpacer from 'react-native-keyboard-spacer'; import { connect } from 'react-redux'; import moment from 'moment'; import { apiSendChat, newMesage } from './../actions/'; const { width, height } = Dimensions.get('window'); const styles = StyleSheet.create({ container: { flex: 1 }, main_text: { fontSize: 16, textAlign: "center", alignSelf: "center", color: "#42C0FB", marginLeft: 5 }, row: { flexDirection: "row", borderBottomWidth: 1, borderBottomColor: "#42C0FB", marginBottom: 10, padding:5 }, back_img: { marginTop: 8, marginLeft: 8, height: 20, width: 20 }, innerRow: { flexDirection: "row", justifyContent: "space-between" }, back_btn: {}, dp: { height: 35, width: 35, borderRadius: 17.5, marginLeft:5, marginRight:5 }, messageBlock: { flexDirection: "column", borderWidth: 1, borderColor: "#42C0FB", padding: 5, marginLeft: 5, marginRight: 5, justifyContent: "center", alignSelf: "flex-start", borderRadius: 6, marginBottom: 5 }, messageBlockRight: { flexDirection: "column", backgroundColor: "#fff", padding: 5, marginLeft: 5, marginRight: 5, justifyContent: "flex-end", alignSelf: "flex-end", borderRadius: 6, marginBottom: 5 }, text: { color: "#5c5c5c", alignSelf: "flex-start" }, time: { alignSelf: "flex-start", color: "#5c5c5c", marginTop:5 }, timeRight: { alignSelf: "flex-end", color: "#42C0FB", marginTop:5 }, textRight: { color: "#42C0FB", alignSelf: "flex-end", textAlign: "right" }, input:{ borderTopColor:"#e5e5e5", borderTopWidth:1, padding:10, flexDirection:"row", justifyContent:"space-between" }, textInput:{ height:30, width:(width * 0.85), color:"#e8e8e8", }, msgAction:{ height:29, width:29, marginTop:13 } }); const username = 'DUMMY_USER'; function mapStateToProps(state) { return { Chats: state.Chats, dispatch: state.dispatch } } class ConversationScreen extends Component { constructor(props) { super(props); const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 != r2}); this.state = { conversation: ds, text:"", username } } componentDidMount(){ const {dispatch, Chats} = this.props; const chats = Chats; chats.sort((a,b)=>{ return moment(a.sent_at).valueOf() - moment(b.sent_at).valueOf(); }); this.setState({ conversation: this.state.conversation.cloneWithRows(chats) }) } componentWillReceiveProps(nextProps) { const {dispatch, Chats} = this.props; const chats = Chats; chats.sort((a,b)=>{ return moment(a.sent_at).valueOf() - moment(b.sent_at).valueOf(); }); this.setState({ conversation: this.state.conversation.cloneWithRows(chats) }) } renderSenderUserBlock(data){ return ( <View style={styles.messageBlockRight}> <Text style={styles.textRight}> {data.message} </Text> <Text style={styles.timeRight}>{moment(data.time).calendar()}</Text> </View> ) } renderReceiverUserBlock(data){ return ( <View style={styles.messageBlock}> <Text style={styles.text}> {data.message} </Text> <Text style={styles.time}>{moment(data.time).calendar()}</Text> </View> ) } renderRow = (rowData) => { return ( <View> {rowData.sender == username ? this.renderSenderUserBlock(rowData) : this.renderReceiverUserBlock(rowData)} </View> ) } sendMessage = () => { const message = this.state.text; const username = this.state.username; const {dispatch, Chats} = this.props; dispatch(apiSendChat(username,message)) } render() { return ( <View style={styles.container}> <View style={styles.row}> <Button style={styles.back_btn} onPress={() => Actions.pop()}> <Image source={require('./../assets/back_chevron.png')} style={styles.back_img}/> </Button> <View style={styles.innerRow}> <Image source={{uri:"https://avatars3.githubusercontent.com/u/11190968?v=3&s=460"}} style={styles.dp}/> <Text style={styles.main_text}>GROUP CHAT</Text> </View> </View> <ListView renderRow={this.renderRow} dataSource={this.state.conversation}/> <View style={styles.input}> <TextInput style={styles.textInput} onChangeText={(text) => this.setState({username:text})} placeholder="Send has?"/> <TextInput style={styles.textInput} onChangeText={(text) => this.setState({text:text})} placeholder="Type a message"/> <Button onPress={this.sendMessage}> <Image source={require('./../assets/phone.png')} style={styles.msgAction}/> </Button> </View> <KeyboardSpacer/> </View> ) } } export default connect(mapStateToProps)(ConversationScreen)
React Native предоставляет вам функцию жизненного цикла componentWillReceiveProps(nextProps)
вызываемую всякий раз, когда компонент собирается получить новые свойства (реквизиты), и именно в этой функции вы обновляете состояние компонента с помощью сообщений чата.
Функция renderSenderUserBlock
отображает сообщение чата, отправленное пользователем, а функция renderReceiverUserBlock
отображает сообщение чата, полученное пользователем.
Функция sendMessage
получает сообщение из состояния, которое пользователь намеревается отправить, имя пользователя получателя и отправляет действие для отправки сообщения чата.
Функция renderRow
переданная компоненту Listview
содержит свойства и отображает данные каждой строки в Listview
.
Вам нужно передать состояние компонентам приложения и использовать для этого библиотеку React-redux. Это позволяет подключать компоненты для редукции и доступа к состоянию приложения.
React-Redux предоставляет вам 2 вещи:
- Компонент «Провайдер», который позволяет передать хранилище в качестве свойства.
- Функция «Соединить», которая позволяет компоненту подключаться к избыточному. Он передает избыточное состояние, к которому компонент подключается, как свойства для компонента.
Наконец, создайте app.js, чтобы связать все вместе:
import React, { Component, StyleSheet, Dimensions} from 'react-native'; import { Provider } from 'react-redux' import configureStore from './store/configureStore' const store = configureStore(); import ConversationScreen from './screens/conversation-screen'; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", }, tabBarStyle: { flex: 1, flexDirection: "row", backgroundColor: "#95a5a6", padding: 0, height: 45 }, sceneStyle: { flex: 1, backgroundColor: "#fff", flexDirection: "column", paddingTop:20 } }) export default class PusherChatApp extends Component { render() { return ( <Provider store={store}> <ConversationScreen /> </Provider> ) } }
И ссылаться на app.js в index.android.js и index.ios.js , заменяя любое текущее содержимое:
import React, { AppRegistry, Component, StyleSheet, Text, View } from 'react-native'; import PusherChatApp from './src/app' AppRegistry.registerComponent('PusherChat', () => PusherChatApp);
Поговори со мной
И это все, масштабируемое и производительное приложение реального времени, которое вы можете легко добавлять и улучшать для своих нужд. Если у вас есть какие-либо вопросы или комментарии, пожалуйста, дайте мне знать ниже.