Xi-Yuer

Xi-Yuer

github

Vue

Common APIs of Vue3#

reactive# Vue Guide#

Data Properties#

The data option of a component is a function. Vue calls this function during the creation of a new component instance. It should return an object, which Vue wraps with the reactivity system and stores in the component instance as $data. For convenience, any top-level properties of this object are also directly exposed through the component instance:

  1. Essentially, the component's data option is just a function.
  2. This function is called when Vue creates the component instance.
  3. This function returns an object.
  4. The returned object is wrapped by Vue as reactive data.
  5. The wrapped reactive data is stored in the component instance as $data.
  6. For convenience, any top-level properties of the object returned by data can be accessed directly through the component instance, provided they are all included in the object returned by the data function.
  7. If new properties are added directly to the component instance, they can be added successfully, but since they are not in the reactive object $data, they will not be reactive.
  8. Properties of the object returned by data should avoid starting with $ and _.

Principle Analysis#

Why is the data property a function instead of an object?

  • Conclusion: data can be defined as either a function or an object, but the root component can be instantiated as either an object or a function, while component instances can only be instantiated as a function.
  • Principle: The root instance is a singleton, which prevents data pollution; component instances are not singletons. To prevent multiple component instances from sharing the same data, which could lead to data pollution, a function form is used. When initializing data, it acts as a factory function that returns a new data object.

Computed Properties#

Application scenarios:

  1. In templates, long expressions can make the template complex and difficult to maintain. To solve this problem, computed was born.
  2. It has a caching effect, which improves performance; when the dependent data has not changed, cached data is used.

For any complex logic that includes reactive data, computed properties should be used.

Computed Properties vs. Methods#

Computed properties are cached based on their reactive dependencies. They will only re-evaluate when their related reactive dependencies change.

In contrast, methods will always execute the function again whenever a re-render is triggered.

Why do we need caching?

Suppose we have a performance-intensive computed property list, which needs to traverse a huge array and perform a lot of calculations. We may have other computed properties that depend on list. Without caching, we will inevitably execute the getter of list multiple times! If you do not want caching, please use method instead.

Watchers#

Options API Implementation of Watch#

Regular Watch#

export default {
    props:['dataList'],
    data(){
        return {
            sourceData:[],
            total:0
        }
    },
    // When the props' dataList changes, the values in data will be recalculated and assigned
    watch:{
        dataList(newVal,oldVal){
            this.total = newVal.length
            this.sourceData = newVal
        }
    }
}

Object Watch#

export default {
    data(){
        return {
            firstName:'Li',
            lastName:'Huan'
        }
    },
    // When the props' dataList changes, the values in data will be recalculated and assigned
    watch:{
        dataList:{
           // getter
            get:function(){
                return this.firstName + this.lastName
            },
            set:function(val){
                // this.dataList = 'Wang Feng'
                const [ firstName,lastName ] = val.split(' ')
                this.firstName = firstName
                this.lastName = lastName
            },
            {
            // Objects and arrays are reference types, and reference type variables store addresses. If the address does not change, it will not trigger watch. In this case, we need to perform a deep watch, adding the deep: true property.
            deep:true,
            // Watch has a characteristic: when the value is first bound, the listener function will not be executed. It will only execute when the value changes. If you want the function to execute when the initial bound value is also executed, you need to add the immediate property.
            immediate:true
        }
        }
    }
}

Reactive API Implementation of Watch#

The watch function is used to listen to specific data sources and execute side effects in the callback function. By default, it is lazy, meaning it only executes the callback when the watched source data changes.

watch(source, callback, [options]);

Parameter description:

  • source: can support String, Object, Function, and Array to specify the reactive variable to be watched.
  • callback: the callback function to be executed.
  • options: supports deep, immediate, and flush options.

Listening to Reactive#

