Frontend/React-Native

[React Native] Toast message 만들기

w00se 2021. 9. 19. 18:19

https://pixabay.com/ko/photos/놀이터-회전-신장-매력-5188459/

 

Toast message는 아래 예시처럼 짧은 시간 나타났다 사라지는 메시지를 말합니다.

 

Toast 메시지 예시

 

Toast message는 Android에서 기본으로 지원하는 기능으로 React Native의 공식 문서에도 Android 한정으로 지원을 해주고 있습니다.

https://reactnative.dev/docs/toastandroid

 

이번 게시글에서는 javascript로 Toast 메시지를 구현하는 방법을 정리하려 합니다.

 

필요 라이브러리

- react-native-reanimeted

* react-native-reanimated v2 설치가 필요하신 분은 아래 링크를 참고해도 좋을 거 같아요😊

https://coding-w00se.tistory.com/39

 

진행 순서

1. Toast 기능 구현

 

step 0. View 생성

기능 구현을 위해 생성한 파일은 총 두 개입니다.

- ToastTestScreen.js: Toast 메시지를 테스트할 screen 파일

- Toast.js: Toast message 컴포넌트

 

ToastTestScreen.js

import React, { useCallback, useRef } from 'react';
import {
    View,
    StyleSheet,
    Button
} from 'react-native';
import Toast from '../components/Toast';

function ToastTestScreen(props) {
    const toastRef = useRef(null);

    const onPress = useCallback(()=>{
        toastRef.current.show("토스트 메시지~!");
    }, []);
    
    return (
        <View style={styles.rootContainer}>
            <Button title="토스트 팝업!" onPress={onPress}/>
            <Toast ref={toastRef}/>
        </View>
    );
}

const styles = StyleSheet.create({
    rootContainer: {
        flex: 1,
        backgroundColor: "#ffffff",
        justifyContent: "center",
        alignItems: "center",
    }
});

export default ToastTestScreen

 

Toast.js

import React, { useRef, useCallback, forwardRef, useImperativeHandle } from 'react';
import {
    View,
    StyleSheet,
    Text
} from 'react-native';
import Animated, {
    useSharedValue,
    useAnimatedStyle,
    withTiming,
    withSequence,
    runOnJS,
} from 'react-native-reanimated';

const Toast = forwardRef((props, ref) => {
    const toastOpacity = useSharedValue(0);
    const isShowed = useRef(false);

    const animatedStyle = useAnimatedStyle(()=>{
        return {
            opacity: toastOpacity.value,
        }
    }, []);

    useImperativeHandle(ref, () => ({
        show: show
    }));
   
    const show = useCallback((message) => {
    }, []);

    return (
        <Animated.View style={[ styles.rootContainer, animatedStyle ]}>
            <Text style={styles.message}>이것은 아마 토스트 메시지?</Text>
        </Animated.View>
    );
})

const styles = StyleSheet.create({
    rootContainer: {
        position: "absolute",
        bottom: 100,
        backgroundColor: "rgb(95, 209, 251)",
        paddingVertical: 9,
        paddingHorizontal: 23,
        borderRadius: 100,
    },
    message: {
        color: "rgb(255, 255, 255)"
    }
});

export default Toast;

 

step 1. Toast 기능 구현

기능은 구현은 아래 두 가지 단계로 나뉩니다.

 

1) 메시지를 보이게 하는 이벤트 구현

2) 이벤트 호출에 따라 메시지의 투명도를 조절

 

1. 메시지를 보이게 하는 이벤트 구현

-> Toast.js의 부모 컴포넌트에서 Toast 메시지를 보이게 하는 함수에 접근할 수 있도록 해야 합니다.

해당 기능은 useImperativeHandle hook으로 할 수 있습니다.

 

*useImperativeHandle hook에 대한 설명은 공식 문서를 통해 확인할 수 있습니다.

https://ko.reactjs.org/docs/hooks-reference.html#useimperativehandle

 

2. 이벤트 호출에 따라 메시지의 투명도를 조절

-> 이벤트가 호출됐을 때 Toast 메시지의 opacity를 1로 만드는 애니메이션 실행, 애니메이션이 완료되면 다시 opacity를 0으로 만드는 애니메이션 실행 순으로 구현할 수 있습니다.

 

Toast 기능 구현 관련 코드는 아래와 같습니다.

 

- Toast.js

import React, { useState, useRef, useCallback, forwardRef, useImperativeHandle } from 'react';
import {
    View,
    StyleSheet,
    Text
} from 'react-native';
import Animated, {
    useSharedValue,
    useAnimatedStyle,
    withTiming,
    withSequence,
    runOnJS,
} from 'react-native-reanimated';

