Vue 3.0 新特性與使用 二

image

setUp 執行時機

執行時機css

  • setup:執行,組件實例還沒有被建立
  • beforeCreate:表示組件剛剛被建立出來,組件的 data 和 methods 尚未初始化好
  • created:表示組件剛剛被建立出來,而且組件的 data 和 methods 已經初始化好

setup 注意點html

  • 因爲在執行 setup 函數的時候,尚未執行 beforeCreate、created 生命週期方法,因此在 setup 函數中,是沒法使用 data 和 methods
  • 因爲咱們不能在 setup 函數中使用 data 和 methods,因此 Vue 爲了不咱們錯誤的使用,它直接將 setup 函數中 this 修改爲了 undefined
  • setup 函數只能同步而不能異步的

組合 Api 本質

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

reactive 理解

  1. 什麼是 reactive?
  • reactive 是 Vue3 中提供的實現響應式數據的方法
  • 在 Vue2 中響應式數據是經過 defineProperty 來實現的,而在 Vue3 中響應式數據是經過 ES6 的 Proxy 來實現的
  1. reactive 注意點:
  • reactive 參數必須是對象(json/arr)
  • 若是給 rective 傳遞了其餘對象
    • 默認狀況下使用該對象的方法來修改對象,界面不會自動更新
    • 若是想更新,能夠經過從新賦值的方式

例如使用了 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}
    }
}
複製代碼

ref 理解

  1. 什麼是 ref?
  • ref 和 reactive 同樣,也是用來實現響應式數據的方法
  • 因爲 reactive 必須傳遞一個對象,因此致使在企業開發中,若是咱們只想讓某個變量實現響應式的時候會很是麻煩,因此 Vue3 就給咱們提供了 ref 方法,實現對簡單值的監聽
  1. ref 本質:
  • ref 在源碼中會對傳入的數據進行類型判斷,若是判斷爲對象數據類型會使用 reactive 去進行響應式分裝的;
  • 對於非對象類型 ref 底層的會 new 一個 RefImpl 對象,該對象會定義 get 和 set 方法去取值賦值監聽,這點相似於 Vue2 的 Object.definePropert
  1. ref 注意點:
  • 在 Vue template 中使用 ref 的值不用經過 value 獲取,由於 Vue 會在編譯 {{ age }} 的時候自動加上變成 {{ age.value }}
  • 在 JS 中使用 ref 的值必須經過 value 獲取,由於 ref 傳入的值會報存在 RefImpl 對象的 _value 和 value 中

ref 和 reactive 的區別

  1. ref 和 reactive 區別:
  • 若是在 template 裏使用的是 ref 類型的數據,那麼 Vue 會自動幫咱們添加 .value
  • 若是在 template 裏使用的是 reactive 類型的數據,那麼 Vue 不會自動幫咱們添加 .value
  1. Vue 是如何肯定是否須要添加 .value 的?
  • Vue 在解析數據以前,會自動判斷這個數據是不是 ref 數據,若是是就自動添加 .value, 若是不是就不自動添加 .value。
  • 那 Vue 是如何判斷當前的數據是否爲 ref 類型的?,Vue 是經過當前數據的 __v_ref 類判斷的,若是有這個私有的屬性,而且取值爲 true,那麼就表明是一個 ref 類型的數據
  1. ref 和 reactive 輸出
  • ref 是一個 RefImpl 對象,包含了__value 和 __v_isRef
  • reactive 是一個 Proxy 對象
  1. Vue 提供了 2 個方法來判斷 Ref 和 Reactive
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

image

變動後:api

image

image

上述代碼在執行 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>
複製代碼

讀者可自行拷貝代碼進行演示

shallowRef 本質

ref -> refImpl(若是傳入 object -> reactive)
ref(10) -> refImpl{value: 10}
shallowRef -> shallowReactive
shallowRef(10) -> refImpl(若是傳入 object -> shallowReactive)
複製代碼

左邊標識咱們在使用 API 的樣子

右邊爲 Vue3 實際使用的樣子

toRaw

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';
    }
}
複製代碼

markRaw

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 進行響應式數據的封裝將不在其做用了。

toRef

能夠用來爲源響應式對象上的 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 屬性傳下去
  }
}
複製代碼

toRefs

將響應式對象轉換爲普通對象,其中結果對象的每一個 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
    }
  }
}
複製代碼

customRef

建立一個自定義的 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')
    }
  }
}
複製代碼

ref 獲取元素

<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 變量。

readonly

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
複製代碼

shallowReadonly

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});
複製代碼
  • argu_1 控制這該變量不可變動,可是能夠變動裏面的屬性
  • argu_2 控制着每一層屬性都不可變動,可是 argu_2 能夠從新賦值
  • argu_3 控制着第一層屬性不可變動,深層的屬性能夠變動,好比 sex 是能夠變動的,argu_3 能夠從新賦值

emits 定義自定義事件

這個語法相似於 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 })
    }
  }
})
複製代碼

script scoped 支持全局規則或只針對插槽內容的規則

deep 選擇器

<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())僞元素。

scoped 狀況下定義全局樣式

<style scoped>
    /* one-off global rule */
    ::v-global(.foo) {}
    /* shorthand */
    :global(.foo) {}
</style>
複製代碼

當前,要添加全局CSS規則,咱們須要使用單獨的無做用域塊。咱們將引入一個新的:: v-global()(簡寫爲:: global())僞元素,以用於一次性全局規則。

實驗狀態的特性

script setup

<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 屬性

script vars

支持將組件狀態驅動的 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);
}
複製代碼

參考文獻

系列

相關文章
相關標籤/搜索