찾다
웹 프론트엔드JS 튜토리얼Angular 구성 요소 간의 상호 작용을 위한 샘플 코드

본 글에서는 Angular 컴포넌트 간의 상호작용을 위한 샘플 코드를 주로 소개하며, 데이터 전송 방향에 따라 상위 컴포넌트에서 하위 컴포넌트로, 하위 컴포넌트에서 상위 컴포넌트로, 서비스 전달의 세 가지 상호작용 방식으로 구분됩니다. 실용성이 매우 좋습니다. 필요한 친구들이 참고하면 됩니다

Angular 애플리케이션 개발에서는 컴포넌트가 어디에나 있다고 할 수 있습니다. 이 문서에서는 둘 이상의 구성 요소 간의 상호 작용 방법인 몇 가지 일반적인 구성 요소 통신 시나리오를 소개합니다.

데이터 전송 방향에 따라 상위 컴포넌트에서 하위 컴포넌트로, 하위 컴포넌트에서 상위 컴포넌트로, 서비스 전달의 세 가지 상호작용 방식으로 구분됩니다.

상위 구성 요소는

을 하위 구성 요소에 전달합니다. 그런 다음 하위 구성 요소는 하위 구성 요소를 참조할 때 이러한 입력 속성을 통해 하위 구성 요소에 데이터를 전달합니다. 하위 구성 요소는 setter 또는 ngOnChanges()를 전달하여 입력 속성 값의 변경 사항을 가로챌 수 있습니다.

먼저 하위 구성 요소인 DemoChildComponent와 상위 구성 요소인 DemoParentComponent를 정의합니다.

하위 구성 요소:

@Component({
 selector: 'demo-child',
 template: `
 <p>{{paramOne}}</p>
 <p>{{paramTwo}}</p>
 `
})
export class DemoChildComponent {
 @Input() paramOne: any; // 输入属性1
 @Input() paramTwo: any; // 输入属性2
}

하위 구성 요소는 @Input()을 통해 입력 속성 paramOne 및 paramTwo를 정의합니다. 속성 값은 모든 데이터 유형이 될 수 있습니다)

부모 구성 요소:

@Component({
 selector: &#39;demo-parent&#39;,
 template: `
 <demo-child [paramOne]=&#39;paramOneVal&#39; [paramTwo]=&#39;paramTwoVal&#39;></demo-child>
 `
})
export class DemoParentComponent {
 paramOneVal: any = &#39;传递给paramOne的数据&#39;;
 paramTwoVal: any = &#39;传递给paramTwo的数据&#39;;
}

부모 구성 요소는 템플릿의 선택기 데모-하위를 통해 하위 구성 요소 DemoChildComponent를 참조하고 두 개의 입력 속성 paramOne과 전달합니다. 하위 구성 요소의 paramTwo를 하위 구성 요소에 데이터를 전달하고 마지막으로 paramOne에 전달된 데이터와 paramTwo에 전달된 데이터라는 두 줄의 텍스트가 하위 구성 요소의 템플릿에 표시됩니다.

setter를 통한 입력 속성 값 변경 차단

실제 응용에서는 입력 속성 값이 변경되면 해당 연산을 수행해야 하는 경우가 많기 때문에 이때 입력 속성 setter를 사용하여 입력 속성 값의 변경 사항을 차단합니다.

하위 구성 요소 DemoChildComponent를 다음과 같이 변환합니다.

@Component({
 selector: &#39;demo-child&#39;,
 template: `
 <p>{{paramOneVal}}</p>
 <p>{{paramTwo}}</p>
 `
})
export class DemoChildComponent {
 private paramOneVal: any;
 
 @Input() 
 set paramOne (val: any) { // 输入属性1
  this.paramOneVal = val;
  // dosomething
 };
 get paramOne () {
  return this.paramOneVal;
 };
 
 @Input() paramTwo: any; // 输入属性2
}

위 코드에서 가로채는 값 val이 paramOne 속성의 설정자를 통해 내부 전용 속성 paramOneVal에 할당되는 것을 볼 수 있습니다. 상위 항목에 도달 구성 요소가 하위 구성 요소에 데이터를 전달하는 효과입니다. 물론 가장 중요한 것은 setter에서 더 많은 다른 작업을 수행할 수 있어 프로그램이 더욱 유연해진다는 것입니다.

ngOnChanges()를 사용하여 입력 속성 값의 변경 사항을 가로채기

