22.05.26

컴포넌트 기초

2가지 개념

  1. 캡슐화(모듈화)//얄약같은 감싸는용도. 같이작동할수도 있고 따로 동작할수도 있음
  2. 재사용
    드롭다운메뉴같은것
    컴포넌트 만드는방법 2가지
  3. 전역 등록 전역컴포넌트를 만드는건 적음
  4. 지역 등록

부모 자식간의 데이터 통신
props 부모와 자식관계 데이터 전달
부모에서 자식으로 들어가기만 하는 !!단방향 데이터
그래서 수정권한은 부모한테 있다
props속성만들기(이름자유지만 유지보수쉽게)
그래서! emits을 사용해 줘야지 됨!!!
emits 자식에서 부모를 올려주는 것. 이벤트발생하는것 v-on , @

부모->자식 props 단방향데이터
자식->부모 emits

컴포넌트 속성 상속

App.vue

<template>
  <MyBtn>Banana</MyBtn>
  <!-- Props -->
  <!-- 부모의 데이터바인딩 색상적용 -->
  <MyBtn :color="color">
    <span style="color: red">Cherry</span>
  </MyBtn>
  <MyBtn
    color="royalblue"
    large>
    Grape
  </MyBtn>
  <MyBtn>Apple</MyBtn>
  <MyBtn
    class="jessi">

    <span style="color: yellow">JEESIE</span>
  </MyBtn>
  <MyBtn
    class="jessi"
    style="color: red"
    title="Hello world">
    JEESIE
  </MyBtn>
  <button>Banana</button>
</template>
<script>
import MyBtn from '~/components/MyBtn.vue'

export default {
  components: {
    MyBtn
  },
  data() {
    return  {
      color: '#000'
    }
  }
}
</script>

MyBtn.vue

<template>
  <div 
    :class="{large: large}"
    :style="{ backgroundColor: color }"
    class="btn">
    <slot></slot>
  </div>
  <h1 v-bind="$attrs"></h1>
</template>
<script>
  export default {
    // 어떠한 속성도 상속 안하고 싶을때
    inheritAttrs: false,
    props: {
      color: {
        type: String,
        default: 'lightpink'
      },
      large: {
        type: Boolean,
        dsfault: false
      },     
    },
    // 상속을 특정한 요소에 직접적으로 해주고 싶을때 $attrs
    created() {
      // $attrs객체연결
      console.log(this.$attrs)
    } 
  }
</script>

<style>
  .btn {
    display: inline-block;
    padding: 6px 12px;
    border-radius: 4px;
  }
  .btn.large {
    font-size: 20px;
    padding: 10px 20px;
  }
</style>

컴포넌트 Emit

부모컴포넌트와 자식 컴포넌트연결

App.vue

<template>
  <!-- 카멜케이스는 안먹히니까 대시케이스로 꼭 수정 -->
  <MyBtn
    @onHello="log"
    @change-msg="logMsg">
    BlueBerry
  </MyBtn>
</template>
<script>
import MyBtn from '~/components/MyBtn.vue'

export default {
  components: {
    MyBtn
  },
  methods: {
    log(event) {
      console.log('DoubleClick')
      console.log(event)
    },
    logMsg(msg) {
      console.log(msg)
    }
  }
}
</script>
<template>
  <!-- App.vue는 MyBtn의 최상위 요소에 적용되 있음 -->
  <!-- 만약 형제요소가 생긴다면? log()가 다시 안먹힘! -->
  <div class="btn">
    <!-- BlueBerry가 들어가는 slot -->
    <slot></slot>
  </div>
  <!-- $emit은 두번째인수로는 데이터를 같이 넘겨줄 수 있음 $event라는 객체를 넘겨주면? MouseEvent-->
  <h1 @dblclick="$emit('onHello', $event)">
    ABC
  </h1>
  <!-- v-model 양방향 데이터 -->
  <input
    v-model="msg">
</template>

MyBtn.vue

<script>
  export default {
    // 속성 상속 거부 App.vue파일의 log()함수가 작동안됨
    // inheritAttrs: false
    // emits를 배열데이터로 작성 후 부모요소의App.vue에 있는 click이벤트를 사용하기위해 가져와서
    // 자식요소 $emit()이 붙어있는 곳에 명시된 'click'을 그대로 붙여넣으면 적용이 됨!
    // 즉 특정한 이벤트를 상속받아서 $emit메소드를 통해서 부모요소에 연결이 되있는 클릭이벤트를 실행하게됨
    // 컴포넌트가 사용되는 곳에서 우리가 1.연결하는 이벤트는 원하는 이름 아무거나,
    // 2.대신에 정확히 emits라는 옵션에 받아서 3.그것이 어디서 어떻게 사용될지 $emit()정의 후 실제 원하는이벤트 연결해서 쓸 수 있음
    emits: [
      'onHello',
      'changeMsg'
    ],
    data() {
      return {
        msg: 'Hello Vvue!'
      }
    },
    watch: {
      msg(e) {
        this.$emit('changeMsg', this.msg.trim(e))
      }
    }
  }
