執行時機css
setup 注意點html
export default {
props: {
title: String
},
data() {
return {
name: 'Benson'
}
},
methods: {
setName() {
console.log(this.name);
}
}
setup(props) {
const sex = '男';
function setSex() {
console.log(this.sex);
}
return { sex, setSex };
}
}
複製代碼
composition Api 實際上最終會把 setup return 的變量和方法會放到和 option Api 的 data 和 methods 同樣的地方,因此咱們能後直接 this.xx 去使用。vue
例如使用了 data 對象:react
import { reactive } from 'vue';
export default {
name: 'App',
setup(props, context) {
// 建立一個響應式數據
// 本質:就是將傳入的數據包裝成一個 Propxy 對象
const state = reactive({
time: new Date();
})
function myFn() {
// 直接修改之前的,界面是不會更新
state.time.setDate(state.time.getDate() + 1);
// 從新賦值方式才能響應式變動界面
const newTime = new Date(state.time.getTime());
newTime.setDate(state.time.getDate() + 1);
state.time = newTime;
console.log(state.time);
}
return {state, myFn}
}
}
複製代碼
import {ref, reactive, isRef, isReactive} from 'vue';
export default {
name: 'App',
setup() {
const age = ref(18);
const state = reactive({name: 'Benson'});
console.log(isRef(age));
console.log(isReactive(state));
return {age, state};
}
}
複製代碼
<template>
<div>
<p>State1: {{ state1 }}</p>
<p>State2: {{ state2 }}</p>
<button @click="fn">變動</button>
</div>
</template>
<script>
import { ref, reactive } from 'vue';
export default {
name: 'App',
setup() {
let state1 = reactive({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
});
let state2 = ref({
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
});
function fn() {
state1.a = '1';
state1.gf.b = '2';
state1.gf.f.c = '3';
state1.gf.f.s.d = '4';
console.log('-----------State1-----------');
console.log(state1);
console.log(state1.gf);
console.log(state1.gf.f);
console.log(state1.gf.f.s);
state2.value.a = '1';
state2.value.gf.b = '2';
state2.value.gf.f.c = '3';
state2.value.gf.f.s.d = '4';
console.log('-----------State2-----------');
console.log(state2.value);
console.log(state2.value.gf);
console.log(state2.value.gf.f);
console.log(state2.value.gf.f.s);
}
return { state1, state2, fn };
},
};
</script>
複製代碼
變動前:json
變動後:api
上述代碼在執行 fn 方法的時候,會發現頁面同時發生了變化,而且在查看控制檯的時候,你會發現輸出來的全是進行過 Proxy 封裝過的數據。數組
這就是 Vue3 的遞歸監聽,reactive 和 ref 會遞歸循環數據,爲每一層數據進行 Proxy 封裝。瀏覽器
默認狀況下,不管是經過 ref 仍是 reactive 都是遞歸監聽的。markdown
遞歸監聽存在必定的問題,若是數據量比較大,是很是消耗性能的app
通常狀況下咱們使用 ref 和 reactive 便可
只有在須要監聽的數據量比較大的時候,咱們才使用 shallowRef /shallowReactive
<template>
<div>
<p>State1: {{ state1 }}</p>
<p>State2: {{ state2 }}</p>
<button @click="fn">變動</button>
</div>
</template>
<script>
import { shallowRef, shallowReactive, triggerRef } from 'vue';
export default {
name: 'App',
setup() {
const data = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c',
s: {
d: 'd',
},
},
},
};
// 注意點:shallowReactive 建立的數據,只進行第一層的數據監聽
let state1 = shallowReactive(JSON.parse(JSON.stringify(data)));
// shallowRef 建立的數據,只進行第一層的數據監聽
let state2 = shallowRef(JSON.parse(JSON.stringify(data)));
function fn() {
// 只有修改這個第一層才能監聽到數據變動UI
// state1.a = '1';
state1.gf.b = '2';
state1.gf.f.c = '3';
state1.gf.f.s.d = '4';
console.log('-----------State1-----------');
console.log(state1);
console.log(state1.gf);
console.log(state1.gf.f);
console.log(state1.gf.f.s);
// 只有修改這個第一層才能監聽到數據變動UI
// state2.value = JSON.parse(JSON.stringify(data));
state2.value.a = '1';
state2.value.gf.b = '2';
state2.value.gf.f.c = '3';
state2.value.gf.f.s.d = '4';
// Vue3 只提供了 triggerRef 方法,沒有提供 triggerReactive 方法
// 因此若是是 reative 類型的數據,那麼是沒法主動觸發界面更新的
triggerRef(state2);
console.log('-----------State2-----------');
console.log(state2);
console.log(state2.value);
console.log(state2.value.gf);
console.log(state2.value.gf.f);
console.log(state2.value.gf.f.s);
}
return { state1, state2, fn };
},
};
</script>
<style></style>
複製代碼
讀者可自行拷貝代碼進行演示
ref -> refImpl(若是傳入 object -> reactive)
ref(10) -> refImpl{value: 10}
shallowRef -> shallowReactive
shallowRef(10) -> refImpl(若是傳入 object -> shallowReactive)
複製代碼
左邊標識咱們在使用 API 的樣子
右邊爲 Vue3 實際使用的樣子
toRaw 從 Reactive 或 Ref 中獲得原始數據
toRaw 做用就是作一些不想被監聽的事情(提高性能)
setup() {
let obj = {name: 'Benson', age 18};
/**
* ref / reactive 數據類型的特色
* 每次修改都會被追蹤,都會更新 UI 界面,可是這樣實際上是很是消耗性能的
* 全部若是咱們有一些操做不須要追蹤,不須要更新 UI 界面,那麼這個時候,
* 咱們就能夠經過 toRaw 方法拿到它的原始數據,對原始數據進行修改
* 這樣就不會被追蹤,這樣就不會更新 UI 界面,這樣性能就行了
*/
let state = reactive(obj);
let obj2 = toRaw(state);
console.log(obj === obj2); // true
console.log(obj === state); // false
// state 和 obj 的關係:
// 引用關係,state 的本質是一個 Proxy 對象,在這個 Proxy 對象引用了 obj
function fn() {
// 若是直接修改 obj,那麼是沒法觸發界面更新的
// 只有經過包裝後的 Proxy 對象來修,纔會觸發界面的更新
obj.name = 'HAHA';
}
}
複製代碼
import { reactive, markRaw } from 'vue';
export defalut {
name: 'App',
setup() {
let obj = {name: 'Benson', age: 18};
obj = markRow(obj);
let state = reactive(obj);
function fn() {
state.name = 'zs';
}
return { state, fn }
}
}
複製代碼
markRaw 的做用就是禁止源數據不能被用於監聽,通過上述處理後,reactive 處理 obj 進行響應式數據的封裝將不在其做用了。
能夠用來爲源響應式對象上的 property 性建立一個 ref。而後能夠將 ref 傳遞出去,從而保持對其源 property 的響應式鏈接。
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
// 往 fooRef property 找,fooRef property 指向 state property 的 ref
fooRef.value++;
console.log(state.foo) // 2
state.foo++;
console.log(fooRef.value) // 3
複製代碼
當您要將 prop 的 ref 傳遞給複合函數時,toRef 頗有用:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo')); // 將 foo 屬性傳下去
}
}
複製代碼
將響應式對象轉換爲普通對象,其中結果對象的每一個 property 都是指向原始對象相應 property 的 ref。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
Type of stateAsRefs:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref 和 原始property 「連接」
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
複製代碼
當從合成函數返回響應式對象時,toRefs 很是有用,這樣消費組件就能夠在不丟失響應性的狀況下對返回的對象進行分解/擴散:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// 邏輯運行狀態
// 返回時轉換爲ref
return toRefs(state)
}
export default {
setup() {
// 能夠在不失去響應性的狀況下破壞結構
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
複製代碼
建立一個自定義的 ref,並對其依賴項跟蹤和更新觸發進行顯式控制。它須要一個工廠函數,該函數接收 track 和 trigger 函數做爲參數,並應返回一個帶有 get 和 set 的對象。
使用 v-model 使用自定義 ref 實現 debounce 的示例:
Html
<input v-model="text" />
複製代碼
Js
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
export default {
setup() {
return {
text: useDebouncedRef('hello')
}
}
}
複製代碼
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// DOM元素將在初始渲染後分配給ref
console.log(root.value) // <div>這是根元素</div>
})
return { root }
}
}
</script>
複製代碼
setup
return
響應式變量 root,template 中,ref="root"
在 onMounted
掛載以後就會將相應的元素或者組件實例將分配給該 root 變量。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 適用於響應性追蹤
console.log(copy.count)
})
// 變動 original 會觸發 watchEffect
original.count++
// 變動副本將失敗並致使警告
copy.count++ // 警告!
// 經過 isReadonly 判斷是否爲 readonly 對象
console.log(isReadonly(copy)); // true
console.log(isReadonly(original)); // false
複製代碼
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2
}
});
// 改變狀態自己的 property 將失敗
state.foo++;
// ...但適用於嵌套對象
isReadonly(state.nested); // false
state.nested.bar++; // 適用
複製代碼
只讀效果,僅僅控制在 第一層,深層的屬性仍是能夠變動新的
還有一個注意點,ES6 的 const
也能控制只讀,但請看下面例子:
const argu_1 = {name: '小明', attrs: {sex: 18}};
const argu_2 = readonly({name: '小紅'}, attrs: {sex: 16});
const argu_3 = shallowReadonly({name: '小夏'}, attrs: {sex: 17});
複製代碼
sex
是能夠變動的,argu_3 能夠從新賦值這個語法相似於 vue 組件中使用 props 校驗傳入的參數。
例子:
app.component('custom-form', {
// 數組方式, 只對自定義事件名稱校驗
emits: ['click', 'submit'],
// 對象方式
emits: {
// 沒有驗證
click: null,
// 驗證submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm() {
this.$emit('submit', { email, password })
}
}
})
複製代碼
<style scoped>
/* deep selectors */
::v-deep(.foo) {}
/* shorthand */
:deep(.foo) {}
</style>
複製代碼
最初,支持>>>組合器以使選擇器爲「 deep」。可是,某些CSS預處理器(例如SASS)在解析它時會遇到問題,由於這不是官方的CSS組合器。
後來切換到/ deep /,這曾經是CSS的實際建議添加(甚至是Chrome自己提供的),但後來刪除了。這引發了一些用戶的困惑,由於他們擔憂在Vue SFC中使用/ deep /會致使在刪除該功能的瀏覽器中不支持其代碼。可是,就像>>>同樣,/ deep /僅由Vue的SFC編譯器用做編譯時提示以重寫選擇器,並在最終CSS中被刪除。
爲了不丟失的/ deep /組合器形成混亂,引入了另外一個自定義組合器:: v-deep,此次更加明確地代表這是Vue特定的擴展,並使用僞元素語法,以便任何-處理器應該可以解析它。
因爲當前Vue 2 SFC編譯器的兼容性緣由,仍支持深度組合器的先前版本,這又可能使用戶感到困惑。在v3中,咱們再也不支持>>>和/ deep /。
在研究適用於v3的新SFC編譯器時,咱們注意到CSS僞元素實際上在語義上不是組合符。僞元素改成接受參數,這與慣用CSS更加一致,所以,咱們也使:: v-deep()那樣工做。若是您不關心顯式的v-前綴,也可使用更短的:deep()變體,其工做原理徹底相同。
當前仍支持:: v-deep做爲組合器,但已將其棄用,並會發出警告。
<style scoped>
/* targeting slot content */
::v-slotted(.foo) {}
/* shorthand */
:slotted(.foo) {}
</style>
複製代碼
當前,從父級傳入的 slot 內容受父級的做用域樣式和子級的做用域樣式的影響。沒法編寫僅明確指定 slot 內容或不影響 slot 內容的規則。
在v3中,咱們打算默認使子範圍的樣式不影響 slot 的內容。爲了顯式地指定插槽內容,可使用:: v-slotted()(簡寫爲:: slotted())僞元素。
<style scoped>
/* one-off global rule */
::v-global(.foo) {}
/* shorthand */
:global(.foo) {}
</style>
複製代碼
當前,要添加全局CSS規則,咱們須要使用單獨的無做用域塊。咱們將引入一個新的:: v-global()(簡寫爲:: global())僞元素,以用於一次性全局規則。
<template>
<button @click="inc">{{ count }}</button>
</template>
<script setup="props, { emit }">
import { ref, onMounted } from 'vue'
export const count = ref(0)
export const inc = () => count.value++
onMounted(() => {
emit('foo');
)
</script>
複製代碼
最新方案:
import { useContext, defineProps, defineEmit } from 'vue'
const emit = defineEmit(['onClick'])
const ctx = useContext()
const props = defineProps({
msg: String
})
複製代碼
其中 ctx
包含了 attrs、emit、props、slots、expose
屬性
支持將組件狀態驅動的 CSS 變量注入到「單個文件組件」樣式中。
案例:
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
<style vars="{ color }">
.text {
color: var(--color);
}
</style>
複製代碼
注意的一點,目前 Vue SFC 樣式提供了直接的 CS 配置和封裝,可是它是純靜態的-這意味着到目前爲止,尚沒法根據組件的狀態在運行時動態更新樣式。
當樣式存在 scoped 局部樣式和又要使用全局 var 的狀況:
<style scoped vars="{ color }">
h1 {
color: var(--color);
font-size: var(--global:fontSize);
}
</style>
複製代碼
這裏的 fontSize 是全局的 var css 變量
通過 compiles 後:
h1 {
color: var(--6b53742-color);
font-size: var(--fontSize);
}
複製代碼