๐Ÿ“ฑ Cross Platform/React Native

[React Native] ChatApp Socket.io Room ์ ์šฉํ•˜๊ธฐ (feat. TypeScript)

Fomagran ๐Ÿ’ป 2022. 8. 10. 05:33
728x90
๋ฐ˜์‘ํ˜•

์•ˆ๋…•ํ•˜์„ธ์š” Foma ์ž…๋‹ˆ๋‹ค.

 

์ €๋ฒˆ ๊ธ€์—์„œ Node.js์—์„œ Socket.io๋ฅผ ์ด์šฉํ•˜์—ฌ Room์„ ๋งŒ๋“ค์–ด ๋ณด์•˜๋Š”๋ฐ์š”.

 

(ํ˜น์‹œ ์•ˆ๋ณด์‹  ๋ถ„๋“ค์€ ์—ฌ๊ธฐ ์—์„œ ํ™•์ธํ•ด ์ฃผ์„ธ์š”!)

 

์˜ค๋Š˜์€ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์—์„œ ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ํ”„๋ก ํŠธ์— ์ ์šฉํ•ด ๋ณด๋Š” ๊ณผ์ •์„ ์ •๋ฆฌํ•ด ๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์‹œ์ž‘ํ• ๊ฒŒ์š”~


Preview


Install

 

npm install @react-navigation/native
npm install @react-navigation/native-stack
npm install react-native-screens
npm install react-native-safe-area-context
npm install socket.io-client

ScreenEnums

 

์Šคํฌ๋ฆฐ ์ข…๋ฅ˜๋ฅผ Enum์œผ๋กœ ๋งŒ๋“ค์–ด ๋†“์Šต๋‹ˆ๋‹ค.

 

export enum ScreenEnums {
  Login = 'LoginScreen',
  Chat = 'ChatScreen',
  ChatRoom = 'ChatRoomScreen',
}

Navigation/index.tsx

 

ํ™”๋ฉด ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์„ค์ •ํ•ด ์ค๋‹ˆ๋‹ค.

 

(์ž์„ธํ•œ ์„ค๋ช…์€ ์—ฌ๊ธฐ ์—์„œ ํ™•์ธํ•ด ์ฃผ์„ธ์š”!)

 

import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {NavigationContainer} from '@react-navigation/native';
import React from 'react';

import LoginScreen from '../Screens/LoginScreen';
import ChatScreen from '../Screens/ChatScreen';
import {ChatRoomScreen} from '../Screens/ChatRoomScreen';
import {ScreenEnums as screens} from '../Models/ScreenEnums';

const Stack = createNativeStackNavigator();

export default function Navigation() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName={screens.Login}>
        <Stack.Screen name={screens.Login} component={LoginScreen} />
        <Stack.Screen name={screens.ChatRoom} component={ChatRoomScreen} />
        <Stack.Screen name={screens.Chat} component={ChatScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Login.tsx

 

์œ ์ €์˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜๊ณ  ์ž…์žฅํ•  ํ™”๋ฉด์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

 

 

 

์ž…์žฅ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ChatRoomScreen์œผ๋กœ navigate ๋˜๋Š”๋ฐ ๊ทธ ๋•Œ ์ธํ’‹ ํ…์ŠคํŠธ๋ฅผ user ๊ฐ’์œผ๋กœ ์ „๋‹ฌํ•ด ์ค๋‹ˆ๋‹ค.

 

import {Pressable, Text, TextInput, View} from 'react-native';
import React, {useState} from 'react';
import {styles} from '../Styles/LoginStyles';
import {ScreenEnums as screens} from '../Models/ScreenEnums';

export default function LoginScreen({navigation}) {
  const [inputText, setInputText] = useState('');
  return (
    <View>
      <TextInput
        style={styles.textInput}
        onChangeText={text => setInputText(text)}></TextInput>
      <Pressable
        onPress={() => {
          navigation.navigate(screens.ChatRoom, {
            navigation: navigation,
            user: inputText,
          });
        }}>
        <Text>์ž…์žฅ</Text>
      </Pressable>
    </View>
  );
}

 

styles

 

import {StyleSheet} from 'react-native';

export const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 30,
    padding: 8,
  },
  textInput: {
    borderRadius: 10,
    borderWidth: 1,
    borderColor: 'black',
  },
});

ChatRoomCell.tsx

 

 

์ฑ—๋ฃธ ๋ฆฌ์ŠคํŠธ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

 

import React from 'react';
import {Text, View} from 'react-native';
import {styles} from '../Styles/ChaRoomCellStyles';

