TS 데코레이터(2/2): 클래스 데코레이터(종속성 주입 예제 포함)
Examples
소개
This is the second part of my series about TypeScript decorators. This post is all about class decorators.
By using class decorators, we have access to the constructor and also its prototype (for explanation about constructors and prototype see this MDN explanation of inheritance ). 따라서 전체 클래스를 수정할 수 있습니다. 프로토타입을 사용하여 메서드를 추가하고, 생성자에 전달된 매개변수의 기본값을 설정하고, 속성을 추가하고, 이를 제거하거나 래핑할 수도 있습니다.
제네릭 제약 조건이 있는 클래스 데코레이터
In I already described the signature of the different types of decorators including the class decorator. We can use TypeScripts extends
keyword to ensure the target is a constructor. That enables us to treat target
as a constructor (that is why I renamed it to constructor
in the following example) and use features like extending constructor
.
type Constructor = {
new (...args: any[]): {}
}
function classDecorator <T extends Constructor>(constructor: T): T | void {
console.log(constructor)
return class extends constructor {} // exentds works
}
// original signature as in typescript/lib/lib.es5.d.ts
// not only restricted to target being a constructor, therefore extending target does not work
// function classDecorator<TFunction extends Function>(target: TFunction): TFunction | void {
// console.log(target)
// return class extends target {}
// }
@classDecorator
class User {
constructor(public name: string) {}
}
// Output:
// [LOG]: class User {
// constructor(name) {
// this.name = name;
// }
// }
Open example in Playground
제한 사항
There is a limitation of modifying the class using a class decorator, which you should be aware of:
TypeScript supports the runtime semantics of the decorator proposal, but does not currently track changes to the shape of the target. Adding or removing methods and properties, for example, will not be tracked by the type system.
You can modify the class, but it's type will not be changed. Open the examples in the next section in the Playground to get an idea of what that means.
There is an ongoing open issue (2015년 이후) TypeScript 리포지토리에서 해당 제한 사항에 대해 설명합니다.
인터페이스 병합을 사용하는 해결 방법이 있지만 그렇게 해야 하는 것은 처음부터 데코레이터를 사용하는 요점을 놓치는 것입니다.
function printable <T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
print() {
console.log(constructor.name)
}
}
}
// workaround to fix typing limitation
// now print() exists on User
interface User {
print: () => void;
}
@printable
class User {
constructor(public name: string) {}
}
const jannik = new User("Jannik");
console.log(jannik.name)
jannik.print() // without workaround: Property 'print' does not exist on type 'User'.
// Output:
// [LOG]: "Jannik"
// [LOG]: "User"
Open example in Playground
예
Finally, some examples to get an idea of what you can do. There are very few limitations of what you can do since you essentially could just replace the whole class.
속성 추가
The following example shows how to add additional attributes to the class and modifying them by passing a function to the decorator factory (see part 1 for the concept of ).
interface Entity {
id: string | number;
created: Date;
}
function Entity(generateId: () => string | number) {
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor implements Entity {
id = generateId();
created = new Date();
}
}
}
@Entity(Math.random)
class User {
constructor(public name: string) {}
}
const jannik = new User("Jannik");
console.log(jannik.id)
console.log(jannik.created)
// Output:
// [LOG]: 0.48790990206152396
// [LOG]: Date: "2021-01-23T10:36:12.914Z"
Open example in Playground
이것은 어딘가에 저장하려는 엔티티에 매우 유용할 수 있습니다. 메서드를 전달하여 엔터티id
를 생성하면 created
타임스탬프가 자동으로 설정됩니다. 예를 들어 타임스탬프 형식을 지정하는 함수를 전달하여 이러한 예를 확장할 수도 있습니다.
클래스 수정 방지
In this example we use Object.seal()
on the constructor itself and on its prototype in order to prevent adding/removing properties and make existing properties non-configurable. This could be handy for (parts of) libraries, which should be modified.
function sealed<T extends { new (...args: any[]): {} }>(constructor: T) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class User {
constructor(public name: string) {}
}
User.prototype.isAdmin = true; // changing the prototype
const jannik = new User("Jannik");
console.log(jannik.isAdmin) // without @sealed -> true
Open example in Playground
의존성 주입
An advanced usage of class decorators (in synergy with parameter decorators) would be Dependency Injection (DI). This concept is heavily used by frameworks like Angular and NestJs. I will provide a minimal working example. Hopefully you get an idea of the overall concept after that.
DI can be achieved by three steps:
- Register an instance of a class that should be injectable in other classes in a
Container
(also called Registry
)
- Use a parameter decorator to mark the classes to be injected (here:
@inject()
; commonly done in the constructor of that class, called constructor based injection).
- Use a class decorator (here:
@injectionTarget
) for a class that should be the target of injections.
The following example shows the UserRepository
being injected into the UserService
. The created instance of UserService
has access to an instance of UserRepository
without having a repository passed to its constructor (it has been injected). You can find the explanation as comments in the code.
class Container {
// holding instances of injectable classes by key
private static registry: Map<string, any> = new Map();
static register(key: string, instance: any) {
if (!Container.registry.has(key)) {
Container.registry.set(key, instance);
console.log(`Added ${key} to the registry.`);
}
}
static get(key: string) {
return Container.registry.get(key)
}
}
// in order to know which parameters of the constructor (index) should be injected (identified by key)
interface Injection {
index: number;
key: string;
}
// add to class which has constructor paramteters marked with @inject()
function injectionTarget() {
return function injectionTarget <T extends { new (...args: any[]): {} }>(constructor: T): T | void {
// replacing the original constructor with a new one that provides the injections from the Container
return class extends constructor {
constructor(...args: any[]) {
// get injections from class; previously created by @inject()
const injections = (constructor as any).injections as Injection[]
// get the instances to inject from the Container
// this implementation does not support args which should not be injected
const injectedArgs: any[] = injections.map(({key}) => {
console.log(`Injecting an instance identified by key ${key}`)
return Container.get(key)
})
// call original constructor with injected arguments
super(...injectedArgs);
}
}
}
}
// mark constructor parameters which should be injected
// this stores the information about the properties which should be injected
function inject(key: string) {
return function (target: Object, propertyKey: string | symbol, parameterIndex: number) {
const injection: Injection = { index: parameterIndex, key }
const existingInjections: Injection[] = (target as any).injections || []
// create property 'injections' holding all constructor parameters, which should be injected
Object.defineProperty(target, "injections", {
enumerable: false,
configurable: false,
writable: false,
value: [...existingInjections, injection]
})
}
}
type User = { name: string; }
// example for a class to be injected
class UserRepository {
findAllUser(): User[] {
return [{ name: "Jannik" }, { name: "Max" }]
}
}
@injectionTarget()
class UserService {
userRepository: UserRepository;
// an instance of the UserRepository class, identified by key 'UserRepositroy' should be injected
constructor(@inject("UserRepository") userRepository?: UserRepository) {
// ensures userRepository exists and no checks for undefined are required throughout the class
if (!userRepository) throw Error("No UserRepository provided or injected.")
this.userRepository = userRepository;
}
getAllUser(): User[] {
// access to an instance of UserRepository
return this.userRepository.findAllUser()
}
}
// initially register all classes which should be injectable with the Container
Container.register("UserRepository", new UserRepository())
const userService = new UserService()
// userService has access to an instance of UserRepository without having it provided in the constructor
// -> it has been injected!
console.log(userService.getAllUser())
// Output:
// [LOG]: "Added UserRepository to the registry."
// [LOG]: "Injecting an instance identified by key UserRepository"
// [LOG]: [{"name": "Jannik"}, {"name": "Max"}]
Open in Playground
물론 이것은 누락된 기능이 많은 기본 예제이지만 클래스 데코레이터의 잠재력과 DI의 개념을 꽤 잘 보여줍니다.
DI를 구현하는 몇 가지 라이브러리가 있습니다.
🔷 InversifyJS
🔷 typedi
🔷 TSyringe
마무리
Class decorators can be very powerful, because you can change the whole class it is decorating. There is a limitation, because the type of a class changed by a decorator will not reflect that change.
💁🏼️ Have you ever written your own class decorators? What class decorators have you used?
피드백 환영
I'd really appreciate your feedback. What did you (not) like? Why? Please let me know, so I can improve the content.
I also try to create valuable content on Twitter: .
Read more about frontend and serverless on my blog .
Reference
이 문제에 관하여(TS 데코레이터(2/2): 클래스 데코레이터(종속성 주입 예제 포함)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/jannikwempe/typescript-decorators-part-2-class-decorators-including-dependency-injection-example-gjh
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
In I already described the signature of the different types of decorators including the class decorator. We can use TypeScripts extends
keyword to ensure the target is a constructor. That enables us to treat target
as a constructor (that is why I renamed it to constructor
in the following example) and use features like extending constructor
.
type Constructor = {
new (...args: any[]): {}
}
function classDecorator <T extends Constructor>(constructor: T): T | void {
console.log(constructor)
return class extends constructor {} // exentds works
}
// original signature as in typescript/lib/lib.es5.d.ts
// not only restricted to target being a constructor, therefore extending target does not work
// function classDecorator<TFunction extends Function>(target: TFunction): TFunction | void {
// console.log(target)
// return class extends target {}
// }
@classDecorator
class User {
constructor(public name: string) {}
}
// Output:
// [LOG]: class User {
// constructor(name) {
// this.name = name;
// }
// }
제한 사항
There is a limitation of modifying the class using a class decorator, which you should be aware of:
TypeScript supports the runtime semantics of the decorator proposal, but does not currently track changes to the shape of the target. Adding or removing methods and properties, for example, will not be tracked by the type system.
You can modify the class, but it's type will not be changed. Open the examples in the next section in the Playground to get an idea of what that means.
There is an ongoing open issue (2015년 이후) TypeScript 리포지토리에서 해당 제한 사항에 대해 설명합니다.
인터페이스 병합을 사용하는 해결 방법이 있지만 그렇게 해야 하는 것은 처음부터 데코레이터를 사용하는 요점을 놓치는 것입니다.
function printable <T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
print() {
console.log(constructor.name)
}
}
}
// workaround to fix typing limitation
// now print() exists on User
interface User {
print: () => void;
}
@printable
class User {
constructor(public name: string) {}
}
const jannik = new User("Jannik");
console.log(jannik.name)
jannik.print() // without workaround: Property 'print' does not exist on type 'User'.
// Output:
// [LOG]: "Jannik"
// [LOG]: "User"
Open example in Playground
예
Finally, some examples to get an idea of what you can do. There are very few limitations of what you can do since you essentially could just replace the whole class.
속성 추가
The following example shows how to add additional attributes to the class and modifying them by passing a function to the decorator factory (see part 1 for the concept of ).
interface Entity {
id: string | number;
created: Date;
}
function Entity(generateId: () => string | number) {
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor implements Entity {
id = generateId();
created = new Date();
}
}
}
@Entity(Math.random)
class User {
constructor(public name: string) {}
}
const jannik = new User("Jannik");
console.log(jannik.id)
console.log(jannik.created)
// Output:
// [LOG]: 0.48790990206152396
// [LOG]: Date: "2021-01-23T10:36:12.914Z"
Open example in Playground
이것은 어딘가에 저장하려는 엔티티에 매우 유용할 수 있습니다. 메서드를 전달하여 엔터티id
를 생성하면 created
타임스탬프가 자동으로 설정됩니다. 예를 들어 타임스탬프 형식을 지정하는 함수를 전달하여 이러한 예를 확장할 수도 있습니다.
클래스 수정 방지
In this example we use Object.seal()
on the constructor itself and on its prototype in order to prevent adding/removing properties and make existing properties non-configurable. This could be handy for (parts of) libraries, which should be modified.
function sealed<T extends { new (...args: any[]): {} }>(constructor: T) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class User {
constructor(public name: string) {}
}
User.prototype.isAdmin = true; // changing the prototype
const jannik = new User("Jannik");
console.log(jannik.isAdmin) // without @sealed -> true
Open example in Playground
의존성 주입
An advanced usage of class decorators (in synergy with parameter decorators) would be Dependency Injection (DI). This concept is heavily used by frameworks like Angular and NestJs. I will provide a minimal working example. Hopefully you get an idea of the overall concept after that.
DI can be achieved by three steps:
- Register an instance of a class that should be injectable in other classes in a
Container
(also called Registry
)
- Use a parameter decorator to mark the classes to be injected (here:
@inject()
; commonly done in the constructor of that class, called constructor based injection).
- Use a class decorator (here:
@injectionTarget
) for a class that should be the target of injections.
The following example shows the UserRepository
being injected into the UserService
. The created instance of UserService
has access to an instance of UserRepository
without having a repository passed to its constructor (it has been injected). You can find the explanation as comments in the code.
class Container {
// holding instances of injectable classes by key
private static registry: Map<string, any> = new Map();
static register(key: string, instance: any) {
if (!Container.registry.has(key)) {
Container.registry.set(key, instance);
console.log(`Added ${key} to the registry.`);
}
}
static get(key: string) {
return Container.registry.get(key)
}
}
// in order to know which parameters of the constructor (index) should be injected (identified by key)
interface Injection {
index: number;
key: string;
}
// add to class which has constructor paramteters marked with @inject()
function injectionTarget() {
return function injectionTarget <T extends { new (...args: any[]): {} }>(constructor: T): T | void {
// replacing the original constructor with a new one that provides the injections from the Container
return class extends constructor {
constructor(...args: any[]) {
// get injections from class; previously created by @inject()
const injections = (constructor as any).injections as Injection[]
// get the instances to inject from the Container
// this implementation does not support args which should not be injected
const injectedArgs: any[] = injections.map(({key}) => {
console.log(`Injecting an instance identified by key ${key}`)
return Container.get(key)
})
// call original constructor with injected arguments
super(...injectedArgs);
}
}
}
}
// mark constructor parameters which should be injected
// this stores the information about the properties which should be injected
function inject(key: string) {
return function (target: Object, propertyKey: string | symbol, parameterIndex: number) {
const injection: Injection = { index: parameterIndex, key }
const existingInjections: Injection[] = (target as any).injections || []
// create property 'injections' holding all constructor parameters, which should be injected
Object.defineProperty(target, "injections", {
enumerable: false,
configurable: false,
writable: false,
value: [...existingInjections, injection]
})
}
}
type User = { name: string; }
// example for a class to be injected
class UserRepository {
findAllUser(): User[] {
return [{ name: "Jannik" }, { name: "Max" }]
}
}
@injectionTarget()
class UserService {
userRepository: UserRepository;
// an instance of the UserRepository class, identified by key 'UserRepositroy' should be injected
constructor(@inject("UserRepository") userRepository?: UserRepository) {
// ensures userRepository exists and no checks for undefined are required throughout the class
if (!userRepository) throw Error("No UserRepository provided or injected.")
this.userRepository = userRepository;
}
getAllUser(): User[] {
// access to an instance of UserRepository
return this.userRepository.findAllUser()
}
}
// initially register all classes which should be injectable with the Container
Container.register("UserRepository", new UserRepository())
const userService = new UserService()
// userService has access to an instance of UserRepository without having it provided in the constructor
// -> it has been injected!
console.log(userService.getAllUser())
// Output:
// [LOG]: "Added UserRepository to the registry."
// [LOG]: "Injecting an instance identified by key UserRepository"
// [LOG]: [{"name": "Jannik"}, {"name": "Max"}]
Open in Playground
물론 이것은 누락된 기능이 많은 기본 예제이지만 클래스 데코레이터의 잠재력과 DI의 개념을 꽤 잘 보여줍니다.
DI를 구현하는 몇 가지 라이브러리가 있습니다.
🔷 InversifyJS
🔷 typedi
🔷 TSyringe
마무리
Class decorators can be very powerful, because you can change the whole class it is decorating. There is a limitation, because the type of a class changed by a decorator will not reflect that change.
💁🏼️ Have you ever written your own class decorators? What class decorators have you used?
피드백 환영
I'd really appreciate your feedback. What did you (not) like? Why? Please let me know, so I can improve the content.
I also try to create valuable content on Twitter: .
Read more about frontend and serverless on my blog .
Reference
이 문제에 관하여(TS 데코레이터(2/2): 클래스 데코레이터(종속성 주입 예제 포함)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/jannikwempe/typescript-decorators-part-2-class-decorators-including-dependency-injection-example-gjh
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
TypeScript supports the runtime semantics of the decorator proposal, but does not currently track changes to the shape of the target. Adding or removing methods and properties, for example, will not be tracked by the type system.
function printable <T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
print() {
console.log(constructor.name)
}
}
}
// workaround to fix typing limitation
// now print() exists on User
interface User {
print: () => void;
}
@printable
class User {
constructor(public name: string) {}
}
const jannik = new User("Jannik");
console.log(jannik.name)
jannik.print() // without workaround: Property 'print' does not exist on type 'User'.
// Output:
// [LOG]: "Jannik"
// [LOG]: "User"
Finally, some examples to get an idea of what you can do. There are very few limitations of what you can do since you essentially could just replace the whole class.
속성 추가
The following example shows how to add additional attributes to the class and modifying them by passing a function to the decorator factory (see part 1 for the concept of ).
interface Entity {
id: string | number;
created: Date;
}
function Entity(generateId: () => string | number) {
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor implements Entity {
id = generateId();
created = new Date();
}
}
}
@Entity(Math.random)
class User {
constructor(public name: string) {}
}
const jannik = new User("Jannik");
console.log(jannik.id)
console.log(jannik.created)
// Output:
// [LOG]: 0.48790990206152396
// [LOG]: Date: "2021-01-23T10:36:12.914Z"
이것은 어딘가에 저장하려는 엔티티에 매우 유용할 수 있습니다. 메서드를 전달하여 엔터티
id
를 생성하면 created
타임스탬프가 자동으로 설정됩니다. 예를 들어 타임스탬프 형식을 지정하는 함수를 전달하여 이러한 예를 확장할 수도 있습니다.클래스 수정 방지
In this example we use Object.seal()
on the constructor itself and on its prototype in order to prevent adding/removing properties and make existing properties non-configurable. This could be handy for (parts of) libraries, which should be modified.
function sealed<T extends { new (...args: any[]): {} }>(constructor: T) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class User {
constructor(public name: string) {}
}
User.prototype.isAdmin = true; // changing the prototype
const jannik = new User("Jannik");
console.log(jannik.isAdmin) // without @sealed -> true
의존성 주입
An advanced usage of class decorators (in synergy with parameter decorators) would be Dependency Injection (DI). This concept is heavily used by frameworks like Angular and NestJs. I will provide a minimal working example. Hopefully you get an idea of the overall concept after that.
DI can be achieved by three steps:
- Register an instance of a class that should be injectable in other classes in a
Container
(also calledRegistry
) - Use a parameter decorator to mark the classes to be injected (here:
@inject()
; commonly done in the constructor of that class, called constructor based injection). - Use a class decorator (here:
@injectionTarget
) for a class that should be the target of injections.
The following example shows the UserRepository
being injected into the UserService
. The created instance of UserService
has access to an instance of UserRepository
without having a repository passed to its constructor (it has been injected). You can find the explanation as comments in the code.
class Container {
// holding instances of injectable classes by key
private static registry: Map<string, any> = new Map();
static register(key: string, instance: any) {
if (!Container.registry.has(key)) {
Container.registry.set(key, instance);
console.log(`Added ${key} to the registry.`);
}
}
static get(key: string) {
return Container.registry.get(key)
}
}
// in order to know which parameters of the constructor (index) should be injected (identified by key)
interface Injection {
index: number;
key: string;
}
// add to class which has constructor paramteters marked with @inject()
function injectionTarget() {
return function injectionTarget <T extends { new (...args: any[]): {} }>(constructor: T): T | void {
// replacing the original constructor with a new one that provides the injections from the Container
return class extends constructor {
constructor(...args: any[]) {
// get injections from class; previously created by @inject()
const injections = (constructor as any).injections as Injection[]
// get the instances to inject from the Container
// this implementation does not support args which should not be injected
const injectedArgs: any[] = injections.map(({key}) => {
console.log(`Injecting an instance identified by key ${key}`)
return Container.get(key)
})
// call original constructor with injected arguments
super(...injectedArgs);
}
}
}
}
// mark constructor parameters which should be injected
// this stores the information about the properties which should be injected
function inject(key: string) {
return function (target: Object, propertyKey: string | symbol, parameterIndex: number) {
const injection: Injection = { index: parameterIndex, key }
const existingInjections: Injection[] = (target as any).injections || []
// create property 'injections' holding all constructor parameters, which should be injected
Object.defineProperty(target, "injections", {
enumerable: false,
configurable: false,
writable: false,
value: [...existingInjections, injection]
})
}
}
type User = { name: string; }
// example for a class to be injected
class UserRepository {
findAllUser(): User[] {
return [{ name: "Jannik" }, { name: "Max" }]
}
}
@injectionTarget()
class UserService {
userRepository: UserRepository;
// an instance of the UserRepository class, identified by key 'UserRepositroy' should be injected
constructor(@inject("UserRepository") userRepository?: UserRepository) {
// ensures userRepository exists and no checks for undefined are required throughout the class
if (!userRepository) throw Error("No UserRepository provided or injected.")
this.userRepository = userRepository;
}
getAllUser(): User[] {
// access to an instance of UserRepository
return this.userRepository.findAllUser()
}
}
// initially register all classes which should be injectable with the Container
Container.register("UserRepository", new UserRepository())
const userService = new UserService()
// userService has access to an instance of UserRepository without having it provided in the constructor
// -> it has been injected!
console.log(userService.getAllUser())
// Output:
// [LOG]: "Added UserRepository to the registry."
// [LOG]: "Injecting an instance identified by key UserRepository"
// [LOG]: [{"name": "Jannik"}, {"name": "Max"}]
물론 이것은 누락된 기능이 많은 기본 예제이지만 클래스 데코레이터의 잠재력과 DI의 개념을 꽤 잘 보여줍니다.
DI를 구현하는 몇 가지 라이브러리가 있습니다.
🔷 InversifyJS
🔷 typedi
🔷 TSyringe
마무리
Class decorators can be very powerful, because you can change the whole class it is decorating. There is a limitation, because the type of a class changed by a decorator will not reflect that change.
💁🏼️ Have you ever written your own class decorators? What class decorators have you used?
피드백 환영
I'd really appreciate your feedback. What did you (not) like? Why? Please let me know, so I can improve the content.
I also try to create valuable content on Twitter: .
Read more about frontend and serverless on my blog .
Reference
이 문제에 관하여(TS 데코레이터(2/2): 클래스 데코레이터(종속성 주입 예제 포함)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/jannikwempe/typescript-decorators-part-2-class-decorators-including-dependency-injection-example-gjh
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
I'd really appreciate your feedback. What did you (not) like? Why? Please let me know, so I can improve the content.
I also try to create valuable content on Twitter: .
Read more about frontend and serverless on my blog .Reference
이 문제에 관하여(TS 데코레이터(2/2): 클래스 데코레이터(종속성 주입 예제 포함)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/jannikwempe/typescript-decorators-part-2-class-decorators-including-dependency-injection-example-gjh텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)