setter를 통해 입력 속성 값의 변경 사항을 가로채는 방법은 단일 속성 값의 변경 사항만 모니터링할 수 있으며, 여러 대화형으로 모니터링해야 하는 경우에만 가능합니다. 입력 속성의 경우 이 방법은 부적절한 것 같습니다. OnChanges 라이프 사이클 후크 인터페이스(@Input 데코레이터를 통해 구성 요소에 의해 명시적으로 지정된 변수 값이 변경될 때 호출됨)의 ngOnChanges() 메서드를 사용하면 여러 입력 속성 값의 변경 사항을 동시에 모니터링할 수 있습니다.

하위 구성 요소 DemoChildComponent에 ngOnChanges를 추가하세요.

@Component({
 selector: &#39;demo-child&#39;,
 template: `
 <p>{{paramOneVal}}</p>
 <p>{{paramTwo}}</p>
 `
})
export class DemoChildComponent implements OnChanges {
 private paramOneVal: any;
 
 @Input() 
 set paramOne (val: any) { // 输入属性1
  this.paramOneVal = val;
  // dosomething
 };
 get paramOne () {
  return this.paramOneVal;
 };
 
 @Input() paramTwo: any; // 输入属性2
 
 ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
  for (let propName in changes) { // 遍历changes
   let changedProp = changes[propName]; // propName是输入属性的变量名称
   let to = JSON.stringify(changedProp.currentValue); // 获取输入属性当前值
   if (changedProp.isFirstChange()) { // 判断输入属性是否首次变化
    console.log(`Initial value of ${propName} set to ${to}`);
   } else {
    let from = JSON.stringify(changedProp.previousValue); // 获取输入属性先前值
    console.log(`${propName} changed from ${from} to ${to}`);
   }
  }
 }
}

새 ngOnChanges 메서드에서 수신한 매개변수 변경 사항은 입력 속성 이름이 키이고 값이 SimpleChange인 개체입니다. 현재 입력 속성이 변경사항, 이전 값, 현재 값 등과 같은 첫 번째 속성인지 여부 따라서 ngOnChanges 메소드에서는 여러 입력 속성 값을 모니터링하고 변경 객체를 순회하여 해당 작업을 수행할 수 있습니다.

상위 구성 요소 인스턴스 가져오기

이전 소개에서는 하위 구성 요소가 @Input 데코레이터를 통해 입력 속성을 정의하므로 상위 구성 요소가 입력 속성을 통해 하위 구성 요소에 데이터를 전달할 수 있다는 것이었습니다.

물론 상위 구성 요소 인스턴스를 얻은 다음 상위 구성 요소의 속성이나 메서드를 호출하여 필요한 데이터를 얻는 보다 적극적인 방법을 생각할 수 있습니다. 각 컴포넌트 인스턴스가 인젝터의 컨테이너에 추가된다는 점을 고려하면, 종속성 주입을 통해 상위 컴포넌트의 인스턴스를 찾을 수 있습니다.

부모 구성 요소가 하위 구성 요소 인스턴스(템플릿 변수 @ViewChild 또는 @ViewChildren을 통해 직접 얻음)를 얻는 것보다 하위 구성 요소가 상위 구성 요소 인스턴스를 얻는 것이 더 번거롭습니다.

하위 구성 요소에서 상위 구성 요소의 인스턴스를 얻으려면 다음 두 가지 상황이 있습니다.

상위 구성 요소의 유형이 알려져 있습니다.

이 경우 알려진 유형을 직접 주입하여 얻을 수 있습니다. 생성자의 DemoParentComponent 상위 구성 요소 참조, 코드 예제는 다음과 같습니다.

@Component({
 selector: &#39;demo-child&#39;,
 template: `
 <p>{{paramOne}}</p>
 <p>{{paramTwo}}</p>
 `
})
export class DemoChildComponent {
 paramOne: any;
 paramTwo: any;

 constructor(public demoParent: DemoParentComponent) {

  // 通过父组件实例demoParent获取数据
  this.paramOne = demoParent.paramOneVal;
  this.paramTwo = demoParent.paramTwoVal;
 }
}

알 수 없는 상위 구성 요소 유형

구성 요소가 여러 구성 요소의 하위 구성 요소일 수 있습니다. Angular에서 직접 알 수 없으며 클래스 인터페이스 메서드를 통해 검색할 수 있습니다. 즉, 상위 구성 요소는 클래스 인터페이스 식별자와 동일한 이름을 가진 별칭을 제공하여 검색을 지원할 수 있습니다.

먼저 구현(할당) 없이 paramOneVal 및 paramTwoVal 속성만 선언하는 DemoParent 추상 클래스를 만듭니다. 샘플 코드는 다음과 같습니다.

export abstract class DemoParent {
 paramOneVal: any;
 paramTwoVal: any;
}