interface ChatRoomCellParams {
  chatRoom: ChatRoom;
}

export default function ChatRoomCell({chatRoom}: ChatRoomCellParams) {
  return (
    <View style={styles.container}>
      <Text style={styles.title}> {chatRoom.title} </Text>
      <Text style={styles.message}> {chatRoom.lastMessage} </Text>
      <Text style={styles.time}> 2022/08/09 </Text>
    </View>
  );
}

 

styles

 

import {StyleSheet} from 'react-native';

export const styles = StyleSheet.create({
  container: {
    margin: 20,
    padding: 20,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: 'black',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: 'black',
  },
  message: {
    fontSize: 16,
    color: 'black',
  },
  time: {
    alignSelf: 'flex-end',
    fontSize: 14,
    color: '#D3D3D3',
  },
});

ChaRoom.ts

 

์ฑ—๋ฃธ์˜ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

 

interface ChatRoom {
  title: string;
  lastMessage: string;
  time: Date;
}

ChatRoomScreen.tsx

 

์ฑ„ํŒ…๋ฃธ ํ™”๋ฉด์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

 

 

์ฑ—๋ฃธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ฑ—์Šคํฌ๋ฆฐ์œผ๋กœ ์ด๋™ํ•˜๊ฒŒ ๋˜๊ณ , ๊ทธ ๋•Œ ์œ ์ €์˜ ์ •๋ณด์™€ ๋ฃธ ์ด๋ฆ„์„ ๊ฐ™์ด ์ „๋‹ฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

import {NativeStackHeaderProps} from '@react-navigation/native-stack';
import React from 'react';
import {FlatList, Pressable, Text, View} from 'react-native';
import ChatRoomCell from '../Components/ChatRoomCell';
import {ScreenEnums as screens} from '../Models/ScreenEnums';

type ChatRoomParams = {
  route: {
    params: {
      navigation: any;
      user: string;
    };
  };
};

type Navigation = NativeStackHeaderProps & ChatRoomParams;

export function ChatRoomScreen({route}: Navigation) {
  const mockData: ChatRoom[] = [
    {title: 'A', lastMessage: 'test A', time: new Date()},
    {title: 'B', lastMessage: 'test B', time: new Date()},
    {title: 'C', lastMessage: 'test C', time: new Date()},
  ];

  return (
    <View>
      <FlatList
        contentContainerStyle={{paddingBottom: 50}}
        data={mockData}
        keyExtractor={item => item.lastMessage}
        renderItem={({item}) => (
          <Pressable
            onPress={() => {
              route.params.navigation.navigate(screens.Chat, {
                user: route.params.user,
                room: item.title,
              });
            }}>
            <ChatRoomCell chatRoom={item}></ChatRoomCell>
          </Pressable>
        )}
      />
    </View>
  );
}

 

styles

 

import {StyleSheet} from 'react-native';

export const styles = StyleSheet.create({
  container: {
    margin: 20,
    padding: 20,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: 'black',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: 'black',
  },
  message: {
    fontSize: 16,
    color: 'black',
  },
  time: {
    alignSelf: 'flex-end',
    fontSize: 14,
    color: '#D3D3D3',
  },
});

ChatScreen.tsx

 

 

 

Chat ํ™”๋ฉด์— ํ•„์š”ํ•œ ๊ฐ’๋“ค์„ ์„ธํŒ…ํ•ด ์ค๋‹ˆ๋‹ค.

 

import {Text, View, FlatList, TextInput, Pressable} from 'react-native';
import React, {useState, useEffect, useRef} from 'react';
import {ChatScreenStyles as styles} from '../Styles/ChatScreenStyles';
import io from 'socket.io-client';
import {NativeStackHeaderProps} from '@react-navigation/native-stack';

type ChatScreenParams = {
  route: {
    params: {
      navigation: any;
      user: string;
      room: string;
    };
  };
};

type Navigation = NativeStackHeaderProps & ChatScreenParams;

