Exploring Vue 3 New Features: Composition API, Props, Pinia State Management, and Reactive Patterns
This article introduces Vue 3's latest capabilities—including the Composition API, setup syntax sugar, Pinia state management, and advanced reactive patterns—demonstrating how they simplify component logic, improve code reuse, and enhance maintainability for modern frontend development.
Introduction: The article introduces several new features of Vue 3, such as the Composition API, setup syntax sugar, and the Pinia state management library, showing how they improve code clarity and reusability.
It discusses the advantages of using setup lang="ts" , the Composition API for separating logic and UI, and essential tools like Vite and the Volar VSCode extension.
Reactive data handling is explored in depth, explaining ref , reactive , and the watch function with multiple scenarios, including deep watching and watching getter functions. Example code:
import { ref, reactive, watch, nextTick } from 'vue';
// Define four kinds of reactive data/state
// 1. ref with primitive value
const simplePerson = ref('张三');
// 2. ref with object (equivalent to person.value = reactive({ name: '张三' }))
const person = ref({ name: '张三' });
// 3. ref with nested object
const complexPerson = ref({ name: '张三', info: { age: 18 } });
// 4. reactive object
const reactivePerson = reactive({ name: '张三', info: { age: 18 } });
nextTick(() => {
simplePerson.value = '李四';
person.value.name = '李四';
complexPerson.value.info.age = 20;
reactivePerson.info.age = 22;
});
// Scenario 1: watch RefImpl
watch(simplePerson, (newVal) => {
console.log(newVal); // 输出:李四
});
// Scenario 2: watch primitive value (invalid)
watch(simplePerson.value, (newVal) => {
console.log(newVal); // illegal, no watch
});
// Scenario 3: watch RefImpl with deep option
watch(person, (newVal) => {
console.log(newVal); // 输出:{name: '李四'}
}, { deep: true });
// Scenario 4: watch reactive object directly
watch(person.value, (newVal) => {
console.log(newVal); // 输出:{name: '李四'}
});
// Scenario 5: watch primitive property (invalid)
watch(person.value.name, (newVal) => {
console.log(newVal); // illegal, no watch
});
// Scenario 6: watch getter returning primitive
watch(() => person.value.name, (newVal) => {
console.log(newVal); // 输出:李四
});
// Scenario 7: watch reactive object (deep by default)
watch(complexPerson.value.info, (newVal, oldVal) => {
console.log(newVal); // 输出:Proxy {age: 20}
console.log(newVal === oldVal); // 输出:true
});
// Scenario 8: watch getter returning reactive object (needs deep)
watch(() => complexPerson.value.info, (newVal) => {
console.log(newVal); // need deep:true to detect changes
});
// Scenario 9: watch reactive object without deep option (works)
watch(reactivePerson, (newVal) => {
console.log(newVal); // detects changes
});A custom hook useDynamicTree is presented for building a dynamic logical tree, with functions to add, remove, toggle operators, and traverse nodes. The full TypeScript implementation is included.
export type TreeNode = {
id?: string;
pid: string;
nodeUuid?: string;
partentUuid?: string;
nodeType: string;
nodeValue?: any;
logicValue?: any;
children: TreeNode[];
level?: number;
};
export const useDynamicTree = (root?: TreeNode) => {
const tree = ref
(root ? [root] : []);
const level = ref(0);
// Add node
const add = (node: TreeNode, pid = 'root'): boolean => {
if (pid === '') {
tree.value = [node];
return true;
}
level.value = 0;
const pNode = find(tree.value, pid);
if (!pNode) return false;
if (pNode.level && pNode.level > 2) return false;
if (!node.id) node.id = nanoid();
if (pNode.nodeType === 'operator') {
pNode.children.push(node);
} else {
const current = JSON.parse(JSON.stringify(pNode));
current.pid = pid;
current.id = nanoid();
Object.assign(pNode, {
nodeType: 'operator',
nodeValue: 'and',
logicValue: undefined,
nodeUuid: undefined,
parentUuid: undefined,
children: [current, node],
});
}
return true;
};
// Delete node
const remove = (id: string) => {
const node = find(tree.value, id);
if (!node) return;
if (node.pid === '') {
tree.value = [];
return;
}
const pNode = find(tree.value, node.pid);
if (!pNode) return;
const index = pNode.children.findIndex(item => item.id === id);
if (index === -1) return;
pNode.children.splice(index, 1);
if (pNode.children.length === 1) {
const [one] = pNode.children;
Object.assign(pNode, { ...one, pid: pNode.pid });
if (pNode.pid === '') pNode.id = 'root';
}
};
// Toggle logical operator (and/or)
const toggleOperator = (id: string) => {
const node = find(tree.value, id);
if (!node) return;
if (node.nodeType !== 'operator') return;
node.nodeValue = node.nodeValue === 'and' ? 'or' : 'and';
};
// Find node by id
const find = (node: TreeNode[], id: string): TreeNode | undefined => {
for (let i = 0; i < node.length; i++) {
if (node[i].id === id) {
Object.assign(node[i], { level: level.value });
return node[i];
}
if (node[i].children?.length > 0) {
level.value += 1;
const result = find(node[i].children, id);
if (result) return result;
level.value -= 1;
}
}
return undefined;
};
// Depth‑first traversal with callback
const dfs = (node: TreeNode[], callback: (node: TreeNode) => void) => {
for (let i = 0; i < node.length; i++) {
callback(node[i]);
if (node[i].children?.length > 0) {
dfs(node[i].children, callback);
}
}
};
return { tree, add, remove, toggleOperator, dfs };
};The hook is used in two UI components (UI1 and UI2) that render the logical tree in different visual forms, demonstrating how the same business logic can be shared across multiple component layouts.
The article also shows how to integrate Pinia for state management by defining a user store with reactive state, a computed full name, and a setter function.
import { computed, reactive } from 'vue';
import { defineStore } from 'pinia';
type UserInfo = {
userName: string;
realName: string;
headImg: string;
organizationFullName: string;
};
export const useUserStore = defineStore('user', () => {
const userInfo = reactive
({
userName: '',
realName: '',
headImg: '',
organizationFullName: ''
});
const fullName = computed(() => `${userInfo.userName}[${userInfo.realName}]`);
const setUserInfo = (info: UserInfo) => {
Object.assign(userInfo, { ...info });
};
return { userInfo, fullName, setUserInfo };
});In component templates, the store data is displayed with Element‑Plus UI components, illustrating a clean separation between UI and state.
Finally, the article poses discussion questions about whether Vue 2 can achieve similar logic‑UI separation and under what circumstances it is acceptable to modify props internally, concluding that Vue 3’s Composition API and Pinia enable clearer, more maintainable frontend development.
JD Tech
Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.