首页 >web前端 >js教程 >Vue 黑暗面备忘单 |部分反应性

Vue 黑暗面备忘单 |部分反应性

Susan Sarandon
Susan Sarandon原创
2024-10-02 08:17:02705浏览

안녕하세요 DEV.to 커뮤니티입니다!

이 기사에는 Vue 3에서 주로 사용되거나 다소 어두운 면이 있고 예상한 대로 주의를 기울이지 않는 여러 측면이 포함됩니다.

Vue 3를 설명하면서 기존 옵션 API가 아닌 구성 API를 사용하겠습니다. 두 방법의 개념은 동일하므로 구성 API에 매우 빠르게 적응할 수 있습니다. 나는 아무것도 지시할 수 없으며 모든 프로그래머는 자신의 프로그램 작성 방식을 자유롭게 선택할 수 있지만 개인적인 의견으로는 간결한 구문과 더 나은 코드 관리를 위해 구성 API를 선호합니다. 따라서 여전히 구성 API로 변경하는 것이 두렵다면 그만한 가치가 있으므로 시도해 보시기 바랍니다.

Vue  Cheat Sheet of The Dark Side | Part  Reactivity

이 글을 읽으시면 이런 느낌이 드실 겁니다. 바랍니다 :).

목차

  • 반동
    • 참고
    • 반응형
    • 얕은 참조
    • 얕은 반응성
    • 얕은 반응성
    • 읽기 전용
    • 얕은 읽기 전용
  • 계산됨
    • 쓰기 가능 계산
  • 보고 있다
    • 보다
      • 즉시
      • 한 번
      • Getter 기능 보기
      • 여러 값 보기
    • 시청효과

반동

반응성을 이야기할 때 염두에 두어야 할 것은 반응뿐만이 아닙니다. 반응성은 데이터 변경에 따라 반응하는 엔터티(웹페이지 제공)의 능력을 나타냅니다. 이 개념을 MVVM이라고 알 수도 있습니다. MVVM은 Model-View-View-Model의 약칭입니다. 이름에서 알 수 있듯이 데이터가 변경되면 보기가 변경되고 그 반대도 마찬가지입니다.

Vue의 반응성을 활용하기 위해 우리가 다룰 몇 가지 옵션이 있습니다.

참조

ref는 Vue 애플리케이션 내에서 사용할 수 있는 특별한 종류의 변수라고 생각할 수 있습니다. 이 설명은 처음으로 Vue 작업을 시작할 때만 해당됩니다. 나중에는 좀 더 복잡해지기 때문입니다.

ref를 정의하는 방법은 다음과 같이 간단합니다.

const message = ref("Hello World")

보간 구문을 사용하여 템플릿 내에서 사용하세요.

<span>{{ message }}</span>

왜 변수라고 불렀는데 const 키워드를 사용하여 메시지를 선언했는지 궁금하시다면 그렇게 하셔도 됩니다.

아시다시피 상수 값은 const 키워드의 목적이므로 변경할 수 없습니다. 하지만 당신이 알아야 할 미묘하고 작은 사실이 있습니다. const 키워드는 변수의 데이터를 변경할 수 없지만 중첩된 데이터에는 신경 쓰지 않습니다! 이는 심판의 경우에도 마찬가지이다. 이 상황을 더 잘 이해하려면 아래 코드를 사용해 보세요.

const o = {
  a: 1,
  b: 2,
  c: 3
}
console.log(o.a) // 1
o.a = 4
console.log(o.a) // 4

보시다시피 객체는 참조일 뿐 그 자체로는 전체 값이 아니기 때문에 o.a의 값을 변경할 수 있습니다. 따라서 객체 내부의 값을 변경하면 const 제한이 적용되지 않습니다. 물론, o 자체에 값을 할당하려는 경우 오류가 발생하여 할당할 수 없습니다. 예를 들어 아래 코드는 잘못되었습니다.

const o = {
  a: 1,
  b: 2,
  c: 3
}
o = "hello"

이것은 ref(그리고 나중에 여기서 보게 될 다른 내용)를 사용할 때도 마찬가지입니다. ref 함수를 호출하면 수신한 모든 것을 객체로 변환합니다. 이것을 포장이라고 합니다. 다음 코드를 사용해 보세요:

const message = ref("Hello World")
console.log(message)

아래 이미지와 같은 내용이 표시됩니다.

Vue  Cheat Sheet of The Dark Side | Part  Reactivity

메시지 변수를 로깅할 때 볼 수 있듯이 Hello World를 직접 수신하는 것이 아니라 개체 내부에 있으며 앞서 언급한 개체의 값 키를 사용하여 실제 값에 액세스할 수 있습니다. 이를 통해 Vue는 변경 사항을 감시하고 MVVM 작업을 수행할 수 있습니다! :)

Vue 템플릿 내에서 참조에 액세스할 때 message.value처럼 액세스할 필요가 없습니다. Vue는 객체 대신 템플릿 내부에 실제 값을 렌더링할 만큼 똑똑합니다. 하지만 스크립트 내의 참조 값에 액세스하거나 수정하려면 .value:
를 사용해야 합니다.

message.value = "Adnan!"
console.log(message.value) // Adnan!

반응성

ref를 사용할 때 본 것처럼 Vue는 데이터를 객체 내부에 래핑하고 .value를 통해 액세스할 수 있도록 합니다. 일반적으로 가장 많이 사용되는 경우입니다. ref를 사용하여 거의 모든 것을 래핑하고 반응형으로 만들 수 있습니다.

Vue가 어떻게 값 변경을 감시하고 뷰를 계속해서 렌더링하는지 궁금하다면 JavaScript 프록시를 확인해 보세요.

값이 객체 자체인 경우 ref 대신 반응형을 사용할 수 있습니다. 반응성 함수는 값을 래핑하지 않고 대신 객체 자체를 반응적이고 관찰 가능하게 만듭니다.

const o = reactive({count: 0})

o 상수를 인쇄해 보면 큰 변화 없이 실제로 객체라는 것을 알 수 있습니다.

Vue  Cheat Sheet of The Dark Side | Part  Reactivity

Now you may manipulate the key count as you would normally do in JavaScript and Vue will render the changes as soon as possible.

Here is an example:

const increase = () => {
    o.count++
}

If you had ref instead of reactive it would have looked like this:

const o = ref({count: 0})

const increase = () => {
    o.value.count++
}

If you are still unsure which one to use, keep in mind that ref is a safe option to use.

Shallow Ref

Give that you have a ref like below:

const state = ref({
  names: {
    adnan: 'babakan',
    arian: 'amini',
    ata: 'parvin',
    mahdi: 'salehi'
  }
})

And printed my last name in your template as below:

<span>{{ state.names.adnan }}</span>

If you every changed my last name like this:

state.value.names.adnan = 'masruri'

Your template will be updated to show masruri instead of babakan. This is due to the fact that ref makes a deeply watched object and the changes to the view (template) are triggered for nested data as well.

There is an option to prevent such behaviour if that's what you want. To do so you may use shallowRef. A shallowRef acts exactly like ref does, with an exception of not watching for deep changes.

const state = shallowRef({
  names: {
    adnan: 'babakan',
    arian: 'amini',
    ata: 'parvin',
    mahdi: 'salehi'
  }
})

onMounted(() => {
  state.value.names.adnan = 'masruri'
})

The code above will result in your template showing babakan as it is not watched. But changing the .value entirely will trigger changes. So the code below will result in your template getting updated as well:

const state = shallowRef({
  names: {
    adnan: 'babakan',
    arian: 'amini',
    ata: 'parvin',
    mahdi: 'salehi'
  }
})

onMounted(() => {
  state.value = {
    names: {
      adnan: 'masruri',
      arian: 'amini',
      ata: 'parvin',
      mahdi: 'salehi'
    }
  }
})

This is a great option for performance-related concerns.

Shallow Reactive

So far we've known that ref wraps the data and watches it deeply and shallowRef wraps the data and watches it shallowly. Now tell me this, if reactive makes an object reactive, what does shallowReactive do?

const state = shallowReactive({
  names: {
    adnan: 'babakan',
    arian: 'amini',
    ata: 'parvin',
    mahdi: 'salehi',
  },
})

onMounted(() => {
  state.names.adnan = 'masruri'
})

As you might have guessed the template won't be updated.

