컴포넌트간의 데이터 공유

이동언·2024년 8월 21일

new world

목록 보기
30/62
post-thumbnail

8.21 (수)

1. Provide / Inject

👉 기존에 사용하던 props / emits는 각 컴포넌트간에 바로 상위 바로 하위컴포넌트끼리 전달을 할 수 있어서 여러단계 아래의 컴포넌트에게 전달하기 위해선 각각의 컴포넌트에 선언을 해야한다는 불편함이 존재했다.

👉 이러한 불편함을 줄이기위해 하나의 컴포넌트에서 선언할시 여러 컴포넌트에서 해당 데이터를 사용할 수 있는 기능이 Provide와 Inject이다.

1-1. Provide

👉 props와 대응되는 기능으로 부모컴포넌트에서 선언하는것이다.

const data = ref({num:10})
provide('data',data); //부모 컴포넌트에서 data를 provide

1-2. Inject

👉 emits와 대응되는 기능으로 자식컴포넌트에서 선언하는것이다.

<h1>{{ data.num }}</h1>

const data = inject('data') //자식 컴포넌트에서 data를 inject




2. LocalStorage

👉 쿠키와 비슷한 개념으로 브라우저에 값을 저장하는 LocalStorage가 있다.
👉 하지만 쿠키는 시간이 지나면 소멸해버리는 기능이 있어 개인정보와 관련된 로그인에 특화되어있지만, 로컬스토리지는 안전이 필요한 데이터를 저장하기엔 무리가 있다.

localStorage.setItem('count', JSON.stringify(data.value))
// data.value라는 값을 count라는 이름으로 로컬스토리지에 저장하는 코드이다
// 하지만 데이터를 넣기 위해선 json문자열로 변경이 필요하므로 stringify를 통해 형변환을 거친다

onMounted(() => {
    console.log("mounted...............")
    const str = localStorage.getItem('count')
    // 스토리지에 저장된 count 값을 str변수에 넣기

    if(!str){
        return
    }

    const obj = JSON.parse(str) // json문자열에서 일반문자열로 변경
    data.value = obj
})

👉 해당 코드처럼 onMounted라는 함수를 사용하는데, 이는 마운트가 완료되면 내부 함수가 실행되는 코드이다.
👉 마운트는 쉽게 말해 해당 컴포넌트가 화면에 표시될때를 의미한다.




3. Counter - LocalStorage

3-1. CountContainer.vue

<template>
    <div>
        <CountDisplay></CountDisplay> 
        <CountButtons></CountButtons>
    </div>
</template>

<script setup>
import { onMounted, provide, ref } from 'vue';
import CountDisplay from './CountDisplay.vue';
import CountButtons from './CountButtons.vue';


const data = ref({num:10})
provide('data',data);


const changeNum = (amount) => {
    data.value.num += amount

    localStorage.setItem('count', JSON.stringify(data.value))
    // 값이 변경될때마다 로컬스토리지에 저장하기
    // data.value를 count라는 이름으로 스토리지에 저장하는데, json 문자열 형태로 저장해야하므로 형변환
}
provide('changeNum',changeNum)

// 컴포넌트가 마운트 되면 함수 실행
// 마운트뜻은 컴포넌트가 화면에 실제로 표시되는 과정
onMounted(() => {
    console.log("mounted...............")
    const str = localStorage.getItem('count')
    // 스토리지에 저장된 count 값을 str변수에 넣기

    if(!str){
        return
    }

    const obj = JSON.parse(str) // json문자열에서 일반문자열로 변경
    data.value = obj
})
</script>

<style lang="scss" scoped>

</style>




3-2. CountButton.vue

<template>
    <div><!--버튼의 다양한 종류를 만들기 위한 컨테이너-->
        <CustomButton :label="'PLUS'" :amount="1"></CustomButton>
        <CustomButton :label="'MINUS'" :amount="-1"></CustomButton>
    </div>
</template>

<script setup>
import CustomButton from './CustomButton.vue';



</script>
<style lang="scss" scoped>
</style>




3-3. CountDisplay.vue

<template>
    <div>
        <h1>{{ data.num }}</h1>
    </div>
</template>

<script setup>
import { inject } from 'vue';


const data = inject('data')

</script>

<style lang="scss" scoped>

</style>




3-4. CustomButton.vue

<template>
    <div>
        <button @click="()=>changeNum(props.amount)"> 
                {{ props.label }}
        </button>
    </div>
</template>

<script setup>
import { inject } from 'vue';
const changeNum = inject('changeNum')
const props = defineProps(['label','amount']) // countButtons에서 만든 속성값들을 props를 통해서 받기


</script>

<style lang="scss" scoped>

</style>




4. TodoList ver 0.2

4-1. TodoContainer.vue

<template>
    <div>
        <TodoList></TodoList>
    </div>
</template>

<script setup>
import { provide, ref } from 'vue';
import { v4 as uuidv4 } from 'uuid';
import TodoList from './TodoList.vue';

const todoList = ref([])

const addTodo = (title, content) => {
    
    const todo = { // 해당todoo는 addTodo 내부에 위치해야 아래 todoList의 값이 동일하게 변경된다.
        tno: uuidv4(),
        title: title,
        content: content
    }
    todoList.value = [...todoList.value, todo]
}

const removeTodos = (tnoArr) => { // 선택한 tnoArr에 포함된 tno가 있는 todolist를 제거
   todoList.value =  todoList.value.filter(todo => !tnoArr.includes(todo.tno))
}