</script>

컴포넌트 Slot

v-slot의 약어 : #
이름을 갖는 슬롯(Named Slots)

App.vue

<template>
  <MyBtn>
    <!-- `v-slot`의 약어 : `#` -->
    <template #text>
      <span>Bolt</span>
    </template>

    <template #icon>
      <span>&#9889;</span>
    </template>
  </MyBtn>
</template>

<script>
import MyBtn from '~/components/MyBtn.vue'
export default {
  components: {
    MyBtn
  }
}
</script>

MyBtn.vue

<template>
  <div class="btn">
    <!-- Fallback contents MyBtn내용이 없을때 대체되서 나올 수 있는 내용 -->
    <!-- 이름을 갖는 슬롯(Named Slots) slot에 name을 부여해서 순서를 보장해줌 App.vue파일에 순서가 바껴도 순서바뀜없이 잘 나옴 -->
    <slot name="icon"></slot>
    <slot name="icon"></slot>
    <slot name="text"></slot>
    <slot name="icon"></slot>
    <slot name="icon"></slot>
  </div>
</template>

<style>
  .btn {
    background-color: lightcoral;
    display: inline-block;
  }
</style>

컴포넌트 Provide, Inject

App.vue파일에 자식인 Parent.vue가 있고 그 하위요소로 Child.vue가 있는 상태
Child.vue파일에 있는 내용을 App.vue 파일에 전달할려면,
Child->Parent->App으로 import해서 가야하는 비효율적인 단계를 거쳐야함
아래 예시를 보면 Parent.vue는 전달만 하는 비효율적인 사태가 벌어짐
props에 다시 props를 적용하고있음

효율성을 높이기 위해서
조상요소에서 특정한 하위요소로 보내줄 수 있음
단점은 조상요소에서 데이터가 변경되더라도 하위요소에는 적용이 안됨. props와 다르게 반응성을 가지지 않음
App.vue 파일에 Provide:{} 를 만들어주고,
Child.vue 파일에 Inject:[] 배열데이터를 만들어준다
!-- Provide, Inject 적용 전 --------------------------------------------------

!-- App.vue --------------------------------------------------

<template>
  <Parent :msg= "message" />
</template>

<script>
import Parent from '~/components/Parent'

export default {
  components: {
    Parent
  },
  data() {
    return {
      message: 'Hello world'
    }
  }
}
</script>

!-- Parent.vue --------------------------------------------------

<template>
  <Child :msg="msg" />
</template>

<script>
import Child from '~/components/Child'

export default  {
  components: {
    Child
  },
  props: {
    msg: {
      type: String,
      default:''
    }
  }
}
</script>

!-- Child.vue --------------------------------------------------

<template>
  <div>
    {{ msg }}
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: ''
    }
  }
}
</script>

!--! Provide, Inject 적용 후 --------------------------------------------------
!--? App.vue --------------------------------------------------

<template>
  <button @click="message = 'Good?'">
    Click
  </button>
  <h1>App: {{ message }}</h1>
  <Parent />
</template>

<script>
import Parent from '~/components/Parent'
// 계산된
import { computed } from 'vue'

export default {
  components: {
    Parent
  },
  // provide()로 반응성을 유지하는 데이터를 만들려면 computed를 사용해 콜백사용
  provide() {
    return {
      msg: computed ( () => this.message )
    }
  },
  data() {
    return {
      message: 'Hello world'
    }
  }
}
</script>

!--? Parent.vue --------------------------------------------------

<template>
  <Child />
</template>

<script>
import Child from '~/components/Child'

export default  {
  components: {
    Child
  }
}
</script>

!--? Child.vue --------------------------------------------------

<template>
  <div>
    Child: {{ msg.value }}
  </div>
</template>

<script>
export default {
  //데이터처럼취급
  inject: ['msg']
}
</script>

컴포넌트 Refs

class id 선택자 대신 vue에서 제공하고 있는 ref(reference참조)
mounted 라이플사이클 사용할것. 왜? html이 연결된후라서

App.vue

<template>
  <Hello ref="hello" />
</template>

<script>
import Hello from '~/components/Hello.vue'