export default function ChatScreen({route}: Navigation) {
  const [messageText, setMessageText] = useState('');
  const [serverMessages, setServerMessages] = useState([]);
  const serverMessagesList: Message[] = [];
  const webSocket = useRef(null);
  const [user, setUser] = useState('');

 

useEffect์— ์†Œ์ผ“์—์„œ ๋ฐ›์•„์˜จ ์ด๋ฒคํŠธ๋“ค์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

welcome: ์œ ์ €๊ฐ€ ์ ‘์†ํ•œ ์ •๋ณด,

message: ๋ฉ”์„ธ์ง€ ์ •๋ณด,

leave: ์œ ์ €๊ฐ€ ๋– ๋‚˜๋Š” ์ •๋ณด

 

  useEffect(() => {
    setUser(route.params.user);
    webSocket.current = io(`http://192.168.111.34:3001/chat`);
    webSocket.current.on('connect', () => {
      let message = {
        type: 'Welcome',
        user: route.params.user,
        message: `${route.params.user} ๋‹˜์ด ์ž…์žฅํ•˜์…จ์Šต๋‹ˆ๋‹ค.`,
        room: route.params.room,
      };

      webSocket.current.emit('welcome', message);
      console.log('Connected Server');
    });

    webSocket.current.on('message', e => {
      console.log(route.params.user, 'message', e);
      serverMessagesList.push(e);
      setServerMessages([...serverMessagesList]);
    });

    webSocket.current.on('welcome', e => {
      console.log('welcome', e);
      serverMessagesList.push(e);
      setServerMessages([...serverMessagesList]);
    });

    webSocket.current.on('leave', e => {
      serverMessagesList.push(e);
      setServerMessages([...serverMessagesList]);
    });

    webSocket.current.on('error', e => {
      console.log(e.message);
    });

    webSocket.current.on('disconnect', e => {
      console.log('Disconnected. Check internet or server.');
    });

    return () => {
      let message = {
        type: 'Leave',
        user: route.params.user,
        message: `${route.params.user} ๋‹˜์ด ํ‡ด์žฅํ•˜์…จ์Šต๋‹ˆ๋‹ค.`,
        room: route.params.room,
      };
      webSocket.current.emit('leave', message);
      webSocket.current.disconnect();
    };
  }, []);

 

๋ฉ”์„ธ์ง€๋ฅผ ์„œ๋ฒ„์— ๋ณด๋‚ด๋Š” ํ•จ์ˆ˜

 

  const sendMessage = () => {
    let message = {
      type: 'Chat',
      user: user,
      message: messageText,
      room: route.params.room,
    };
    webSocket.current.emit('message', message);
    setMessageText('');
  };

 

View

 

  return (
    <View>
      <View
        style={{
          padding: 5,
          flexGrow: 1,
        }}>
        <FlatList
          style={styles.list}
          contentContainerStyle={{paddingBottom: 50}}
          data={serverMessages}
          keyExtractor={(item, index) => item.message + index}
          renderItem={({item}) =>
            item.type == 'Welcome' || item.type == 'Leave' ? (
              <Text style={styles.welcomeChat}>{item.message}</Text>
            ) : item.user == user ? (
              <Text style={styles.myChat}>{item.message}</Text>
            ) : (
              <Text style={styles.otherChat}>{item.message}</Text>
            )
          }
        />
      </View>
      <View style={styles.bottomContainer}>
        <TextInput
          style={styles.input}
          placeholder={'Add Message'}
          onChangeText={text => {
            setMessageText(text);
          }}
          value={messageText}></TextInput>
        <Pressable onPress={sendMessage} disabled={messageText == ''}>
          <Text style={styles.send}>Send</Text>
        </Pressable>
      </View>
    </View>
  );

 

styles

 

import {StyleSheet} from 'react-native';

export const ChatScreenStyles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 30,
    padding: 8,
  },
  welcomeChat: {
    alignSelf: 'center',
    padding: 10,
    color: 'white',
    backgroundColor: '#212124',
    fontSize: 16,
    borderRadius: 20,
    marginVertical: 10,
  },
  myChat: {
    alignSelf: 'flex-end',
    padding: 10,
    color: 'white',
    backgroundColor: 'yellow',
    fontSize: 16,
    borderRadius: 20,
    marginVertical: 10,
  },
  otherChat: {
    alignSelf: 'flex-start',
    padding: 10,
    color: 'white',
    backgroundColor: 'gray',
    fontSize: 16,
    borderRadius: 20,
    marginVertical: 10,
  },
  input: {
    width: '70%',
    borderWidth: 1,
    borderColor: 'black',
    padding: 10,
    marginRight: 30,
  },
  send: {
    backgroundColor: 'black',
    color: 'white',
    padding: 20,
    textAlign: 'center',
    textAlignVertical: 'center',
  },
  bottomContainer: {
    marginHorizontal: 10,
    flexDirection: 'row',
    marginBottom: 80,
  },
  list: {
    height: '80%',
  },
});
728x90
๋ฐ˜์‘ํ˜•