// 더미데이터 생성
addTodo('aaa','aaaa')
addTodo('bbb','bbbb')
addTodo('fff','ffff')
addTodo('aaa','aaaa')
addTodo('bbb','bbbb')
addTodo('fff','ffff')
addTodo('aaa','aaaa')
addTodo('bbb','bbbb')
addTodo('fff','ffff')

// provide를 통해 하위 컴포넌트에 전달
provide('addTodo',addTodo)
provide('todoList',todoList)
provide('removeTodos', removeTodos)




</script>

<style lang="scss" scoped>

</style>




4-2. TodoList.vue

<template>
    <div>
        <div><input type="checkbox" :value="'ALL'" v-model="selectAll"> SelectAll</div>
        <ul>
            <li v-for="todo in todoList" :key="todo.tno">
                    <input type="checkbox" :value="todo.tno" v-model="selectList"> {{ todo.tno }} -- {{ todo.title }}
                    <!--v-model은 양방향으로 여기서 체크하면 아래 script태그의 selectList의 배열에도 포함되도록-->
            </li>
        </ul>
        <button @click="handleClickRemove">REMOVE</button>
    </div>
</template>

<script setup>
import { inject, ref, watch } from 'vue';

const selectList = ref([])
const selectAll = ref([])

const todoList = inject('todoList')
const removeTodos = inject('removeTodos')


const handleClickRemove = () => {
    removeTodos(selectList.value)
}

watch(selectAll,(newValue) => { // watch는 상태가 변경됐는지 안됐는지 감시하는 역할

   if(!newValue || newValue == '') { // todoList
    
    console.log("uncheck")
    selectList.value.length = 0

   } else if(newValue == 'ALL')
   console.log("check")

   todoList.value.forEach(t => {
    selectList.value.push(t.tno)
   })
})


</script>

<style lang="scss" scoped>

</style>




5. 비동기처리

5-1. fetch-API를 이용한 비동기처리

  • promise : 비동기작업의 완료를 나타내는 객체, promise는 then을 이용하여 결과처리가 가능

  • async : async함수는 항상 promise를 반환하는 함수

  • await : promise가 해결될떄까지 실행을 중단. 하고 promise의 결과값 반환

  • then : promise가 성공적으로 완료되었을때 실행할 콜백 함수를 지정

<template>
<div>
<ul>
<li v-for="t in todos" :key="[t.id](http://t.id/)"> {{ t }}</li>
</ul>

</template>

<script setup>
import { ref } from 'vue';

const todos = ref([]) // 비동기를 통해 정보가 나중에 도착하므로 도착했을때 데이터를 실시간으로 변경해주려면 ref사용

const getData = async() => { // async는 함수를 비동기적으로 실행한다는뜻, 함수앞에 async가 있으면 항상 promise를 반환
const res = await fetch('https://jsonplaceholder.typicode.com/todos') // fetch를 이용하여 외부 API데이터를 가져오는 비동기 작업을 하는데
// fetch를 사용하면 promise를 반환하는데 await를 이용하여 promise가 해결될때까지 기다림
const jsonData = await res.json()
// fetch로 받은 값을 json형태의 데이터로 변경
return jsonData
}

getData().then(arr => todos.value = arr)
// getData는 promise를 반환하므로 then을 사용하여 then메서드의 콜백함수가 실행됨

</script>

<style lang="scss" scoped>

</style>




5-2. axios를 이용한 비동기처리

<template>
    <div>
        <ul>
            <li v-for="todo in todos" :key="todo.id"> {{ todo }}</li>
        </ul>
    </div>
</template>

<script setup>
import axios from 'axios';
import { ref } from 'vue';


const todos = ref([])
// 비동기처리를 할건데, 비동기 처리는 시간이 좀 지난뒤 갱신이 되므로 반응형으로 처리

// async를 사용하면 비동기로 실행됨, return값은 promise
const getData = async() => {
    const res = await axios.get('https://jsonplaceholder.typicode.com/todos')
    // fetch를 이용하여 외부 API 데이터를 가져오는 비동기 작업
    // await를 사용하면 비동기 작업이 완료될 때까지 기다린 후 코드가 실행됨. 

    return res.data
    // res.data는 promise
}

getData().then(arr => todos.value = arr)
// getData는 결국 promise를 반환하므로 then에서는 콜백함수가 실행됨.
// arr은 getData에서 반환된 데이터 -> API에서의 todos목록
// then = 앞에꺼 끝났으면 이거 해줘

</script>

<style lang="scss" scoped>

</style>




6. axios 추가 예제 (맛집 위도경도)

<template>
    <div>
        <ul>
            <li v-for="i in arr" :key="i.sno">
                {{ i }}
            </li>
        </ul>
        <div>
            <button @click="() => handleClick('s1.json')">s1</button>
            <button @click="() => handleClick('s2.json')">s2</button>
        </div>    
    </div>
</template>

<script setup>
import axios from 'axios';
import { ref } from 'vue';


const getStores = async(fileName) => { // fileName (json파일)을 매개변수로

    const res = await axios.get(`http://localhost:5173/${fileName}`)

    console.log(res)

    return res.data // res 로그내부에 data라는 속성값을 반환한다.
}

const arr = ref([])

const handleClick = (file) => { // 클릭하는 이벤트를 만드는데, 매개변수로 file이름을 받는다.

    getStores(file).then( result => { //getStores의 promise가 return되면 then 내부가 실행되어 ref인 arr이 result값(file값)이 적용된다.
    arr.value = result
} )
// getStores의 return값은 promise로 주는데, return값인 res.data를 받은 이후 then이 실행됨.


}


</script>

<style lang="scss" scoped>

</style>

0개의 댓글