Trigger Ref

Given that you are using a shallowRef and changed a value and now want your template to be updated according to the new data as well, you may use the triggerRef function:

const state = shallowRef({
  names: {
    adnan: 'babakan',
    arian: 'amini',
    ata: 'parvin',
    mahdi: 'salehi'
  }
})

onMounted(() => {
  state.value.names.adnan = 'masruri'
  triggerRef(state)
})

Now the template will also show masruri. This is more like changing from an automatic gear to a manual gear if you will.

This is usable for both shallowRef and shallowReactive.

Read Only

The readonly function receives a ref or a reactive as an argument and returns an exact copy that is only possible to be read from. This is used when you want to make sure your data is safe and is not possible to change when watching for it.

Example:

<template>
  <div>
    {{ infoReadOnly }}
  </div>
</template>

<script setup>
const info = ref({
  first: 'Adnan',
  last: 'Babakan'
})

const infoReadOnly = readonly(info)

onMounted(() => {
  info.value.first = 'Arian'
})
</script>

Though I've changed the value of info, since infoReadOnly is actually a live copy of info my changes are reflected in infoReadOnly as well. Yet you may not change the values using infoReadOnly directly:

const info = ref({
  first: 'Adnan',
  last: 'Babakan'
})

const infoReadOnly = readonly(info)

onMounted(() => {
  infoReadOnly.value.first = 'Arian'
})

This won't change the data and will warn you in the console as below:

Vue  Cheat Sheet of The Dark Side | Part  Reactivity

Shallow Read Only

If we have ref and shallowRef, reactive and shallowReactive, why not have a shallowReadonly?

A shallowReadonly only makes the root level elements readonly whilst you can change the nested data:

const stateShallowReadonly = shallowReadonly({
  name: 'Adnan',
  friends: [
    { name: 'Arian' },
    { name: 'Ata' },
    { name: 'Mahdi' }
  ]
})

onMounted(() => {
  stateShallowReadonly.name = 'Brad'
})

The code above will warn you and won't change the value of name since it is a direct property.

But you can freely change anything inside friends since it is nested:

const stateShallowReadonly = shallowReadonly({
  name: 'Adnan',
  friends: [
    { name: 'Arian' },
    { name: 'Ata' },
    { name: 'Mahdi' }
  ]
})

onMounted(() => {
  stateShallowReadonly.friends[0].name = 'Brad'
})

Computed

Man, I love computed in Vue! You can imagine it as a glass in which you can mix your potions and still have your potions standing there intact!

A computed is like a ref or reactive that can be accessed and watched but not changed. Then what's the difference between a computed and a readonly you might ask. A computed can be a mixture of stuff. For example:

const state = ref({
  first: 'Adnan',
  last: 'Babakan'
})

const fullName = computed(() => state.value.first + ' ' + state.value.last)

Now you have a fullName which you may access its value inside a template with {{ fullName }} or inside your script using fullName.value.

The value of fullName will always depend on the state.value.first and state.value.last and will change if those guys change.

A computed receives a function that returns a value and can depend on multiple reactive data.

Writable Computed

Though a computed is mostly used to read a combination of data, the possibility to make a computed writable is also there.

Instead of passing a function to computed you may pass an object including two properties called get and set that both are functions.

For instance:

const state = ref({
  first: 'Adnan',
  last: 'Babakan'
})

const fullName = computed({
  get: () => state.value.first + ' ' + state.value.last,
  set: (value) => {
    const [first, last] = value.split(' ')
    state.value.first = first
    state.value.last = last
  }
})

Now if you try to write the value of fullName like below:

fullName.value = 'Ata Parvin'

It will split your string into 2 parts using a space and assign the first part to state.value.first and the second to state.value.last.

This is not a good way to determine which part of a name is a first name and which is a last name, but for the sake of demonstration, this is the only thing that came to my mind. :D

Watching

Watching is something that you will probably need a lot. Watching is referred to the act in which you want to run something in case a reactive data changes. In different systems there are various naming for this act, sometimes they are called hooks as well. But in Vue, we will call them watches.

Watch

The first thing you will encounter. A watch function receives two arguments. The reactive data to watch and the function to be invoked when the data changes respectively.

Here is a simple watch:

