LifeSports Application(ReactNative & Nest.js) - 21. post-service(2)
33559 ワード
#1冗長モジュール
post-serviceで作成したエンドポイントにapiを作成します.
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モジュールを作成します.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モジュールを作成します.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
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コンポーネントは、投稿のタイプ(「一緒に」、「手伝って」)を決定するために使用されます.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;
これらのコンポーネントでタイトルとコンテンツを作成できます.また、最上位の投稿タイプに応じて、現在の大文字の履歴を投稿に含め、ヘルプを大文字の履歴を表示しないように設定できます.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を呼び出し、現在のユーザーが特定のリストのコンポーネントをロードできるようにします.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に含まれる構成部品を許可します.選択したデータは青で表示されます.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)ボタンの下部にあるコンポーネントを囲みます.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テスト
1)一緒にやろう-inputが空のエラー
2)一緒にしましょう-ビッグデータを含む投稿
3)一緒にしましょう-ビッグデータを含まない投稿
2),3)データに関するREST要求がうまくいっており,データベースの状態を調べる.
4)助けてください-inputが空のエラー
助けてくれ
1)から5)のテストが完了すると、テストが正常に動作していることがわかります.次の記事では、[投稿の詳細]ページを使用します.
Reference
この問題について(LifeSports Application(ReactNative & Nest.js) - 21. post-service(2)), 我々は、より多くの情報をここで見つけました https://velog.io/@biuea/LifeSports-ApplicationReactNative-Nest.js-21.-post-service2テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol