- 组合式API(composition api) 选项式API(Options API)
# setup 语法糖
setup是Vue3.0推出的语法糖,并且在Vue3.2版本进行了大更新,像写普通JS一样写Vue组件,对于开发者更加友好了;
按需引入computed、watch、directive等选项,一个业务逻辑可以集中编写在一起,让代码更加简洁便于浏览
# 一、基本用法
只需在<script>里添加一个setup属性,编译时会把<script setup></script>里的代码编译成一个setup函数
<script setup>
console.log('hello script setup');
</script>
普通<script>只会在组件被首次引入的时候执行一次,<script setup>里的代码会在每次组件实例被创建的时候执行。
# 二、data和methods
<script setup>里生命的变量和函数,不需要return暴露出去,就可以直接在template使用
<script setup>
import {ref, reactive} from 'vue';
// 普通变量
const msg = 'Hello';
// 响应式变量
let num = ref(111); // ref声明基本类型变量
const obj = reactive({ // reactive声明对象类型变量,如Object、Array、Date...
key: 'this is a object'
})
// 函数
function log() {
console.log(msg); // hello
console.log(num.value); // 111(可根据inpu输入值而改变)
console.log(obj.key); // this is a object
}
</script>
<template>
<h1>{{ msg }}</h1>
<p>{{ obj.key }}</p>
<input v-model="num" type="text"/>
<button @click="log">打印日志</button>
</template>
# 三、计算属性computed
<script setup>
import { ref, computed } from 'vue';
let count = ref(0);
const countPlus = computed(() => {
return count.value + 1;
})
</script>
<template>
<h1>计数: {{ countPlus }}</h2>
</template>
# 四、监听器watch、watchEffect
watch监听器除了使用方式有区别之外,其他的与vue2.0没啥变化
<script setup> import { ref, reactive, watch } from 'vue'; // 监听ref let count = ref(0); watch(count, (newVal, oldVal) => { console.log('修改后', newVal); console.log('修改前', oldVal); }) // 监听reactive属性 const obj = reactive({ count: 0 }) watch( () => obj.count, // 一个函数,返回监听属性 (newVal, oldVal) => { console.log('修改后', newVal); console.log('修改前', oldVal); }, { immediate: true, // 立即执行,默认为false deep: true // 深度监听,默认为false } ) const onChange = function() { count.value++; obj.count++; } </script> <template> <button @click="onChange">改变count</button> </template>watchEffect
watchEffect是Vue3.0新增的一个监听属性的方法,它与watch的区别在于watchEffect不需要指定监听对象,回调函数里可直接获取到修改后的值
<script setup> import { ref, reactive, watchEffect } from 'vue'; let count = ref(0); const obj = reactive({ count: 0 }) setTimeout(() => { count.value++; obj.count++; }, 1000); watchEffect(()=> { console.log('修改后的count', count.value); console.log('修改后的obj', obj, obj.count); }) </script>
# 五、自定义指令directive
以 vNameOfDirective 的形式来命名本地自定义指令,可以直接在模板中使用
<script setup>
// 导入指令可重命名
// import { myDirective as vMyDirective } from './MyDirective.js';
// 自定义指令
const vMyDirective = {
beforeMount: (el) => {
// 在元素上做些操作
}
}
</script>
<template>
<h1 v-my-directive>This is a Heading</h1>
</template>
# 六、import 导入的内容可直接使用
导入的模块内容,不需要通过methods来暴露它
// utils.js export const onShow = function(name) { return 'my name is' + name; }<!-- show.vue --> <script setup> import { onShow } from './utils.js'; </script> <template> <div>{ onShow('jack') }</div> </template>导入外部组件,不需要通过components注册使用
<!-- Child.vue --> <template> <div>I am a child</div> </template> <!-- Parent.vue --> <script setup> import Child from './Child.vue'; </script> <template> <child></child> </template>
# 7、声明props和emits
使用 defineProps 和 defineEmits API 来声明 props 和 emits
<!-- Child.vue -->
<script setup>
import { defineProps, defineEmits } from 'vue';
// 声明 props
const props = defineProps({
info: {
type: String,
default: ''
}
})
// 声明emits
const $emit = defineEmits(['change']);
const onChange = function() {
$emit('change', 'child返回值');
}
</script>
<template>
<h1>信息: {{info}}</h1>
<button @click="onChange">点击我</button>
</template>
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue';
import Child from './Child.vue';
const msg = ref('hello setup!');
const onAction = function(event) {
console.log(event);
}
</script>
<template>
<child :info="msg" @change="onAction"></child>
</template>
demo
const props = withDefaults( // 设置默认值withDefaults
defineProps<{
modelValue?: string | number;
placeholder?: string;
type?: FieldType;
prefixIcon?: string;
maxlength: number;
minLimit?: number;
disabled?: boolean;
autosize?: boolean;
showWordLimit?: boolean;
row?: number;
fieldRef?: FieldInstance;
autofocus?: boolean;
addVisible?: boolean;
noLineBreak?: boolean; // 不允许换行
}>(),
{
autofocus: location.href.indexOf('mod') || location.href.indexOf('role') ? env.isAndroid : false
}
);
# 八、父组件获取子组件的数据
父组件要想通过ref获取子组件的变量或函数,子组件须用defineExpose暴露出去
<!-- Child.vue -->
<script setup>
import { ref, defineExpose } from 'vue';
const info = ref('I am child');
const onChange = function() {
console.log('Function of child');
}
defineExpose({
info,
onChange
})
</script>
<template>
<h1>信息: {{ info }}</h1>
<button @click="onChange">点击我</button>
</template>
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref()
const onAction = function() {
console.log(childRef.value.info) // I am child
console.log(childRef.value.onChange()) // Function of child
}
</script>
<template>
<child ref="childRef"></child>
<button @click="onAction">获取子值</button>
</template>
# 九、provide 和 inject 传值
无论组件层次结构有多深,父组件都可以通过 provide 选项来给其所有子组件提供数据,子组件通过 inject 接收数据
<!-- Parent.vue -->
<script setup>
import { ref, provide } from 'vue';
import Child from './Child.vue';
const msg = ref('Hello, my son');
const onShow = function() {
console.log('I am your parent');
}
provide('myProvide', {
msg,
onShow
})
</script>
<template>
<child></child>
</template>
<!-- Child.vue -->
<script setup>
import { inject } from 'vue';
const provideState = inject('myProvide');
const getData = function() {
console.log(provideState.msg); // Hello, my son
console.log(provideState.onShow()); // I am your parent
}
</script>
<template>
<button @click="getData">获取父值</button>
</template>
# 十、路由 useRoute 和 useRouter
<script setup>
import { useRoute, useRouter } from 'vue-router';
const $route = useRoute();
const $router = useRouter();
// 路由信息
console.log($route.query);
// 路由跳转
$router.push('/login');
</script>
# 十一、对await异步的支持
<script setup>中可以使用顶层 await。结果代码会被编译成 async setup();
<script setup>
const post = await fetch('/api/post/1').then(r => r.json());
</script>
# 十二、nextTick
<!-- 方式一 -->
<script setup>
import { nextTick } from 'vue';
nextTick(() => {
console.log('DOM已更新')
})
</script>
<!-- 方式二 -->
<script setup>
import { nextTick } from 'vue';
await nextTick(); // nextTick是一个异步函数,返回一个promise实例
// console.log('Dom已更新!')
</script>
# 十三、全局属性 globalProperties
// main.js定义
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
// 定义一个全局属性$global
app.config.globalProperties.$global = 'This is a global property';
app.mount('#app');
<!-- 组件内使用 -->
<script setup>
import { getCurrentInstance } from 'vue';
// 获取Vue实例
const { proxy } = getCurrentInstance();
// 输出
console.log(proxy.$global); // This is a global property
</script>
# 十四、生命周期
setup()里访问组件的生命周期需要在生命周期钩子钱加上"on",并且没有beforeCreate和created生命周期钩子
TIP
因为setup是围绕 beforeCreate 和 created 声明周期钩子运行的,所以不需要显式的定义它们。换句话说,在这些钩子中编写的任何代码都应该在 setup函数中编写。
| 选项式API | Hook inside setup |
|---|---|
| beforeCreate | Not needed |
| created | Not needed |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
| activated | onActivated |
| deactivated | onDeactivated |
<!-- 使用方式 -->
<script setup>
import { onMounted } from 'vue';
onMounted( => {
console.log('onMounted')
})
</script>
# 十五、与普通的script标签一起使用
<script setup>可以和普通的<script>一起使用。普通的<script>在有这些需要的情况下或许会被使用到:
- 无法在<script setup>声明的选项,例如 inheritAttrs 或通过插件启用的自定义的选项;
- 声明命名导出,<script setup >定义的组件默认以组件文件的名称作为组件名;
- 运行副作用或创建只需要执行一次的对象。
<script>
// 普通 script,在模块范围下执行(只执行一次)
runSideEffectOnce();
// 声明额外的选项
export default {
name: 'ComponentName', // 组件重命名
inheritAttrs: false,
customOptions: {}
}
</script>
<script setup>
// 在 setup() 作用域中执行 (对每个实例皆如此)
</script>
# 十六、v-memo新指令
该指令与v-once类似,v-once是只渲染一次之后的更新不在渲染,而v-memo是根据条件渲染.该指令接收一个固定长度的数组作为依赖值进行记忆比对,如果数组中的每个值都和上次渲染的时候相同,则该元素(含子元素)不刷新
应用于普通元素或者组件
<template> <!-- 普通元素 --> <div v-memo="[valueA, valueB]"> <!-- ... --> </div> <!-- 组件 --> <component v-memo="[valueA, valueB]"></component> </template> <script setup> import component from '../components/component.vue' </script>当组件重新渲染的时候,如果valueA 与 valueB 都位置不变,那么对这个<div>以及它的所有自己诶单的更新豆浆杯跳过
结合 v-for使用
v-memo仅提供性能敏感场景的针对性优化,会用到的场景应该很少。渲染v-for长列表(长度大于1000)可能是他最有用的场景
<template> <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"> <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p> <p>...more child nodes</p> </div> </template>selected发生变化时,只有item.id===selected的该项重新渲染,其余不刷新。
# 十七、Hooks封装
// useCounter.js
import { ref } from 'vue';
export function useCounter(initial = 0) {
const count = ref(initial);
function increment() {
count.value++;
}
return {count, increment}
}
// used
import { useCounter } from './useCounter';
const {count, increment} = useCounter(10);
# 十八、watchEffect
watchEffect 相比于 watch,它能自动帮我们收集依赖进行监听响应时变化
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
const count = ref(0);
const message = ref('');
watchEffect(() => {
message.value = `count 的值为:${count.value}`
})
</script>
# 十九、provide+inject
当你在封装一些比较复杂的组件时,设计到的组件层级比较多的时候,使用props进行数据传递比较麻烦,所以可以使用 provide + inject 来进行数据传递共享
<!-- Parent.vue -->
<script setup lang="ts">
import { provide, ref } from 'vue';
const theme = ref('light');
provide('theme', theme);
</script>
<!-- DeepChild.vue -->
<script setup lang="ts">
import { inject } from 'vue';
const theme = inject('theme');
console.log(theme.value);
</script>
# 二十、shallowRef
当有数据量打的数组或者对象时,我们如果只需要监听渐层响应式变化的话,可以不需要使用ref,而是使用shallowRef,这样的话只会监听浅层变化,而不会监听深层变化,提升了性能
<script setup lang="ts">
import { shallowRef } from 'vue';
const largeObject = shallowRef({/**海量属性 */});
function updateObject() {
largeObject.value = {/**大型对象 */}
}
</script>
# 二十一、defineExpose
如果你想要在使用组件的页面中,去调用组件中的方法,可以使用 defineExpose 将组件内的方法暴露出来,供外部使用
<!-- ParentComponent.vue -->
<script setup lang="ts">
const modal = ref();
</script>
<template>
<button @click="modal.open()">打开模拟框</button>
<AppModal ref="modal"/>
</template>
# 二十二、effectScope全局状态管理
Pinia 相信大家都用过吧,它其实原理就是基于 effectScope 实现的, effectScope 可以用来做全局或者局部状态管理,Vueuse 中的状态管理 Hooks createGlobalState 也是基于 effectScope 实现的
import { effectScope } from 'vue';
export function createGlobalState<Fn extends (...args: any[]) => any>(stateFactory:Fn): Fn {
let initialized = false;
let state : any;
const scope = effectScope(true);
return ((...args: any[]) => {
if(!initialized) {
state = scope.run(() => stateFactory(...args))!;
initialized = true
}
return state;
}) as Fn
}
# 其他
# unref、isRef、toRef、toRefs
unref 如果参数是一个ref 则返回它的value,否则返回参数本身
unref(val)相当于val = isRef(val) ? val.value : val
function initialCount(value: number | Ref<number>) { // Make the output be true console.log(value === 10) } const initial = ref(10); initialCount(unRef(initial)); // 必然返回trueisref 检查一个值是否是一个ref对象
toRef、toRefs的本质是引用,修改响应式数据,会影响到原始数据,视图不会更新
- toRef 一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的那个属性
- toRefs 作用是将响应式对象中所有属性转换为单独的响应式数据,将对象中的每个属性都做一次ref操作,使每个属性都具有响应式
const state = reactive({ foo:1, bar: 2 }) // const fooRef = toRef(state, 'foo'); const fooRef = toRefs(state).foo fooRef.value++; console.log(state.foo === 2); state.foo++; console.log(fooRef.value === 2)<template> <div> <p> <span @click="update(count - 1)">-</span> {{count}} <span @click="update(count + 1)">-</span> </p> </div> </template> <script setup lang="ts"> import { reactive, toRefs } from 'vue'; function useCount() { const state = reacitve({ count: 0 }) function update(value: number) { state.count = value; console.log(state.count) } return { state: toRefs(state), update, } } const {state: {count}, update} = useCount(); </script>展开运算符
响应式对象的处理,是加给对象的,如果对对象做了展开操作,那么就会丢失响应式效果。
<template> <button @click="name='张三'">修改名字</button> {{name}} </template> <script lang="ts"> import { reactive, toRef } form 'vue'; export default { setup() { const user = reactive<any>({ name: '小明', age: 10, addr: { province: '山东', city: '青岛' } }) return { ...toRefs(user); } } } </script>
# 资料
SFC 语法规范 | Vue.js (opens new window)
Vue3.2 setup语法糖、Composition API、状态库Pinia归纳总结 (opens new window)
Vue3.2 setup语法糖、Composition API、状态库Pinia归纳总结 --pinia (opens new window)