그런 다음 상위 공급자의 공급자 메타데이터에 별칭 공급자를 정의합니다. 구성 요소 DemoParentComponent, useExisting을 사용하여 상위 구성 요소 DemoParentComponent의 인스턴스를 삽입합니다. 코드 예제는 다음과 같습니다.

@Component({
 selector: &#39;demo-parent&#39;,
 template: `
 <demo-child [paramOne]=&#39;paramOneVal&#39; [paramTwo]=&#39;paramTwoVal&#39;></demo-child>
 `,
 providers: [{provider: DemoParent, useExisting: DemoParentComponent}]
})
export class DemoParentComponent implements DemoParent {
 paramOneVal: any = &#39;传递给paramOne的数据&#39;;
 paramTwoVal: any = &#39;传递给paramTwo的数据&#39;;
}

然后在子组件中就可通过DemoParent这个标识找到父组件的示例了,示例代码如下:

@Component({
 selector: &#39;demo-child&#39;,
 template: `
 <p>{{paramOne}}</p>
 <p>{{paramTwo}}</p>
 `
})
export class DemoChildComponent {
 paramOne: any;
 paramTwo: any;

 constructor(public demoParent: DemoParent) {

  // 通过父组件实例demoParent获取数据
  this.paramOne = demoParent.paramOneVal;
  this.paramTwo = demoParent.paramTwoVal;
 }
}

子组件向父组件传递

依然先定义两个组件,分别为子组件DemoChildComponent和父组件DemoParentComponent.

子组件:

@Component({
 selector: &#39;demo-child&#39;,
 template: `
 <p>子组件DemoChildComponent</p>
 `
})
export class DemoChildComponent implements OnInit {
 readyInfo: string = &#39;子组件DemoChildComponent初始化完成!&#39;;
 @Output() ready: EventEmitter = new EventEmitter<any>(); // 输出属性
 
 ngOnInit() {
  this.ready.emit(this.readyInfo);
 }
}

父组件:

@Component({
 selector: &#39;demo-parent&#39;,
 template: `
 <demo-child (ready)="onReady($event)" #demoChild></demo-child>
 <p>
  <!-- 通过本地变量获取readyInfo属性,显示:子组件DemoChildComponent初始化完成! -->
  readyInfo: {{demoChild.readyInfo}}
 </p>
 <p>
  <!-- 通过组件类获取子组件示例,然后获取readyInfo属性,显示:子组件DemoChildComponent初始化完成! -->
  readyInfo: {{demoChildComponent.readyInfo}}
 </p>
 `
})
export class DemoParentComponent implements AfterViewInit {
 // @ViewChild(&#39;demoChild&#39;) demoChildComponent: DemoChildComponent; // 通过模板别名获取
 @ViewChild(DemoChildComponent) demoChildComponent: DemoChildComponent; // 通过组件类型获取
 
 ngAfterViewInit() {
  console.log(this.demoChildComponent.readyInfo); // 打印结果:子组件DemoChildComponent初始化完成!
 }

 onReady(evt: any) {
  console.log(evt); // 打印结果:子组件DemoChildComponent初始化完成!
 }
}

父组件监听子组件的事件