export default {
  components: {
    Hello
  },
  // ref접근은 $refs로 reference이름으로 hello를 사용함
  // this.$refs.hello = <h1 ref = "hello"></h1>
  // * created는 컴포넌트가 생성된 직후
  // * mounted는 html구조에 컴포넌트가 연결된 직후
  mounted() {
    console.log(this.$refs.hello.$el) //최상위가 한개가 아니면 제대로된 값을 불러오지 못함
    console.log(this.$refs.hello.$refs.good)
  },
}
</script>

Hello.vue

<template>
  <h1>
    Hello Vue!
  </h1>
  <h2 ref="good">
    Good Vue!
  </h2>
</template>

컴포지션 API

setup()은 반응성이 없기 때문에 vue에서 제공하고 있는 ref를 객체분해로 들고와서
반응성을 제공해 줘야함
초기값을 할당을 해주고 동작시키면 객체데이터를 할당
객체데이터를 데이터로 쓰기 위해서는 value속성을 사용해주기

<template>
  <div @click="increase">
    {{ count }}
  </div>
</template>
<script>
import { ref } from 'vue'
  export default {

    // data() {
    //   return {
    //     count: 0
    //   }
    // },
    // methods: {
    //   increase() {
    //     this.count += 1
    //   }
    // },
    // 반응성 x 그래서 ref기능을 객체구조분해로 vue패키지에서 가져와줘야함
    setup() {
      // 함수를 실행해서 그 함수의 인수로 초기값을 만들어줘야함! 초기화할 0을 넣어줌
      // 반응성을 가진 하나의 객체데이터가 count로 반환이됨
      // count는 객체데이터이다 보니까 바로 사용할 수 없음
      // 데이터로 사용해 주기 위해서 value속성 사용해줌
      let count = ref(0)
      function increase() {
        count.value += 1
      }
      return {
        count, 
        increase
      }
    }
  }
</script>

컴포지션 API 비교

App.vue

<template>
  <h1 @click="increase">
    {{ count }} / {{ doubleCount }}
  </h1>
  <h1 @click="changeMessage">
    {{ message }} / {{ reverseMessage }}
  </h1>
</template>
<script>
export default {
  data() {
    return {
      count: 0,
      message: 'Hello World'
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    },
    reverseMessage() {
      return this.message.split('').reverse().join('')
    }
  },
  watch: {
    message(msgValue) {
      console.log(msgValue)
    }
  },
  // 라이프사이클
  // created()는 컴포넌트 생성된 직후
  created() {
    console.log(this.count)
  },
  // mounted() html, 컴포넌트가 연결될 직후
  mounted() {
    console.log(this.count)
  },
  methods: {
    increase() {
      this.count += 1
    },
    changeMessage() {
      this.message = 'Good!'
    }
  } 
}
</script>

App.Composition.vue

<template>
  <h1 @click="increase">
    {{ count }} / {{ doubleCount }}
  </h1>
  <h1 @click="changeMessage">
    {{ message }} / {{ reverseMessage }}
  </h1>
</template>
<script>
// vue패키지에서 ref 객체구조분해를 통해 가져옴
// mount -> onMounted
// 객체구조분해후 computed는 변수선언후 computed콜백함수실행, watch도 감시할대상, 두번째에 변수선언.
// onMounted 함수 선언 후 콜백함수사용
// created, beforeCreated는 라이프사이클이필요하지 않음 왜??
// setup은 created, beforeCreated 라이프사이클 훅 사이에 실행되는 시점이라서
import { ref, computed, watch, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    // computed는 변수 선언 후 함수처럼 사용해주면 됨
    // 콜백함수로 반환된 값이 doubleCount에서 사용 가능해지게됨
    const doubleCount = computed(function () {
      return count.value * 2
    })
    function increase() {
      count.value += 1
    }

    const message = ref('Hello World')
    const reverseMessage = computed(() => {
      return message.value.split('').reverse().join('')
    })
    watch(message, msgValue => {
      console.log(msgValue)
    })
    function changeMessage() {
      message.value = 'Good!'
    }
    //created()라이프사이클과 동일효과
    console.log(message.value)

    onMounted(()=> {
      console.log(count.value)
    })

    // setup()데이터 내부에서 반환을 해줘야함
    return {
      count,
      doubleCount,
      increase,
      message,
      changeMessage,
      reverseMessage
    }
  }


}
</script>

'프론트엔드 > Vue.js' 카테고리의 다른 글

(22.06.08)Vue.js  (0) 2022.06.08
Vue.js  (0) 2022.06.06
(22.05.25) Vue.js  (0) 2022.05.25
(22.05.23) Vue.js  (0) 2022.05.23

+ Recent posts