Self-study front-end development - VUE framework (4) Combined API

@[TOC](Self-study front-end development - VUE Framework (4) Combined API)
vue2 uses an optional API, and vue3 introduces a combined API. The optional API is easy to learn and use, and the location for writing code has been agreed upon. However, the code is poorly organized, similar logic codes are not easy to reuse, and complex logic codes are difficult to read. When the functional logic of the combined API is complex and numerous, the functional logic codes are organized together to facilitate reading and maintenance. The disadvantage is that it requires good code organization capabilities and the ability to split logic.

In the learning stage, you can choose the optional API to write some simple programs. Although vue supports both styles, you still need to have some understanding of both styles when using some UI frameworks or learning programs written by others. In addition, although Vue can mix the two styles, it will cause confusion in reading, member access, etc., so mixing is not recommended.

Basic usage

The combined API is also based on application instances , so you need to create an application instance first . Different from the optional API, when creating an application instance, the optional API passes in an object composed of several options, while the combined API mainly passes in the setup function object. All option functions in the option API are written in the setup function.

<div id="app">
	<p @click="increment">{
   
   { num }}</p>
</div>

<script>
    const {
      
      createApp, ref} = Vue;
    let app = createApp({
      
      
        setup() {
      
      
            // 响应式状态
            let num = ref(0);

            // 用来修改状态、触发更新的函数
            function increment() {
      
      
                num.value++
            }
            // 返回值会暴露给模板和其他的选项式 API 钩子
            return {
      
      num, increment}
        }
    }).mount('#app')
</script>

The setup function will return an object, and the data, methods, and responsive state required for page rendering need to be placed in this object.

It is very tedious to manually expose a large number of states and methods in the setup function. When working with Single File Components (SFC), you can use <script setup>to greatly simplify your code.

Responsive state

The data provided by the optional API is stored in data, computed and other options, and will be created as responsive data. The combined API uses the reactive api within the setup function to declare the reactive state. It should be noted that in the combined API, only the proxy object declared using the responsive API is responsive, and the data defined in the ordinary way is non-responsive.

reactive

reactive()Functions can create a proxy for a reactive object or array :

const state = reactive({
    
     count: 0 })

Reactive transformations are "deep": they affect all nested properties.

Reactive has two limitations:

  1. It is only valid for object types (objects, arrays, and collection types such as Map and Set), but not for primitive types such as string, number, and boolean.
  2. Because Vue's reactive system tracks property access, we must always keep the same reference to the reactive object. This means that we cannot arbitrarily "replace" a reactive object, as this will cause the reactive connection to the original reference to be lost:
let state = reactive({
    
     count: 0 })

// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({
    
     count: 1 })

reactive()The various limitations are ultimately due to the fact that JavaScript does not have a "reference" mechanism that can act on all value types. For this purpose, Vue provides a ref()method that allows us to create reactive objects that can use any value type.

ref

The ref method accepts an internal value and returns a responsive, changeable ref object that has only one property pointing to its internal value .value.

The ref object is mutable, which means you can .valueassign new values ​​to it. It is also reactive, i.e. all .valueoperations on will be tracked, and writes will trigger side effects associated with them.

If an object is assigned to ref, the object will be reactive()converted into a deeply responsive object. This also means that if the object contains nested refs, they will be unwrapped deeply. To avoid this deep conversion, use shallowRef()instead.

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

In short, ref()allows us to create a "reference" to an arbitrary value and pass these references around without losing responsiveness. This feature is important because it is often used to extract logic into compositional functions.

When refs are accessed as top-level properties in templates, they are automatically "unwrapped" so there is no need to use them .value. On the contrary, if used in the template, .valueno data will be obtained.

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

const count = ref(0)
const object = ref({
      
       a: 1, b: {
      
       c: 1} })

function increment() {
      
      
  count.value++;
  object.value.a++;
  object.value.b.c++;
}
</script>

<template>
  <button @click="increment">
    {
   
   { count }} <!-- 无需 .value -->
    {
   
   { object.a }}
  </button>
</template>

Note that automatic "unwrapping" only applies if ref is a top-level property of the template rendering context. If ref is { { }}the final value calculated by textual interpolation (i.e. a symbol), it will also be unpacked.

const object = {
    
     foo: ref(1) }
<!-- 不会像预期一样工作,因为 object.foo 是一个 ref 对象,且不是顶层属性 -->
{
   
   { object.foo + 1 }}
<!--
可以通过将 foo 改成顶层属性来解决这个问题:
const { foo } = object
则可以使用
{
    
    { foo + 1 }}