import { defineComponent, reactive, watch } from 'vue'
export default defineComponent({
    setup(){
        const state = reactive({
            firstName:'Li',
            lastName:'Wang'
        })
        
        // When firstName is modified, the watch callback function will be automatically triggered
        watch(
            ()=>state.firstName,
            (newVal,oldVal)=>{
            	console.log(`New value: ${newVal}`,`Old value: ${oldVal}`)
        	},
        	{deep:true})
    }
})

Listening to Multiple Data#

const stop = watch([() => state.firstName, () => state.lastName], ([curAge, newVal], [preAge, oldVal]) => {
  console.log('New value:', curAge, 'Old value:', preAge);
  console.log('New value:', newVal, 'Old value:', oldVal);
});

unmounted(()=>{
    // Stop listening
    stop()
})

watchEffect#

watchEffect does not require dependencies to be passed in like watch; watchEffect will automatically collect dependencies as long as a callback function is specified. During component initialization, it will execute once to collect dependencies, and when the collected dependencies change, it will execute the callback function again.

watchEffect(()=>{
    // Automatically collect dependencies, execute once during initialization
    console.log(firstName);
    console.log(lastName)
})

Style Settings#

Object Syntax#

<template>
    <div class='static' :class="{active:isActive,danger:hasError}"></div>
</template>
<script>
	export default {
        data(){
            return {
                isActive:true,
                hasError:true
            }
        }
    }
</script>
<!-- After rendering -->
<div class="static active danger"></div>

Directly Passing an Object#

<template>
    <div class='static' :class="classObj"></div>
</template>
<script>
	export default {
        data(){
            return {
               classObj:{
                   active:true,
                   hasError:true
               }
            }
        }
    }
</script>

Passing Computed#

<template>
	<div class="static" :class="clsObject"></div>
</template>
<script>
	export default {
        data(){
            return {
                isActive:true,
                hasError:true
            }
        },
        computed:{
            classObj(){
                return {
                    active:this.isActive,
                    hasError:this.hasError
                }
            }
        }
    }
</script>

Conditional Rendering#

<template>
	<div v-if='show'></div>
	 <div v-else></div>
	<div v-show='show'></div>
</template>

<script>
	export default {
        data(){
            return {
                show:true
            }
        }
    }
</script>

v-if vs v-show:

  • v-show simply modifies the element's css property, setting the element's display to none.
  • v-if removes the element from the component tree. When the element is a component, changing the show value will trigger the lifecycle functions of the component element.

Built-in Components#

component#

Dynamic components

// Dynamic component
<template>
	<!-- The component also changes when currentTab changes -->
	<component :is="tabs[currentTab]"></component>
</template>

In the above example, the value passed to :is can be one of the following:

  • Registered component name
  • Imported component object

transition#

Animation component

<!-- Template -->
<template>
	<Transition name="slide-fade">

	</Transition>
</template>
/*
  Enter and leave animations can use different
  durations and speed curves.
*/
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}

image-20230726165423050

  • Transition hook functions
<template>
	<Transition
      @before-enter="onBeforeEnter"
      @enter="onEnter"
      @after-enter="onAfterEnter"
      @enter-cancelled="onEnterCancelled"
      @before-leave="onBeforeLeave"
      @leave="onLeave"
      @after-leave="onAfterLeave"
      @leave-cancelled="onLeaveCancelled"
  >
  </Transition>
</template>
// Called before the element is inserted into the DOM
// Use this to set the element's "enter-from" state
function onBeforeEnter(el) {}

// Called in the next frame after the element is inserted into the DOM
// Use this to start the enter animation
function onEnter(el, done) {
  // Call the callback function done to indicate the transition is complete
  // If used with CSS, this callback is optional
  done()
}

// Called when the enter transition is complete.
function onAfterEnter(el) {}
function onEnterCancelled(el) {}

// Called before the leave hook
// Most of the time, you will only use the leave hook
function onBeforeLeave(el) {}

// Called when the leave transition starts
// Use this to start the leave animation
function onLeave(el, done) {
  // Call the callback function done to indicate the transition is complete
  // If used with CSS, this callback is optional
  done()
}