const count = ref(0)

const increase = () => {
  count.value++
}

watch(count, () => {
  console.log('Count changed to ' + count.value)
})

Now whenever the value of count is changed, you will see the log Count changed to ((count)) in your console.

The callback function also receives two arguments which are passed to it when the watch is triggered. The first argument holds the new value and the second one holds the old value. Here is an example:

const count = ref(0)

const increase = () => {
  count.value++
}

watch(count, (newValue, oldValue) => {
  console.log('Counter changed from ' + oldValue + ' to ' + newValue)
})

Note: Be careful when using the newValue and oldValue with objects as objects are passed by reference.

To be more accurate, a watch function receives a third argument as well. This third argument is an object that holds some options which can change the behaviour of the watching action.

Immediate

An immediate watch function is triggered at the instance it's created as well as when a change happens. You can think of it as the difference between a while loop and a do...while loop if you know what I mean. In other words, even if there is never a change, your callback function will run at least once:

watch(count, () => {
  console.log('Count changed to ' + count.value)
}, {
  immediate: true,
})

The value for immediate can be true or false. And the default value is false.

Once

If you want your watcher to run only once, you may define the once option and set its value to true. The default value is false.

watch(count, () => {
  console.log('Count changed to ' + count.value)
}, {
  once: true,
})

This will only trigger once when the value of count changes.

Advanced Watchers

Previously we've mentioned that watchers accept a reactive data as the first argument. While this is true, this is not the whole case.

A watch function can receive a getter function or an array of reactive objects and getter functions. This is used for when we need to watch for multiple data changes, and/or when we need to watch the result of two or more things when affecting each other. Let's have some examples.

Watching Getter Function

Take the code below as an example:

<template>
  <div>
    <div>
      <div>Timer one: {{ timerOne }}</div>
      <div>Timer two: {{ timerTwo }}</div>
    </div>
    <button @click="timerOne++">Accelerate timer one</button>
    <button @click="timerTwo++">Accelerate timer two</button>
  </div>
</template>

<script setup>
const timerOne = ref(0)
const timerTwo = ref(0)

onMounted(() => {
  setInterval(() => {
    timerOne.value++
    timerTwo.value++
  }, 1000)
})

watch(() => timerOne.value - timerTwo.value, () => {
  console.log('There was a change in the gap between timerOne and timerTwo. Gap is ' + (Math.abs(timerOne.value - timerTwo.value)) + ' seconds.')
})
</script>

It's a simple code that makes 2 refs holding a number and increasing both of them 1 by 1 each second. Logically the difference of these two refs are always equal to zero unless one gets changes out of its turn. As both increase the difference stays 0 so the watched won't get triggered as it only watches for the changes to the result of timerOne.value - timerTwo.value.

Yet there are two buttons that each adds 1 to timerOne and timerTwo respectively. When you click on any of those buttons the difference will be more or less than 0 thus the watch being triggered and logging the gap between these two timers.

Watching Multiple Values

Here is an example of an array of reactive data being passed to the first argument of the watch function:

<template>
  <div>
    <div>
      <div>Counter one: {{ counterOne }}</div>
      <div>Counter two: {{ counterTwo }}</div>
    </div>
    <button @click="counterOne++">Increase counter one</button>
    <button @click="counterTwo++">Increase counter two</button>
  </div>
</template>

<script setup>
const counterOne = ref(0)
const counterTwo = ref(0)

watch([
  counterOne,
  counterTwo,
], () => {
  console.log('One of the counters changes')
})
</script>

No matter which ref changes, the watcher will be triggered.

Watch Effect

A watchEffect function acts almost exactly like a watch function with a main difference. In a watchEffect you don't need to define what to watch and any reactive data that is used inside the callback you provide your watchEffect is watched automatically. For example:

const count = ref(0)

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

In case our count is changed the watchEffect will trigger its callback function since count.value is used inside it. This is good if you have complex logic to watch for.


Hope this was useful and you've enjoyed it. In case you spot any mistakes or feel like there should be an improvement, kindly let me know.


BTW! Check out my free Node.js Essentials E-book here:

Feel free to contact me if you have any questions or suggestions.

以上是Vue 黑暗面备忘单 |部分反应性的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn