SlideShare ist ein Scribd-Unternehmen logo
1 von 99
Downloaden Sie, um offline zu lesen
METEOR REACT
TUTORIAL
v1 - 09/01/2020 따라하기
Jiam Seo <jams7777@gmail.com>
METEOR DOCKER 환경 ( 윈도우10 기준 )
• 단비 기본 도커환경을 우선 사용
• Jams777/meteor-tutorial 이미지를 kitematic 에서 다운로드
• Port 는 3000 : 3000 셋팅
• Port 는 3001 : 3001 셋팅 ( 몽고디비 )
• 쉘은 bash 로 함
• Vscode 에서 도커에 직접 연결해서 사용
• Vscode Remote – Containers 확장 기능 사용
METEOR DOCKER 환경
METEOR 프로젝트 생성
• 튜토리얼 페이지 접속
• https://react-tutorial.meteor.com/simple-todos/01-creating-app.html
• 설치는 도커라 패스
• 도커환경 bash shell 실행(exec)
• 프로젝트 생성
• meteor create --react . 현재폴더에 생성
VSCODE 도커 연결
• Vscode Remote – Containers 설치
• F1 클릭 후 Remote-Containers: Attach to Running Container..
• meteor-tutorial 이미지 선택 ( jams777/meteor-tutorial )
• 폴더 열기 버튼 클릭
• /app/web 선택
VSCODE 도커 연결
VSCODE 도커 연결
METEOR 프로젝트 생성
• meteor create --react .
METEOR 프로젝트 실행
• meteor run
• 브라우저로 http://localhost:3000 접속
REACT 할일 컴포넌트 생성
• 파일 생성 imports/ui/Task.jsx
import React from 'react';
export const Task = ({ task }) => {
return <li>{task.text}</li>
};
REACT 할일 샘플 데이터 임시 셋팅
• 파일 수정 imports/ui/App.jsx
import React from 'react';
import { Hello } from './Hello.jsx';
import { Info } from './Info.jsx’;
// 추가
const tasks = [
{_id: 1, text: 'First Task'},
{_id: 2, text: 'Second Task'},
{_id: 3, text: 'Third Task'},
];
export const App = () => ( ………
REACT 할일 샘플 데이터 화면 표시
• 파일 수정 imports/ui/App.jsx
import React from 'react';
// import { Hello } from './Hello.jsx';
// import { Info } from './Info.jsx';
import { Task } from './Task';
const tasks = [
{_id: 1, text: 'First Task'},
{_id: 2, text: 'Second Task'},
{_id: 3, text: 'Third Task'},
];
export const App = () => (
<div>
<h1>Welcome to Meteor!</h1>
<ul>
{ tasks.map(task => <Task key={ task._id } task={ task }/>) }
</ul>
{/*<Hello/>
<Info/>*/}
</div>
);
REACT 할일 샘플 데이터 화면 표시 확인
모바일에서 잘보이게 하기
• 파일 수정 client/main.html
<head>
<title>web</title>
<meta charset="utf-8"/>
<meta http-equiv="x-ua-compatible" content="ie=edge"/>
<meta
name="viewport"
content="width=device-width, height=device-height, viewport-fit=cover, initial-
scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
</head>
모바일에서 잘보이게 하기
데이터베이스 연결
• https://react-tutorial.meteor.com/simple-todos/02-collections.html
• 몽고디비가 개발 시 깔려있음. 미니몽고로도 사용됨.
할일 컬렉션 생성 ( RDB테이블 같은것 )
• 파일 생성 imports/api/TasksCollection.js
import { Mongo } from 'meteor/mongo';
export const TasksCollection = new Mongo.Collection('tasks’);
할일 샘플 데이터 임시 생성
• 파일 수정 server/main.js
import { Meteor } from 'meteor/meteor';
import { TasksCollection } from '/imports/api/TasksCollection';
// 추가 함수
const insertTask = taskText => TasksCollection.insert({ text: taskText });
Meteor.startup(() => {
// 데이터 없을때 7개 생성
if (TasksCollection.find().count() === 0) {
[
'First Task',
'Second Task',
'Third Task',
'Fourth Task',
'Fifth Task',
'Sixth Task',
'Seventh Task'
].forEach(insertTask)
}
});
화면에 데이터 연동 모듈 설치
• meteor add react-meteor-data
• React hook 를 사용한다고 함 ( https://reactjs.org/docs/hooks-faq.html )
REACT 할일 DB 데이터 화면 표시
• 파일 수정 imports/ui/App.jsx
import React from 'react';
import { useTracker } from 'meteor/react-meteor-data'; // 여기추가
import { TasksCollection } from '/imports/api/TasksCollection'; // 여기추가
import { Task } from './Task';
// 삭제
const tasks = [
{_id: 1, text: 'First Task'},
{_id: 2, text: 'Second Task'},
{_id: 3, text: 'Third Task'},
];
export const App = () => {
const tasks = useTracker(() => TasksCollection.find({}).fetch()); // 여기추가
return (
<div>
<h1>Welcome to Meteor!</h1>
<ul>
{ tasks.map(task => <Task key={ task._id } task={ task }/>) }
</ul>
</div>
);
};
REACT 할일 DB 데이터 화면 표시 확인
몽고디비 데이터 확인 도구
• NoSQLBooster 무료도구
• https://nosqlbooster.com/downloads
할일 입력 화면과 기능 추가
• https://react-tutorial.meteor.com/simple-todos/03-forms-and-events.html
• 입력할 화면을 만들고 사용자가 직접 입력할 수 있는 기능추가.
REACT 할일 입력화면
• 파일 생성 imports/ui/TaskForm.jsx
import React, { useState } from 'react';
export const TaskForm = () => {
const [text, setText] = useState("");
return (
<form className="task-form">
<input
type="text"
placeholder="Type to add new tasks"
/>
<button type="submit">Add Task</button>
</form>
);
};
REACT 할일 입력화면 메인에 추가
• 파일 수정 imports/ui/App.jsx
import React from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { TasksCollection } from '/imports/api/TasksCollection';
import { Task } from './Task';
import { TaskForm } from './TaskForm'; // 여기 추가
export const App = () => {
const tasks = useTracker(() => TasksCollection.find({}).fetch());
return (
<div>
<h1>Welcome to Meteor!</h1>
<TaskForm/> {/*여기추가*/}
<ul>
{ tasks.map(task => <Task key={ task._id } task={ task }/>) }
</ul>
</div>
);
};
REACT 할일 입력화면 스타일
• 파일 수정 client/main.css
.task-form {
margin-top: 1rem;
}
REACT 할일 입력화면 생성기능 구현
• 파일 수정 imports/ui/TaskForm.jsx
import React, { useState } from 'react';
import { TasksCollection } from '/imports/api/TasksCollection';
export const TaskForm = () => {
const [text, setText] = useState("");
// 추가
const handleSubmit = e => {
e.preventDefault();
if (!text) return;
TasksCollection.insert({
text: text.trim(),
createdAt: new Date()
});
setText("");
};
return (
<form className="task-form" onSubmit={handleSubmit}>
<input
type="text"
placeholder="Type to add new tasks"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button type="submit">Add Task</button>
</form>
);
};
REACT 할일 입력화면 생성기능 테스트
REACT 할일 최신순으로
• 파일 수정 imports/ui/App.jsx
• 디비 조회 시 정렬조건 추가
....
export const App = () => {
// const tasks = useTracker(() => TasksCollection.find({}).fetch());
const tasks = useTracker(() => TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch());
....
REACT 할일 최신순으로 테스트
할일 수정 삭제 기능 추가
• https://react-tutorial.meteor.com/simple-todos/04-update-and-remove.html
• 할일 완료 체크기능. 체크시 완료로 변경
• 할일 삭제 버튼 추가. 클릭시 데이터 삭제
할일 완료(수정) 하기 기능 - 체크박스
• 파일 수정 imports/ui/Task.jsx
import React from 'react';
export const Task = ({ task, onCheckboxClick }) => {
return (
<li>
<input
type="checkbox"
checked={!!task.isChecked}
onClick={() => onCheckboxClick(task)}
readOnly
/>
<span>{task.text}</span>
</li>
);
};
할일 완료(수정) 하기 기능 - 디비수정
• 파일 수정 imports/ui/App.jsx
import { TaskForm } from './TaskForm';
// 여기 추가
const toggleChecked = ({ _id, isChecked }) => {
TasksCollection.update(_id, {
$set: {
isChecked: !isChecked
}
})
};
export const App = () => {
....
<ul>
{/* tasks.map(task => <Task key={ task._id } task={ task }/>) */}
{ tasks.map(task => <Task key={ task._id } task={ task } onCheckboxClick={toggleChecked} />) }
</ul>
....
할일 완료(수정) 하기 기능 - 테스트
할일 삭제하기 기능 - 삭제버튼
• 파일 수정 imports/ui/Task.jsx
import React from 'react';
export const Task = ({ task, onCheckboxClick, onDeleteClick }) => {
return (
<li>
<input
type="checkbox"
checked={!!task.isChecked}
onClick={() => onCheckboxClick(task)}
readOnly
/>
<span>{task.text}</span>
<button onClick={ () => onDeleteClick(task) }>&times;</button>
</li>
);
};
할일 삭제하기 기능 - 디비수정
• 파일 수정 imports/ui/App.jsx
// 여기 추가
const deleteTask = ({ _id }) => TasksCollection.remove(_id);
export const App = () => {
....
<div>
<h1>Welcome to Meteor!</h1>
<TaskForm/>
<ul>
{ tasks.map(task => <Task
key={ task._id }
task={ task }
onCheckboxClick={toggleChecked}
onDeleteClick={deleteTask}
/>) }
</ul>
</div>
....
할일 삭제하기 기능 - 테스트
화면 스타일
• https://react-tutorial.meteor.com/simple-todos/05-styles.html
• 스타일 변경
화면 스타일 – CSS
• 파일 수정 client/main.css
body {
font-family: sans-serif;
background-color: #315481;
background-image: linear-
gradient(to bottom, #315481, #918e82 100%);
background-attachment: fixed;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0;
margin: 0;
font-size: 14px;
}
button {
font-weight: bold;
font-size: 1em;
border: none;
color: white;
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
padding: 5px;
cursor: pointer;
}
button:focus {
outline: 0;
}
.app {
display: flex;
flex-direction: column;
height: 100vh;
}
.app-header {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.main {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: auto;
background: white;
}
.main::-webkit-scrollbar {
width: 0;
height: 0;
background: inherit;
}
header {
background: #d2edf4;
background-image: linear-
gradient(to bottom, #d0edf5, #e1e5f0 100%);
padding: 20px 15px 15px 15px;
position: relative;
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
}
.app-bar {
display: flex;
justify-content: space-between;
}
.app-bar h1 {
font-size: 1.5em;
margin: 0;
display: inline-block;
margin-right: 1em;
}
화면 스타일 – CSS
• 파일 수정 client/main.css
.task-form {
display: flex;
margin: 16px;
}
.task-form > input {
flex-grow: 1;
box-sizing: border-box;
padding: 10px 6px;
background: transparent;
border: 1px solid #aaa;
width: 100%;
font-size: 1em;
margin-right: 16px;
}
.task-form > input:focus {
outline: 0;
}
.task-form > button {
min-width: 100px;
height: 95%;
background-color: #315481;
}
.tasks {
list-style-type: none;
padding-inline-start: 0;
padding-left: 16px;
padding-right: 16px;
margin-block-start: 0;
margin-block-end: 0;
}
.task {
display: flex;
padding: 16px;
border-bottom: #eee solid 1px;
}
.task > span {
flex-grow: 1;
}
.task > button {
justify-self: flex-end;
background-color: #ff3046;
}
스타일 적용
• 파일 수정 imports/ui/App.jsx
....
return (
<div className="app">
<header>
<div className="app-bar">
<div className="app-header">
<h1>Welcome to Meteor!</h1>
</div>
</div>
</header>
<div className="main">
<TaskForm />
<ul className="tasks">
….
</ul>
</div>
</div>
);
};
스타일 적용
• 파일 수정 imports/ui/Task.jsx
....
export const Task = ({ task, onCheckboxClick, onDeleteClick }) => {
return (
<li className="task">
<input
....
헤더 글자 변경
• 파일 수정 imports/ui/App.jsx
....
<h1>📝️ To Do List</h1>
....
화면 스타일 - 테스트
데이터 필터링 – 미완료만 보기
• https://react-tutorial.meteor.com/simple-todos/06-filter-tasks.html
• 미완료만 보기 기능 추가
데이터 필터링 – 미완료 버튼추가
• 파일 수정 imports/ui/App.jsx
import React, { useState } from 'react';
....
export const App = () => {
const [hideCompleted, setHideCompleted] = useState(false);
....
<div className="main">
<TaskForm />
<div className="filter">
<button onClick={() => setHideCompleted(!hideCompleted)}>
{hideCompleted ? 'Show All' : 'Hide Completed'}
</button>
</div>
....
데이터 필터링 – 미완료 버튼스타일
• 파일 수정 client/main.css
.filter {
display: flex;
justify-content: center;
}
.filter > button {
background-color: #62807e;
}
데이터 필터링 – 미완료 필터링로직
• 파일 수정 imports/ui/App.jsx
....
export const App = () => {
const [hideCompleted, setHideCompleted] = useState(false);
const hideCompletedFilter = { isChecked: { $ne: true } };
const tasks = useTracker(() =>
TasksCollection.find(hideCompleted ? hideCompletedFilter : {}, {
sort: { createdAt: -1 },
}).fetch()
);
....
데이터 필터링 – 테스트
개발도구로 프로토콜/데이터 확인
• https://chrome.google.com/webstore/detail/meteor-devtools-
evolved/ibniinmoafhgbifjojidlagmggecmpgf
• Meteor DevTools Evolved 크롬 확장기능 설치
데이터 필터링 – 미완료 갯수 보이기
• 파일 수정 imports/ui/App.jsx
....
const pendingTasksCount = useTracker(() =>
TasksCollection.find(hideCompletedFilter).count()
);
const pendingTasksTitle = `${
pendingTasksCount ? ` (${pendingTasksCount})` : ''
}`;
....
<div className="app-header">
<h1>📝️ To Do List {pendingTasksTitle} </h1>
</div>
....
데이터 필터링 – 미완료 갯수 테스트
할일 등록 사용자 기능 추가
• https://react-tutorial.meteor.com/simple-todos/07-adding-user-accounts.html
• 사용자 관리 기능 추가
• 로그인 기능
• 자기 할일만 관리
• 로그아웃 기능
METEOR 사용자 기능 추가
• meteor add accounts-password
• meteor npm install --save bcrypt
• Node 암호화 모듈 bcrypt 사용
서버에 사용자 생성
• 파일 수정 server/main.js
....
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TasksCollection } from '/imports/api/TasksCollection';
....
const SEED_USERNAME = 'jams777';
const SEED_PASSWORD = '1111';
Meteor.startup(() => {
if (!Accounts.findUserByUsername(SEED_USERNAME)) {
Accounts.createUser({
username: SEED_USERNAME,
password: SEED_PASSWORD,
});
}
....
로그인 화면 생성
• 파일 생성 imports/ui/LoginForm.jsx
import { Meteor } from 'meteor/meteor';
import React, { useState } from 'react';
export const LoginForm = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const submit = e => {
e.preventDefault();
Meteor.loginWithPassword(username, password);
};
return (
<form onSubmit={submit} className="login-form"><div>
<label htmlFor="username">Username</label>
<input
type="text"
placeholder="Username"
name="username"
required
onChange={e => setUsername(e.target.value)}
/>
<label htmlFor="password">Password</label>
<input
type="password"
placeholder="Password"
name="password"
required
onChange={e => setPassword(e.target.value)}
/>
<button type="submit">Log In</button>
</div></form>
);
};
로그인 화면 연결
• 파일 수정 imports/ui/App.jsx
import React, { useState, Fragment } from 'react';
....
import { LoginForm } from './LoginForm';
....
export const App = () => {
const user = useTracker(() => Meteor.user());
....
<div className="main">
{user ? (
<Fragment>
<TaskForm />
……….
</ul>
</Fragment>
) : (
<LoginForm />
)}
</div>
....
로그인 화면 스타일
• 파일 수정 client/main.css
.login-form {
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
align-items: center;
}
.login-form > div {
margin: 8px;
}
.login-form > div > label {
font-weight: bold;
}
.login-form > div > input {
flex-grow: 1;
box-sizing: border-box;
padding: 10px 6px;
background: transparent;
border: 1px solid #aaa;
width: 100%;
font-size: 1em;
margin-right: 16px;
margin-top: 4px;
}
.login-form > div > input:focus {
outline: 0;
}
.login-form > div > button {
background-color: #62807e;
}
로그인 화면 테스트
할일의 사용자 정보 같이 입력
• 기존 정보 삭제
• db.tasks.remove({});
할일의 사용자 정보 같이 입력
• 파일 수정 server/main.js
....
const insertTask = (taskText, user) =>
TasksCollection.insert({
text: taskText,
userId: user._id,
createdAt: new Date(),
});
....
Meteor.startup(() => {
....
}
const user = Accounts.findUserByUsername(SEED_USERNAME);
// 데이터 없을때 7개 생성
if (TasksCollection.find().count() === 0) {
[ ……
].forEach(taskText => insertTask(taskText, user));
}
....
할일의 사용자 정보 처리
• 파일 수정 imports/ui/App.jsx
....
const hideCompletedFilter = { isChecked: { $ne: true } };
const userFilter = user ? { userId: user._id } : {};
const pendingOnlyFilter = { ...hideCompletedFilter, ...userFilt
er };
const tasks = useTracker(() => {
if (!user) {
return [];
}
return TasksCollection.find(
hideCompleted ? pendingOnlyFilter : userFilter,
{
sort: { createdAt: -1 },
}
).fetch();
});
const pendingTasksCount = useTracker(() => {
if (!user) {
return 0;
}
return TasksCollection.find(pendingOnlyFilter).count();
});
....
<TaskForm user={user} />
....
할일의 사용자 정보 처리
• 파일 수정 imports/ui/TaskForm.jsx
....
export const TaskForm = ({ user }) => {
....
TasksCollection.insert({
text: text.trim(),
createdAt: new Date(),
userId: user._id
});
setText("");
};
return (
....
할일의 사용자 정보 처리 테스트
로그아웃 기능 - 버튼
• 파일 수정 imports/ui/App.jsx
....
const logout = () => Meteor.logout();
export const App = () => {
....
<Fragment>
<div className="user" onClick={logout}>
{user.username} 🚪
</div>
<TaskForm user={user} />
....
로그아웃 기능 - 스타일
• 파일 수정 client/main.css
.user {
display: flex;
align-self: flex-end;
margin: 8px 16px 0;
font-weight: bold;
}
로그인 로그아웃 테스트
할일 데이터 관리 백엔드 서비스로
• https://react-tutorial.meteor.com/simple-todos/08-methods.html
• 화면에서 데이터 베이스 접속안되게
• meteor remove insecure
• 백엔드 서비스로 할일 관리 함수 변경
• 화면에서 Meteor.call 로 호출하기
• Optimistic UI ( https://blog.meteor.com/optimistic-ui-with-meteor-67b5a78c3fcf )
• 조회는 미니몽고에서
할일 데이터 관리 백엔드 서비스
• 파일 생성 imports/api/tasksMethods.js
import { check } from 'meteor/check';
import { TasksCollection } from './TasksCollection';
Meteor.methods({
'tasks.insert'(text) {
check(text, String);
if (!this.userId) {
throw new Meteor.Error('Not authorized.');
}
TasksCollection.insert({
text,
createdAt: new Date,
userId: this.userId,
})
},
'tasks.remove'(taskId) {
check(taskId, String);
if (!this.userId) {
throw new Meteor.Error('Not authorized.');
}
TasksCollection.remove(taskId);
},
'tasks.setIsChecked'(taskId, isChecked) {
check(taskId, String);
check(isChecked, Boolean);
if (!this.userId) {
throw new Meteor.Error('Not authorized.');
}
TasksCollection.update(taskId, {
$set: {
isChecked
}
});
}
});
할일 데이터 관리 백엔드 서비스 등록
• 파일 수정 server/main.js
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';
....
할일 데이터 관리 서비스 호출
• 파일 수정 imports/ui/TaskForm.jsx
import { Meteor } from 'meteor/meteor';
import React, { useState } from 'react';
// import { TasksCollection } from '/imports/api/TasksCollection'; // 이제 사용안함
export const TaskForm = () => { // 이제 사용자 안받음
const [text, setText] = useState("");
// 추가
const handleSubmit = e => {
e.preventDefault();
if (!text) return;
// 백엔드 호출로 변경
Meteor.call('tasks.insert', text);
setText("");
};
return (
....
할일 데이터 관리 서비스 호출
• 파일 수정 imports/ui/App.jsx
import { Meteor } from 'meteor/meteor';
import React, { useState, Fragment } from 'react';
……
const toggleChecked = ({ _id, isChecked }) => {
Meteor.call('tasks.setIsChecked', _id, !isChecked);
};
const deleteTask = ({ _id }) => Meteor.call('tasks.remove', _id);
const logout = () => Meteor.logout();
……
<TaskForm />
…..
할일 데이터 연결 함수 폴더 변경
• 파일 수정 imports/api/tasksMethods.js
• 파일 수정 imports/ui/TaskForm.jsx
• 파일 수정 server/main.js
• 파일 수정 imports/ui/App.jsx
// 이걸로 변경
import { TasksCollection } from '/imports/db/TasksCollection';
사용자별로 구독처리되게 개선
• https://react-tutorial.meteor.com/simple-todos/09-publications.html
• 현재 전체 데이터 구독되는 부분제거
• meteor remove autopublish
• 구독 API 추가
• 화면 구독방식 변경
사용자별로 구독 서비스 추가
• 파일 생성 imports/api/tasksPublications.js
import { Meteor } from 'meteor/meteor';
import { TasksCollection } from '/imports/db/TasksCollection';
Meteor.publish('tasks', function publishTasks() {
return TasksCollection.find({ userId: this.userId });
});
사용자별로 구독 서비스 등록
• 파일 수정 server/main.js
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';
import '/imports/api/tasksPublications';
....
화면 구독 서비스 사용
• 파일 수정 imports/ui/App.jsx
.... const pendingOnlyFilter = { ...hideCompletedFilter, ...userFilter };
const { tasks, pendingTasksCount, isLoading } = useTracker(() => {
const noDataAvailable = { tasks: [], pendingTasksCount: 0 };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe('tasks');
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const tasks = TasksCollection.find(
hideCompleted ? pendingOnlyFilter : userFilter,
{
sort: { createdAt: -1 },
}
).fetch();
const pendingTasksCount = TasksCollection.find(pendingOnlyFilter).count();
return { tasks, pendingTasksCount };
});
const pendingTasksTitle = `${
....
화면 구독 중 상태 관리
• 파일 수정 imports/ui/App.jsx
....
<div className="filter">
<button onClick={() => setHideCompleted(!hideCompleted)}>
{hideCompleted ? 'Show All' : 'Hide Completed'}
</button>
</div>
{isLoading && <div className="loading">loading...</div>}
<ul className="tasks">
....
화면 구독 중 상태 스타일
• 파일 수정 client/main.css
.loading {
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
align-items: center;
font-weight: bold;
}
자신의 할일만 수정할 수 있는 권한 추가
• 파일 수정 imports/api/tasksMethods.js
....
'tasks.remove'(taskId) {
…..
if (!this.userId) {
throw new Meteor.Error('Not authorized.');
}
const task = TasksCollection.findOne({ _id: taskId, u
serId: this.userId });
if (!task) {
throw new Meteor.Error('Access denied.');
}
TasksCollection.remove(taskId);
},
....
....
'tasks.setIsChecked'(taskId, isChecked) {
check(taskId, String);
check(isChecked, Boolean);
if (!this.userId) {
throw new Meteor.Error('Not authorized.');
}
const task = TasksCollection.findOne({ _id: taskId, use
rId: this.userId });
if (!task) {
throw new Meteor.Error('Access denied.');
}
TasksCollection.update(taskId, {
$set: {
isChecked
}
});
}
....
모바일에서 사용
• https://react-tutorial.meteor.com/simple-todos/10-running-on-mobile.html
• iOS Simulator
• meteor add-platform ios
• meteor run ios
• Android Emulator
• meteor add-platform android
• meteor run android
모바일에서 사용
• Android Device
• meteor run android-device
• iPhone or iPad
• meteor run ios-device
테스트 케이스 작성
• https://react-tutorial.meteor.com/simple-todos/11-testing.html
• 프론트엔드, 백엔드 전부 테스트 가능
• meteor add meteortesting:mocha
• meteor npm install --save-dev chai
테스트 모드
• TEST_WATCH=1 meteor test --driver-package meteortesting:mocha
스캐폴드 테스트(SCAFFOLD TEST)
• 파일 생성 imports/api/tasksMethods.tests.js
import { Meteor } from 'meteor/meteor';
if (Meteor.isServer) {
describe('Tasks', () => {
describe('methods', () => {
it('can delete owned task', () => {});
});
});
}
스캐폴드 테스트 등록
• 파일 수정 tests/main.js
import assert from "assert";
import '/imports/api/tasksMethods.tests.js';
....
데이터베이스 준비 전처리
• 파일 수정 imports/api/tasksMethods.tests.js
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { TasksCollection } from '/imports/db/TasksCollection';
if (Meteor.isServer) {
describe('Tasks', () => {
describe('methods', () => {
const userId = Random.id();
let taskId;
beforeEach(() => {
TasksCollection.remove({});
taskId = TasksCollection.insert({
text: 'Test Task',
createdAt: new Date(),
userId,
});
});
});
});
}
테스트 데이터 삭제되게
• meteor add quave:testing
테스트 데이터 삭제되게
• 파일 생성 imports/api/tasks.tests.js
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { mockMethodCall } from 'meteor/quave:testing';
import { assert } from 'chai';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';
if (Meteor.isServer) {
describe('Tasks', () => {
describe('methods', () => {
const userId = Random.id();
let taskId;
beforeEach(() => {
TasksCollection.remove({});
taskId = TasksCollection.insert({
text: 'Test Task',
createdAt: new Date(),
userId,
});
});
it('can delete owned task', () => {
mockMethodCall('tasks.remove', taskId, { context: { u
serId } });
assert.equal(TasksCollection.find().count(), 0);
});
});
});
}
테스트 데이터 삭제되게 등록
• 파일 수정 tests/main.js
import assert from "assert";
import '/imports/api/tasksMethods.tests.js';
import '/imports/api/tasks.tests.js';
....
테스트 데이터 삭제 결과
추가 테스트 케이스
• 파일 수정 imports/api/tasks.tests.js
....
it(`can't delete task without an user authenticated`, () => {
const fn = () => mockMethodCall('tasks.remove', taskId);
assert.throw(fn, /Not authorized/);
assert.equal(TasksCollection.find().count(), 1);
});
it(`can't delete task from another owner`, () => {
const fn = () =>
mockMethodCall('tasks.remove', taskId, {
context: { userId: 'somebody-else-id' },
});
assert.throw(fn, /Access denied/);
assert.equal(TasksCollection.find().count(), 1);
});
it('can change the status of a task', () => {
const originalTask = TasksCollection.findOne(taskId);
mockMethodCall('tasks.setIsChecked', taskId, !originalTask.isChecked, {
context: { userId },
});
const updatedTask = TasksCollection.findOne(taskId);
assert.notEqual(updatedTask.isChecked, originalTask.isChecked);
});
it('can insert new tasks', () => {
const text = 'New Task';
mockMethodCall('tasks.insert', text, {
context: { userId },
});
const tasks = TasksCollection.find({}).fetch();
assert.equal(tasks.length, 2);
assert.isTrue(tasks.some(task => task.text === text));
});
});
});
}
추가 테스트 케이스 결과
테스트 수행 명령어
• 파일 확인 package.json
• meteor npm test
• meteor npm run test-app
....
"scripts": {
"start": "meteor run",
"test": "meteor test --once --driver-package meteortesting:mocha",
"test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
"visualize": "meteor --production --extra-packages bundle-visualizer"
},
....
테스트 수행 명령어
사용 라이브러리 확인
• 파일 확인 package.json
• meteor npm visualize
만든 앱 배포
• https://react-tutorial.meteor.com/simple-todos/12-deploying.html
• Meteor 에서 제공하는 Galaxy 에 대한 설명으로 pass
• Mongodb 는 서비스 받는 것이 좋음
마무리
• 오랜만에 Meteor을 해봤음.
• React 와 함께 사용하는 방법을 익혔음.
• React Route 를 추가로 해봐야 겠음.
• Redux 보다는 Meteor Pub/Sub 으로 처리하고
• 화면에서 캐시형태로 Redux 를 사용하면 좋을듯 함.
끝

Weitere ähnliche Inhalte

Was ist angesagt?

스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원
스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원
스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원
탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Web Components 101 polymer & brick
Web Components 101 polymer & brickWeb Components 101 polymer & brick
Web Components 101 polymer & brick
yongwoo Jeon
 

Was ist angesagt? (20)

진짜기초 Node.js
진짜기초 Node.js진짜기초 Node.js
진짜기초 Node.js
 
자바스크립트 프레임워크 살펴보기
자바스크립트 프레임워크 살펴보기자바스크립트 프레임워크 살펴보기
자바스크립트 프레임워크 살펴보기
 
스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원
스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원
스프링프레임워크 & 마이바티스 무.료 강의자료 제공 (Spring IoC & DI)_ 구로자바학원/구로오라클학원/구로IT학원
 
[115] clean fe development_윤지수
[115] clean fe development_윤지수[115] clean fe development_윤지수
[115] clean fe development_윤지수
 
How to Grunt.js
How to Grunt.jsHow to Grunt.js
How to Grunt.js
 
okspring3x
okspring3xokspring3x
okspring3x
 
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기
 
Html5 web workers
Html5 web workersHtml5 web workers
Html5 web workers
 
Web workers
Web workersWeb workers
Web workers
 
Angular2를 활용한 컴포넌트 중심의 개발
Angular2를 활용한 컴포넌트 중심의 개발Angular2를 활용한 컴포넌트 중심의 개발
Angular2를 활용한 컴포넌트 중심의 개발
 
06.실행환경 실습교재(easy company,해답)
06.실행환경 실습교재(easy company,해답)06.실행환경 실습교재(easy company,해답)
06.실행환경 실습교재(easy company,해답)
 
혁신적인 웹컴포넌트 라이브러리 - Polymer
혁신적인 웹컴포넌트 라이브러리 - Polymer혁신적인 웹컴포넌트 라이브러리 - Polymer
혁신적인 웹컴포넌트 라이브러리 - Polymer
 
Spring boot 공작소(1-4장)
Spring boot 공작소(1-4장)Spring boot 공작소(1-4장)
Spring boot 공작소(1-4장)
 
01.실행환경 실습교재(공통기반)
01.실행환경 실습교재(공통기반)01.실행환경 실습교재(공통기반)
01.실행환경 실습교재(공통기반)
 
HTML5의 web worker
HTML5의 web workerHTML5의 web worker
HTML5의 web worker
 
Web Components 101 polymer & brick
Web Components 101 polymer & brickWeb Components 101 polymer & brick
Web Components 101 polymer & brick
 
Spring boot actuator
Spring boot   actuatorSpring boot   actuator
Spring boot actuator
 
React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작
 
AngularJS의 개발방식에 대하여
AngularJS의 개발방식에 대하여AngularJS의 개발방식에 대하여
AngularJS의 개발방식에 대하여
 
React 튜토리얼 2차시
React 튜토리얼 2차시React 튜토리얼 2차시
React 튜토리얼 2차시
 

Ähnlich wie Meteor React Tutorial 따라하기

캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012
캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012
캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012
Daum DNA
 
Jenkins를 활용한 javascript 개발
Jenkins를 활용한 javascript 개발Jenkins를 활용한 javascript 개발
Jenkins를 활용한 javascript 개발
지수 윤
 

Ähnlich wie Meteor React Tutorial 따라하기 (20)

Nest js 101
Nest js 101Nest js 101
Nest js 101
 
[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초
 
캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012
캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012
캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012
 
[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridapp[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridapp
 
Jenkins를 활용한 javascript 개발
Jenkins를 활용한 javascript 개발Jenkins를 활용한 javascript 개발
Jenkins를 활용한 javascript 개발
 
Polymer따라잡기
Polymer따라잡기Polymer따라잡기
Polymer따라잡기
 
Vue.js 기초 실습.pptx
Vue.js 기초 실습.pptxVue.js 기초 실습.pptx
Vue.js 기초 실습.pptx
 
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
 
Front-end Development Process - 어디까지 개선할 수 있나
Front-end Development Process - 어디까지 개선할 수 있나Front-end Development Process - 어디까지 개선할 수 있나
Front-end Development Process - 어디까지 개선할 수 있나
 
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
 
Node.js and react
Node.js and reactNode.js and react
Node.js and react
 
React Hooks 마법. 그리고 깔끔한 사용기
React Hooks 마법. 그리고 깔끔한 사용기React Hooks 마법. 그리고 깔끔한 사용기
React Hooks 마법. 그리고 깔끔한 사용기
 
What's new in IE11
What's new in IE11What's new in IE11
What's new in IE11
 
Deep dive into Modern frameworks - HTML5 Forum 2018
Deep dive into Modern frameworks - HTML5 Forum 2018Deep dive into Modern frameworks - HTML5 Forum 2018
Deep dive into Modern frameworks - HTML5 Forum 2018
 
[React-Native-Seoul] React-Native 초심자를 위한 실습위주의 간단한 소개 및 구현법 안내
[React-Native-Seoul] React-Native 초심자를 위한 실습위주의 간단한 소개 및 구현법 안내[React-Native-Seoul] React-Native 초심자를 위한 실습위주의 간단한 소개 및 구현법 안내
[React-Native-Seoul] React-Native 초심자를 위한 실습위주의 간단한 소개 및 구현법 안내
 
Springmvc
SpringmvcSpringmvc
Springmvc
 
코틀린 멀티플랫폼, 미지와의 조우
코틀린 멀티플랫폼, 미지와의 조우코틀린 멀티플랫폼, 미지와의 조우
코틀린 멀티플랫폼, 미지와의 조우
 
Jenkins와 Gitlab으로 쉽고 빠르게 구축하는 협업시스템
Jenkins와 Gitlab으로 쉽고 빠르게 구축하는 협업시스템Jenkins와 Gitlab으로 쉽고 빠르게 구축하는 협업시스템
Jenkins와 Gitlab으로 쉽고 빠르게 구축하는 협업시스템
 
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
 
Hacosa jquery 1th
Hacosa jquery 1thHacosa jquery 1th
Hacosa jquery 1th
 

Meteor React Tutorial 따라하기

  • 1. METEOR REACT TUTORIAL v1 - 09/01/2020 따라하기 Jiam Seo <jams7777@gmail.com>
  • 2. METEOR DOCKER 환경 ( 윈도우10 기준 ) • 단비 기본 도커환경을 우선 사용 • Jams777/meteor-tutorial 이미지를 kitematic 에서 다운로드 • Port 는 3000 : 3000 셋팅 • Port 는 3001 : 3001 셋팅 ( 몽고디비 ) • 쉘은 bash 로 함 • Vscode 에서 도커에 직접 연결해서 사용 • Vscode Remote – Containers 확장 기능 사용
  • 4. METEOR 프로젝트 생성 • 튜토리얼 페이지 접속 • https://react-tutorial.meteor.com/simple-todos/01-creating-app.html • 설치는 도커라 패스 • 도커환경 bash shell 실행(exec) • 프로젝트 생성 • meteor create --react . 현재폴더에 생성
  • 5. VSCODE 도커 연결 • Vscode Remote – Containers 설치 • F1 클릭 후 Remote-Containers: Attach to Running Container.. • meteor-tutorial 이미지 선택 ( jams777/meteor-tutorial ) • 폴더 열기 버튼 클릭 • /app/web 선택
  • 8. METEOR 프로젝트 생성 • meteor create --react .
  • 9. METEOR 프로젝트 실행 • meteor run • 브라우저로 http://localhost:3000 접속
  • 10. REACT 할일 컴포넌트 생성 • 파일 생성 imports/ui/Task.jsx import React from 'react'; export const Task = ({ task }) => { return <li>{task.text}</li> };
  • 11. REACT 할일 샘플 데이터 임시 셋팅 • 파일 수정 imports/ui/App.jsx import React from 'react'; import { Hello } from './Hello.jsx'; import { Info } from './Info.jsx’; // 추가 const tasks = [ {_id: 1, text: 'First Task'}, {_id: 2, text: 'Second Task'}, {_id: 3, text: 'Third Task'}, ]; export const App = () => ( ………
  • 12. REACT 할일 샘플 데이터 화면 표시 • 파일 수정 imports/ui/App.jsx import React from 'react'; // import { Hello } from './Hello.jsx'; // import { Info } from './Info.jsx'; import { Task } from './Task'; const tasks = [ {_id: 1, text: 'First Task'}, {_id: 2, text: 'Second Task'}, {_id: 3, text: 'Third Task'}, ]; export const App = () => ( <div> <h1>Welcome to Meteor!</h1> <ul> { tasks.map(task => <Task key={ task._id } task={ task }/>) } </ul> {/*<Hello/> <Info/>*/} </div> );
  • 13. REACT 할일 샘플 데이터 화면 표시 확인
  • 14. 모바일에서 잘보이게 하기 • 파일 수정 client/main.html <head> <title>web</title> <meta charset="utf-8"/> <meta http-equiv="x-ua-compatible" content="ie=edge"/> <meta name="viewport" content="width=device-width, height=device-height, viewport-fit=cover, initial- scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> <meta name="mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-capable" content="yes"/> </head>
  • 16. 데이터베이스 연결 • https://react-tutorial.meteor.com/simple-todos/02-collections.html • 몽고디비가 개발 시 깔려있음. 미니몽고로도 사용됨.
  • 17. 할일 컬렉션 생성 ( RDB테이블 같은것 ) • 파일 생성 imports/api/TasksCollection.js import { Mongo } from 'meteor/mongo'; export const TasksCollection = new Mongo.Collection('tasks’);
  • 18. 할일 샘플 데이터 임시 생성 • 파일 수정 server/main.js import { Meteor } from 'meteor/meteor'; import { TasksCollection } from '/imports/api/TasksCollection'; // 추가 함수 const insertTask = taskText => TasksCollection.insert({ text: taskText }); Meteor.startup(() => { // 데이터 없을때 7개 생성 if (TasksCollection.find().count() === 0) { [ 'First Task', 'Second Task', 'Third Task', 'Fourth Task', 'Fifth Task', 'Sixth Task', 'Seventh Task' ].forEach(insertTask) } });
  • 19. 화면에 데이터 연동 모듈 설치 • meteor add react-meteor-data • React hook 를 사용한다고 함 ( https://reactjs.org/docs/hooks-faq.html )
  • 20. REACT 할일 DB 데이터 화면 표시 • 파일 수정 imports/ui/App.jsx import React from 'react'; import { useTracker } from 'meteor/react-meteor-data'; // 여기추가 import { TasksCollection } from '/imports/api/TasksCollection'; // 여기추가 import { Task } from './Task'; // 삭제 const tasks = [ {_id: 1, text: 'First Task'}, {_id: 2, text: 'Second Task'}, {_id: 3, text: 'Third Task'}, ]; export const App = () => { const tasks = useTracker(() => TasksCollection.find({}).fetch()); // 여기추가 return ( <div> <h1>Welcome to Meteor!</h1> <ul> { tasks.map(task => <Task key={ task._id } task={ task }/>) } </ul> </div> ); };
  • 21. REACT 할일 DB 데이터 화면 표시 확인
  • 22. 몽고디비 데이터 확인 도구 • NoSQLBooster 무료도구 • https://nosqlbooster.com/downloads
  • 23. 할일 입력 화면과 기능 추가 • https://react-tutorial.meteor.com/simple-todos/03-forms-and-events.html • 입력할 화면을 만들고 사용자가 직접 입력할 수 있는 기능추가.
  • 24. REACT 할일 입력화면 • 파일 생성 imports/ui/TaskForm.jsx import React, { useState } from 'react'; export const TaskForm = () => { const [text, setText] = useState(""); return ( <form className="task-form"> <input type="text" placeholder="Type to add new tasks" /> <button type="submit">Add Task</button> </form> ); };
  • 25. REACT 할일 입력화면 메인에 추가 • 파일 수정 imports/ui/App.jsx import React from 'react'; import { useTracker } from 'meteor/react-meteor-data'; import { TasksCollection } from '/imports/api/TasksCollection'; import { Task } from './Task'; import { TaskForm } from './TaskForm'; // 여기 추가 export const App = () => { const tasks = useTracker(() => TasksCollection.find({}).fetch()); return ( <div> <h1>Welcome to Meteor!</h1> <TaskForm/> {/*여기추가*/} <ul> { tasks.map(task => <Task key={ task._id } task={ task }/>) } </ul> </div> ); };
  • 26. REACT 할일 입력화면 스타일 • 파일 수정 client/main.css .task-form { margin-top: 1rem; }
  • 27. REACT 할일 입력화면 생성기능 구현 • 파일 수정 imports/ui/TaskForm.jsx import React, { useState } from 'react'; import { TasksCollection } from '/imports/api/TasksCollection'; export const TaskForm = () => { const [text, setText] = useState(""); // 추가 const handleSubmit = e => { e.preventDefault(); if (!text) return; TasksCollection.insert({ text: text.trim(), createdAt: new Date() }); setText(""); }; return ( <form className="task-form" onSubmit={handleSubmit}> <input type="text" placeholder="Type to add new tasks" value={text} onChange={(e) => setText(e.target.value)} /> <button type="submit">Add Task</button> </form> ); };
  • 28. REACT 할일 입력화면 생성기능 테스트
  • 29. REACT 할일 최신순으로 • 파일 수정 imports/ui/App.jsx • 디비 조회 시 정렬조건 추가 .... export const App = () => { // const tasks = useTracker(() => TasksCollection.find({}).fetch()); const tasks = useTracker(() => TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch()); ....
  • 31. 할일 수정 삭제 기능 추가 • https://react-tutorial.meteor.com/simple-todos/04-update-and-remove.html • 할일 완료 체크기능. 체크시 완료로 변경 • 할일 삭제 버튼 추가. 클릭시 데이터 삭제
  • 32. 할일 완료(수정) 하기 기능 - 체크박스 • 파일 수정 imports/ui/Task.jsx import React from 'react'; export const Task = ({ task, onCheckboxClick }) => { return ( <li> <input type="checkbox" checked={!!task.isChecked} onClick={() => onCheckboxClick(task)} readOnly /> <span>{task.text}</span> </li> ); };
  • 33. 할일 완료(수정) 하기 기능 - 디비수정 • 파일 수정 imports/ui/App.jsx import { TaskForm } from './TaskForm'; // 여기 추가 const toggleChecked = ({ _id, isChecked }) => { TasksCollection.update(_id, { $set: { isChecked: !isChecked } }) }; export const App = () => { .... <ul> {/* tasks.map(task => <Task key={ task._id } task={ task }/>) */} { tasks.map(task => <Task key={ task._id } task={ task } onCheckboxClick={toggleChecked} />) } </ul> ....
  • 34. 할일 완료(수정) 하기 기능 - 테스트
  • 35. 할일 삭제하기 기능 - 삭제버튼 • 파일 수정 imports/ui/Task.jsx import React from 'react'; export const Task = ({ task, onCheckboxClick, onDeleteClick }) => { return ( <li> <input type="checkbox" checked={!!task.isChecked} onClick={() => onCheckboxClick(task)} readOnly /> <span>{task.text}</span> <button onClick={ () => onDeleteClick(task) }>&times;</button> </li> ); };
  • 36. 할일 삭제하기 기능 - 디비수정 • 파일 수정 imports/ui/App.jsx // 여기 추가 const deleteTask = ({ _id }) => TasksCollection.remove(_id); export const App = () => { .... <div> <h1>Welcome to Meteor!</h1> <TaskForm/> <ul> { tasks.map(task => <Task key={ task._id } task={ task } onCheckboxClick={toggleChecked} onDeleteClick={deleteTask} />) } </ul> </div> ....
  • 39. 화면 스타일 – CSS • 파일 수정 client/main.css body { font-family: sans-serif; background-color: #315481; background-image: linear- gradient(to bottom, #315481, #918e82 100%); background-attachment: fixed; position: absolute; top: 0; bottom: 0; left: 0; right: 0; padding: 0; margin: 0; font-size: 14px; } button { font-weight: bold; font-size: 1em; border: none; color: white; box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4); padding: 5px; cursor: pointer; } button:focus { outline: 0; } .app { display: flex; flex-direction: column; height: 100vh; } .app-header { flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .main { display: flex; flex-direction: column; flex-grow: 1; overflow: auto; background: white; } .main::-webkit-scrollbar { width: 0; height: 0; background: inherit; } header { background: #d2edf4; background-image: linear- gradient(to bottom, #d0edf5, #e1e5f0 100%); padding: 20px 15px 15px 15px; position: relative; box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4); } .app-bar { display: flex; justify-content: space-between; } .app-bar h1 { font-size: 1.5em; margin: 0; display: inline-block; margin-right: 1em; }
  • 40. 화면 스타일 – CSS • 파일 수정 client/main.css .task-form { display: flex; margin: 16px; } .task-form > input { flex-grow: 1; box-sizing: border-box; padding: 10px 6px; background: transparent; border: 1px solid #aaa; width: 100%; font-size: 1em; margin-right: 16px; } .task-form > input:focus { outline: 0; } .task-form > button { min-width: 100px; height: 95%; background-color: #315481; } .tasks { list-style-type: none; padding-inline-start: 0; padding-left: 16px; padding-right: 16px; margin-block-start: 0; margin-block-end: 0; } .task { display: flex; padding: 16px; border-bottom: #eee solid 1px; } .task > span { flex-grow: 1; } .task > button { justify-self: flex-end; background-color: #ff3046; }
  • 41. 스타일 적용 • 파일 수정 imports/ui/App.jsx .... return ( <div className="app"> <header> <div className="app-bar"> <div className="app-header"> <h1>Welcome to Meteor!</h1> </div> </div> </header> <div className="main"> <TaskForm /> <ul className="tasks"> …. </ul> </div> </div> ); };
  • 42. 스타일 적용 • 파일 수정 imports/ui/Task.jsx .... export const Task = ({ task, onCheckboxClick, onDeleteClick }) => { return ( <li className="task"> <input ....
  • 43. 헤더 글자 변경 • 파일 수정 imports/ui/App.jsx .... <h1>📝️ To Do List</h1> ....
  • 44. 화면 스타일 - 테스트
  • 45. 데이터 필터링 – 미완료만 보기 • https://react-tutorial.meteor.com/simple-todos/06-filter-tasks.html • 미완료만 보기 기능 추가
  • 46. 데이터 필터링 – 미완료 버튼추가 • 파일 수정 imports/ui/App.jsx import React, { useState } from 'react'; .... export const App = () => { const [hideCompleted, setHideCompleted] = useState(false); .... <div className="main"> <TaskForm /> <div className="filter"> <button onClick={() => setHideCompleted(!hideCompleted)}> {hideCompleted ? 'Show All' : 'Hide Completed'} </button> </div> ....
  • 47. 데이터 필터링 – 미완료 버튼스타일 • 파일 수정 client/main.css .filter { display: flex; justify-content: center; } .filter > button { background-color: #62807e; }
  • 48. 데이터 필터링 – 미완료 필터링로직 • 파일 수정 imports/ui/App.jsx .... export const App = () => { const [hideCompleted, setHideCompleted] = useState(false); const hideCompletedFilter = { isChecked: { $ne: true } }; const tasks = useTracker(() => TasksCollection.find(hideCompleted ? hideCompletedFilter : {}, { sort: { createdAt: -1 }, }).fetch() ); ....
  • 50. 개발도구로 프로토콜/데이터 확인 • https://chrome.google.com/webstore/detail/meteor-devtools- evolved/ibniinmoafhgbifjojidlagmggecmpgf • Meteor DevTools Evolved 크롬 확장기능 설치
  • 51. 데이터 필터링 – 미완료 갯수 보이기 • 파일 수정 imports/ui/App.jsx .... const pendingTasksCount = useTracker(() => TasksCollection.find(hideCompletedFilter).count() ); const pendingTasksTitle = `${ pendingTasksCount ? ` (${pendingTasksCount})` : '' }`; .... <div className="app-header"> <h1>📝️ To Do List {pendingTasksTitle} </h1> </div> ....
  • 52. 데이터 필터링 – 미완료 갯수 테스트
  • 53. 할일 등록 사용자 기능 추가 • https://react-tutorial.meteor.com/simple-todos/07-adding-user-accounts.html • 사용자 관리 기능 추가 • 로그인 기능 • 자기 할일만 관리 • 로그아웃 기능
  • 54. METEOR 사용자 기능 추가 • meteor add accounts-password • meteor npm install --save bcrypt • Node 암호화 모듈 bcrypt 사용
  • 55. 서버에 사용자 생성 • 파일 수정 server/main.js .... import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { TasksCollection } from '/imports/api/TasksCollection'; .... const SEED_USERNAME = 'jams777'; const SEED_PASSWORD = '1111'; Meteor.startup(() => { if (!Accounts.findUserByUsername(SEED_USERNAME)) { Accounts.createUser({ username: SEED_USERNAME, password: SEED_PASSWORD, }); } ....
  • 56. 로그인 화면 생성 • 파일 생성 imports/ui/LoginForm.jsx import { Meteor } from 'meteor/meteor'; import React, { useState } from 'react'; export const LoginForm = () => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const submit = e => { e.preventDefault(); Meteor.loginWithPassword(username, password); }; return ( <form onSubmit={submit} className="login-form"><div> <label htmlFor="username">Username</label> <input type="text" placeholder="Username" name="username" required onChange={e => setUsername(e.target.value)} /> <label htmlFor="password">Password</label> <input type="password" placeholder="Password" name="password" required onChange={e => setPassword(e.target.value)} /> <button type="submit">Log In</button> </div></form> ); };
  • 57. 로그인 화면 연결 • 파일 수정 imports/ui/App.jsx import React, { useState, Fragment } from 'react'; .... import { LoginForm } from './LoginForm'; .... export const App = () => { const user = useTracker(() => Meteor.user()); .... <div className="main"> {user ? ( <Fragment> <TaskForm /> ………. </ul> </Fragment> ) : ( <LoginForm /> )} </div> ....
  • 58. 로그인 화면 스타일 • 파일 수정 client/main.css .login-form { display: flex; flex-direction: column; height: 100%; justify-content: center; align-items: center; } .login-form > div { margin: 8px; } .login-form > div > label { font-weight: bold; } .login-form > div > input { flex-grow: 1; box-sizing: border-box; padding: 10px 6px; background: transparent; border: 1px solid #aaa; width: 100%; font-size: 1em; margin-right: 16px; margin-top: 4px; } .login-form > div > input:focus { outline: 0; } .login-form > div > button { background-color: #62807e; }
  • 60. 할일의 사용자 정보 같이 입력 • 기존 정보 삭제 • db.tasks.remove({});
  • 61. 할일의 사용자 정보 같이 입력 • 파일 수정 server/main.js .... const insertTask = (taskText, user) => TasksCollection.insert({ text: taskText, userId: user._id, createdAt: new Date(), }); .... Meteor.startup(() => { .... } const user = Accounts.findUserByUsername(SEED_USERNAME); // 데이터 없을때 7개 생성 if (TasksCollection.find().count() === 0) { [ …… ].forEach(taskText => insertTask(taskText, user)); } ....
  • 62. 할일의 사용자 정보 처리 • 파일 수정 imports/ui/App.jsx .... const hideCompletedFilter = { isChecked: { $ne: true } }; const userFilter = user ? { userId: user._id } : {}; const pendingOnlyFilter = { ...hideCompletedFilter, ...userFilt er }; const tasks = useTracker(() => { if (!user) { return []; } return TasksCollection.find( hideCompleted ? pendingOnlyFilter : userFilter, { sort: { createdAt: -1 }, } ).fetch(); }); const pendingTasksCount = useTracker(() => { if (!user) { return 0; } return TasksCollection.find(pendingOnlyFilter).count(); }); .... <TaskForm user={user} /> ....
  • 63. 할일의 사용자 정보 처리 • 파일 수정 imports/ui/TaskForm.jsx .... export const TaskForm = ({ user }) => { .... TasksCollection.insert({ text: text.trim(), createdAt: new Date(), userId: user._id }); setText(""); }; return ( ....
  • 64. 할일의 사용자 정보 처리 테스트
  • 65. 로그아웃 기능 - 버튼 • 파일 수정 imports/ui/App.jsx .... const logout = () => Meteor.logout(); export const App = () => { .... <Fragment> <div className="user" onClick={logout}> {user.username} 🚪 </div> <TaskForm user={user} /> ....
  • 66. 로그아웃 기능 - 스타일 • 파일 수정 client/main.css .user { display: flex; align-self: flex-end; margin: 8px 16px 0; font-weight: bold; }
  • 68. 할일 데이터 관리 백엔드 서비스로 • https://react-tutorial.meteor.com/simple-todos/08-methods.html • 화면에서 데이터 베이스 접속안되게 • meteor remove insecure • 백엔드 서비스로 할일 관리 함수 변경 • 화면에서 Meteor.call 로 호출하기 • Optimistic UI ( https://blog.meteor.com/optimistic-ui-with-meteor-67b5a78c3fcf ) • 조회는 미니몽고에서
  • 69. 할일 데이터 관리 백엔드 서비스 • 파일 생성 imports/api/tasksMethods.js import { check } from 'meteor/check'; import { TasksCollection } from './TasksCollection'; Meteor.methods({ 'tasks.insert'(text) { check(text, String); if (!this.userId) { throw new Meteor.Error('Not authorized.'); } TasksCollection.insert({ text, createdAt: new Date, userId: this.userId, }) }, 'tasks.remove'(taskId) { check(taskId, String); if (!this.userId) { throw new Meteor.Error('Not authorized.'); } TasksCollection.remove(taskId); }, 'tasks.setIsChecked'(taskId, isChecked) { check(taskId, String); check(isChecked, Boolean); if (!this.userId) { throw new Meteor.Error('Not authorized.'); } TasksCollection.update(taskId, { $set: { isChecked } }); } });
  • 70. 할일 데이터 관리 백엔드 서비스 등록 • 파일 수정 server/main.js import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { TasksCollection } from '/imports/db/TasksCollection'; import '/imports/api/tasksMethods'; ....
  • 71. 할일 데이터 관리 서비스 호출 • 파일 수정 imports/ui/TaskForm.jsx import { Meteor } from 'meteor/meteor'; import React, { useState } from 'react'; // import { TasksCollection } from '/imports/api/TasksCollection'; // 이제 사용안함 export const TaskForm = () => { // 이제 사용자 안받음 const [text, setText] = useState(""); // 추가 const handleSubmit = e => { e.preventDefault(); if (!text) return; // 백엔드 호출로 변경 Meteor.call('tasks.insert', text); setText(""); }; return ( ....
  • 72. 할일 데이터 관리 서비스 호출 • 파일 수정 imports/ui/App.jsx import { Meteor } from 'meteor/meteor'; import React, { useState, Fragment } from 'react'; …… const toggleChecked = ({ _id, isChecked }) => { Meteor.call('tasks.setIsChecked', _id, !isChecked); }; const deleteTask = ({ _id }) => Meteor.call('tasks.remove', _id); const logout = () => Meteor.logout(); …… <TaskForm /> …..
  • 73. 할일 데이터 연결 함수 폴더 변경 • 파일 수정 imports/api/tasksMethods.js • 파일 수정 imports/ui/TaskForm.jsx • 파일 수정 server/main.js • 파일 수정 imports/ui/App.jsx // 이걸로 변경 import { TasksCollection } from '/imports/db/TasksCollection';
  • 74. 사용자별로 구독처리되게 개선 • https://react-tutorial.meteor.com/simple-todos/09-publications.html • 현재 전체 데이터 구독되는 부분제거 • meteor remove autopublish • 구독 API 추가 • 화면 구독방식 변경
  • 75. 사용자별로 구독 서비스 추가 • 파일 생성 imports/api/tasksPublications.js import { Meteor } from 'meteor/meteor'; import { TasksCollection } from '/imports/db/TasksCollection'; Meteor.publish('tasks', function publishTasks() { return TasksCollection.find({ userId: this.userId }); });
  • 76. 사용자별로 구독 서비스 등록 • 파일 수정 server/main.js import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { TasksCollection } from '/imports/db/TasksCollection'; import '/imports/api/tasksMethods'; import '/imports/api/tasksPublications'; ....
  • 77. 화면 구독 서비스 사용 • 파일 수정 imports/ui/App.jsx .... const pendingOnlyFilter = { ...hideCompletedFilter, ...userFilter }; const { tasks, pendingTasksCount, isLoading } = useTracker(() => { const noDataAvailable = { tasks: [], pendingTasksCount: 0 }; if (!Meteor.user()) { return noDataAvailable; } const handler = Meteor.subscribe('tasks'); if (!handler.ready()) { return { ...noDataAvailable, isLoading: true }; } const tasks = TasksCollection.find( hideCompleted ? pendingOnlyFilter : userFilter, { sort: { createdAt: -1 }, } ).fetch(); const pendingTasksCount = TasksCollection.find(pendingOnlyFilter).count(); return { tasks, pendingTasksCount }; }); const pendingTasksTitle = `${ ....
  • 78. 화면 구독 중 상태 관리 • 파일 수정 imports/ui/App.jsx .... <div className="filter"> <button onClick={() => setHideCompleted(!hideCompleted)}> {hideCompleted ? 'Show All' : 'Hide Completed'} </button> </div> {isLoading && <div className="loading">loading...</div>} <ul className="tasks"> ....
  • 79. 화면 구독 중 상태 스타일 • 파일 수정 client/main.css .loading { display: flex; flex-direction: column; height: 100%; justify-content: center; align-items: center; font-weight: bold; }
  • 80. 자신의 할일만 수정할 수 있는 권한 추가 • 파일 수정 imports/api/tasksMethods.js .... 'tasks.remove'(taskId) { ….. if (!this.userId) { throw new Meteor.Error('Not authorized.'); } const task = TasksCollection.findOne({ _id: taskId, u serId: this.userId }); if (!task) { throw new Meteor.Error('Access denied.'); } TasksCollection.remove(taskId); }, .... .... 'tasks.setIsChecked'(taskId, isChecked) { check(taskId, String); check(isChecked, Boolean); if (!this.userId) { throw new Meteor.Error('Not authorized.'); } const task = TasksCollection.findOne({ _id: taskId, use rId: this.userId }); if (!task) { throw new Meteor.Error('Access denied.'); } TasksCollection.update(taskId, { $set: { isChecked } }); } ....
  • 81. 모바일에서 사용 • https://react-tutorial.meteor.com/simple-todos/10-running-on-mobile.html • iOS Simulator • meteor add-platform ios • meteor run ios • Android Emulator • meteor add-platform android • meteor run android
  • 82. 모바일에서 사용 • Android Device • meteor run android-device • iPhone or iPad • meteor run ios-device
  • 83. 테스트 케이스 작성 • https://react-tutorial.meteor.com/simple-todos/11-testing.html • 프론트엔드, 백엔드 전부 테스트 가능 • meteor add meteortesting:mocha • meteor npm install --save-dev chai
  • 84. 테스트 모드 • TEST_WATCH=1 meteor test --driver-package meteortesting:mocha
  • 85. 스캐폴드 테스트(SCAFFOLD TEST) • 파일 생성 imports/api/tasksMethods.tests.js import { Meteor } from 'meteor/meteor'; if (Meteor.isServer) { describe('Tasks', () => { describe('methods', () => { it('can delete owned task', () => {}); }); }); }
  • 86. 스캐폴드 테스트 등록 • 파일 수정 tests/main.js import assert from "assert"; import '/imports/api/tasksMethods.tests.js'; ....
  • 87. 데이터베이스 준비 전처리 • 파일 수정 imports/api/tasksMethods.tests.js import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { TasksCollection } from '/imports/db/TasksCollection'; if (Meteor.isServer) { describe('Tasks', () => { describe('methods', () => { const userId = Random.id(); let taskId; beforeEach(() => { TasksCollection.remove({}); taskId = TasksCollection.insert({ text: 'Test Task', createdAt: new Date(), userId, }); }); }); }); }
  • 88. 테스트 데이터 삭제되게 • meteor add quave:testing
  • 89. 테스트 데이터 삭제되게 • 파일 생성 imports/api/tasks.tests.js import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { mockMethodCall } from 'meteor/quave:testing'; import { assert } from 'chai'; import { TasksCollection } from '/imports/db/TasksCollection'; import '/imports/api/tasksMethods'; if (Meteor.isServer) { describe('Tasks', () => { describe('methods', () => { const userId = Random.id(); let taskId; beforeEach(() => { TasksCollection.remove({}); taskId = TasksCollection.insert({ text: 'Test Task', createdAt: new Date(), userId, }); }); it('can delete owned task', () => { mockMethodCall('tasks.remove', taskId, { context: { u serId } }); assert.equal(TasksCollection.find().count(), 0); }); }); }); }
  • 90. 테스트 데이터 삭제되게 등록 • 파일 수정 tests/main.js import assert from "assert"; import '/imports/api/tasksMethods.tests.js'; import '/imports/api/tasks.tests.js'; ....
  • 92. 추가 테스트 케이스 • 파일 수정 imports/api/tasks.tests.js .... it(`can't delete task without an user authenticated`, () => { const fn = () => mockMethodCall('tasks.remove', taskId); assert.throw(fn, /Not authorized/); assert.equal(TasksCollection.find().count(), 1); }); it(`can't delete task from another owner`, () => { const fn = () => mockMethodCall('tasks.remove', taskId, { context: { userId: 'somebody-else-id' }, }); assert.throw(fn, /Access denied/); assert.equal(TasksCollection.find().count(), 1); }); it('can change the status of a task', () => { const originalTask = TasksCollection.findOne(taskId); mockMethodCall('tasks.setIsChecked', taskId, !originalTask.isChecked, { context: { userId }, }); const updatedTask = TasksCollection.findOne(taskId); assert.notEqual(updatedTask.isChecked, originalTask.isChecked); }); it('can insert new tasks', () => { const text = 'New Task'; mockMethodCall('tasks.insert', text, { context: { userId }, }); const tasks = TasksCollection.find({}).fetch(); assert.equal(tasks.length, 2); assert.isTrue(tasks.some(task => task.text === text)); }); }); }); }
  • 94. 테스트 수행 명령어 • 파일 확인 package.json • meteor npm test • meteor npm run test-app .... "scripts": { "start": "meteor run", "test": "meteor test --once --driver-package meteortesting:mocha", "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha", "visualize": "meteor --production --extra-packages bundle-visualizer" }, ....
  • 96. 사용 라이브러리 확인 • 파일 확인 package.json • meteor npm visualize
  • 97. 만든 앱 배포 • https://react-tutorial.meteor.com/simple-todos/12-deploying.html • Meteor 에서 제공하는 Galaxy 에 대한 설명으로 pass • Mongodb 는 서비스 받는 것이 좋음
  • 98. 마무리 • 오랜만에 Meteor을 해봤음. • React 와 함께 사용하는 방법을 익혔음. • React Route 를 추가로 해봐야 겠음. • Redux 보다는 Meteor Pub/Sub 으로 처리하고 • 화면에서 캐시형태로 Redux 를 사용하면 좋을듯 함.
  • 99.