>[success] # 響應性API
~~~
1.目前'setup' 的返回對象參數可以直接渲染到模板上,問題是數據不是響應式,想讓數據變成響應式
需要使用'3.x' 提供的響應式API
~~~
>[info] ## Ref
~~~
1.接受一個內部值并返回一個響應式且可變的 ref 對象。ref 對象具有指向內部值的單個 property .value
來理解官方這句話'ref() 返回的是value reference (包裝對象)并且這個對象value屬性指向包裝的值讓其具備了響應'
2.注意第一條說的是'value' 屬性指向了包裝對象并且是value具備了響應
2.1.數據操作的是使用是'xxx.value'
2.2.但在模板中簡化了操作流程因此可以直接'xxx'不需要調用'value' 屬性
3.一般定義基本類型的響應事數據使用'ref' 常見的基本類型'String','Number','BigInt','Boolean','Symbol','Null'
'Undefined'
4.如果你綁定的是對象非上面提出的基本類型他將會調用'reactive ',此時'.value'返回的是一個'proxy' 對象,
關于這一點稍后會詳細解釋,現在簡單看一下如果"const per = ref({name:'z'})" 包裹的是對象相對的你的修改
你需要'per.value.name = "zz" '依然要通過'value' 并且此時'per.value' 是一個'prxoy'對象
5.注意下面案例中其實我們使用const定義參數也間接想表達'ref'包裹的基礎類型已經是一個對象(是一個ref對象)
~~~
>[danger] ##### 案例
~~~
<template>
<div >
<button @click="print">觸發事件</button>
{{str}}
<input v-model="str" />
</div>
</template>
<script>
import {ref,reactive} from 'vue'
export default {
name: 'testlison',
setup(prop, ctx) {
const str = ref('z')
const num = ref(1)
const boolean = ref(false)
const ull = ref(null)
const un = ref(undefined)
const print = ()=>{
str.value = "q"
num.value = 100
boolean.value = true
ull.value = {name:'zz'}
un.value = 1000
}
return { str,print}
},
~~~
>[danger] ##### 是所有情況下ref 在模板渲染時候都不用.value么
~~~
1.答案是否定的,只有當'ref'包裹的對象是直接從setup返回的可以省略'value',但是如果是嵌套的依然
需要你在模板中使用value屬性
~~~
~~~html
<template>
<div>
<span>{{ count }}</span>
<button @click="count ++">Increment count</button>
<!-- 因為count ref對象被二次包裹在nested中因省略value 響應改變是不生效 -->
<!-- <button @click="nested.count ++">Nested Increment count</button> -->
<!-- 正確寫法 -->
<button @click="nested.count.value ++">Nested Increment count</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count,
nested: {
count
}
}
}
}
</script>
~~~
>[info] ## reactive
~~~
1.'reactive' 返回對象的響應式副本,并且他響應轉換是'深層'的它影響所有嵌套 property,基于'Proxy'實現,
和 '2.x' 的 Vue.observable()等同
2.解釋上面官方的話
2.1.定義對象 時候要使用我并且會返回一個'proxy'對象這個對象具有響應式
2.2.'深層'就是這個對象內部嵌套多深所有嵌套的屬性都能影響到舉個例子
"const obj =reactive( {name:'ww',per:{age:1100}})" 從obj的name屬性到per甚至你的per.age屬性都能被我代理了
并且無論你是delete刪除還是說新增新的屬性都可以被響應到,'reactive'的返回的'proxy'對象可以被響應,如果你操
作原對象不享受這個待遇
2.3.內部基于 ES6 的 Proxy 實現,通過代理對象操作源對象內部數據都是響應式的
3.現在可以在進一步理解'ref' 如果傳值是對象內部會調用'reactive' 這一點了(注意說的是ref對對象會內部自動調用'reactive')
4.當你從組件中的data()返回一個對象時,它是由reactive()在內部做出反應的
~~~
>[danger] ##### 案例
~~~
<template>
<div >
<button @click="print">觸發事件</button>
{{proxyObj.count}}
{{proxyObj.name}}
</div>
</template>
<script>
import {ref,reactive} from 'vue'
export default {
name: 'testlison',
setup(prop, ctx) {
// 要注意了你直接操作obj 是沒有響應效果
const obj = {count:0}
// 你需操作我我是響應的
const proxyObj = reactive(obj)
const print = ()=>{
proxyObj.count ++
// 感動了吧以前你還需要$set 現在不用了
proxyObj.name = "ww"
}
return { proxyObj,print}
},
}
</script>
<style scoped lang="less"></style>
~~~
>[danger] reactive 包裹一個對象包含ref的對象 -- Ref Unwrapping(包裝對象的自動展開)
~~~
1.當一個ref作為一個響應式對象的屬性被訪問或改變時,它會自動打開內部值,這樣它的行為就像一個普通的屬性
簡單的說'當一個包裝對象被作為另一個響應式對象的(屬性)引用的時候也會被自動展開'
2.理解官方這句話就是 現在我定義了一個ref對象,'const count =ref(0)' =>我將ref對象做一個對象的屬性
'const obj = {count}'=>此時我使用是'obj.count.value' 但是如果我用reactive 包裹著含有ref屬性的對象即
'const state = reactive(obj)' 他會自動將這個value給打開使用效果變成'state.count'不用再加value屬性了
在進一步抽象理解當還有ref屬性的對象被reactive包裹實際上效果是'const state = reactive(count:count.value)'
const count = ref(0)
const obj = reactive({
count
})
console.log(obj.count) // 0
obj.count++
console.log(obj.count) // 1
console.log(count.value) // 1
count.value++
console.log(obj.count) // 2
console.log(count.value) // 2
~~~
~~~
<template>
<div>
<!-- count 和 rcount.count 同時都改變了 -->
<span>{{ count }}--{{rcount.count}}</span>
<button @click="print">Increment count</button>
</div>
</template>
<script>
import { ref ,reactive} from 'vue'
export default {
setup() {
const count = ref(0)
// 這里是{count}用一個對象吧包裹了ref
// 注意不是直接一個reactive 包裹count 錯誤寫法reactiv(count)
const rcount = reactive({count})
const print=() =>{
rcount.count ++
}
return {
rcount,
count,
print
}
}
}
</script>
~~~
>[danger] ##### 非對象中的屬性包含ref對象并且用reactive包裹
~~~
1.'當一個包裝對象被作為另一個響應式對象的(屬性)引用的時候也會被自動展開' 但是當從數組或本地集合類型(如
Map)訪問ref時,不會執行展開操作
~~~
~~~
const books = reactive([ref('Vue 3 Guide')])
// need .value here 這里依舊需要value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// need .value here 這里依舊需要value
console.log(map.get('count').value)
~~~
>[danger] ##### 上面 reactive 包裹 ref 情況過于復雜我該怎么做
~~~
1.以上這些關于包裝對象的細節可能會讓你覺得有些復雜,但實際使用中你只需要記住一個基本的規則:
只有當你直接以變量的形式引用一個包裝對象的時候才會需要用 .value 去取它內部的值 —— 在模版中你甚至
不需要知道它們的存在。
~~~
>[info] ## 現在對比reactive與ref-細節
~~~
1.ref用來處理基本類型數據, reactive用來處理對象(遞歸深度響應式)
2.如果用ref對象/數組, 內部會自動將對象/數組轉換為reactive的代理對象
3.ref內部: 通過給value屬性添加getter/setter來實現對數據的劫持
4.reactive內部: 通過使用Proxy來實現對對象內部所有數據的劫持, 并通過Reflect操作對象內部數據
5.ref的數據操作: 在js中要.value, 在模板中不需要(內部解析模板時會自動添加.value)
~~~
>[info] ## ref 和 reactive 設計出現
~~~
1.我們知道在 JavaScript 中,原始值類型如 string 和 number 是只有值,沒有引用的。如果在一個函數中返
回一個字符串變量,接收到這個字符串的代碼只會獲得一個值,是無法追蹤原始變量后續的變化的
~~~
>[danger] ##### 我應該什么時候去使用這兩個Api
~~~
1.最簡單就是基本類型用'ref' ,引用類型用'reactive' ,并且這也是依賴于你的編程習慣,在定義變量的時候
是屬于喜歡單獨每一項定義還是使用用對象定義
~~~
* 舉個例子單獨每一項定義
~~~
import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
onMounted(() => {
window.addEventListener("mousemove", update);
});
onUnmounted(() => {
window.removeEventListener("mousemove", update);
});
return { x, y };
}
~~~
* 對象定義
~~~
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0
});
// ...
return toRefs(pos);
}
~~~
[參考文章鏈接](https://www.danvega.dev/blog/2020/02/12/vue3-ref-vs-reactive/)
- Vue--基礎篇章
- Vue -- 介紹
- Vue -- MVVM
- Vue -- 創建Vue實例
- Vue -- 模板語法
- Vue -- 指令用法
- v-cloak -- 遮蓋
- v-bind -- 標簽屬性動態綁定
- v-on -- 綁定事件
- v-model -- 雙向數據綁定
- v-for -- 只是循環沒那么簡單
- 小知識點 -- 計劃內屬性
- key -- 屬性為什么要加
- 案例說明
- v-if/v-show -- 顯示隱藏
- v-for 和 v-if 同時使用
- v-pre -- 不渲染大大胡語法
- v-once -- 只渲染一次
- Vue -- class和style綁定
- Vue -- filter 過濾器
- Vue--watch/computed/fun
- watch -- 巧妙利用watch思想
- Vue -- 自定義指令
- Vue -- $方法
- Vue--生命周期
- Vue -- 專屬ajax
- Vue -- transition過渡動畫
- 前面章節的案例
- 案例 -- 跑馬燈效果
- 案例 -- 選項卡內容切換
- 案例-- 篩選商品
- 案例 -- 搜索/刪除/更改
- 案例 -- 用computed做多選
- 案例 -- checked 多選
- Vue--組件篇章
- component -- 介紹
- component -- 使用全局組件
- component -- 使用局部組件
- component -- 組件深入
- component -- 組件傳值父傳子
- component -- 組件傳值子傳父
- component -- 子傳父語法糖拆解
- component -- 父組件操作子組件
- component -- is 動態切換組件
- component -- 用v-if/v-show控制子組件
- component -- 組件切換的動畫效果
- component -- slot 插槽
- component -- 插槽2.6
- component -- 組件的生命周期
- component -- 基礎組件全局注冊
- VueRouter--獲取路由參數
- VueRouter -- 介紹路由
- VueRouter -- 安裝
- VueRouter -- 使用
- VueRouter--router-link簡單參數
- VueRouter--router-link樣式問題
- VueRouter--router-view動畫效果
- VueRouter -- 匹配優先級
- vueRouter -- 動態路由
- VueRouter -- 命名路由
- VueRouter -- 命名視圖
- VueRouter--$router 獲取函數
- VueRouter--$route獲取參數
- VueRouter--路由嵌套
- VueRouter -- 導航守衛
- VueRouter -- 寫在最后
- Vue--模塊化方式結構
- webpack--自定義配置
- webpack -- 自定義Vue操作
- VueCli -- 3.0可視化配置
- VueCli -- 3.0 項目目錄
- Vue -- 組件升級篇
- Vue -- 組件種類與組件組成
- Vue -- 組件prop、event、slot 技巧
- Vue -- 組件通信(一)
- Vue -- 組件通信(二)
- Vue -- 組件通信(三)
- Vue -- 組件通信(四)
- Vue -- 組件通信(五)
- Vue -- 組件通信(六)
- Vue -- bus非父子組件通信
- Vue -- 封裝js插件成vue組件
- vue組件分裝 -- 進階篇
- Vue -- 組件封裝splitpane(分割面板)
- UI -- 正式封裝
- Vue -- iview 可編輯表格案例
- Ui -- iview 可以同時編輯多行
- Vue -- 了解遞歸組件
- UI -- 正式使用遞歸菜單
- Vue -- iview Tree組件
- Vue -- 利用通信仿寫一個form驗證
- Vue -- 使用自己的Form
- Vue -- Checkbox 組件
- Vue -- CheckboxGroup.vue
- Vue -- Alert 組件
- Vue -- 手動掛載組件
- Vue -- Alert開始封裝
- Vue -- 動態表單組件
- Vue -- Vuex組件的狀態管理
- Vuex -- 參數使用理解
- Vuex -- state擴展
- Vuex -- getters擴展
- Vuex--mutations擴展
- Vuex -- Action 異步
- Vuex -- plugins插件
- Vuex -- v-model寫法
- Vuex -- 更多
- VueCli -- 技巧總結篇
- CLI -- 路由基礎
- CLI -- 路由升級篇
- CLI --異步axios
- axios -- 封裝axios
- CLI -- 登錄寫法
- CLI -- 權限
- CLI -- 簡單權限
- CLI -- 動態路由加載
- CLI -- 數據性能優化
- ES6 -- 類的概念
- ES6類 -- 基礎
- ES6 -- 繼承
- ES6 -- 工作實戰用類數據管理
- JS -- 適配器模式
- ES7 -- 裝飾器(Decorator)
- 裝飾器 -- 裝飾器修飾類
- 裝飾器--修飾類方法(知識擴展)
- 裝飾器 -- 裝飾器修飾類中的方法
- 裝飾器 -- 執行順序
- Reflect -- es6 自帶版本
- Reflect -- reflect-metadata 版本
- 實戰 -- 驗證篇章(基礎)
- 驗證篇章 -- 搭建和目錄
- 驗證篇章 -- 創建基本模板
- 驗證篇章 -- 使用
- 實戰 -- 更新模型(為了迎合ui升級)
- 實戰 -- 模型與接口對接
- TypeSprict -- 基礎篇章
- TS-- 搭建(一)webpack版本
- TS -- 搭建(二)直接使用
- TS -- 基礎類型
- TS -- 枚舉類型
- TS -- Symbol
- TS -- interface 接口
- TS -- 函數
- TS -- 泛型
- TS -- 類
- TS -- 類型推論和兼容
- TS -- 高級類型(一)
- TS -- 高級類型(二)
- TS -- 關于模塊解析
- TS -- 聲明合并
- TS -- 混入
- Vue -- TS項目模擬
- TS -- vue和以前代碼對比
- TS -- vue簡單案例上手
- Vue -- 簡單弄懂VueRouter過程
- VueRouter -- 實現簡單Router
- Vue-- 原理2.x源碼簡單理解
- 了解 -- 簡單的響應式工作原理
- 準備工作 -- 了解發布訂閱和觀察者模式
- 了解 -- 響應式工作原理(一)
- 了解 -- 響應式工作原理(二)
- 手寫 -- 簡單的vue數據響應(一)
- 手寫 -- 簡單的vue數據響應(二)
- 模板引擎可以做的
- 了解 -- 虛擬DOM
- 虛擬dom -- 使用Snabbdom
- 閱讀 -- Snabbdom
- 分析snabbdom源碼 -- h函數
- 分析snabbdom -- init 方法
- init 方法 -- patch方法分析(一)
- init 方法 -- patch方法分析(二)
- init方法 -- patch方法分析(三)
- 手寫 -- 簡單的虛擬dom渲染
- 函數表達解析 - h 和 create-element
- dom操作 -- patch.js
- Vue -- 完成一個minVue
- minVue -- 打包入口
- Vue -- new實例做了什么
- Vue -- $mount 模板編譯階段
- 模板編譯 -- 分析入口
- 模板編譯 -- 分析模板轉譯
- Vue -- mountComponent 掛載階段
- 掛載階段 -- vm._render()
- 掛載階段 -- vnode
- 備份章節
- Vue -- Nuxt.js
- Vue3 -- 學習
- Vue3.x --基本功能快速預覽
- Vue3.x -- createApp
- Vue3.x -- 生命周期
- Vue3.x -- 組件
- vue3.x -- 異步組件???
- vue3.x -- Teleport???
- vue3.x -- 動畫章節 ??
- vue3.x -- 自定義指令 ???
- 深入響應性原理 ???
- vue3.x -- Option API VS Composition API
- Vue3.x -- 使用set up
- Vue3.x -- 響應性API
- 其他 Api 使用
- 計算屬性和監聽屬性
- 生命周期
- 小的案例(一)
- 小的案例(二)-- 泛型
- Vue2.x => Vue3.x 導讀
- v-for 中的 Ref 數組 -- 非兼容
- 異步組件
- attribute 強制行為 -- 非兼容
- $attrs 包括 class & style -- 非兼容
- $children -- 移除
- 自定義指令 -- 非兼容
- 自定義元素交互 -- 非兼容
- Data選項 -- 非兼容
- emits Option -- 新增
- 事件 API -- 非兼容
- 過濾器 -- 移除
- 片段 -- 新增
- 函數式組件 -- 非兼容
- 全局 API -- 非兼容
- 全局 API Treeshaking -- 非兼容
- 內聯模板 Attribute -- 非兼容
- key attribute -- 非兼容
- 按鍵修飾符 -- 非兼容
- 移除 $listeners 和 v-on.native -- 非兼容
- 在 prop 的默認函數中訪問 this -- ??
- 組件使用 v-model -- 非兼容
- 渲染函數 API -- ??
- Slot 統一 ??
- 過渡的 class 名更改 ???
- Transition Group 根元素 -- ??
- v-if 與 v-for 的優先級對比 -- 非兼容
- v-bind 合并行為 非兼容
- 監聽數組 -- 非兼容