리액트 네이티브에서 Parse 클라우드 함수 및 컨텍스트 활용
리액트 네이티브 앱에서 클라우드 함수 검증기 사용하기
소개
이 가이드에서는 리액트 네이티브 앱에서 Parse Cloud Code 함수 검증기와 컨텍스트 사용법을 배우게 됩니다. 클라우드 함수에 구현된 검증기의 예시, 서로 다른 함수 간의 컨텍스트 사용법, 그리고 이러한 개선된 함수를 사용하여 Back4App에서 리액트 네이티브 컴포넌트를 구현하는 방법을 확인할 수 있습니다.
전제 조건
이 튜토리얼을 완료하려면 다음이 필요합니다:
목표
리액트 네이티브 앱에서 Back4App의 검증기 및 컨텍스트 사용으로 Parse Cloud Code 함수를 실행합니다.
1 - 클라우드 코드 함수 검증기 이해하기
애플리케이션에서 클라우드 코드 함수를 사용하면 코드의 유연성이 크게 향상되어 재사용 가능한 메서드를 앱에서 분리하고 그 동작을 더 잘 제어할 수 있습니다. 우리의 클라우드 함수 시작 가이드를 통해 사용하는 방법을 확인하거나 검토할 수 있습니다.
이 클라우드 함수에 전송된 데이터는 예기치 않은 오류를 피하기 위해 검증되어야 하며, Parse Cloud Code(버전 4.4.0부터)는 완전한 통합된 검증기, 이를 객체로 전달하거나 다른 함수로 전달할 수 있습니다. 이러한 테스트는 함수 내의 코드를 실행하기 전에 호출되므로, 검증기가 데이터를 수락하면 데이터가 유효하다고 항상 가정할 수 있습니다.
다음 클라우드 함수를 예로 들어보겠습니다. 이 함수는 특정 영화 평점의 평균을 계산합니다. 영화 이름은 필수입니다(영화), 따라서 다음 객체를 사용하여 전달되도록 할 수 있습니다. 영화 매개변수가 전달되지 않으면 함수는 “검증 실패. 영화를 위한 데이터를 지정해 주세요.”라는 메시지를 포함한 오류를 반환합니다.
1 Parse.Cloud.define('getMovieAverageRating', async (request) => {
2 const query = new Parse.Query("Review");
3 query.equalTo("movie", request.params.movie);
4 const results = await query.find();
5 let sum = 0;
6 for (let review of results) {
7 sum += review.get("rate");
8 }
9 return {
10 result: sum / results.length,
11 };
12 },{
13 fields : ['movie'],
14 });또한 검증기, 예를 들어 함수를 호출하는 사용자가 인증되었는지 확인하는 requireUser 플래그와 같은 더 고급 옵션을 전달할 수 있습니다. 또 다른 유용한 추가 사항은 매개변수 값의 유효성을 검증하는 조건을 추가하는 것입니다. 예를 들어, 영화 값이 문자열이고 최소 3자 이상인지 확인하는 것입니다.
1 Parse.Cloud.define('getMovieAverageRating', async (request) => {
2 const query = new Parse.Query("Review");
3 query.equalTo("movie", request.params.movie);
4 const results = await query.find();
5 let sum = 0;
6 for (let review of results) {
7 sum += review.get("rate");
8 }
9 return {
10 result: sum / results.length,
11 };
12 },{
13 fields : {
14 movie : {
15 required: true,
16 type: String,
17 options: val => {
18 return val.length >= 3;
19 },
20 error: 'Movie must have at least 3 characters'
21 }
22 },
23 });전체 유효성 검사 옵션 목록은 Parse 문서에서 확인할 수 있습니다.
2 - 클라우드 코드 함수 컨텍스트 이해하기
클라우드 코드(버전 4.3.0부터)를 사용하여 애플리케이션에서 저장 트리거를 사용할 때, 컨텍스트 사전을 Parse.Object.save 메서드에 전달할 수 있으며, 또한 beforeSave 핸들러에서 afterSave 핸들러로 전달할 수 있습니다. 이는 데이터 일관성을 보장하거나 객체를 성공적으로 저장한 후에만 수행할 수 있는 비동기 작업을 수행하는 데 사용될 수 있습니다.
다음 클라우드 함수 트리거를 예로 들어보겠습니다. 여기서 리뷰 객체가 저장되며, 객체 저장 호출이 애플리케이션에서 이루어졌는지 대시보드에서 이루어졌는지를 구분하고자 합니다. 이는 애플리케이션 코드에서 객체를 저장하기 전에 컨텍스트를 설정하여 달성할 수 있습니다:
1 const Review = new Parse.Object('Review');
2 Review.set('text', 'Great movie!');
3 Review.set('rate', 5);
4 Review.set('movie', 'Mission Very Possible');
5
6 const context = { fromApplication: true };
7 const savedReview = await Review.save(null, {context: context});여기 저장 트리거 함수가 있습니다. 두 함수 모두에서 요청에서 컨텍스트 객체에 직접 접근할 수 있습니다:
1 Parse.Cloud.beforeSave('Review', async (request) => {
2 // Try to get context
3 try {
4 const context = request.context;
5 if (context.fromApplication === true) {
6 // Set a flag in the object before saving
7 request.object.set('fromApplication', true);
8 }
9 } catch(error){}
10
11 const text = request.object.get('text');
12 if (text.length > 20) {
13 // Truncate and add a ...
14 request.object.set('text', text.substring(0, 17) + '...');
15 }
16 });
17
18 Parse.Cloud.afterSave('Review', async (request) => {
19 // Try to get context
20 try {
21 const context = request.context;
22 if (context.fromApplication === true) {
23 // Do something when coming from application, like sending a notification or email
24 console.log('Got context fromApplication when saving Review object');
25 }
26 } catch(error){}
27 });3 - React Native 컴포넌트에서 검증된 클라우드 코드 사용하기
이제 마지막 가이드에서 동일한 컴포넌트 예제를 기반으로 사용하고, 이제 검증자와 컨텍스트를 사용하는 함수를 강조하기 위해 몇 가지 변경 사항을 추가하겠습니다. 1단계와 2단계에서 보여준 클라우드 함수를 배포하는 것을 잊지 말고, 그 후에 “리뷰” 객체 생성 함수를 다음과 같이 변경하여 save 메서드에 컨텍스트 인수를 추가하세요:
1 const createReview = async function () {
2 try {
3 // This values come from state variables linked to
4 // the screen form fields
5 const reviewTextValue = reviewText;
6 const reviewRateValue = Number(reviewRate);
7
8 // Creates a new parse object instance
9 let Review = new Parse.Object('Review');
10
11 // Set data to parse object
12 // Simple title field
13 Review.set('text', reviewTextValue);
14
15 // Simple number field
16 Review.set('rate', reviewRateValue);
17
18 // Set default movie
19 Review.set('movie', 'Mission Very Possible');
20
21 // After setting the values, save it on the server
22 try {
23 // Add
24 const context = {fromApplication: true};
25 await Review.save(null, {context: context});
26 // Success
27 Alert.alert('Success!');
28 // Updates query result list
29 doReviewQuery();
30 runGetMovieAverageRating();
31 return true;
32 } catch (error) {
33 // Error can be caused by lack of Internet connection
34 Alert.alert('Error!', error.message);
35 return false;
36 }
37 } catch (error) {
38 // Error can be caused by lack of field values
39 Alert.alert('Error!', error.message);
40 return false;
41 }
42 };영화 평균 계산 함수 검증기를 강조하기 위해, “Me”라는 영화를 쿼리하는 새로운 함수를 추가하고 이를 호출하는 또 다른 버튼을 추가하세요. 우리의 검증기는 영화 이름 길이 때문에 요청이 실패하게 만들 것임을 기억하세요. 여기 새로운 함수 코드가 있습니다:
1 const createReview = async function () {
2 try {
3 // This values come from state variables linked to
4 // the screen form fields
5 const reviewTextValue = reviewText;
6 const reviewRateValue = Number(reviewRate);
7
8 // Creates a new parse object instance
9 let Review = new Parse.Object('Review');
10
11 // Set data to parse object
12 // Simple title field
13 Review.set('text', reviewTextValue);
14
15 // Simple number field
16 Review.set('rate', reviewRateValue);
17
18 // Set default movie
19 Review.set('movie', 'Mission Very Possible');
20
21 // After setting the values, save it on the server
22 try {
23 // Add
24 const context = {fromApplication: true};
25 await Review.save(null, {context: context});
26 // Success
27 Alert.alert('Success!');
28 // Updates query result list
29 doReviewQuery();
30 runGetMovieAverageRating();
31 return true;
32 } catch (error) {
33 // Error can be caused by lack of Internet connection
34 Alert.alert('Error!', error.message);
35 return false;
36 }
37 } catch (error) {
38 // Error can be caused by lack of field values
39 Alert.alert('Error!', error.message);
40 return false;
41 }
42 };새로운 전체 구성 요소 코드가 이렇게 배치되어 있습니다. 리뷰가 애플리케이션에서 생성되었는지 여부를 보여주는 목록 항목의 제목에 새 줄이 있다는 점에 유의하세요:
1 import React, {FC, ReactElement, useState} from 'react';
2 import {Alert, Image, View, ScrollView, StyleSheet} from 'react-native';
3 import Parse from 'parse/react-native';
4 import {
5 List,
6 Title,
7 TextInput as PaperTextInput,
8 Button as PaperButton,
9 Text as PaperText,
10 } from 'react-native-paper';
11
12 export const MovieRatings: FC<{}> = ({}): ReactElement => {
13 // State variable
14 const [queryResults, setQueryResults] = useState(null);
15 const [ratingsAverage, setRatingsAverage] = useState('');
16 const [reviewText, setReviewText] = useState('');
17 const [reviewRate, setReviewRate] = useState('');
18
19 const runGetMovieAverageRating = async function (): Promise<boolean> {
20 try {
21 const params: {movie: string} = {
22 movie: 'Mission Very Possible',
23 };
24 let resultObject: {result: number} = await Parse.Cloud.run(
25 'getMovieAverageRating',
26 params,
27 );
28 // Set query results to state variable using state hook
29 setRatingsAverage(resultObject.result.toFixed(1));
30 return true;
31 } catch (error) {
32 // Error can be caused by lack of Internet connection
33 // or by not having an valid Review object yet
34 Alert.alert(
35 'Error!',
36 'Make sure that the cloud function is deployed and that the Review class table is created',
37 );
38 return false;
39 }
40 };
41
42 const runGetMeMovieAverageRating = async function (): Promise<boolean> {
43 try {
44 const params: {movie: string} = {
45 movie: 'Me',
46 };
47 let resultObject: object = await Parse.Cloud.run(
48 'getMovieAverageRating',
49 params,
50 );
51 return true;
52 } catch (error) {
53 // Error can be caused by lack of Internet connection
54 // or by not having an valid Review object yet
55 Alert.alert('Error!', JSON.stringify(error.message));
56 return false;
57 }
58 };
59
60 const doReviewQuery = async function (): Promise<boolean> {
61 // Create our query
62 let parseQuery: Parse.Query = new Parse.Query('Review');
63 try {
64 let results: [Parse.Object] = await parseQuery.find();
65 // Set query results to state variable
66 setQueryResults(results);
67 return true;
68 } catch (error) {
69 // Error can be caused by lack of Internet connection
70 Alert.alert('Error!', error.message);
71 return false;
72 }
73 };
74
75 const createReview = async function (): Promise<boolean> {
76 try {
77 // This values come from state variables linked to
78 // the screen form fields
79 const reviewTextValue: string = reviewText;
80 const reviewRateValue: number = Number(reviewRate);
81
82 // Creates a new parse object instance
83 let Review: Parse.Object = new Parse.Object('Review');
84
85 // Set data to parse object
86 // Simple title field
87 Review.set('text', reviewTextValue);
88
89 // Simple number field
90 Review.set('rate', reviewRateValue);
91
92 // Set default movie
93 Review.set('movie', 'Mission Very Possible');
94
95 // After setting the values, save it on the server
96 try {
97 const context = {fromApplication: true};
98 await Review.save(null, {context: context});
99 // Success
100 Alert.alert('Success!');
101 // Updates query result list
102 doReviewQuery();
103 runGetMovieAverageRating();
104 return true;
105 } catch (error) {
106 // Error can be caused by lack of Internet connection
107 Alert.alert('Error!', error.message);
108 return false;
109 }
110 } catch (error) {
111 // Error can be caused by lack of field values
112 Alert.alert('Error!', error.message);
113 return false;
114 }
115 };
116
117 return (
118 <>
119 <View style={Styles.header}>
120 <Image
121 style={Styles.header_logo}
122 source={ {
123 uri:
124 'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png',
125 } }
126 />
127 <PaperText style={Styles.header_text}>
128 <PaperText style={Styles.header_text_bold}>
129 {'React Native on Back4App - '}
130 </PaperText>
131 {' Cloud Code Movie Ratings'}
132 </PaperText>
133 </View>
134 <ScrollView style={Styles.wrapper}>
135 <View>
136 <Title>{'Mission Very Possible Reviews'}</Title>
137 <PaperText>{`Ratings Average: ${ratingsAverage}`}</PaperText>
138 {/* Query list */}
139 {queryResults !== null &&
140 queryResults !== undefined &&
141 queryResults.map((result: Parse.Object) => (
142 <List.Item
143 key={result.id}
144 title={`Review text: ${result.get('text')}`}
145 description={`Rate: ${result.get('rate')}\nFrom app: ${
146 result.get('fromApplication') !== undefined ? 'Yes' : 'No'
147 }`}
148 titleStyle={Styles.list_text}
149 style={Styles.list_item}
150 />
151 ))}
152 {queryResults === null ||
153 queryResults === undefined ||
154 (queryResults !== null &&
155 queryResults !== undefined &&
156 queryResults.length <= 0) ? (
157 <PaperText>{'No results here!'}</PaperText>
158 ) : null}
159 </View>
160 <View>
161 <Title>Action Buttons</Title>
162 <PaperButton
163 onPress={() => runGetMovieAverageRating()}
164 mode="contained"
165 icon="search-web"
166 color={'#208AEC'}
167 style={Styles.list_button}>
168 {'Calculate Review Average'}
169 </PaperButton>
170 <PaperButton
171 onPress={() => runGetMeMovieAverageRating()}
172 mode="contained"
173 icon="search-web"
174 color={'#208AEC'}
175 style={Styles.list_button}>
176 {'Calculate Me Movie Review Average'}
177 </PaperButton>
178 <PaperButton
179 onPress={() => doReviewQuery()}
180 mode="contained"
181 icon="search-web"
182 color={'#208AEC'}
183 style={Styles.list_button}>
184 {'Query Reviews'}
185 </PaperButton>
186 </View>
187 <View>
188 <Title>Add new review</Title>
189 <PaperTextInput
190 value={reviewText}
191 onChangeText={text => setReviewText(text)}
192 label="Text"
193 mode="outlined"
194 style={Styles.form_input}
195 />
196 <PaperTextInput
197 value={reviewRate}
198 onChangeText={text => setReviewRate(text)}
199 keyboardType={'number-pad'}
200 label="Rate (1-5)"
201 mode="outlined"
202 style={Styles.form_input}
203 />
204 <PaperButton
205 onPress={() => createReview()}
206 mode="contained"
207 icon="plus"
208 style={Styles.submit_button}>
209 {'Add'}
210 </PaperButton>
211 </View>
212 </ScrollView>
213 </>
214 );
215 };
216
217 // These define the screen component styles
218 const Styles = StyleSheet.create({
219 header: {
220 alignItems: 'center',
221 paddingTop: 30,
222 paddingBottom: 50,
223 backgroundColor: '#208AEC',
224 },
225 header_logo: {
226 height: 50,
227 width: 220,
228 resizeMode: 'contain',
229 },
230 header_text: {
231 marginTop: 15,
232 color: '#f0f0f0',
233 fontSize: 16,
234 },
235 header_text_bold: {
236 color: '#fff',
237 fontWeight: 'bold',
238 },
239 wrapper: {
240 width: '90%',
241 alignSelf: 'center',
242 },
243 list_button: {
244 marginTop: 6,
245 marginLeft: 15,
246 height: 40,
247 },
248 list_item: {
249 borderBottomWidth: 1,
250 borderBottomColor: 'rgba(0, 0, 0, 0.12)',
251 },
252 list_text: {
253 fontSize: 15,
254 },
255 form_input: {
256 height: 44,
257 marginBottom: 16,
258 backgroundColor: '#FFF',
259 fontSize: 14,
260 },
261 submit_button: {
262 width: '100%',
263 maxHeight: 50,
264 alignSelf: 'center',
265 backgroundColor: '#208AEC',
266 },
267 });렌더링 및 쿼리 함수 중 하나로 쿼리한 후 컴포넌트가 어떻게 보여야 하는지입니다:

결론
이 가이드의 끝에서, Parse Cloud Code 함수 검증기 및 컨텍스트를 사용하는 방법을 배웠습니다. 다음 가이드에서는 Parse에서 사용자와 작업하는 방법을 배울 것입니다.