爲了可以使用Composition API, 咱們須要有一個能夠實際使用它的地方。在vue組件中,咱們將此位置稱爲setuphtml
setup函數是在組件建立以前執行的,setup函數中的props一旦被解析,就將做爲Composition API 的入口vue
須要注意的是: 咱們不能在setup函數中使用this, 由於它不會找到組件實例。 緣由是 雖然組件實例是在執行setup以前就會被建立出來了,可是 setup函數被調用以前,data,computed,methods等都沒有被解析,因此它們沒法在setup中被獲取。node
做爲setup函數中的第一個參數,props是響應式的,當傳入新的prop時,它將會被更新react
其實就是父組件傳遞過來的屬性會被放到props對象中web
export default {
props:{
message:{
type:String,
default:"hello"
}
},
setup(props){
consloe.log(props.message)
}
}
複製代碼
!! 因爲 props
是響應式的, 因此咱們不能用 解構,它會消除props的響應性。api
若是須要結構props,那麼須要用到 toRefs
函數來完成操做。數組
import {toRefs} from 'vue'
setup(props){
const message = toRefs(props,'message')
console.log(message.value)
}
複製代碼
做爲setup函數的第二個參數, context是一個普通的JavaScript對象,它暴露組件的三個property安全
!!因爲context只是一個普通的JavaScript對象,因此它不是響應式的,這就意味着咱們能夠安全地對 context
使用 解構markdown
export default{
setup(props,{attrs,slots,emit}){
...
}
}
複製代碼
因爲setup是一個函數,那麼它就會有返回值網絡
若是setup的返回值是一個對象,那麼該對象的property就能夠在模板中訪問到。
注意的是:從setup中返回的 refs
在模板中是會自動淺解包的,因此沒必要在模板中用 .value
返回對象的響應式副本
若是想在setup中定義的數據是具備響應式的,那麼就可使用 reactive
const state = reactive({ name:'wangpf',age:18 })
複製代碼
爲何會經過 reactive
就能夠變成響應式的呢?
reactive
函數將其變成了響應式對象的注意的倆點:
reactive
將解包全部深層的 refs,同時維持着 ref 的響應性
const count = ref(0)
const obj = reactive({ count })
// ref 會被解包
console.log(count.value === obj.value) // true
// 會更新到 'obj.count'
count.value++
console.log(count.value) // 1
console.log(obj.count) // 1
// 也會更新到 ref 'count'
obj.count++
console.log(count.value) // 2
console.log(obj,value) // 2
複製代碼
當把 ref
分配給 reactive
時,將會自動解包
const count = ref(1)
const obj = reactive({ })
obj.count = count
console.log(obj.count) // 1
console.log(obj.count === count.value) // true
複製代碼
可是因爲 reactive
對傳入的類型是有限制的,它要求咱們必須傳入的是一個對象或者數組
若是咱們傳入的是一個基本的數據類型(String,Number,Boolean)則會報警告.
這時,咱們可使用另外一個API, ref
接受一個內部值並返回一個響應式且可變的 ref 對象。 ref對象具備指向內部值的單個property (.vlaue)
ref 會返回一個可變的響應式對象,該對象做爲一個響應式的引用(reference) 維護着它內部的值。
內部的值是在 ref的 vlaue屬性中被維護的
咱們經過
reactive
或者ref
能夠獲取到一個響應式的對象,可是某些狀況下, 咱們傳入給其餘地方(組件) 的這個響應式對象但願在另一個地方(組件)被使用,可是不能被修改,這個時候就能夠用 readonly 了
readnoly 會返回原生對象的只讀代理 原理在於: 利用 proxy 中的set,在set方法中將其劫持,而且設置該值不能修改
檢查對象是否由
reactive
或readonly
建立的 proxy
const state = reactive({ count : 0 })
const count = 0
isProxy(state) // true
isProxy(readonly(state)) // true
isProxy(count) // false
複製代碼
檢查對象是否由
reactive
建立處理的相應手機代理
但若是代理是由 readonly
建立的,但包裹了由 reactive
建立 另外一個代理,也會返回 true
const state = reactive({ count : 0 })
isReactive(state) // true
isReactive(readonly(state)) // true
複製代碼
檢查對象是否由
readonly
建立的只讀代理
返回
reactive 或 readonly 代理的原始對象
(不建議保留對原始對象的持久化引用,謹慎使用)
shallow(淺層)
建立一個響應式代理,他跟蹤其自身property的響應性,但不執行嵌套對象的深層響應式轉換 (深層仍是原生對象)
相似於淺拷貝,只把第一層轉爲響應式了,深層仍是原始對象
建立一個 proxy , 使其自身的 property 爲只讀, 但不執行嵌套對象的深度只讀轉換
就是說第一層是隻讀的,可是深層仍是可讀,可寫的
因爲咱們使用ES6的解構語法對 reactive 返回的對象進行解構賦值,那麼解構後的數據是不具備響應式的
而使用 toRefs 能夠將 reative 返回的對象中的屬性都轉成 ref 這樣咱們再次解構出來的數據都是 ref的。
const state = reactive({ name:'wangpf' , age:18 });
const { name, age } = state; // 這樣解構出來的數據是沒有響應式的
const { name, age } = toRefs(state) // 這樣的解構出來的數據轉換成了ref的,是響應式的
// 上述的作法, 至關於在 state.name 和 name.value 之間創建了鏈接, 修改任何一個都會引發另一個變化
複製代碼
若是咱們但願轉換一個 reactive 對象中的屬性爲 ref ,那麼可使用 toRef 的方法
const state = reactive({ name:'wangpf' , age:18 });
const name = toRef(state,"name"); // 該 name 是ref的,
// 一樣的, name.value 和 state.name 之間創建了鏈接, 修改會互相影響
複製代碼
若是想要獲取一個ref引用中的value,能夠經過 unref 方法:
若是參數是一個 ref, 則返回內部值,不然返回參數本事
實際上是一個語法糖:
val = isRef(val) ? val.value : val
複製代碼
function useFoo(x: number | Ref<number>) {
const unwrapped = unref(x) // unwrapped 如今必定是數字類型
}
複製代碼
判斷值是不是一個ref對象
建立一個淺層的ref對象
const info = shallowRef({ name: "wangpf" })
const changeInfo = () => {
info.value.name = 'wpf' // 此時的修改是不可以實現響應式的
}
複製代碼
手動觸發和 shallowRef 相關聯的反作用
const info = shallowRef({ name: "wangpf" })
const changeInfo = () => {
info.value.name = 'wpf' // 此時的修改是不可以實現響應式的
// 使用 triggerRef 能夠觸發
triggerRef(info)
}
複製代碼
建立一個自定義的 ref ,並對其依賴項跟蹤和更新觸發進行顯示控制
import { customRef } from "vue";
export default function (value, delay = 300) {
let timer = null;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
trigger();
}, delay);
},
};
});
}
複製代碼
<template>
<input type="text" v-model="message" />
<p>{{ message }}</p>
</template>
<script>
import useDebounceRef from "../hooks/useDebounceRef";
export default {
name: "Demo2",
setup() {
const message = useDebounceRef("hello", 300);
return {
message,
};
},
};
</script>
複製代碼
該API 的方法 和 vue2 的同樣, 只不過寫的地方是在setup函數中了, 但還需注意的點是 computed 返回的值是一個 ref
const firstName = ref("wangpf");
const lastName = ref("ok");
const fullName = computed(()=> `${firstName.value} ${lastName.value}`)
複製代碼
const firstName = ref("wangpf");
const lastName = ref("ok");
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(newValue) {
const names = newValue.split(" ");
firstName.value = names[0];
lastName.value = names[1];
},
});
const changeName = () => {
fullName.value = "wpf err";
console.log(fullName.value);
};
複製代碼
在 Composition API 中, 咱們可使用 watchEffect 和 watch 來完成響應式數據的偵聽
當偵聽到某些響應式數據變化時,咱們但願執行某些操做,這個時候就能夠用 watchEffect
來看一個案例:
const name = ref('wangpf')
const age = ref(18)
watchEffect(() => {
console.log('watchEffect執行了',name.value,age.value)
})
複製代碼
看一個案例:
const stopWatch = watchEffect(() => {
console.log('watchEffect執行了',name.value,age.value)
})
const changeAge = () => {
age.value++;
if(age.value > 20){
stopWatch() // 掉用 watchEffect 的返回值就能夠中止偵聽了
}
}
複製代碼
用途: 好比在開發中咱們須要在偵聽函數中執行網絡請求,可是在網絡請求尚未達到的時候,咱們中止了偵聽器,或者偵聽器偵聽函數被再次執行了,這時咱們須要把上一次的網絡請求 取消掉, 就能夠用到該方法了
看了一個案例:
watchEffect((onInvalidate) => {
console.log('watchEffect執行了',name.value,age.value)
const timer = setTimeout(() => {
console.log('1s後執行的操做')
},1000)
onInvalidate(() => {
// 在這裏操做一些清除工做
clearTimeour(timer)
})
})
複製代碼
在上述代碼中,咱們給 watchEffect 傳入的函數被回調時,能夠獲取到一個參數:onInvalidate (該參數是一個函數),能夠在這個參數中,執行一些清除工做。
在默認狀況下 , 組件的更新會在 watchEffect (反作用函數) 以前執行
const title = ref(null); // 該title 已和 div 標籤綁定了
watchEffect(() => {
console.log(title.value);
});
return { title };
複製代碼
那麼,當咱們在 watchEffect (反作用函數) 獲取元素時, 第一次執行是確定是個null, 是不能夠的。只有當DOM掛載完畢後,纔會給 title 賦新的值,watchEffect (反作用函數)纔會再次執行, 打印出對應的元素
咱們但願第一次就打印出該元素的話,這時候須要給 watchEffect 傳入第二個參數, 該參數是一個 對象,對象中的 flush 可取三個值 : 'pre' (默認的) , 'post' , 'async' (不建議使用)
// 在組件更新後觸發,這樣你就能夠訪問更新的 DOM。
// 注意:這也將推遲反作用的初始運行,直到組件的首次渲染完成。
watchEffect(
() => {
/* ... */
},
{
flush: 'post' // 在 DOM元素掛載或更新以後執行, "pre" 當即執行 (默認的)
}
)
複製代碼
watch API 的功能和 vue 2 的 option API 中的 watch 功能同樣, 默認狀況下, watch 只有當被偵聽的源發送改變時纔會去回調
與 watchEffect 比較,不一樣的是:
想要偵聽單個數據源的話, 有倆種方法: 傳入 getter 函數 或 ref 對象
// 偵聽 getter
const state = reactive({ name : 'wangpf' })
watch(() => state.name , (newVal,oldVal) => {
/* ... */
})
// 偵聽 ref
const name = ref('wamgpf')
watch(name,(newVal,oldVal) => {
/* ... */
// 這裏的 newVal,oldVal 的值是 是返回的 ref.value 的值
})
複製代碼
方法: 傳入數組
const firstName = ref("AAA");
const lastName = ref("bbb");
const changeName = () => {
firstName.value = "A";
lastName.value = "b";
};
watch([firstName, lastName], (newVal, oldVal) => {
console.log("newVal:", newVal, "oldVal:", oldVal);
});
// newVal: ["A", "b"] oldVal: ["AAA", "bbb"]
複製代碼
就是偵聽 reactive 對象
const numbers = reactive([1, 2, 3, 4])
watch(
() => [...numbers],
(numbers, prevNumbers) => {
console.log(numbers, prevNumbers)
}
)
numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
複製代碼
想要深度偵聽嵌套對象或數組時,須要 deep 設爲 true,
const state = reactive({
id: 1,
attributes: {
name: '',
}
})
watch(
() => state,
(state, prevState) => {
console.log(
'not deep',
state.attributes.name,
prevState.attributes.name
)
}
)
watch(
() => state,
(state, prevState) => {
console.log(
'deep',
state.attributes.name,
prevState.attributes.name
)
},
{ deep: true }
)
state.attributes.name = 'Alex' // 日誌: "deep" "Alex" "Alex"
複製代碼
可是會發現 新的值和舊的值是同樣的。這時候爲了徹底偵聽,須要使用深拷貝了
em.... 去看文檔吧,
功能和以前同樣,
咱們能夠經過 provide 來提供數據
咱們能夠經過 jnject 來注入須要的內容
let count = ref(100)
let info = { name : "wangpf" , age : 18 }
provide("count",readonly(count))
provide("info",readonly(info)) // 這裏建議使用 readonly 對值進行包裹,防止傳遞的數據不會被 inject 的組件更改
// 在後代組件中 經過 inject 來獲取
const count = inject("count")
const info = inject("info")
複製代碼
vue在生成真實DOM以前,會將咱們的節點轉換成VNode,而VNode組合起來會造成一顆樹結構,即 虛擬DOM
在 template 中的 html 是使用渲染函數生成的對應的VNode
若是咱們想要利用 JavaScript 來編寫 createVNode 函數,生成對應的VNode 那麼就可使用 h() 函數
h() 函數是一個用於常見 VNode 的一個函數, 其實更準確的命名是 createVNode() 函數,但爲了簡便 vue 將其簡化爲 h() 函數
h() 函數接收三個參數: (標籤,屬性,後代)
h(
// {String | Object | Function} tag
// 一個 HTML 標籤名、一個組件、一個異步組件、或
// 一個函數式組件。
//
// 必需的。
'div',
// {Object} props
// 與 attribute、prop 和事件相對應的對象。
// 咱們會在模板中使用。
//
// 可選的。
{},
// {String | Array | Object} children
// 子 VNodes, 使用 `h()` 構建,
// 或使用字符串獲取 "文本 Vnode" 或者
// 有插槽的對象。
//
// 可選的。
[
'Some text comes first.',
h('h1', 'A headline'),
h(MyComponent, {
someProp: 'foobar'
})
]
)
複製代碼
注意:若是沒有props,能夠將 children 做爲第二個參數傳入, 可是會產生歧義,因此通常會將 null 做爲第二個參數傳入,將 children 做爲第三個參數傳入
export default {
render(){
return h('div', { class:'app' }, 'hello app')
}
}
export default {
setup(){
return () => h('div', { class:'app' }, 'hello app')
}
}
複製代碼
這樣寫代碼,不只慢並且閱讀性通常, 因此 推薦使用 jsx, 語法和 react同樣, 這裏不細說了。可看文檔
在 Vue 中,代碼複用和抽象的主要形式是組件。然而,有的狀況下,你仍然須要對普通 DOM 元素進行底層操做,這時候就會用到自定義指令。
自定義指令分爲倆種:
一個指令定義的對象,Vue提供了以下的幾個鉤子函數:
這幾個鉤子函數中可傳入四個參數:el
、binding
、vnode
和 prevNnode
瞬移組件, 能夠將該組件轉移到其餘dom元素上。
一般用於 封裝模態框、土司之類的,將它放在Body元素上和 div#app 元素平級
<teleport :to='#demo'>
<h2>hello</h2>
</teleport>
// 該元素就會被轉移到 id 爲 demo 元素上
複製代碼
一般狀況下,咱們向Vue全局去添加一些功能時,會採用插件的模式
install
的函數,該函數會在安裝插件時執行// plugin_obect.js
export default {
install(app) {
app.config.globalProperties.$name = "wangpf";
},
};
// main.js
import plugin_object from "./plugins/plugin_object";
app.use(plugin_object);
// app.vue
import { getCurrentInstance } from "vue";
setup() {
const Instance = getCurrentInstance();
console.log("Instance", Instance.appContext.config.globalProperties.$name);
}
複製代碼