子组件暴露一个 EventEmitter 属性,当事件发生时,子组件利用该属性 emits(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。

在上面定义好的子组件和父组件,我们可以看到:

子组件通过@Output()定义输出属性ready,然后在ngOnInit中利用ready属性的 emits(向上弹射)事件。

父组件在其模板中通过选择器demo-child引用子组件DemoChildComponent,并绑定了一个事件处理器(onReady()),用来响应子组件的事件($event)并打印出数据(onReady($event)中的$event是固定写法,框架(Angular)把事件参数(用 $event 表示)传给事件处理方法)。

父组件与子组件通过本地变量(模板变量)互动

父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法。

在上面定义好的子组件和父组件,我们可以看到:

父组件在模板demo-child标签上定义了一个demoChild本地变量,然后在模板中获取子组件的属性:

<p>
 <!-- 获取子组件的属性readyInfo,显示:子组件DemoChildComponent初始化完成! -->
 readyInfo: {{demoChild.readyInfo}}
</p>

父组件调用@ViewChild()

本地变量方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。

如果父组件的类需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。

当父组件类需要这种访问时,可以把子组件作为 ViewChild,注入到父组件里面。

在上面定义好的子组件和父组件,我们可以看到:

父组件在组件类中通过@ViewChild()获取到子组件的实例,然后就可以在模板或者组件类中通过该实例获取子组件的属性:

<p>
 <!-- 通过组件类获取子组件示例,然后获取readyInfo属性,显示:子组件DemoChildComponent初始化完成! -->
 readyInfo: {{demoChildComponent.readyInfo}}
</p>

ngAfterViewInit() {
 console.log(this.demoChildComponent.readyInfo); // 打印结果:子组件DemoChildComponent初始化完成!
}

通过服务传递

Angular的服务可以在模块注入或者组件注入(均通过providers注入)。

在模块中注入的服务在整个Angular应用都可以访问(除惰性加载的模块)。

在组件中注入的服务就只能该组件和其子组件进行访问,这个组件子树之外的组件将无法访问该服务或者与它们通讯。

下面的示例就以在组件中注入的服务来进行父子组件之间的数据传递:

通讯的服务:

@Injectable()
export class CallService {
 info: string = &#39;我是CallService的info&#39;;
}

父组件:

@Component({
 selector: &#39;demo-parent&#39;,
 template: `
 <demo-child></demo-child>
 <button (click)="changeInfo()">父组件改变info</button>
 <p>
  <!-- 显示:我是CallService的info -->
  {{callService.info}}
 </p>
 `,
 providers: [CallService]
})
export class DemoParentComponent {
 constructor(public callService: CallService) {
  console.log(callService.info); // 打印结果:我是CallService的info
 }
 
 changeInfo() {
  this.callService.info = &#39;我是被父组件改变的CallService的info&#39;;
 }
}

子组件:

@Component({
 selector: &#39;demo-child&#39;,
 template: `
 <button (click)="changeInfo()">子组件改变info</button>
 `
})
export class DemoChildComponent {
 constructor(public callService: CallService) {
  console.log(callService.info); // 打印结果:我是CallService的info
 }
 
 changeInfo() {
  this.callService.info = &#39;我是被子组件改变的CallService的info&#39;;
 }
}

上面的代码中,我们定义了一个CallService服务,在其内定义了info属性,后面将分别在父子组件通过修改这个属性的值达到父子组件互相传递数据的目的。

然后通过DemoParentComponent的providers元数据数组提供CallService服务的实例,并通过构造函数分别注入到父子组件中。

此时,通过父组件改变info按钮或子组件改变info按钮在父组件或子组件中改变CallService服务的info属性值,然后在页面可看到改变之后对应的info属性值。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

jQuery实现文字超过1行、2行或规定的行数时自动加省略号的方法

vue渲染时闪烁{{}}的问题及解决方法

浅谈js获取ModelAndView值的问题

위 내용은 Angular 구성 요소 간의 상호 작용을 위한 샘플 코드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
JavaScript 엔진 : 구현 비교JavaScript 엔진 : 구현 비교Apr 13, 2025 am 12:05 AM

각각의 엔진의 구현 원리 및 최적화 전략이 다르기 때문에 JavaScript 엔진은 JavaScript 코드를 구문 분석하고 실행할 때 다른 영향을 미칩니다. 1. 어휘 분석 : 소스 코드를 어휘 단위로 변환합니다. 2. 문법 분석 : 추상 구문 트리를 생성합니다. 3. 최적화 및 컴파일 : JIT 컴파일러를 통해 기계 코드를 생성합니다. 4. 실행 : 기계 코드를 실행하십시오. V8 엔진은 즉각적인 컴파일 및 숨겨진 클래스를 통해 최적화하여 Spidermonkey는 유형 추론 시스템을 사용하여 동일한 코드에서 성능이 다른 성능을 제공합니다.

브라우저 너머 : 실제 세계의 JavaScript브라우저 너머 : 실제 세계의 JavaScriptApr 12, 2025 am 12:06 AM

실제 세계에서 JavaScript의 응용 프로그램에는 서버 측 프로그래밍, 모바일 애플리케이션 개발 및 사물 인터넷 제어가 포함됩니다. 1. 서버 측 프로그래밍은 Node.js를 통해 실현되며 동시 요청 처리에 적합합니다. 2. 모바일 애플리케이션 개발은 재교육을 통해 수행되며 크로스 플랫폼 배포를 지원합니다. 3. Johnny-Five 라이브러리를 통한 IoT 장치 제어에 사용되며 하드웨어 상호 작용에 적합합니다.

Next.js (백엔드 통합)로 멀티 테넌트 SAAS 애플리케이션 구축Next.js (백엔드 통합)로 멀티 테넌트 SAAS 애플리케이션 구축Apr 11, 2025 am 08:23 AM

일상적인 기술 도구를 사용하여 기능적 다중 테넌트 SaaS 응용 프로그램 (Edtech 앱)을 구축했으며 동일한 작업을 수행 할 수 있습니다. 먼저, 다중 테넌트 SaaS 응용 프로그램은 무엇입니까? 멀티 테넌트 SAAS 응용 프로그램은 노래에서 여러 고객에게 서비스를 제공 할 수 있습니다.

Next.js (Frontend Integration)를 사용하여 멀티 테넌트 SaaS 응용 프로그램을 구축하는 방법Next.js (Frontend Integration)를 사용하여 멀티 테넌트 SaaS 응용 프로그램을 구축하는 방법Apr 11, 2025 am 08:22 AM

이 기사에서는 Contrim에 의해 확보 된 백엔드와의 프론트 엔드 통합을 보여 주며 Next.js를 사용하여 기능적인 Edtech SaaS 응용 프로그램을 구축합니다. Frontend는 UI 가시성을 제어하기 위해 사용자 권한을 가져오고 API가 역할 기반을 준수하도록합니다.

JavaScript : 웹 언어의 다양성 탐색JavaScript : 웹 언어의 다양성 탐색Apr 11, 2025 am 12:01 AM

JavaScript는 현대 웹 개발의 핵심 언어이며 다양성과 유연성에 널리 사용됩니다. 1) 프론트 엔드 개발 : DOM 운영 및 최신 프레임 워크 (예 : React, Vue.js, Angular)를 통해 동적 웹 페이지 및 단일 페이지 응용 프로그램을 구축합니다. 2) 서버 측 개발 : Node.js는 비 차단 I/O 모델을 사용하여 높은 동시성 및 실시간 응용 프로그램을 처리합니다. 3) 모바일 및 데스크탑 애플리케이션 개발 : 크로스 플랫폼 개발은 개발 효율을 향상시키기 위해 반응 및 전자를 통해 실현됩니다.

