References: 13 Noteworthy Points from Google’s JavaScript Style Guide

 

13 Noteworthy Points from Google’s JavaScript Style Guide

For anyone who isn’t already familiar with it, Google puts out a style guide for writing JavaScript that lays out (what Google believes to…

medium.freecodecamp.org

 

다음의 내용은 위의 글을 참조한 내용입니다.

 

 

1.Use spaces, not tabs - 탭 대신 스페이스를 사용하세요.

 

The ASCII horizontal space character (0x20) is the only whitespace character that appears anywhere in a source file. 
- ASCII 수평 공백 문자 (0x20)는 소스 파일의 아무 곳에 나 나타나는 유일한 공백 문자입니다.

This implies that… Tab characters are not used for indentation.
- 탭 문자는 들여쓰기에 사용하지 않습니다.

 

// bad
function foo() {
∙∙∙∙let name;
}

// bad
function bar() {
∙let name;
}

// good
function baz() {
∙∙let name;
}

 

 

2. Semicolons ARE required - 세미콜론은 필요합니다.

 

Every statement must be terminated with a semicolon.
-모든 문장은 세미콜론으로 끝나야 합니다.

Relying on automatic semicolon insertion is forbidden.
- 자동으로 세미콜론이 삽입되는것에 의존하는것은 금지되어있습니다.

 

왜 이 아이디어에 반대하는 사람이 있는지는 상상할 수 없습니다만, JS에서 세미콜론을 일관되게 사용하는것이 새로운 'Space 대 Tab' 논쟁이 되고 있습니다. 구글은 세미콜론을 지키기 위해 단호히 나왔습니다.

 

// bad
let luke = {}
let leia = {}
[luke, leia].forEach(jedi => jedi.father = 'vader')

// good
let luke = {};
let leia = {};
[luke, leia].forEach((jedi) => {
  jedi.father = 'vader';
});

 

 

3. Don’t use ES6 modules (yet) - (아직은) ES6 모듈을 사용하지 마세요.

 

Do not use ES6 modules yet (i.e. the export and import keywords), as their semantics are not yet finalized.
- 의미론이 아직 확정되지 않았으므로 ES6 모듈 (예를 들면 export와 import 키워드들)을 사용하지 마세요.

Note that this policy will be revisited once the semantics are fully-standard.
- 이 정책은 의미론이 완전히 표준화되면 다시 검토될 것입니다.

 

//이러한 것들을 아직 하지 마세요:

//------ lib.js ------
export function square(x) {
  return x * x;
}
export function diag(x, y) {
  return sqrt(square(x) + square(y));
}

//------ main.js ------
import { square, diag } from 'lib';

 

 

4. Horizontal alignment is discouraged (but not forbidden) - 수평정렬은 권장되지 않습니다. (그러나 금지되지는 않습니다.)

 

This practice is permitted, but it is generally discouraged by Google Style.
- 이 방법은 허용이 됩니다만 일반적으로 구글 스타일에서는 권장되지 않습니다.

It is not even required to maintain horizontal alignment in places where it was already used.
- 이미 사용된 장소에서도 수평정렬을 유지할 필요가 없습니다.

 

// bad
{
  tiny:     42,
  longer: 435,
};

// good
{
  tiny: 42,
  longer: 435,
};

 

 

5. Don’t use var anymore. - 더이상 var을 사용하지 마세요.

 

Declare all local variables with either const or let.
- 모든 로컬 변수들을 const나 let으로 선언하세요.

Use const by default, unless a variable needs to be reassigned.
- 변수에 재할당이 필요한 경우가 아니면 const를 기본으로 사용하세요.

The var keyword must not be used.
- var 키워드는 사용되지 말아야 합니다.

 

아직도 StackOverflow 및 다른곳에 있는 코드 샘플에서 var를 사용하는 사람이 보입니다. 누군가가 그로 인해 사건을 만들지, 단지 죽어가는 오래된 습관인지는 알 수 없습니다.

 

// bad
var example = 42;

// good
let example = 42;

 

 

6. Arrow functions are preferred - 화살표 함수가 선호됩니다.

 

Arrow functions provide a concise syntax and fix a number of difficulties with this.
- 화살표 함수는 간결한 구문을 제공하고 그로인해 많은 어려움을 해결합니다.

Prefer arrow functions over the function keyword, particularly for nested functions.
- 특히 중첩된 함수의 경우 화살표 함수가 function 키워드보다 선호됩니다.

 

솔직히 화살표 함수가 더 간결하고 보기 좋기 때문에 더 좋다고 생각했습니다. 화살표 함수는 꽤나 중요한 목적 또한 수행하는 것으로 밝혀졌습니다.

 

// bad
[1, 2, 3].map(function (x) {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

 

 

7. Use template strings instead of concatenation - 연결하는것 대신에 탬플릿 문자열을 사용하세요.

 

Use template strings (delimited with `) over complex string concatenation, particularly if multiple string literals re involved.
- 여러 string 리터럴이 관련되어있는 경우 특히 복잡한 문자열 연결에 대해서 템플릿 문자열 (`로 구분됨)을 사용하세요.

Template strings may span multiple lines.
- 템플릿 문자열은 여러 줄에 걸쳐있을 수도 있습니다.

 

// bad
function sayHi(name) {
  return 'How are you, ' + name + '?';
}

// bad
function sayHi(name) {
  return ['How are you, ', name, '?'].join();
}

// bad
function sayHi(name) {
  return `How are you, ${ name }?`;
}

// good
function sayHi(name) {
  return `How are you, ${name}?`;
}

 

 

8. Don’t use line continuations for long strings - 긴 문자열에서 줄 연속을 쓰지 마세요.

 

Do not use line continuations (that is, ending a line inside a string literal with a backslash) in either ordinary or emplate string literals.
- 일반적이거나 템플릿 문자열의 리터럴에서 줄 연속 (문자열 리터럴 내에서 백 슬래시로 줄을 끝내는것)을 사용하지 마세요.

Even though ES5 allows this, it can lead to tricky errors if any trailing whitespace comes after the slash, and is less obvious to readers.
- ES5에서는 이것을 허용하지만 슬래시 뒤에 오는 공백이 있으면 보는이가 쉽게 알 수 없으므로 까다로운 오류가 발생할 수 있습니다.

 

흥미롭게도 이것은 Google과 Airbnb가 동의하지 않는 규칙입니다 (Airbnb의 스펙 참조).
Google은 긴 문자열 (아래 그림 참조)을 연결하는 것을 권장하지만 Airbnb의 스타일 가이드는 본질적으로 아무 것도하지 말고 긴 문자열을 필요하면 길게 사용할 수 있도록 권장합니다.

 

// bad (미안하지만 모바일에선 잘 표시되지 않습니다.)
const longString = 'This is a very long string that \
  far exceeds the 80 column limit. It unfortunately \
  contains long stretches of spaces due to how the \
  continued lines are indented.';

// good
const longString = 'This is a very long string that ' +
  'far exceeds the 80 column limit. It does not contain ' +
  'long stretches of spaces since the concatenated ' +
  'strings are cleaner.';

 

 

9. “for… of” is the preferred type of ‘for loop’ - "for ... of"는 'for loop'의 바람직한 유형입니다.

 

With ES6, the language now has three different kinds of for loops.
- ES6에서는 이제 언어에 3 가지 다른 종류의 for loop를 갖습니다.

All may be used, though 
for-of loops should be preferred when possible.

 

내게 묻는다면 이상한 것이지만, 난 구글이 for루프의 바람직한 유형을 선헌한것이 꽤나 흥미롭기 때문에 이것을 포함시킬 것이라고 생각했습니다.

저는 항상 "... in" 루프는 객체에 더 좋았고, "for ... of"는 배열에 더 적합하다는 생각 이었습니다. -  "올바른 작업을 위한 올바른 도구"유형의 상황.

구글의 사향이 반드시 그 생각과 상반되는것은 아니지만, 특히 이 루프에 대한 선호가 있단것을 하는것은 여전히 흥미롭습니다.

 

 

10. Don’t use eval() - eval()을 사용하지 마세요

 

Do not use eval or the Function(...string) constructor (except for code loaders).
eval 또는 Function (... string) 생성자를 사용하지 마세요 (코드 로더는 제외됩니다). 

These features are potentially dangerous and simply do not work in CSP environments.

 

eval()의 MDN 페이지에는 "Do not ever use eval!"라는 섹션이 있습니다.

 

// bad
let obj = { a: 20, b: 30 };
let propName = getPropName(); // returns "a" or "b"
eval( 'var result = obj.' + propName );

// good
let obj = { a: 20, b: 30 };
let propName = getPropName(); // returns "a" or "b"
let result = obj[ propName ]; // obj[ "a" ] is the same as obj.a

 

 

11. Constants should be named in ALL_UPPERCASE separated by underscores - 상수는 밑줄로 구분된 대문자로 명명되어야 합니다.

 

Constant names use CONSTANT_CASE: all uppercase letters, with words separated by underscores.

 

변수가 변경되지 않아야한다고 절대적으로 확신하는 경우 상수의 이름을 대문자로 표시하여 이를 나타낼 수 있습니다. 이렇게하면 코드 전체에서 사용됨에 따라 상수의 불변성이 명확해집니다.
이 규칙의 주목할만한 예외는 상수가 function-scope인 경우입니다. 이 경우 camelCase로 작성해야합니다.

 

// bad
const number = 5;

// good
const NUMBER = 5;

 

 

12. One variable per declaration - 선언 하나에 변수 하나

 

Every local variable declaration declares only one variable: declarations such as let a = 1, b = 2; are not used.
- 모든 지역 변수의 선언은 하나의 변수만 선언합니다: let a = 1, b = 2;와 같은 선언은 사용하지 않습니다.

 

// bad
let a = 1, b = 2, c = 3;

// good
let a = 1;
let b = 2;
let c = 3;

 

 

13. Use single quotes, not double quotes - 큰따옴표(") 대신 작은따옴표(')를 사용하세요.

 

Ordinary string literals are delimited with single quotes ('), rather than double quotes (").
- 일반적인 문자열 리터럴은 큰 따옴표(") 대신 작은 따옴표(')로 구분됩니다.

Tip: if a string contains a single quote character, consider using a template string to avoid having to escape the quote.

 

// bad
let directive = "No identification of self or mission."

// bad
let saying = 'Say it ain\u0027t so.';

// good
let directive = 'No identification of self or mission.';

// good
let saying = `Say it ain't so`;

 

마지막으로

 

제가 처음에 말했듯이, 이것들은 명령이 아닙니다. 

Google은 많은 기술 대기업 중 하나일 뿐이며 이러한 것들은 단지 권고사항일 뿐 입니다.
즉 훌륭한 코드를 작성하는 데 많은 시간을 할애하는 훌륭한 사람들을 고용하고있는 Google과 같은 회사에서 제안한 스타일 권장 사항을 살펴 보는 것은 흥미로운것 입니다.
'Google compliant source code’에 대한 가이드 라인을 따르고 싶다면 이러한 규칙들을 따라야 합니다. 

물론 많은 사람들이 동의하지 않으며 이 중 일부 또는 전부를 마음대로 쓸 수 있습니다.
저는 개인적으로 Airbnb의 사양이 Google보다 더 매력적이라고 생각합니다.

이러한 특정 규칙을 취하는 태도에 상관없이 모든 종류의 코드를 작성할 때 문체의 일관성을 염두에 두는 것이 중요합니다.

 

 

 

반응형

1. 생성된 유저 확인

>USE mysql;

>SELECT host, user FROM user;

 

2. 유저 생성

>CREATE USER 'userNameHere'@'accessRange' IDENTIFIED BY 'userPasswordHere';

** Access Range

> '%' 모든 ip주소에 대하여 접근 허용

> 'localhost' 로컬에서의 접근만 허용

> '192.168.0.%' 특정 ip주소에 대하여 접근 허용

 

3. 권한 부여

>GRANT ALL PRIVILEGES ON databaseNameHere.* TO 'userNameHere'@'accessRange';

 

4. 권한 새로고침

>FLUSH PRIVILEGES;

 

반응형

 

웹팩

 

* 웹팩은 프로젝트의 구조를 분석하고 자바스크립트 모듈을 비롯한 관련 리소스들을 찾은 다음 이를 브라우저에서 이용할 수 있는 번들로 묶고 패킹하는 모듈 번들러(Module bundler)다.

 

* 빌드툴이 아닌 모듈 번들러다. Grunt나 Gulp와는 다르다.

 

Webpack

반응형

References: Creating your first app


마지막으로 테스팅을 진행해 보도록 합니다.



1. 테스트 진행하기



자바스크립트 테스트 드라이버와 어설션 라이브러리를 설치합니다.


$ meteor add meteortesting:mocha

$ meteor npm install --save-dev chai









반응형

References: Creating your first app



프로젝트 폴더에서 콘솔로 meteor list를 입력해 봅시다.


몇 가지 패키지가 눈에 띄시지 않나요 ?



지난 포스팅에서도 언급했듯이 저 패키지를 그대로 내보내는 건 보안 이슈가 있습니다.


이번에는 이 패키지를 제거해 보겠습니다.




1. 메서드를 이용한 보안



일단 insecure 패키지를 제거합시다. 콘솔에서 meteor remove insecure를 입력해 패키지를 제거해 주세요



이로써 클라이언트 측에 기본으로 주어지던 DB 사용 권한이 다 사라졌습니다.  


이제 새로 디비에 접근하기 위한 메서드를 정의합니다.


/imports/api/tasks.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor'
import { check } from 'meteor/check';
 
export const Tasks = new Mongo.Collection('tasks');
 
Meteor.methods({
    'tasks.insert'(text) {
      check(text, String);
      // Make sure the user is logged in before inserting a task
      if (! this.userId) {
        throw new Meteor.Error('not-authorized');
      }
      Tasks.insert({
        text,
        createdAt: new Date(),
        owner: this.userId,
        username: Meteor.users.findOne(this.userId).username,
      });
    },
    'tasks.remove'(taskId) {
      check(taskId, String);
      Tasks.remove(taskId);
    },
    'tasks.setChecked'(taskId, setChecked) {
      check(taskId, String);
      check(setChecked, Boolean);
      Tasks.update(taskId, { $set: { checked: setChecked } });
    },
  });
cs


기존 task 컬렉션에 직접 붙어서 수행하던 것을 새로 정의한 메서드로 대체 합니다.


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
  handleSubmit(event) {
    event.preventDefault();
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
    Meteor.call('tasks.insert', text);
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
cs


/imports/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { Component } from 'react';
import { Meteor } from 'meteor/meteor';
 
// Task component - represents a single todo item
export default class Task extends Component {
    toggleChecked() {
        // Set the checked property to the opposite of its current value
        Meteor.call('tasks.setChecked', this.props.task._id, !this.props.task.checked);
    }
 
    deleteThisTask() {
        Meteor.call('tasks.remove', this.props.task._id);
    }
 
    render() {
        // Give tasks a different className when they are checked off,
        // so that we can style them nicely in CSS
        const taskClassName = this.props.task.checked ? 'checked' : '';
        return (
            <li className={taskClassName}>
                <button className="delete" onClick={this.deleteThisTask.bind(this)}> &times; </button>
                <input type="checkbox" readOnly checked={!!this.props.task.checked} onClick={this.toggleChecked.bind(this)} />
                <span className="text">
                    <strong>{this.props.task.username}</strong>: {this.props.task.text}
                </span>
            </li>
        );
    }
}
cs


그리고 화면을 확인합니다.


모든 인풋과 버튼이 동작함을 확인할 수 있습니다.


위와 같이 수정함으로써 클라이언트 코드와 DB코드가 분리되는 장점을 추가로 얻게 됩니다.




2. 발행과 구독



자동으로 DB에서 모든 데이터를 가져오는걸 막기 위해 autopublish 패키지를 제거합니다. 


$ meteor remove autopublisk



이제 자동으로 서버측 몽고디비에서 데이터를 가져오지 않습니다.


Publish/Subscribe 메서드를 구현합니다.


/imports/api/tasks.js 


1
2
3
4
5
6
7
8
9
10
11
12
...
export const Tasks = new Mongo.Collection('tasks');
 
if (Meteor.isServer) {
    // This code only runs on the server
    Meteor.publish('tasks'function tasksPublication() {
      return Tasks.find();
    });
  }
 
Meteor.methods({
...
cs


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
9
...
export default withTracker(() => {
  Meteor.subscribe('tasks');
  return {
    tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
    incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    currentUser: Meteor.user(),
  };
})(App);
cs


이제 다시 작업 리스트가 보입니다.


서버에서 Meteor.publish를 호출하면 tasks라는 이름으로 발행을 진행합니다.


이후 클라이언트에서 Meteor.subscribe를 통해 발행된 정보를 가져옵니다.




3. 비공개 작업 추가하기.



css 클래스의 이름별로 다른 설정을 하기 위해 classnames 모듈을 설치합니다.


$ meteor npm install --save classnames



공개/비공개 버튼을 추가하고 비공개일 경우 작업을 추가한 유저가 아니면 보이지 않도록 하는 코드를 추가합니다.


/imports/api/tasks.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
 
export const Tasks = new Mongo.Collection('tasks');
 
if (Meteor.isServer) {
    // This code only runs on the server
    // Only publish tasks that are public or belong to the current user
    Meteor.publish('tasks'function tasksPublication() {
        return Tasks.find({
            $or: [
                { private: { $ne: true } },
                { owner: this.userId },
            ],
        });
    });
}
 
Meteor.methods({
    'tasks.insert'(text) {
        check(text, String);
        // Make sure the user is logged in before inserting a task
        if (!this.userId) {
            throw new Meteor.Error('not-authorized');
        }
        Tasks.insert({
            text,
            createdAt: new Date(),
            owner: this.userId,
            username: Meteor.users.findOne(this.userId).username,
        });
    },
    'tasks.remove'(taskId) {
        check(taskId, String);
        const task = Tasks.findOne(taskId);
        if (task.private && task.owner !== this.userId) {
          // If the task is private, make sure only the owner can delete it
          throw new Meteor.Error('not-authorized');
        }
        Tasks.remove(taskId);
    },
    'tasks.setChecked'(taskId, setChecked) {
        check(taskId, String);
        check(setChecked, Boolean);
        const task = Tasks.findOne(taskId);
        if (task.private && task.owner !== this.userId) {
          // If the task is private, make sure only the owner can check it off
          throw new Meteor.Error('not-authorized');
        }
        Tasks.update(taskId, { $set: { checked: setChecked } });
    },
    'tasks.setPrivate'(taskId, setToPrivate) {
        check(taskId, String);
        check(setToPrivate, Boolean);
        const task = Tasks.findOne(taskId);
        // Make sure only the task owner can make a task private
        if (task.owner !== this.userId) {
            throw new Meteor.Error('not-authorized');
        }
        Tasks.update(taskId, { $set: { private: setToPrivate } });
    },
});
cs


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
import AccountsUIWrapper from './AccountsUIWrapper.jsx';
import { Meteor } from 'meteor/meteor';
 
// App component - represents the whole app
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hideCompleted: false,
    };
  }
 
  handleSubmit(event) {
    event.preventDefault();
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
    Meteor.call('tasks.insert', text);
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
 
  renderTasks() {
    let filteredTasks = this.props.tasks;
    if (this.state.hideCompleted) {
      filteredTasks = filteredTasks.filter(task => !task.checked);
    }
    return filteredTasks.map((task) => {
      const currentUserId = this.props.currentUser && this.props.currentUser._id;
      const showPrivateButton = task.owner === currentUserId;
      return (
        <Task
          key={task._id}
          task={task}
          showPrivateButton={showPrivateButton}
        />
      );
    });
  }
 
  toggleHideCompleted() {
    this.setState({
      hideCompleted: !this.state.hideCompleted,
    });
  }
 
  render() {
    return (
      <div className="container">
        <header>
        <h1>Todo List ({this.props.incompleteCount})</h1>
          <label className="hide-completed">
            <input
              type="checkbox"
              readOnly
              checked={this.state.hideCompleted}
              onClick={this.toggleHideCompleted.bind(this)}
            />
            Hide Completed Tasks
          </label>
          <AccountsUIWrapper />
          { this.props.currentUser ?
            <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
              <input
                type="text"
                ref="textInput"
                placeholder="Type to add new tasks"
              />
            </form> : ''
          }
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}
 
export default withTracker(() => {
  Meteor.subscribe('tasks');
  return {
    tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
    incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    currentUser: Meteor.user(),
  };
})(App);
cs

 

/imports/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React, { Component } from 'react';
import { Meteor } from 'meteor/meteor';
import classnames from 'classnames';
 
// Task component - represents a single todo item
export default class Task extends Component {
    toggleChecked() {
        // Set the checked property to the opposite of its current value
        Meteor.call('tasks.setChecked', this.props.task._id, !this.props.task.checked);
    }
 
    deleteThisTask() {
        Meteor.call('tasks.remove', this.props.task._id);
    }
 
    togglePrivate() {
        Meteor.call('tasks.setPrivate', this.props.task._id, !this.props.task.private);
    }
 
    render() {
        // Give tasks a different className when they are checked off,
        // so that we can style them nicely in CSS
        const taskClassName = classnames({
            checked: this.props.task.checked,
            private: this.props.task.private,
        });
        return (
            <li className={taskClassName}>
                <button className="delete" onClick={this.deleteThisTask.bind(this)}> &times; </button>
                <input type="checkbox" readOnly checked={!!this.props.task.checked} onClick={this.toggleChecked.bind(this)} />
                {this.props.showPrivateButton ? (
                    <button className="toggle-private" onClick={this.togglePrivate.bind(this)}>
                        {this.props.task.private ? 'Private' : 'Public'}
                    </button>
                ) : ''}
                <span className="text">
                    <strong>{this.props.task.username}</strong>: {this.props.task.text}
                </span>
            </li>
        );
    }
}
cs


이제 화면을 보면 Private 로 작업을 변경할 수 있습니다.



Private작업은 로그아웃 하면 보이지 않습니다.






반응형

References: Creating your first app



이번엔 User Account를 추가해 봅니다.



1. 유저 기능 추가하기



미티어 프레임 워크에서 제공해주는 accounts-ui와 accounts-password 패키지를 사용합니다.


콘솔에서 다음 명령을 실행합니다: meteor add accounts-ui accoutns-password



accounts-ui는 블레이즈 UI 컴포넌트이므로 리액트에서 사용하기 위해 래핑해줍니다.


/imports/ui/AccountsUIWrapper.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Template } from 'meteor/templating';
import { Blaze } from 'meteor/blaze';
 
export default class AccountsUIWrapper extends Component {
  componentDidMount() {
    // Use Meteor Blaze to render login buttons
    this.view = Blaze.render(Template.loginButtons,
      ReactDOM.findDOMNode(this.refs.container));
  }
  componentWillUnmount() {
    // Clean up Blaze view
    Blaze.remove(this.view);
  }
  render() {
    // Just render a placeholder container that will be filled in
    return <span ref="container" />;
  }
}
cs


래퍼를 사용하도록 /imports/ui/app.jsx파일을 수정해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
import AccountsUIWrapper from './AccountsUIWrapper.jsx';
...
            ...
            Hide Completed Tasks
          </label>
          <AccountsUIWrapper />
          <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
            <input
                ...
 
cs


accounts-ui의 기본 id포맷은 email입니다. 이걸 변경할 수 있도록 설정을 추가합니다.


/imports/startup/accounts-config.js


1
2
3
4
5
import { Accounts } from 'meteor/accounts-base';
 
Accounts.ui.config({
  passwordSignupFields: 'USERNAME_ONLY'
});
cs


추가한 설정 파일을 적용합니다.


/clinet/main.jsx


1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
 
import App from '../imports/ui/App';
import '../imports/startup/accounts-config.js';
 
Meteor.startup(() => {
  render(<App />document.getElementById('render-target'));
});
 
cs


이후 화면을 확인하면 Sing in이 추가된 것을 확인할 수 있으며 이를 클릭하면 다음과 같은 화면이 출력됩니다.





2. 작업 목록에 유저 기능 적용하기.



이제 작업 목록에 유저에 대한 정보를 적용 시켜 보겠습니다.

** owner: 작업을 작성한 사용자의 _id

** username: 작업을 작성한 사용자의 이름.


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
import AccountsUIWrapper from './AccountsUIWrapper.jsx';
import { Meteor } from 'meteor/meteor';
 
// App component - represents the whole app
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hideCompleted: false,
    };
  }
 
  handleSubmit(event) {
    event.preventDefault();
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
      owner: Meteor.userId(),           // _id of logged in user
      username: Meteor.user().username,  // username of logged in user
    });
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
 
...
 
export default withTracker(() => {
  return {
    tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
    incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    currentUser: Meteor.user(),
  };
})(App);
cs


그리고 로그인 한 경우에만 새 작업을 생성할 수 있도록 합니다.


/imports/ui/App.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
  render() {
    return (
      <div className="container">
        <header>
        <h1>Todo List ({this.props.incompleteCount})</h1>
          <label className="hide-completed">
            <input
              type="checkbox"
              readOnly
              checked={this.state.hideCompleted}
              onClick={this.toggleHideCompleted.bind(this)}
            />
            Hide Completed Tasks
          </label>
          <AccountsUIWrapper />
          { this.props.currentUser ?
            <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
              <input
                type="text"
                ref="textInput"
                placeholder="Type to add new tasks"
              />
            </form> : ''
          }
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
...
cs


리스트에 유저 이름과 작업이 함께 나오도록 수정합니다.


/imports/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component } from 'react';
import { Tasks } from '../api/tasks.js';
 
// Task component - represents a single todo item
export default class Task extends Component {
    toggleChecked() {
        // Set the checked property to the opposite of its current value
        Tasks.update(this.props.task._id, {
            $set: { checked: !this.props.task.checked },
        });
    }
 
    deleteThisTask() {
        Tasks.remove(this.props.task._id);
    }
 
    render() {
        // Give tasks a different className when they are checked off,
        // so that we can style them nicely in CSS
        const taskClassName = this.props.task.checked ? 'checked' : '';
        return (
            <li className={taskClassName}>
                <button className="delete" onClick={this.deleteThisTask.bind(this)}> &times; </button>
                <input type="checkbox" readOnly checked={!!this.props.task.checked} onClick={this.toggleChecked.bind(this)} />
                <span className="text">
                    <strong>{this.props.task.username}</strong>: {this.props.task.text}
                </span>
            </li>
        );
    }
}
cs


기존에 있던 작업을 지우고 회원가입을 합니다.



이후 새로운 작업을 추가하면 다음과 같이 보입니다.






반응형

References: Creating your first app




1. Input Form 만들기



계속 콘솔로 데이터를 넣어줄 순 없으니 인풋 폼을 만들어 줍니다.


/imports/ui/app.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
 
// App component - represents the whole app
class App extends Component {
  handleSubmit(event) {
    event.preventDefault();
 
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
 
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
    });
 
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
  renderTasks() {
    return this.props.tasks.map((task) => (
      <Task key={task._id} task={task} />
    ));
  }
  render() {
    return (
      <div className="container">
        <header>
          <h1>Todo List</h1>
          <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
            <input
              type="text"
              ref="textInput"
              placeholder="Type to add new tasks"
            />
          </form>
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}
 
export default withTracker(() => {
  return {
    tasks: Tasks.find({}).fetch(),
  };
})(App);
cs


다음과 같이 인풋 창이 생깁니다. (New task!를 입력한 상태)



이 상태에서 엔터키를 입력하면 데이터가 저장됩니다.


만약 가장 최근에 등록한 글을 맨 위에 보고 싶으면 다음과 같이 수정 해 주시면 됩니다.


/imports/ui/app.jsx Line:49


export default withTracker(() => {
return {
tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
};
})(App);





2. 완료 체크박스와 삭제 버튼 만들기



체크 박스와 삭제 버튼을 추가해 봅시다.


/import/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component } from 'react';
 
import { Tasks } from '../api/tasks.js';
 
// Task component - represents a single todo item
export default class Task extends Component {
  toggleChecked() {
    // Set the checked property to the opposite of its current value
    Tasks.update(this.props.task._id, {
      $set: { checked: !this.props.task.checked },
    });
  }
 
  deleteThisTask() {
    Tasks.remove(this.props.task._id);
  }
 
  render() {
    // Give tasks a different className when they are checked off,
    // so that we can style them nicely in CSS
    const taskClassName = this.props.task.checked ? 'checked' : '';
 
    return (
      <li className={taskClassName}>
        <button className="delete" onClick={this.deleteThisTask.bind(this)}>
          &times;
        </button>
 
        <input
          type="checkbox"
          readOnly
          checked={!!this.props.task.checked}
          onClick={this.toggleChecked.bind(this)}
        />
 
        <span className="text">{this.props.task.text}</span>
      </li>
    );
  }
}
cs


다음과 같은 결과를 확인할 수 있습니다.



이번엔 완료 체크된 작업은 숨길 수 있는 기능과 미완료 작업의 갯수를 표시해 보겠습니다.


/imports/ui/app.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import ReactDOM from 'react-dom';
import Task from './Task';
 
// App component - represents the whole app
class App extends Component {
  constructor(props) {
    super(props);
 
    this.state = {
      hideCompleted: false,
    };
  }
 
  handleSubmit(event) {
    event.preventDefault();
    // Find the text field via the React ref
    const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
    });
    // Clear form
    ReactDOM.findDOMNode(this.refs.textInput).value = '';
  }
 
  renderTasks() {
    let filteredTasks = this.props.tasks;
    if (this.state.hideCompleted) {
      filteredTasks = filteredTasks.filter(task => !task.checked);
    }
    return filteredTasks.map((task) => (
      <Task key={task._id} task={task} />
    ));
  }
 
  toggleHideCompleted() {
    this.setState({
      hideCompleted: !this.state.hideCompleted,
    });
  }
 
  render() {
    return (
      <div className="container">
        <header>
        <h1>Todo List ({this.props.incompleteCount})</h1>
          <label className="hide-completed">
            <input
              type="checkbox"
              readOnly
              checked={this.state.hideCompleted}
              onClick={this.toggleHideCompleted.bind(this)}
            />
            Hide Completed Tasks
          </label>
          <form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
            <input
              type="text"
              ref="textInput"
              placeholder="Type to add new tasks"
            />
          </form>
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}
 
export default withTracker(() => {
  return {
      tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
      incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
    };
})(App);
cs


완료되지 않은 작업의 갯수를 확인 할 수 있고 Hide Completed Tasks 옵션을 통해 완료한 작업을 숨길 수 있습니다.







반응형

References: Creating your first app




미티어 프레임워크에서 리액트를 사용해 봅시다.





1. 프로젝트 만들기



콘솔창에 다음과 같이 입력합니다: meteor create --react react-todoes


이후 cd react-todoes입력 후 meteor를 입력하면 알아서 필요한 작업을 진행 한 후 실행됩니다.





2. To-Do 리스트 만들기



본격적으로 소스를 수정해 보도록 하겠습니다.


/client/main.html


1
2
3
4
5
6
7
<head>
  <title>Todo List</title>
</head>
 
<body>
  <div id="render-target"></div>
</body>
cs


/client/main.jsx


1
2
3
4
5
6
7
8
9
10
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
 
import App from '../imports/ui/App';
 
Meteor.startup(() => {
  render(<App />document.getElementById('render-target'));
});
 
cs


/imports/ui/app.jsx


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React, { Component } from 'react';
 
import Task from './Task';
 
// App component - represents the whole app
export default class App extends Component {
  getTasks() {
    return [
      { _id: 1, text: 'This is task 1' },
      { _id: 2, text: 'This is task 2' },
      { _id: 3, text: 'This is task 3' },
    ];
  }
 
  renderTasks() {
    return this.getTasks().map((task) => (
      <Task key={task._id} task={task} />
    ));
  }
 
  render() {
    return (
      <div className="container">
        <header>
          <h1>Todo List</h1>
        </header>
 
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}
cs


/imports/ui/Task.jsx


1
2
3
4
5
6
7
8
9
10
import React, { Component } from 'react';
 
// Task component - represents a single todo item
export default class Task extends Component {
  render() {
    return (
      <li>{this.props.task.text}</li>
    );
  }
}
cs


/client/main.css


/* CSS declarations go here */
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;
}
.container {
max-width: 600px;
margin: 0 auto;
min-height: 100%;
background: white;
}
header {
background: #d2edf4;
background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
padding: 20px 15px 15px 15px;
position: relative;
}
#login-buttons {
display: block;
}
h1 {
font-size: 1.5em;
margin: 0;
margin-bottom: 10px;
display: inline-block;
margin-right: 1em;
}
form {
margin-top: 10px;
margin-bottom: -10px;
position: relative;
}
.new-task input {
box-sizing: border-box;
padding: 10px 0;
background: transparent;
border: none;
width: 100%;
padding-right: 80px;
font-size: 1em;
}
.new-task input:focus{
outline: 0;
}
ul {
margin: 0;
padding: 0;
background: white;
}
.delete {
float: right;
font-weight: bold;
background: none;
font-size: 1em;
border: none;
position: relative;
}
li {
position: relative;
list-style: none;
padding: 15px;
border-bottom: #eee solid 1px;
}
li .text {
margin-left: 10px;
}
li.checked {
color: #888;
}
li.checked .text {
text-decoration: line-through;
}
li.private {
background: #eee;
border-color: #ddd;
}
header .hide-completed {
float: right;
}
.toggle-private {
margin-left: 5px;
}
@media (max-width: 600px) {
li {
padding: 12px 15px;
}
.search {
width: 150px;
clear: both;
}
.new-task input {
padding-bottom: 5px;
}
}


위와 같이 코드를 수정하면 다음과 같은 화면을 확인할 수 있습니다.





3. 몽고DB에 데이터 저장하기.



할일 정보를 몽고디비에 저장합니다.


리액트 컴포넌트에서 미티어의 컬렉션 데이터를 사용하려면 react-meteor-data 패키지를 설치해야 합니다.


콘솔에 다음을 입력합니다: meteor add react-meteor-data

** 확인해보니 제 프로젝트에는 자동으로 깔려 있더군요 


이후 소스코드를 아래와 같이 수정합니다.


/imports/ui/app.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
 
import { Tasks } from '../api/tasks.js';
 
import Task from './Task';
 
// App component - represents the whole app
class App extends Component {
  renderTasks() {
    return this.props.tasks.map((task) => (
      <Task key={task._id} task={task} />
    ));
  }
  render() {
    return (
      <div className="container">
        <header>
          <h1>Todo List</h1>
        </header>
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}
 
export default withTracker(() => {
  return {
    tasks: Tasks.find({}).fetch(),
  };
})(App);
cs


/imports/api/tasks.js


1
2
3
import { Mongo } from 'meteor/mongo';
 
export const Tasks = new Mongo.Collection('tasks');
cs


/server/main.js


1
2
3
4
5
6
import { Meteor } from 'meteor/meteor';
 
import '../imports/api/tasks.js';
 
Meteor.startup(() => { });
 
cs


여기까지 수정하셨으면 화면이 다음과 같이 바뀔 것 입니다.



이제 몽고디비에 데이터를 넣은 후 어떻게 보이는지 확인하겠습니다.


콘솔을 열고 다음을 입력해 주세요.

meteor mongo

db.tasks.insert({ text: "Hello world!", createdAt: new Date() });


그리고 화면을 확인해 봅시다.





반응형

References: What's Going On With Blaze, React, and Meteor?


** 해당 글은 미티어 프레임워크가 리액트를 공식 지원할 쯤에 작성된 글입니다.

** 필요에 의해 직접 번역하였습니다. 의역 오역이 많으니 원문을 보시면 더 잘 이해 할 수 있습니다.





What's Going On With Blaze, React, and Meteor?


최근 미티어에서 리액트, 블레이즈 그리고 프론트엔드 렌더링에 대해 많은 토론이 있습니다.

좀더 명확히하기 위해 여기 QnA형식의 빠른 입문서가 있습니다. 그리고 이건 제 의견을 표현한 것으로 MDG(Meteor Developer Group)에 의하여 보증되지 않습니다.



So What’s The Big Deal?


요약하자면 다음과 같습니다. 미티어는 2012년 출시된 이후로 항상 프론트엔드 렌더링 라이브러리로 최신판의 블레이즈를 보유하고 있습니다. 

블레이즈의 역할은 기본적으로 앱의 로우 데이터와 로직을 가져와서 브라우저가 이해할 수 있도록(HTML 코드와 같이) 만드는것 입니다. 당신이 상상할수 있는것과 같이 이것은 꽤 큰 작업이고 블레이즈를 개발하고 유지하는것에 꽤 심각한 MDG의 자원이 사용됩니다.

리액트는 블레이즈와 거의 같은 역할을 수행합니다. 리엑트는 페이스북에 의해 개발되었으며 복잡한 웹 앱을 빌드하는 모든 이들에게 주요한 옵션으로 빠르게 자리 잡았습다.

질문을 요약하자면 다음과 같습니다: 미티어는 커뮤니티를 방해하는 것을 피하기 위해 자체 개발 프레임워크를 지켜야 하나요? 아니면 리액트를 수용하고 자원을 할당해야 할까요?



How Are Blaze And React Different?


인기와 자원적인 측면 외에도 블레이즈와 리액트는 몇 가지 주요 요소에서 서로 다릅니다.

우선 리액트는 블레이즈의 Spacebars와 같은 템플릿 구문을 갖고 있지 않습니다만 대신 JSX포맷 덕분에 자바스크립트와 HTML을 같은 파일에 섞을 수 있습니다. 

리액트에는 Spacebars의 {{#if}} 나 {{#each}} 같은 게 없는 대신 {{#if}} 나 {{#each}}블록 내부에 있는 것이 무엇이든 간에 렌더링 하는 함수를 정의할 수 있고 그걸 자바스크립트의 if나 map 내에서 호출할 수 있습니다.

실제로 이건 더 복잡한 코드를 만들지만 코드를 컴포넌트화 하는데 이점을 줍니다.(리액트는 컴포넌트에 큰 관심을 가집니다.)

또 다른 주요한 차이는 블레이즈와 다르게 리액트는 엄격한 데이터 통제 메커니즘을 강요하는 것 입니다. 세부적인 내용을 제외하고 블레이즈는 템플릿간 데이터를 전달할 수 있습니다만 리액트는 훨씬 독선적이고 모범 사례를 따르도록 안내 하려고 합니다.

미티어 유저 Brent Anderson의 지적에 따르면

블레이즈는 지역 / 내부 상태를 처리 할 때 부족한 반면 리액트는 속성과 중첩 된 구성 요소 계층을 구성하고 상위 구성 요소에서 하위 구성 요소로 데이터가 이동하면서 매우 정확하게 처리됩니다.

이것은 리액트의 러닝커브가 훨씬 높다고 해석되지만 코드베이스의 효율성과 유지 보수 측면에서 확실한 이점이 됩니다.

마지막으로 블레이즈는 미티어의 반응 시스템인 Tracker하고 더 타이트하게 통합되어 있습니다. Discover Meteor의 Tom Coleman은 다음과 같이 말합니다.

블레이즈에서 리액트로 포팅하는데 있어서 진짜 문제는 문법을 변경하는 것이 아니라 반응이 동작하는 정신적인 모델의 변화이다. .getMeteorData()의 모든 반응 의존성을 준비한 다음 다른 곳의 컴포넌트에 반응하지 않도록 해야 합니다.



So Which One Is Better?


이 문제에 대한 명쾌한 해답이 있으면 좋았을 겁니다. 앞서 언급했듯이 두 프레임워크 모두 장단점이 있습니다.

블레이즈는 배우기 쉽고(몇시간 vs 몇일) 유용하며 복잡하지 않은 템플릿 언어이며 미티어 프레임워크와 잘 통합되어 있습니다(뛰어난 반응성 동작). 

반면에 리액트는 주어진 작업에 대해 올바른 방향이 무엇인지 명확하게 만드는 가이드와 규칙을 지키도록 하기 때문에 좀 더 성숙한 느낌이 듭니다. 그리고 미티어와 아직은 긴밀하게 통합되어 있는 건 아니지만 훨씬 더 큰 전체적인 생태계를 갖고 있으며 또한 블레이즈가 하지 않는 몇 가지 추가적인 주요 기능을 제공한다(서버 측 렌더링과 같은).

그러므로 모두 다 합쳐보자면 리액트가 좀 더 강력하긴 하지만 블레이즈가 배우고 사용하긴 쉽습니다.



What’s The Big News?


두 달전 릴리즈된 미티어 1.2가 리액트와 앵귤러를 정식 지원합니다.

그런 다음 지난 주 Geoff Schmidt는 Meteor 포럼에서 블레이즈에 대한 MDG의 계획을 세웠습니다.

그래서 우리가 계획하고 있는 것은 React 위에 템플릿을 만들고 Blaze 사용자들에게 그것을 업그레이드하도록 장려하는 겁니다(또는 선호하는 사람들은 React JSX를 사용하십시오.).

다른 말로 블레이즈는 리액트 앞의 얇은 레이어가 되어 미티어의 반응성과 함께 잘 통합되도록 하는 템플릿 언어를 제공하며 리액트의 다른 모든 이점을 유지하면서 전반적으로 더 가깝게 하도록 할 것 입니다.

이건 두 프레임워크의 최고의 측면을 지키는 꽤 멋진 방식입니다.



Why Are Some People Unhappy?


만약 가까운 미래에 미티어 생태계에 뛰어들 계획이 있다면 이건 좋은 소식이지만, 이미 블레이즈에 큰 투자를 한 사람들에겐 몇 가지 문제를 제기합니다.

슬프게도 블레이즈에서 블레이즈 2.0으로의 전환은 아마 meteor update만 타이핑하는 정도의 문제가 아닐 것 입니다. 이러한 급진적인 변화는 아마 적어도 당신의 템플릿의 마크업과 로직을 다시 써야할 수도 있다.

이것은 다른 많은 것들 중에서 "useraccount" 또는 "Blaze components"와 같이 블레이즈 위에 구축된 Atmosphere projects가 어떻게 될 지에 대한 질문을 제기합니다.



What Should I Do Right Now?


MDG의 개발에 대한 느리고 섬세한 접근을 알고 있다면 이 새로운 블레이즈 2.0은 아마 6개월에서 1년은 빛을 보지 못할 것 입니다. 그러면 그때까진 뭘 해야 할까요?

가능하다면 리액트를 사용하세요. 리액트를 통달하는 것은 미티어 생태계를 뛰어넘는 즉각적인 혜택이 있을 것이며 리액트앱은 블레이즈 2.0이 출시될 때 어떠한 마이그레이션도 요구하지 않을 겁니다(블레이즈 2.0이 리액트 위에서 작동하기 때문에).

MDG의 Sashko는 다음과 같이 덧붙입니다.

내 생각에 100%확실한 방법은 지금 리액트를 시작하는 것이다. 블레이즈2는 리액트와 JSX 구문에 대해 세부적인 것을 다루고 싶지 않은 사람들을 위한 것이지만 만약 그게 네게 효과가 있다면 난 그게 갈 길이라고 말할 것이다.

그러나 물론 리액트를 사용하는 게 별로 실용적이지 못하다는 많은 이유가 있다. 레거시 블레이즈 코드를 갖고 있거나 리액트를 배울 수 있는 시간이 없거나 그냥 리액트를 좋아하지 않거나. 이 경우엔 블레이즈를 계속 사용해도 무방하다.

결국 블레이즈 2.0의 업그레이드 경로가 완전히 투명하지는 않겠지만 난 여전히 잘 관리 될 것이라고 믿는다.







반응형


references: Meteor.js 시작하기 by 박승현




미티어 프레임워크의 디비 구조는 서버측의 몽고DB와 클라이언트 측의 미니몽고로 이원화 되어 있다.



클라이언트에선 서버측의 몽고디비에서 데이터를 가져오는것이 아니라 미니몽고에서 데이터를 가져온다.


그러나 생각해보면 저번 구현에선 서버측에 데이터를 넣어 놨는데 아무런 문제 없이 데이터를 가져 올 수 있었다.


미티어 프레임워크는 기본패키지로 AutoPublish를 제공하는데 몽고디비랑 미니몽고를 자동으로 싱크해준다.


물론 이는 위험하다.


실제로 Meteor Developer에선 Security Checklist 1번에 명시되어 있을정도



AutoPublish를 제거하면 별도로 Publish/Subscribe를 구현해주어야 한다.



** 아래의 예시는 이전 코드에서 이어집니다.


  • Autopublish 삭제하기.

프로젝트 폴더에서 콘솔을 열고 meteor list를 입력하면 설치된 패키지 리스트가 출력 됩니다.



Autopublish가 가장 위에 있네요. 설명에도 프로토타입에만 사용하라고 되어있습니다.


콘솔에 meteor remove autopublish를 입력해 autopublish 패키지를 삭제합니다.



그리고 localhost:3000/에 들어가보면 보이던 친구목록이 사라진 걸 확인할 수 있습니다.





  • Publish/Subscribe 구현하기
이제 서버에서 데이터를 발행하는 코드를 작성해봅시다.

발행은 서버에서 동작합니다. .\server\publish.js를 추가합니다.

.\server\publish.js

1
2
3
4
Meteor.publish("friendsList",function(obj){
    var condition = obj || {};
    return Friends.find(condition);
});
cs


추가된 publish.js를 서버측 main.js에 추가합니다.


.\server\main.js


1
2
3
4
5
6
7
8
9
import { Meteor } from 'meteor/meteor';
 
import '../lib/collections.js';
import './publish.js';
 
Meteor.startup(() => {
  // code to run on server at startup
});
 
cs


클라이언트 측에서 서버에서 발행한 friendsList를 구독하는 코드를 작성합니다.


구독은 friendList 템플릿이 생성되는 시점에 이루어져야 하므로 다음과 같이 작성합니다.


.\client\firendsList.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Template } from 'meteor/templating';
 
Template.friendsList.helpers({
    listName : function(){
        return "나의 친구들 목록";
    },
    list : function(){
        Friends = new Mongo.Collection("friends");
        return Friends.find();
    }
});
 
Template.friendsList.onCreated(function () {
    this.subscribe("friendsList",{});
});
 
cs


 이제 화면에 다시 데이터가 출력 되는 것을 확인할 수 있습니다.











반응형

+ Recent posts