vue-property-decorator 정리
현재 진행 중인 Vue.js + Typescript 기반의 프로젝트에서 컴포넌트 구성에 필요한 부분을 정리한 것으로 Typescript로 구성하면 자바스크립트로 어떻게 구성되는지를 비교하면서 진행하도록 한다.
특이한 코드 설정 (readonly, !)
Typescript를 사용하면서
‘?’
는 생략 가능 또는 null / undefined도 가능하다라는 코드는 많이 봤을테지만 vue-property-decorator 샘플들을 보면
‘readonly, !:’
와 같은 생소한 코드를 보게 된다.
readonly
대상 멤버를 읽기 전용으로 한정하겠다는 한정자 (OOP 언어에서는 많이 사용)로 Vue에서 Prop이나 Model 등에 readonly를 한정했을 떄 할당을 하면 오류가 발생하게 된다. 따라서 데코레이터들을 사용할 때는 @Prop이나 @Model 등에 readonly 한정자를 선언하는 것이 좋다.
Typescript - reaonly 한정자 선언
1 ...
2 < script lang = "ts" >
3 import { Component , Model , Prop , PropSync , Vue } from 'vue-property-decorator' ;
4
5 @Component
6 export default class SampleComponent extends Vue {
7 @Prop (String) readonly name : string ;
8 @Model ('update' , { type : Object }) readonly profile : IProfile ;
9 @PropSync (String) value : string ; // 할당 가능
10 }
11 < /script>
12 ...
!
대상 멤버에 대한 NonNullAssersion 오퍼레이터로 “!“가 붙은 경우는 null / Undefined를 설정할 수 없음을 나타내는 것이다. 그러나 너무 남용하면 특정 형식에 대한 값을 확정하고 처리하는 것이 때문에 확장성에 제한을 받는 상황이 될 수 있으므로 가능하면 “required: true” 이거나 “기본 값”을 설정하는 경우에 지정하는 것이 좋다.
Typescript - reaonly 한정자 선언
1 ...
2 < script lang = "ts" >
3 import { Component , Prop , Vue } from 'vue-property-decorator' ;
4
5 @Component
6 export default class SampleComponent extends Vue {
7 @Prop ({ type : String , required : true }) readonly name !: string ;
8 @Prop ({ type : Array , default : () => [] }) readonly items !: string [];
9 @Prop (Object) readonly profile? : IProfile ; // null / undefined 가능
10 }
11 < /script>
12 ...
@Component
는 정의한 클래스를 Vue 가 인식할 수 있는 형태로 변환하는 것을 의미한다.
Typescript - @Component 선언
1 ...
2 < script lang = "ts" >
3 import { Component , Vue } from 'vue-property-decorator' ;
4
5 @Component
6 export default class SampleComponent extends Vue {}
7 < /script>
8 ...
Javascript 변환 - Vue Component
1 ...
2 < script >
3 export default {
4 name : 'SampleComponent'
5 };
6 < /script>
7 ...
컴포넌트의 내부 구성을 처리하기 전에 컴포넌트 자체의 옵션들을 설정할 수 있으며 이를 통해서 각 종 Vue 객체들과 연동 정보를 구성할 수 있다. 많이 사용되는 것들은 아래와 같다.
Child Components
Directives
Filters
Mixins
Data
DOM
Life-cycle Hooks
Asset
Configuration
Typescript - @Component 옵션 설정
1 ...
2 < script lang = "ts" >
3 import { Component , Vue } from 'vue-property-decorator' ;
4
5 @Component ({
6 components : {
7 AppButton ,
8 ProductList
9 },
10 directives : {
11 resize
12 },
13 filters : {
14 dateFormat
15 },
16 mixins : [
17 PageMixin
18 ]
19 })
20 export default class SampleComponent extends Vue {}
21 < /script>
22 ...
Javascript 변환 - Vue Component
1 ...
2 < script >
3 export default {
4 name : 'SampleComponent' ,
5 components : {
6 AppButton ,
7 ProductList
8 },
9 directives : {
10 resize
11 },
12 filters : {
13 dateFormat
14 },
15 mixins : [
16 PageMixin
17 ]
18 };
19 < /script>
20 ...
@Prop
컴포넌트 내의 지정한 멤버들을 속성(props)으로 사용할 수 있도록 구성한다.
@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
Typescript - @Prop 선언
1 ...
2 < script lang = "ts" >
3 import { Component , Prop , Vue } from 'vue-property-decorator' ;
4
5 @Component
6 export default class SampleComponent extends Vue {
7 @Prop (Number) readonly propA : number | undefined
8 @Prop ({ default : 'default value' }) readonly propB !: string
9 @Prop ([String, Boolean]) readonly propC : string | boolean | undefined
10 }
11 < /script>
12 ...
Javascript 변환 - Vue Component
1 ...
2 < script >
3 export default {
4 name : 'SampleComponent' ,
5 props : {
6 propA : {
7 type : Number
8 },
9 propB : {
10 default : 'default value'
11 },
12 propC : {
13 type : [String, Boolean]
14 }
15 }
16 };
17 < /script>
18 ...
@Watch
지정한 대상을 모니터링해서 변경되었을 떄 처리를 수행한다.
첫번째 인수 : 모니터링 대상 값
두번쨰 인수 : 모니터링 옵션
@Watch(path: string, opitons: WatchOptions = {})
Typescript - @Watch 선언
1 ...
2 < script lang = "ts" >
3 import { Component , Watch , Vue } from 'vue-property-decorator' ;
4
5 @Component
6 export default class SampleComponent extends Vue {
7 @Watch ('child' )
8 onChildChanged (val : string , oldVal : string ) {}
9
10 @Watch ('person' , { immediate : true , deep : true })
11 onPersonChanged1 (val : Person , oldVal : Person ) {}
12
13 @Watch ('person' )
14 onPersonChanged2 (val : Person , oldVal : Person ) {}
15 }
16 < /script>
17 ...
Javascript 변환 - Vue Component
1 ...
2 < script >
3 export default {
4 name : 'SampleComponent' ,
5 watch : {
6 child : [
7 {
8 handler : 'onChildChanged' ,
9 immediate : false ,
10 deep : false
11 }
12 ],
13 person : [
14 {
15 handler : 'onPersonChanged1' ,
16 immediate : true ,
17 deep : true
18 },
19 {
20 handler : 'onPersonChanged2' ,
21 immediate : false ,
22 deep : false
23 }
24 ]
25 },
26 methods : {
27 onChildChanged (val , oldVal ) {},
28 onPersonChanged1 (val , oldVal ) {},
29 onPersonChanged2 (val , oldVal ) {}
30 }
31 };
32 < /script>
33 ...
위의 코드에서 Watch 옵션으로 지정한 immediate 는 컴포넌트 초기화시에도 실행할지 여부를 지정한 것이다.
@Watch는 대상 변경에 따른 이벤트 처리기와 같은 기능을 수행하기 때문에 동일한 대상을 여러 번 지정할 경우는 가장 마지막에 지정한 것만 유효하다.
@PropSync
Vue.js에서는 props를 정의할 때 .sync 를 지정해서 자식 컴포넌트에서 부모 컴포넌트의 값을 변경할 수 있도록 처리할 수 있으며 @update: 이벤트를 수신하면 Data에 적용하는 처리가 암시적으로 진행된다.
@PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {})
Template - 부모 컴포넌트
1 <template >
2 <!-- 아래의 두 가지 Child 컴포넌트 설정은 동일한 의미를 가진다 . 단지 명시적인 표현 여부만 다르다 . -->
3 <ChildComponent :childValue.sync ="value" />
4 <ChildComponent :childValue ="value" @ update : childValue = "value = $event" />
5 </template >
@PropSync 는 자식 컴포넌트에서 부모 컴포넌트의 .sync 속성을 전달할때 사용하는 데코레이터로 아래와 같이 사용해서 값을 할당하는 것만으로 처리가 가능하다.
Typescript - 자식 컴포넌트에서 @PropSync 선언
1 ...
2 < script lang = "ts" >
3 import { Component , PropSync , Vue } from 'vue-property-decorator' ;
4
5 @Component
6 export default class SampleComponent extends Vue {
7 @PropSync ({ type : String }) childValue : string ;
8 ...
9 // 값 변경 적용
10 updateValue (newVal : string ) {
11 this .childValue = newVal ; // 이 시점에서 부모 컴포넌트로 전달된다.
12 }
13 }
14 < /script>
15 ...
@PropSync를 사용하지 않는다면 아래와 같이 이벤트 호출을 처리해 줘야 한다.
Typescript - 자식 컴포넌트에서 직접 처리하는 경우
1 ...
2 < script lang = "ts" >
3 import { Component , Prop , Vue } from 'vue-property-decorator' ;
4
5 @Component
6 export default class SampleComponent extends Vue {
7 @Prop ({ type : String }) childValue : string ;
8 ...
9 // 값 변경 적용
10 updateValue (newVal : string ) {
11 this .$emit ('update:childValue' , newVal ); // 값 설정 및, 부모 컴포넌트로 이벤트 전달
12 }
13 }
14 < /script>
15 ...
@Emit
Vue.js에서는 컴포넌트간의 데이터 연동이 가능하다.
부모에서 자식으로 전달은 props 사용
자식에서 부모로 전달은 event 사용
자식에서 부모로 값을 전달하는 event 처리에 사용하는 것이 @Emit다.
@Emit(event?: string)
Typescript - 자식 컴포넌트
1 < template >
2 < form @submit = "onSubmit" >
3 < input v - model = "value" >
4 < button type = "submit" > Submit < /button>
5 < /form>
6 < /template>
7
8 < script lang = "ts" >
9 import { Component , Vue } from 'vue-property-decorator' ;
10
11 @Component
12 export default class ChildComponent extends Vue {
13 value = '' ;
14
15 // 부모로 값 전달
16 onSubmit() {
17 this .$emit ('submit' , this .value );
18 }
19 }
20 < /script>
Typescript - 부모 컴포넌트
1 < template >
2 < ChildComponent @submit = "onReceiveSubmit" />
3 < /template>
4
5 < script lang = "ts" >
6 import { Component , Vue } from 'vue-property-decorator' ;
7 import ChildComponent form '@/components/childcomponent.vue' ;
8
9 @Component ({
10 components : {
11 ChildComponent
12 }
13 })
14 export default class ParentComponent extends Vue {
15 async onReceiveSubmit (newVal : string ) {
16 // $emit을 통해서 전달된 값 수신
17 await this .$request .post (newVal );
18 }
19 }
20 < /script>
위의 예제에서 ChildComponent는 직접 $emit 처리를 했지만 이를 @Emit 사용하는 버전으로 바꾸면 좀 더 단순하게 코드를 구성할 수 있다. 처리되는 이벤트의 이름은 @emit의 옵션을 설정해서 구분할 수도 있지만 생략할 경우는 메서드의 이름을 그대로 이벤트 이름으로 사용한다.
Typescript - 자식 컴포넌트 @Emit 선언
1 < template >
2 < form @submit = "onSubmit" >
3 < input v - model = "value" >
4 < button type = "submit" > Submit < /button>
5 < /form>
6 < /template>
7
8 < script lang = "ts" >
9 import { Component , Emit , Vue } from 'vue-property-decorator' ;
10
11 @Component
12 export default class ChildComponent extends Vue {
13 value = '' ;
14
15 // 부모로 값 전달, 옵션이 없더라도 '()'를 생략할 수 없다.
16 // return으로 반환된 값이 전달되며, 메서드 이름을 사용할 경우는 'on' 접두사가 붙어서 처리된다.
17 @Emit ()
18 submit() {
19 return this .value ;
20 }
21 }
22 < /script>
@Ref
@Ref는 $refs에서 참조할 수 있는 요소 또는 컴포넌트를 정의하는 것으로 사전에 정의함으로서 오타나 수정에 대응하기 쉽도록 하는 역할을 담당한다.
@Ref(refKey?: string)
Typescript - @Ref 선언
1 < template >
2 < ChildComponent ref = "childComponent" />
3 < button ref = "submitButton" > Submit < /button>
4 < /template>
5
6 < script lang = "ts" >
7 import { Component , Ref , Vue } from 'vue-property-decorator' ;
8
9 @Component ({
10 components : {
11 ChildComponent
12 }
13 })
14 export default class SampleComponent extends Vue {
15 @Ref () childComponent : ChildComponent ;
16 @Ref () submitButton : HTMLButtomElement ;
17
18 mounted() {
19 // 자식 컴포넌트 메서드 실행
20 this .childComponent .updateValue ();
21 // 버튼에 포커스 설정
22 this .submitButton .focus ()
23 }
24 }
25 < /script>
@Model
Vue.js의 Model을 정의하는 것으로 여러 가지 옵션을 정의할 수 있다. @Model이 정의되면 해당 변수가 @Prop 선언을 동반하게 된다.
@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})
Typescript - @Model 선언
1 ...
2 < script lang = "ts" >
3 import { Component , Model , Vue } from 'vue-property-decorator' ;
4
5 @Component
6 export default class SampleComponent extends Vue {
7 @Model ('change' , { type : Boolean }) readonly checked !: boolean
8 }
9 < /script>
10 ...
Javascript 변환 - Vue Component
1 ...
2 < script >
3 export default {
4 name : 'SampleComponent' ,
5 model : {
6 prop : 'checked' ,
7 event : 'change'
8 },
9 props : {
10 checked : {
11 type : Boolean
12 }
13 }
14 };
15 < /script>
16 ...
@Provide / @Inject
부모 컴포넌트에서 @Provide로 정의된 대상을 자식 컴포넌트에서 @Inject로 참조할 수 있다.
@Provide(key?: string | Symbole)
@Inject(options?: { from?: InjectKey, default?: any } | InjectKey)
Typescript - @Provide / @Inject 선언
1 ...
2 < script lang = "ts" >
3 import { Component , Inject , Provide , Vue } from 'vue-property-decorator' ;
4
5 const symbol = Symbol ('baz' );
6
7 @Component
8 export default class SampleComponent extends Vue {
9 @Inject () readonly foo !: string ;
10 @Inject ('bar' ) readonly bar !: string
11 @Inject ({ from : 'optional' , default : 'default' }) readonly optional !: string
12 @Inject (symbol ) readonly baz !: string
13
14 @Provide () foo = 'foo'
15 @Provide ('bar' ) baz = 'bar'
16 }
17 < /script>
18 ...
Javascript 변환 - Vue Component
1 ...
2 < script >
3 const symbol = Symbol ('baz' )
4
5 export default {
6 name : 'SampleComponent' ,
7 inject : {
8 foo : 'foo' ,
9 bar : 'bar' ,
10 optional : { from : 'optional' , default : 'default' },
11 [symbol ]: symbol
12 },
13 data() {
14 return {
15 foo : 'foo' ,
16 baz : 'bar'
17 }
18 },
19 provide() {
20 return {
21 foo : this.foo ,
22 bar : this.baz
23 }
24 }
25 };
26 < /script>
27 ...
@ProvideReactive / @InjectReactive
”@Provide / @Inject” 의 확장 기능으로 부모 컴포넌트에서 @ProvideReactive로 제공된 대상이 변경되면 자식 컴포넌트에서 인식할 수 있다.
@ProvideReactive(key?: string | symbol)
@InjectReactive(options?: { from?: InjectKey, default?: any } | InjectKey )
Typescript - @ProvideReactive / @InjectReactive 선언
1 ...
2 < script lang = "ts" >
3 import { Component , InjectReactive , ProvideReactive , Vue } from 'vue-property-decorator' ;
4
5 const key = Symbol ()
6
7 // 부모 컴포넌트
8 @Component
9 export default class ParentComponent extends Vue {
10 @ProvideReactive () one = 'value'
11 @ProvideReactive (key ) two = 'value'
12 }
13
14 // 자식 컴포넌트
15 @Component
16 export default class ChildComponent extends Vue {
17 @InjectReactive () one !: string
18 @InjectReactive (key ) two !: string
19 }
20 < /script>
21 ...
참고 자료