JavaScript의 진화 : 현재 동향과 미래 전망JavaScript의 진화 : 현재 동향과 미래 전망Apr 10, 2025 am 09:33 AM

JavaScript의 최신 트렌드에는 Typescript의 Rise, 현대 프레임 워크 및 라이브러리의 인기 및 WebAssembly의 적용이 포함됩니다. 향후 전망은보다 강력한 유형 시스템, 서버 측 JavaScript 개발, 인공 지능 및 기계 학습의 확장, IoT 및 Edge 컴퓨팅의 잠재력을 포함합니다.

Demystifying JavaScript : 그것이하는 일과 중요한 이유Demystifying JavaScript : 그것이하는 일과 중요한 이유Apr 09, 2025 am 12:07 AM

JavaScript는 현대 웹 개발의 초석이며 주요 기능에는 이벤트 중심 프로그래밍, 동적 컨텐츠 생성 및 비동기 프로그래밍이 포함됩니다. 1) 이벤트 중심 프로그래밍을 사용하면 사용자 작업에 따라 웹 페이지가 동적으로 변경 될 수 있습니다. 2) 동적 컨텐츠 생성을 사용하면 조건에 따라 페이지 컨텐츠를 조정할 수 있습니다. 3) 비동기 프로그래밍은 사용자 인터페이스가 차단되지 않도록합니다. JavaScript는 웹 상호 작용, 단일 페이지 응용 프로그램 및 서버 측 개발에 널리 사용되며 사용자 경험 및 크로스 플랫폼 개발의 유연성을 크게 향상시킵니다.

Python 또는 JavaScript가 더 좋습니까?Python 또는 JavaScript가 더 좋습니까?Apr 06, 2025 am 12:14 AM

Python은 데이터 과학 및 기계 학습에 더 적합한 반면 JavaScript는 프론트 엔드 및 풀 스택 개발에 더 적합합니다. 1. Python은 간결한 구문 및 풍부한 라이브러리 생태계로 유명하며 데이터 분석 및 웹 개발에 적합합니다. 2. JavaScript는 프론트 엔드 개발의 핵심입니다. Node.js는 서버 측 프로그래밍을 지원하며 풀 스택 개발에 적합합니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

안전한 시험 브라우저

안전한 시험 브라우저

안전한 시험 브라우저는 온라인 시험을 안전하게 치르기 위한 보안 브라우저 환경입니다. 이 소프트웨어는 모든 컴퓨터를 안전한 워크스테이션으로 바꿔줍니다. 이는 모든 유틸리티에 대한 액세스를 제어하고 학생들이 승인되지 않은 리소스를 사용하는 것을 방지합니다.

맨티스BT

맨티스BT

Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.

SublimeText3 영어 버전

SublimeText3 영어 버전

권장 사항: Win 버전, 코드 프롬프트 지원!

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)