-->
<!-- 会被解包,能够被渲染为1,相当于{
    
    { object.foo.value }} -->
{
   
   { object.foo }}

Compared with ordinary JavaScript variables, we have to use a relatively cumbersome method .valueto obtain the value of ref. This is a drawback due to the limitations of the JavaScript language. However, through compile-time conversion, we can let the compiler save us .valuethe trouble of using . Reactive syntactic sugar is a compile-time macro : it is not a real method that will be called at runtime. Instead, it is used as a flag for the Vue compiler to indicate that the final variable needs to be a reactive variable.

<script setup>
let count = $ref(0)

console.log(count)

function increment() {
      
      
  count++
}
</script>

<template>
  <button @click="increment">{
   
   { count }}</button>
</template>

It should be noted that responsive syntax sugar is still in the experimental stage, and changes or other problems may occur.

Generally for ordinary data types, use ref(), and for array and object data types, use reactive()to declare (because reactive declaration does not need to be used .value)

In addition, if you define an empty ref object and then bind this object in the component, you can .valueobtain the component object instance through the property of this object.

computed

Similar to the computed option in the optional API, the computed function can be used to create computed properties in the combined API. And you can also use the form of declaration get()method and set()method to create writable calculated properties.

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误
const count = ref(1)
const plusOne = computed({
    
    
  get: () => count.value + 1,
  set: (val) => {
    
    
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

method

Unlike the optional API, where methods must be written in the methods option, the combined API can setup()define methods anywhere inside the function without special annotations. Just expose the methods that need to be used externally in the returned object.

life cycle hooks

The life cycle hook of the composite API is no longer an option, but uses some API functions.

onCreated

In fact, the composite API does not have the onCreated hook function. Because the two optional life cycle hooks beforeCreated and created can be written directly in the function setup()and executed in the combined API, the effect is the same.

onMounted

The onMounted hook function is equivalent to the mounted hook option.

<script setup>
import {
      
       onMounted } from 'vue'

onMounted(() => {
      
      
  console.log(`the component is now mounted.`)
})
</script>

onUpdated

Equivalent to updated in the optional API

<script setup>
import {
      
       ref, onUpdated } from 'vue'

const count = ref(0)

onUpdated(() => {
      
      
  // 文本内容应该与当前的 `count.value` 一致
  console.log(document.getElementById('count').textContent)
})
</script>

<template>
  <button id="count" @click="count++">{
   
   { count }}</button>
</template>

onUnmounted

Equivalent to unmounted in the optional API. Unlike the optional API, which is almost never used, the combined API may be used in some situations where resources need to be explicitly released manually. For example, an asynchronous listener will not be bound to the current component, so it needs to be stopped manually.

listener

Composite API listeners are similar to optional API listeners, but there are differences. Mainly listening data source types

Basic usage

The first parameter of watch can be a "data source" in different forms: it can be a ref (including calculated properties), a reactive object, a getter function, or an array of multiple data sources:

const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX,oldX) => {
    
    
  console.log(`x is ${
      
      newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum, oldSum) => {
    
    
    console.log(`sum of x + y is: ${
      
      sum}`)
  }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
    
    
  console.log(`x is ${
      
      newX} and y is ${
      
      newY}`)
})

Multiple data sources

When listening to multiple sources, the callback function accepts two arrays, corresponding to the new value and the old value in the source array:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
    
    
  /* ... */
})

For reactive objects

Currently, for reactive()the reactive object declared, the listener cannot correctly obtain oldValue, and the listener is deep listening, and the deep option is invalid.

For reactive()the properties of the declared reactive object, you cannot listen directly.

const obj = reactive({
    
     count: 0 })

// 错误,因为 watch() 得到的参数 obj.count 是一个 number
watch(obj.count, (count) => {
    
    
  console.log(`count is: ${
      
      count}`)
})

The correct way is to use a getter function that returns the property:

// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    
    
    console.log(`count is: ${
      
      count}`)
  }
)

This kind of listener that uses the getter function is a shallow listener. If you need to convert this kind of listener into a deep listener, you need to explicitly add the deep option:

let a = {
    
    b: {
    
    c: 1}}

watch(() => a.b, (newValue, oldValue) => {
    
    
	// 因为侦听器是浅层侦听
	// 无法获取 c 的状态改变
})
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    
    
    // 注意:`newValue` 此处和 `oldValue` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  {
    
     deep: true }
)

Instant callback

As with the optional API, listeners are executed lazily. In the composition API, the listener function can be explicitly called for immediate execution.

const url = ref('https://...')
const data = ref(null)

async function fetchData() {
    
    
  const response = await fetch(url.value)
  data.value = await response.json()
}

// 立即获取
fetchData()
// ...再侦听 url 变化
watch(url, fetchData)

You can also use the watchEffect function to simplify the above code. watchEffect()The callback function will be executed immediately. If the function produces side effects at this time, Vue will automatically track the dependencies of the side effects and automatically analyze the response source. The above example can be rewritten as:

watchEffect(async () => {
    
    
  const response = await fetch(url.value)
  data.value = await response.json()
})

The triggering time of the callback

Similar to the optional API, the listener will be called before the DOM is updated. You can also add an option to make the listener call after the DOM is updated.

watch(source, callback, {
    
    
  flush: 'post'
})

watchEffect(callback, {
    
    
  flush: 'post'
})

Post-refresh watchEffect()has a more convenient alias watchPostEffect():

import {
    
     watchPostEffect } from 'vue'

watchPostEffect(() => {
    
    
  /* 在 Vue 更新后执行 */
})

Stop listener

Generally, a listener is created synchronously . If it is created using an asynchronous callback , it will not be bound to the current component and must be stopped manually to prevent memory leaks.

<script setup>
import {
      
       watchEffect } from 'vue'

// 它会自动停止
watchEffect(() => {
      
      })

// ...这个则不会!
setTimeout(() => {
      
      
  watchEffect(() => {
      
      })
}, 100)
</script>

To manually stop a listener, call the function returned by watch or watchEffect:

const unwatch = watchEffect(() => {
    
    })

// ...当该侦听器不再需要时
unwatch()

Note that there are very few situations where you need to create a listener asynchronously. Choose to create it synchronously whenever possible. If you need to wait for some asynchronous data, you can use conditional listening logic:

// 需要异步请求得到的数据
const data = ref(null)

watchEffect(() => {
    
    
  if (data.value) {
    
    
    // 数据加载后执行某些操作...
  }
})

template reference

When using the combined API, in addition to using $refsthe properties of the Vue object to access the template reference, you can also expose an empty Ref()object with the same name and access it through this object.

Use $refsattribute

$refsProperties are actually still in the form of option APIs, but they can also be used in combined APIs. The difference is that the combined API is gone this, so it can only be used through Vue object instances.

However, the disadvantage of this method is obvious: before the Vue object is initialized, the Vue object instance cannot be obtained. Therefore, it cannot be used in life cycle hooks such as onMonted, and can only be used in some trigger events.

The advantage is also obvious: there is no need to define and return data, you only need to set the attribute on the element refto directly access the template reference.

Use empty Refobject

Define an empty Refobject with the same name for access

const input = ref();
const table = ref();

onMounted(() => {
    
    
  input.value.focus();
  table.value.clearSort();
})

Note that you use the object's valueproperties to correctly access its reference. Note that you need to Refreturn the object

use array

When v-fora template reference is used in a , the value contained in the corresponding ref is an array that will contain all elements of the entire list after the element is mounted:

<script setup>
const list = ref([
  /* ... */
])

const itemRefs = ref([])

onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {
   
   { item }}
    </li>
  </ul>
</template>

Note that for single access, use itemRefs.value[index]instead of itemRefs[index].value. In addition, the ref array is not guaranteed to be in the same order as the source array.

function template reference

In addition to using a string value as a name, the ref attribute can also be bound to a function that will be called every time the component is updated. The function receives an element reference as its first argument:

<input :ref="el => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

This will ensure the correct order when using arrays

    <li v-for="(item, index) in list" :ref="el => itemRefs[index] = el">
      {
   
   { item }}
    </li>

It should be noted that only dynamic :refbinding can be used to pass in a function. When the bound element is unloaded, the function will also be called once, and the el parameter will be null. It is also possible to bind a component method instead of an inline function.

In addition , the value of the bound ref attribute can be used without being defined. This value will actually be used as this.$refsthe key of this object to save the reference. When using :ref, you must use a function template reference, and the variables used inside the function must be defined and exposed, otherwise the reference will not be accessed normally.

user target audience

You can also use functions to assign template references to members of objects, so that several components can be classified

<script setup>
const inputRefs = ref({
      
      })

onMounted(() => console.log(inputRefs.value['address']))
</script>

<template>
    <input name='address' :ref="el => inputRefs['address'] = el" />
    <input name='age' :ref="el => inputRefs['age'] = el" />
</template>

Guess you like

Origin blog.csdn.net/runsong911/article/details/127867128