LifeSports Application(ReactNative & Nest.js) - 21. post-service(2)

33559 ワード

#1冗長モジュール


post-serviceで作成したエンドポイントにapiを作成します.
  • ./src/lib/api/post.js
  • import client from './client';
    
    export const write = ({
        type,
        title,
        content,
        userId,
        writer,
        rental
    }) => client.post(`http://10.0.2.2:8000/post-service/`, {
        type,
        title,
        content,
        userId,
        writer,
        rental
    });
    
    export const getAll = () => client.get(`http://10.0.2.2:8000/post-service/`);
    
    export const getPostsByType = type => client.get(`http://10.0.2.2:8000/post-service/posts/type/${type}`);
    
    export const getOne = _id => client.get(`http://10.0.2.2:8000/post-service/${_id}/post`);
    
    export const getPostsByUserId = userId => client.get(`http://10.0.2.2:8000/post-service/${userId}/posts`);
    
    export const getPostsByKeyword = keyword => client.get(`http://10.0.2.2:8000/post-service/posts/keyword/${keyword}`);
    
    export const deleteOne = _id => client.delete(`http://10.0.2.2:8000/post-service/${id}/post}`);
    postを作成および削除するためのredusモジュールを作成します.
  • ./src/modules/post.js
  • import { createAction, handleActions } from "redux-actions";
    import createRequestSaga, { createRequestActionTypes } from "../lib/createRequestSaga";
    import * as postAPI from '../lib/api/post';
    import { takeLatest } from "@redux-saga/core/effects";
    
    const INITIALIZE = 'post/INITIALIZE';
    
    const CHANGE_FIELD = 'post/CHANGE_FIELD';
    
    const [
        WRITE,
        WRITE_SUCCESS,
        WRITE_FAILURE
    ] = createRequestActionTypes('post/WRITE');
    
    const [
        DELETE,
        DELETE_SUCCESS,
        DELETE_FAILURE
    ] = createRequestActionTypes('post/DELETE');
    
    export const initialize = createAction(INITIALIZE);
    
    export const changeField = createAction(CHANGE_FIELD, ({
        key,
        value
    }) => ({
        key,
        value
    }));
    
    export const writePost = createAction(WRITE, ({
        type,
        title,
        content,
        userId,
        writer,
        rental
    }) => ({
        type,
        title,
        content,
        userId,
        writer,
        rental
    }));
    
    export const deletePost = createAction(DELETE, _id => _id);
    
    const writePostSaga = createRequestSaga(WRITE, postAPI.write);
    
    const deletePostSaga = createRequestSaga(DELETE, postAPI.deleteOne);
    
    export function* postSaga() {
        yield takeLatest(WRITE, writePostSaga);
        yield takeLatest(DELETE, deletePostSaga);
    };
    
    const initialState = {
        type: null,
        title: null,
        content: null,
        userId: null,
        writer: null,
        rental: '',
        post: null,
        postError: null,
        message: null,
    };
    
    const post = handleActions(
        {
            [INITIALIZE]: state => initialState,
            [CHANGE_FIELD]: (state, { payload: { key, value }}) => ({
                ...state,
                [key]: value,
            }),
            [WRITE_SUCCESS]: (state, { payload: post }) => ({
                ...state,
                post,
            }),
            [WRITE_FAILURE]: (state, { payload: postError }) => ({
                ...state,
                postError,
            }),
            [DELETE_SUCCESS]: (state, { payload: message }) => ({
                ...state,
                message,
            }),
            [DELETE_FAILURE]: (state, { payload: postError }) => ({
                ...state,
                postError
            }),
        },
        initialState,
    );
    
    export default post;
    次にpostを読み出すredusモジュールを作成します.
  • ./src/modules/posts.js
  • import { createAction, handleActions } from "redux-actions";
    import createRequestSaga, { createRequestActionTypes } from "../lib/createRequestSaga";
    import * as postAPI from '../lib/api/post';
    import { takeLatest } from "@redux-saga/core/effects";
    
    const INITIALIZE = 'posts/INITIALIZE';
    
    const [
        READ_POST,
        READ_POST_SUCCESS,
        READ_POST_FAILURE
    ] = createRequestActionTypes('posts/READ_POST');
    
    const [
        LIST_ALL,
        LIST_ALL_SUCCESS,
        LIST_ALL_FAILURE
    ] = createRequestActionTypes('posts/LIST_ALL');
    
    const [
        LIST_POSTS_TYPE,
        LIST_POSTS_TYPE_SUCCESS,
        LIST_POSTS_TYPE_FAILURE
    ] = createRequestActionTypes('posts/LIST_POST_TYPES')
    
    const [
        LIST_POSTS_USERID,
        LIST_POSTS_USERID_SUCCESS,
        LIST_POSTS_USERID_FAILURE
    ] = createRequestActionTypes('posts/LIST_POSTS_USERID');
    
    const [
        LIST_POSTS_KEYWORD,
        LIST_POSTS_KEYWORD_SUCCESS,
        LIST_POSTS_KEYWORD_FAILURE
    ] = createRequestActionTypes('posts/LIST_POSTS_KEYWORD');
    
    export const readPost = createAction(READ_POST, _id => _id);
    
    export const listAll = createAction(LIST_ALL);
    
    export const listType = createAction(LIST_POSTS_TYPE, type => type);
    
    export const listUserId = createAction(LIST_POSTS_USERID, userId => userId);
    
    export const listKeyword = createAction(LIST_POSTS_KEYWORD, keyword => keyword);
    
    const readPostSaga = createRequestSaga(READ_POST, postAPI.getOne);
    
    const listAllSaga = createRequestSaga(LIST_ALL, postAPI.getAll);
    
    const listTypeSaga = createRequestSaga(LIST_POSTS_TYPE, postAPI.getPostsByType);
    
    const listUserIdSaga = createRequestSaga(LIST_POSTS_USERID, postAPI.getPostsByUserId);
    
    const listKeywordSaga = createRequestSaga(LIST_POSTS_KEYWORD, postAPI.getPostsByKeyword);
    
    export function* postsSaga() {
        yield takeLatest(READ_POST, readPostSaga);
        yield takeLatest(LIST_ALL, listAllSaga);
        yield takeLatest(LIST_POSTS_TYPE, listTypeSaga);
        yield takeLatest(LIST_POSTS_USERID, listUserIdSaga);
        yield takeLatest(LIST_POSTS_KEYWORD, listKeywordSaga);
    };
    
    const initialState = {
        post: null,
        posts: null,
        postsError: null,
    };
    
    const posts = handleActions(
        {
            [INITIALIZE]: state => initialState,
            [READ_POST_SUCCESS]: (state, { payload: post }) => ({
                ...state,
                post,
            }),
            [READ_POST_FAILURE]: (state, { payload: postsError }) => ({
                ...state,
                postsError,
            }),
            [LIST_ALL_SUCCESS]: (state, { payload: posts }) => ({
                ...state,
                posts,
            }),
            [LIST_ALL_FAILURE]: (state, { payload: postsError }) => ({
                ...state,
                postsError,
            }),
            [LIST_POSTS_TYPE_SUCCESS]: (state, { payload: posts }) => ({
                ...state,
                posts,
            }),
            [LIST_POSTS_TYPE_FAILURE]: (state, { payload: postsError }) => ({
                ...state,
                postsError
            }),
            [LIST_POSTS_USERID_SUCCESS]: (state, { payload: posts }) => ({
                ...state,
                posts,
            }),
            [LIST_POSTS_USERID_FAILURE]: (state, { payload: postsError }) => ({
                ...state,
                postsError
            }),
            [LIST_POSTS_KEYWORD_SUCCESS]: (state, { payload: posts }) => ({
                ...state,
                posts,
            }),
            [LIST_POSTS_KEYWORD_FAILURE]: (state, { payload: postsError }) => ({
                ...state,
                postsError
            }),
        },
        initialState,
    );
    
    export default posts;
    モジュールが完了しました.UIを作成しながら使用します.

    #2 UI


    ReactネイティブのUIを作成しましょう.
    次のライブラリをインストールします.
    npm install --save react-native-redio-buttons-ext
  • ./src/pages/post/components/WriteNav.js
  • import React from 'react';
    import { useDispatch } from 'react-redux';
    import { StyleSheet, Text, View } from 'react-native';
    import { SegmentedControls } from 'react-native-radio-buttons-ext';
    import { changeField } from '../../../modules/post';
    
    const WriteNav = () => {
        const dispatch = useDispatch();
        const values = [
            '함께해요', 
            '도와주세요'
        ];
        const onSelect = e => {
            dispatch(changeField({
                key: 'type',
                value: e
            }));
        };
    
        return(
            <View style={ styles.container }>
                <View style={ styles.label }>
                    <Text style={ styles.font }>
                        게시글 종류를 정해주세요
                    </Text>
                </View>
                <View style={ styles.radio }>
                    <SegmentedControls options={ values }
                                       onSelection={ onSelect }
                    />
                </View>
            </View>
        );
    };
    
    const styles = StyleSheet.create({
        container: {
            width: 370,
            justifyContent: 'flex-start',
            marginLeft: 20,
            marginTop: 40,
        },
        label: {
            marginBottom: 10,
            marginLeft: 10,
        },
        font: {
            fontWeight: 'bold'
        },
        radio: {
            width: 200,
            marginBottom: 20
        }
    });
    
    export default WriteNav;
    WriteNavコンポーネントは、投稿のタイプ(「一緒に」、「手伝って」)を決定するために使用されます.
  • ./src/pages/post/components/WriteContent.js
  • import React from 'react';
    import { StyleSheet, Text, TextInput, View } from 'react-native';
    import { useDispatch, useSelector } from 'react-redux';
    import { changeField } from '../../../modules/post';
    import palette from '../../../styles/palette';
    import RentalList from './RentalList';
    
    const WriteContent = () => {
        const dispatch = useDispatch();
        const { type } = useSelector(({ post }) => ({ type: post.type }));
        const onChange = e => {
            const inputAccessoryViewID = e.target._internalFiberInstanceHandleDEV.memoizedProps.inputAccessoryViewID;
            const value = e.nativeEvent.text;
    
            dispatch(changeField({
                key: inputAccessoryViewID,
                value,
            }));
        };
    
        return(
            <View>
                <View style={ styles.container }>
                    <View style={ styles.label }>
                        <Text style={ styles.font }>
                            제목을 적어주세요
                        </Text>
                    </View>
                    <TextInput style={ styles.title_input }
                               inputAccessoryViewID="title"
                               onChange={ onChange }
                    />
                </View>
                <View style={ styles.container }>
                    <View style={ styles.label }>
                        <Text style={ styles.font }>
                            내용을 적어주세요
                        </Text>
                    </View>
                    <TextInput style={ styles.content_input }
                               inputAccessoryViewID="content"
                               multiline={ true }
                               textAlignVertical="top"
                               onChange={ onChange }
                    />
                </View>
                {  
                    type === '함께해요' &&
                    <View style={ styles.container }>
                        <View style={ styles.label }>
                            <Text style={ styles.font }>
                                대관 내역이에요
                            </Text>
                        </View>
                        <RentalList />
                    </View>
                }
            </View>
        );
    };
    
    const styles = StyleSheet.create({
        container: {
            width: 370,
            justifyContent: 'flex-start',
            marginLeft: 20,
            marginTop: 40,
            borderBottomColor: palette.gray[3],
            borderBottomWidth: 2,
        },
        label: {
            marginBottom: 10,
            marginLeft: 10,
        },
        font: {
            fontWeight: 'bold'
        },
        title_input: {
            width: 370,
            height: 40,
            justifyContent: 'center',
            alignItems: 'center',
            borderRadius: 6,
            backgroundColor: palette.white[0],
            color: palette.black[0]
        },
        content_input: {
            width: 370,
            height: 200,
            justifyContent: 'center',
            alignItems: 'center',
            borderRadius: 6,
            backgroundColor: palette.white[0],
            color: palette.black[0]
        }
    });
    
    export default WriteContent;
    これらのコンポーネントでタイトルとコンテンツを作成できます.また、最上位の投稿タイプに応じて、現在の大文字の履歴を投稿に含め、ヘルプを大文字の履歴を表示しないように設定できます.
  • ./src/pages/post/components/RentalList.js
  • import React, { useEffect } from 'react';
    import { StyleSheet, Text, View } from 'react-native';
    import { useDispatch, useSelector } from 'react-redux';
    import { getRentals } from '../../../modules/rentals';
    import palette from '../../../styles/palette';
    import RentalCard from './RentalCard';
    
    const RentalList = () => {
        const dispatch = useDispatch();
        const { 
            userId,
            rentals,
        } = useSelector(({ 
            user,
            rentals
        }) => ({
            userId: user.user.userId,
            rentals: rentals.rentals,
        }));
    
        useEffect(() => {
            dispatch(getRentals(userId))    
        }, [dispatch, userId]);
    
        return(
            <View style={ styles.container }>
                {
                    rentals ?
                    rentals.map((item, i) => {
                        return <RentalCard item={ item }/>
                    }) : 
                    <Text>
                        대관 내역이 없어요!
                    </Text>
                }
            </View>
        );
    };
    
    const styles = StyleSheet.create({
        container: {
            width: 370,
            justifyContent: 'center',
        }
    });
    
    export default RentalList;
    大観履歴リスト.rentals reducsモジュールを使用してgetRentalsを呼び出し、現在のユーザーが特定のリストのコンポーネントをロードできるようにします.
  • ./src/pages/post/components/RentalCard.js
  • import React from 'react';
    import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
    import { useDispatch, useSelector } from 'react-redux';
    import { changeField } from '../../../modules/post';
    import palette from '../../../styles/palette';
    
    const RentalCard = ({ item }) => {
        const dispatch = useDispatch();
        const { rental } = useSelector(({ post }) => ({ rental: post.rental }));
        const onChange = e => {
            dispatch(changeField({
                key: 'rental',
                value: item
            }));
        };
    
        return (
            <View style={ 
                rental.rentalId !== item.rentalId ?
                styles.card :
                styles.select_card
            }>
                <TouchableOpacity onPress={ onChange }>
                    <View>
                        <Text style={ 
                            rental.rentalId === item.rentalId &&
                            styles.select_font
                        }>
                            { item.mapName }
                        </Text>
                    </View>
                    <View>
                        <Text style={ 
                            rental.rentalId === item.rentalId &&
                            styles.select_font
                        }>
                            { item.date }
                        </Text>
                    </View>
               </TouchableOpacity>
            </View>
        );
    };
    
    const styles = StyleSheet.create({
        card: {
            width: 350,
            height: 70,
            backgroundColor: palette.gray[2],
            margin: 10,
            padding: 15,
            borderRadius: 10
        },
        select_card: {
            width: 350,
            height: 70,
            backgroundColor: palette.blue[1],
            margin: 10,
            padding: 15,
            borderRadius: 10
        },
        select_font: {
            color: palette.white[0]
        }
    });
    
    export default RentalCard;
    各ビッグデータをクリックして発表します.renet stateに含まれる構成部品を許可します.選択したデータは青で表示されます.
  • ./src/pages/post/components/WriteFooter.js
  • import React from 'react';
    import { StyleSheet, View } from 'react-native';
    import WriteButton from './WriteButton';
    
    const WriteFooter = () => {
        return(
            <View style={ styles.container }>
                <WriteButton />
            </View>
        );
    };
    
    const styles = StyleSheet.create({
        container: {
            width: 370,
            alignItems: 'flex-end',
            margin: 20,
        },
    });
    
    export default WriteFooter;
    [合成完了](Complete Composite)ボタンの下部にあるコンポーネントを囲みます.
  • ./src/pages/post/components/WriteButton.js
  • import React, { useEffect } from 'react';
    import { StyleSheet, Text, ToastAndroid, TouchableOpacity } from 'react-native';
    import { useNavigation } from '@react-navigation/native';
    import { useDispatch, useSelector } from 'react-redux';
    import { changeField, initialize, writePost } from '../../../modules/post';
    import palette from '../../../styles/palette';
    
    const WriteButton = () => {
        const dispatch = useDispatch();
        const navigation = useNavigation();
        const {
            type,
            title,
            content,
            userId,
            writer,
            nickname,
            rental,
            post
        } = useSelector(({ 
            user,
            post 
        }) => ({
            type: post.type,
            title: post.title,
            content: post.content,
            userId: user.user.userId,
            nickname: user.user.nickname,
            writer: post.writer,
            rental: post.rental,
            post: post.post
        }));
        const showToastForError = e => {
            ToastAndroid.show(
                "Empty input! check inputs", 
                ToastAndroid.SHORT
            );
        };
        const onWrite = e => {
            if([
                type,
                title,
                content,
                userId,
                writer,
            ].includes(null)) {
                showToastForError();
    
                return;
            }
    
            dispatch(writePost({
                type,
                title,
                content,
                userId,
                writer,
                rental
            }));
        };
    
        useEffect(() => {
            dispatch(changeField({
                key: 'userId',
                value: userId
            }));
        }, [dispatch, userId]);
    
        useEffect(() => {
            dispatch(changeField({
                key: 'writer',
                value: nickname
            }));
        }, [dispatch, nickname]);
    
        useEffect(() => {
            if(post) {
                dispatch(initialize());
    
                navigation.navigate("Post");
            }
        }, [dispatch, post]);
    
        return <TouchableOpacity style={ styles.shape }
                                 onPress={ onWrite }
                >
                    <Text style={ styles.font }>
                        작성 완료
                    </Text>
               </TouchableOpacity>;
    };
    
    const styles = StyleSheet.create({
        shape: {
            width: 120,
            height: 40,
            backgroundColor: palette.blue[1],
            borderRadius: 5,
            justifyContent: 'center',
            alignItems: 'center'
        },
        font: {
            color: palette.white[0],
            fontWeight: 'bold'
        }
    });
    
    export default WriteButton;
    stateに保存されているすべての値をロードすることで、「合成の完了」ボタンをアクティブにする構成部品です.投稿の要素が空の場合、showToastForErrorメソッドが呼び出され、トーストメッセージが表示されます.正常なパブリケーションの合成が最終的に完了したら、パブリケーションのホームページに移動します.
    実装が成功したかどうかをテストしましょう.

    #3テスト

  • 郵便-サービスの主幹.TSファイルに移動し、ポート番号を7100に設定します.
  • kongaを使用してpost-serviceを登録すると、次の手順でテストします.
    1)一緒にやろう-inputが空のエラー



    2)一緒にしましょう-ビッグデータを含む投稿


    3)一緒にしましょう-ビッグデータを含まない投稿


    2),3)データに関するREST要求がうまくいっており,データベースの状態を調べる.

    4)助けてください-inputが空のエラー

    助けてくれ

    1)から5)のテストが完了すると、テストが正常に動作していることがわかります.次の記事では、[投稿の詳細]ページを使用します.