// Called when the leave transition is complete,
// and the element has been removed from the DOM
function onAfterLeave(el) {}

// Only available in v-show transitions
function onLeaveCancelled(el) {}

keep-alive#

Used to cache component state and avoid re-rendering components.

keep-alive has the following three attributes:

  • include: a string or regular expression; only components with matching names will be cached.
  • exclude: a string or regular expression; any components with matching names will not be cached.
  • max: a number; the maximum number of component instances that can be cached.

slot#

Default slot

<template>
	<!-- Parent component -->
    <Child>
      <div>Default slot</div>
    </Child>

    <!-- Child component -->
    <template>
      <slot>
        <p>Fallback content for the slot</p>
      </slot>
    </template>
</template>

Named Slots#

<!-- Inside the component -->
<div class="container">
  <header>
    <!-- We want to place the header here -->
  </header>
  <main>
    <!-- We want to place the main content here -->
  </main>
  <footer>
    <!-- We want to place the footer here -->
  </footer>
</div>
<!-- Inside the component -->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

Scoped Slots#

When the parent component customizes the slot content in the child component, it needs to access the component data from the child component.

<template>
	<!-- Child -->
    <ul>
      <li v-for="(item, index) in items">
        <slot :item="item"></slot>
      </li>
    </ul>

    <!-- Parent -->
    <todo-list>
      <template #default="{slotProps}">
        <span class="green">{{ slotProps.item }}</span>
      </template>
    </todo-list>
</template>

Teleport#

Teleport is a new feature introduced in Vue3.x that provides a clean way to control which parent node in the DOM the HTML is rendered under.

<template>
	<Teleport to="body">
      <div> teleport to body </div>
	</Teleport>
</template>
import { reactive } from 'vue';
export default {
  // `setup` is a special hook for the Composition API
  setup() {
    const state = reactive({ count: 0 }); // Expose state to the template

    return { state };
  },
};

ref#

import { ref } from 'vue';

const count = ref(0);

console.log(count); // { value: 0 }
console.log(count.value); // 0

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

computed#

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// A computed property ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

watch#

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

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

// Can directly watch a ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    }
  }
})
</script>

<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</template>

watchEffect#

watchEffect() will immediately execute the callback function once. If the function produces side effects, Vue will automatically track the dependencies of the side effects and analyze the reactive sources.

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

watch vs. watchEffect#

Both watch and watchEffect can reactively execute callbacks with side effects. The main difference between them is how they track reactive dependencies: watch only tracks explicitly watched sources. It does not track anything accessed within the callback. Additionally, it only triggers the callback when the reactive source actually changes. watch avoids tracking dependencies during side effects, allowing for more precise control over when the callback function is triggered.
watchEffect, on the other hand, tracks dependencies during side effects. It automatically tracks all accessible reactive properties during synchronous execution. This is more convenient, and the code is often cleaner, but its reactive dependency relationships are less explicit.

State Management with Pinia#

Store#

// stores/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 };
  },
  // could also be defined as
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++;
    },
  },
  getters(){
      finishedTodos(store){
          return store.count++
      }
  }
});

Action#

import { useCounterStore } from '@/stores/counter';

export default {
  setup() {
    const counter = useCounterStore();

    counter.count++;
    // with autocompletion ✨
    counter.$patch({ count: counter.count + 1 });
    // or using an action instead
    counter.increment();
  },
};

vue-router#

router.js

const Home = { template: '<div>Home</div>' };
const About = { template: '<div>About</div>' };

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
];
const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes,
});

const app = Vue.createApp({});

app.use(router);

app.mount('#app');

Rendering

<template>
  <router-view />
</template>

setup Syntax#

defineProps defineEmits#

TypeScript syntax support

const props = defineProps<{
  foo: string
  bar?: number
}>()

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

defineExpose#

The setup component is closed by default.

<script setup>
  import {ref} from 'vue' const a = 1 const b = ref(2) defineExpose({a, b})
</script>
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.