const Toast = forwardRef((props, ref) => {
    const [ message, setMessage ] = useState("");
    const toastOpacity = useSharedValue(0);
    /**
     * isShowed를 통해 애니메이션이 중복으로 실행되는 것을 방지
     */
    const isShowed = useRef(false);

    const animatedStyle = useAnimatedStyle(()=>{
        return {
            opacity: toastOpacity.value,
        }
    }, []);
    
    /**
     * useImperativeHandler를 통해 show 함수를 외부에서 접근할 수 있도록 허용
     */
    useImperativeHandle(ref, () => ({
        show: show
    }));

    const turnOnIsShow = useCallback(()=>{
        isShowed.current = false;
    }, []);
    
    /**
     * show 함수가 실행되면 toastOpacity를 변경하는 animation이 실행된다.
     * 애니메이션은 opacity를 1로 만드는 애니메이션 -> opacity를 0으로 만드는 애니메이션 순으로 실행된다.
     */
    const show = useCallback((message) => {
        if (!isShowed.current) {
            setMessage(message);
            isShowed.current = true;
            toastOpacity.value = withSequence(
                withTiming(1, { duration: 2000 }), 
                withTiming(0, { duration: 2000 }, () => {
                    runOnJS(turnOnIsShow)();
                }),
            );
        }
    }, []);

    return (
        <Animated.View style={[ styles.rootContainer, animatedStyle ]}>
            <Text style={styles.message}>{message}</Text>
        </Animated.View>
    );
})

const styles = StyleSheet.create({
    rootContainer: {
        position: "absolute",
        bottom: 100,
        backgroundColor: "rgb(95, 209, 251)",
        paddingVertical: 9,
        paddingHorizontal: 23,
        borderRadius: 100,
    },
    message: {
        color: "rgb(255, 255, 255)"
    }
});

export default Toast;

 

여기까지 구현하면 아래와 같이 구현이 됩니다.

 

화면 예시

 

 

전체 코드

ToastTestScreen.js

import React, { useCallback, useRef } from 'react';
import {
    View,
    StyleSheet,
    Button
} from 'react-native';
import Toast from '../components/Toast';

function ToastTestScreen(props) {
    const toastRef = useRef(null);

    const onPress = useCallback(()=>{
        toastRef.current.show("토스트 메시지~!");
    }, []);
    
    return (
        <View style={styles.rootContainer}>
            <Button title="토스트 팝업!" onPress={onPress}/>
            <Toast ref={toastRef}/>
        </View>
    );
}

const styles = StyleSheet.create({
    rootContainer: {
        flex: 1,
        backgroundColor: "#ffffff",
        justifyContent: "center",
        alignItems: "center",
    }
});

export default ToastTestScreen

 

Toast.js

import React, { useState, useRef, useCallback, forwardRef, useImperativeHandle } from 'react';
import {
    View,
    StyleSheet,
    Text
} from 'react-native';
import Animated, {
    useSharedValue,
    useAnimatedStyle,
    withTiming,
    withSequence,
    runOnJS,
} from 'react-native-reanimated';

const Toast = forwardRef((props, ref) => {
    const [ message, setMessage ] = useState("");
    const toastOpacity = useSharedValue(0);
    const isShowed = useRef(false);

    const animatedStyle = useAnimatedStyle(()=>{
        return {
            opacity: toastOpacity.value,
        }
    }, []);
    
    useImperativeHandle(ref, () => ({
        show: show
    }));

    const turnOnIsShow = useCallback(()=>{
        isShowed.current = false;
    }, []);
    
    const show = useCallback((message) => {
        if (!isShowed.current) {
            setMessage(message);
            isShowed.current = true;
            toastOpacity.value = withSequence(
                withTiming(1, { duration: 2000 }), 
                withTiming(0, { duration: 2000 }, () => {
                    runOnJS(turnOnIsShow)();
                }),
            );
        }
    }, []);

    return (
        <Animated.View style={[ styles.rootContainer, animatedStyle ]}>
            <Text style={styles.message}>{message}</Text>
        </Animated.View>
    );
})

const styles = StyleSheet.create({
    rootContainer: {
        position: "absolute",
        bottom: 100,
        backgroundColor: "rgb(95, 209, 251)",
        paddingVertical: 9,
        paddingHorizontal: 23,
        borderRadius: 100,
    },
    message: {
        color: "rgb(255, 255, 255)"
    }
});

export default Toast;

 

 

 

 

 


읽어 주셔서 감사합니다 :)

잘못된 부분이 있다면 댓글로 편히 알려주세요😊