资源地址
初始化项目
- 安装 vue-cli3 并 创建项目 Vue3
//npx执行命令下,有未安装的会先安装后再执行命令
npx @vue/cli create vue3
- 项目结构
新特性使用
之前了解过Vue3的文档都知道,要使用新特性,都需要安装依赖:composition-api
composition-api 文档地址:
https://github.com/vuejs/composition-api/blob/master/README.zh-CN.md
现在(20201023)的官方文档表明:
当迁移到 Vue 3 时,只需简单的将 @vue/composition-api 替换成 vue 即可。你现有的代码几乎无需进行额外的改动。
- 查看差异点击传送门:
- setup
- 该函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的新特性提供了统一的入口。
export default { // props 为当前单页面的 props // context 为替代 this 的,里面包含: // context.attrs // context.slots // context.parent // context.root // context.emit // context.refs setup(props, context){ //....代码 //在 setup() 函数中无法访问到 this }, }
- 函数的执行时机(顺序)
会在 beforeCreate 之后、created 之前执行
- reactive
- 什么是 reactive? -reactive是Vue3中提供的实现响应式数据的方法
-在Vue2中响应式数据是通过defineProperty来实现的
而在Vue3屮响应式数据是通过ES6的Proxy来实现的
- reactive 注意点:
-reactive参数必须是对象(json/arr〕
-如果给 reactive传递了其它对象
+默认情况下修改对象,界面不会自动更新
+如果想更新,可以通过重新赋值的方式
- 函数接收一个普通对象,返回一个响应式的数据对象。
- 等价于 vue 2.x 中的 Vue.observable() 函数,vue 3.x 中提供了 reactive() 函数,用来创建响应式的数据对象,基本代码示例如下:
-
import { reactive } from 'vue' export default { setup(){ // 创建响应式数据对象 const state = reactive({count: 0}) // setup 函数中将响应式数据对象 return 出去,供 template 使用 return state }, }
<p>当前的 count 值为:{{count}}</p>
- ref
- 什么是ref?
-ref和reactive—样,也是用来实现响应式数据的方法
-由于reactive必须传递一个对象,所以导致在企业开发中
如果我们只想让某个变垦实现响应式的时候会非常麻烦
所以Vue3就给我们提供fref方法,实现对简单值的监听
- ref本质:
-ref 底层的本质其实还是reactive
系统会自动根据我们给ref传入的值将它转换成
ref(xx) -> reactive ({value :xx})
- ref注意点:
-在Vue中使用ref的位不用通过vaLue获取
-在JS中使用ref的值必须通过value获取
- 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 .value 属性:
-
import { ref } from 'vue' export default { setup(){ // 创建响应式数据对象 count,初始值为 0 const count = ref(0) // 如果要访问 ref() 创建出来的响应式数据对象的值,必须通过 .value 属性才可以 console.log(count.value) // 输出 0 // 让 count 的值 +1 count.value++ // 再次打印 count 的值 console.log(count.value) // 输出 1 return { count } }, }
<p>当前的 count 值为:{{count}}</p>
- ref 和reactive区别
如果在template里使用的是ref类型的数据,那么Vue会自动帮我们添加.value
如果在template里使用的是reactive类型的数据,那么Vue不会自动帮我们添加.value
Vue是如何决定是否需要自动添加.value的
Vue在解析数据之前,会自动判断这个数据是否是ref类型的,
如果是就自动添加.value,如果不是就不自动添加.value
Vue是如何判断当前的数据是否是ref类期的
通过当前数据的 _ _v_ref 来判断的
如果有这个私有的属性,并且取值为true,那么就代表是一个ref类型的数据
- 在 reactive 对象中访问 ref 创建的响应式数据
- 当把 ref() 创建出来的响应式数据对象,挂载到 reactive() 上时,会自动把响应式数据对象展开为原始的值,不需通过 .value 就可以直接被访问,例如:
-
const count = ref(0) const state = reactive({ count }) console.log(state.count) // 输出 0 state.count++ // 此处不需要通过 .value 就能直接访问原始值 console.log(count) // 输出 1
- 新的 ref 会覆盖旧的 ref,示例代码如下:
-
// 创建 ref 并挂载到 reactive 中 const c1 = ref(0) const state = reactive({ c1 }) // 再次创建 ref,命名为 c2 const c2 = ref(9) // 将 旧 ref c1 替换为 新 ref c2 state.c1 = c2 state.c1++ console.log(state.c1) // 输出 10 console.log(c2.value) // 输出 10 console.log(c1.value) // 输出 0
- isRef
- 用来判断某个值是否为 ref() 创建出来的对象;应用场景:当需要展开某个可能为 ref() 创建出来的值的时候,例如:
-
import { isRef } from 'vue' const unwrapped = isRef(foo) ? foo.value : foo
- toRefs
- 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据,最常见的应用场景如下:
-
import { toRefs } from 'vue' setup() { // 定义响应式数据对象 const state = reactive({ count: 0 }) // 定义页面上可用的事件处理函数 const increment = () => { state.count++ } // 在 setup 中返回一个对象供页面使用 // 这个对象中可以包含响应式的数据,也可以包含事件处理函数 return { // 将 state 上的每个属性,都转化为 ref 形式的响应式数据 ...toRefs(state), // 自增的事件处理函数 increment } }
- 页面上可以直接访问 setup() 中 return 出来的响应式数据:
-
<template> <div> <p>当前的count值为:{{count}}</p> <button @click="increment">+1</button> </div> </template>
- toRaw
- 这是一个获取代理对象的根数据方法,在以下使用中,修改根数据后,不影响代理对象 msg 的数据变动,也就不会导致视图重新渲染,所以可作为是一个用来优化资源加载的方案。
-
//reactive 的 toRaw 用法 let obj = {content: 'content'} let msg = reactive(obj); let raw = toRaw(msg); obj.content = 'contentEdit' //修改源数据 //msg.content = 'contentMsgEdit' //修改代理数据 console.log(raw ); //-> {content: 'contentEdit'} console.log(msg ); //-> {content: 'content'}
//ref 的 toRaw 用法 let obj = {content: 'content'} let msg = ref(obj); let raw = toRaw(msg); obj.content = 'contentEdit' //修改源数据 //msg.value.content = 'contentMsgEdit' //修改代理数据 console.log(raw ); //-> {content: 'contentEdit'} console.log(msg ); //-> {content: 'content'}
- markRaw
- 显式标记一个 对象 (不能是简单类型) 为“永远不会转为响应式代理”,函数返回这个对象本身。
-
const foo = markRaw({a:'1'}) console.log(isReactive(reactive(foo))) // false // 如果被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的 const bar = reactive({ foo }) console.log(isReactive(bar.foo)) // false foo.a = 2 console.log(foo) // -> {a:'2'} 1 //为视图显示的原值,因无法代理,所以跟踪不到value被变化而更新视图
- computed
- 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例。使用 computed 之前需要按需导入:
-
import { computed } from 'vue'
- 在调用 computed() 函数期间,传入一个 function 函数,可以得到一个只读的计算属性,示例代码如下:
-
// 创建一个 ref 响应式数据 const count = ref(1) // 根据 count 的值,创建一个响应式的计算属性 plusOne // 它会根据依赖的 ref 自动计算并返回一个新的 ref const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 输出 2 plusOne.value++ // error
- 在调用 computed() 函数期间,传入一个包含 get 和 set 函数的对象,可以得到一个可读可写的计算属性,示例代码如下:
-
// 创建一个 ref 响应式数据 const count = ref(1) // 创建一个 computed 计算属性 const plusOne = computed({ // 取值函数 get: () => count.value + 1, // 赋值函数 set: val => { count.value = val - 1 } }) // 为计算属性赋值的操作,会触发 set 函数 plusOne.value = 9 // 触发 set 函数后,count 的值会被更新 console.log(count.value) // 输出 8
- watch
- 函数用来监视某些数据项的变化,从而触发某些特定的操作,使用之前需要按需导入:
import { watch } from 'vue'
- 基本用法
-
const count = ref(0) // 定义 watch,只要 count 值变化,就会触发 watch 回调 // watch 会在创建时会自动调用一次 watch(() => console.log(count.value)) // 输出 0 setTimeout(() => { count.value++ // 输出 1 }, 1000)
- 监视 reactive 类型的数据源:
-
// 定义数据源 const state = reactive({ count: 0 }) // 监视 state.count 这个数据节点的变化 watch( () => state.count, (count, prevCount) => { /* ... */ } ) //-------------------------------- //监视多个 const state = reactive({ count: 0, name: 'zs' }) watch( [() => state.count, () => state.name], // Object.values(toRefs(state)), ([count, name], [prevCount, prevName]) => { console.log(count) // 新的 count 值 console.log(name) // 新的 name 值 console.log('------------') console.log(prevCount) // 旧的 count 值 console.log(prevName) // 新的 name 值 }, { lazy: true // 在 watch 被创建的时候,不执行回调函数中的代码 } ) setTimeout(() => { state.count++ state.name = 'ls' }, 1000)
- 监视 ref 类型的数据源:
-
// 定义数据源 const count = ref(0) // 指定要监视的数据源 watch(count, (count, prevCount) => { /* ... */ }) //-------------------------------- //监视多个 const count = ref(0) const name = ref('zs') watch( [count, name], // 需要被监视的多个 ref 数据源 ([count, name], [prevCount, prevName]) => { console.log(count) console.log(name) console.log('-------------') console.log(prevCount) console.log(prevName) }, { lazy: true } ) setTimeout(() => { count.value++ name.value = 'xiaomaolv' }, 1000)
- 在 setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可,语法如下:
-
// 创建监视,并得到 停止函数 const stop = watch(() => { /* ... */ }) // 调用停止函数,清除对应的监视 stop()
- 清除创建的异步 watch 监视
-
// 定义响应式数据 keywords const keywords = ref('') // 异步任务:打印用户输入的关键词 const asyncPrint = val => { // 延时 1 秒后打印 return setTimeout(() => { console.log(val) }, 1000) } // 定义 watch 监听 watch( keywords, (keywords, prevKeywords, onCleanup) => { // 执行异步任务,并得到关闭异步任务的 timerId const timerId = asyncPrint(keywords) // 如果 watch 监听被重复执行了,则会先清除上次未完成的异步任务 onCleanup(() => clearTimeout(timerId)) }, // watch 刚被创建的时候不执行 { lazy: true } ) // 把 template 中需要的数据 return 出去 return { keywords }
- watchEffect
- 作用是在响应式数据发生变化的时候产生对应的操作
-
let obj = reactive({foo: 1}); // obj 是响应式数据 watchEffect(() => {console.log(obj.foo);}) // 跟踪依赖数据 (obj.foo) function inc() {obj.foo += 1;} inc() // -> 2 inc() // -> 3 inc() // -> 4
- 值得注意的是,如果跟踪的依赖数据不会向下延伸
-
let obj = reactive({foo: 1}); // obj 是响应式数据 let trackObj = {foo: obj.foo}; watchEffect(() => { console.log(obj.foo); console.log(trackObj.foo.foo); }) // 跟踪依赖数据 (obj.foo) function inc() {obj.foo += 1;} inc() // -> 2 1 inc() // -> 3 1 inc() // -> 4 1
- customRef
- 该函数适用于自定义 ref (所谓的被追踪对象)
-
有的 ref 可以与视图层实现双向数据绑定,而有的则不能。 假如我们需要自定义一个 ref ,当这个 ref 监听的数据变化时,执行我们自己定义的方法, 就像是 watchfEffect 一样去检测一个数据,则可以使用 customRef
- 作用于普通数据的实际用法一:
-
// customRef用于 自定义ref // 自定义 ref 需要提供参数传值 function myRef(value) { // 自定义 ref 需要提供 customerRef 返回值 // customer 需要提供一个函数作为参数 // 该函数默认带参数 track 和 trigger ,都是方法。 // track 告诉 Vue 这个对象需要跟踪 // trigger 告诉 Vue 这个对象更新了,你可以更新视图了 return customRef((track, trigger) => { return { // customer 需要提供一个对象 作为返回值 // 该对象需要包含 get 和 set 方法。 get() { // track 方法放在 get 中,用于提示这个数据是需要追踪变化的 track(); console.log('get', value); // get 方法需要返回值,一般就是 value,当然也可以自定义。 return value; }, // set 传入一个值作为新值,通常用于取代 value set(newValue) { console.log('set', newValue); value = newValue; // 如果需要追踪,记得触发事件 trigger trigger(); } } }) }
- 作用于通过异步获取的后台数据实际用法二:
-
function fetchRef(value) { return customRef((track, trigger) => { // 用于存储获得的数据 let ans; function getAns() { fetch(value) .then((res) => { return res.json(); }).then((data) => { console.log(data); // 将获得的数据存储起来 ans = data; // 提示触发视图层变化 trigger(); }).catch((err) => { console.log(err); }); } getAns(); return { get() { track(); return ans; }, set(newValue) { value = newValue; // 修改 value 的同时再次进行数据的抓取 getAns(); } } }) }
新版生命周期使用
- 新版的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用,代码示例如下:
import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
}
}
- 下面的列表,是 vue 2.x 的生命周期函数与新版 Composition API 之间的映射关系:
- ~~beforeCreate~~ -> setup()
- ~~created~~ -> setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
新增的 provide & inject
- provide() 和 inject() 可以实现嵌套组件之间的数据传递。这两个函数只能在 setup() 函数中使用。父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据。
-
//父组件 import { provide } from 'vue' //以下代码必须在:setup()内执行 provide('navData',data);
//子组件 import { inject} from 'vue' //以下代码必须在:setup()内执行 const data = inject('navData'); console.log(data);
单文件中 refs 的使用
- 通过 ref() 还可以引用页面上的元素或组件。
-
<template> <div> <h3 ref="h3Ref">TemplateRefOne</h3> </div> </template>
import { ref, onMounted } from 'vue' //以下代码必须在:setup()内执行 // 创建一个 DOM 引用 const h3Ref = ref(null) // 在 DOM 首次加载完毕之后,才能获取到元素的引用 onMounted(() => { // 为 dom 元素设置字体颜色 // h3Ref.value 是原生DOM对象 h3Ref.value.style.color = 'red' }) // 把创建的引用 return 出去 return { h3Ref }
- 父子组件使用
- 父组件
-
<template> <div> <h3>父组件</h3> <!-- 4. 点击按钮展示子组件的 count 值 --> <button @click="showNumber">获取TemplateRefTwo中的count值</button> <hr /> <!-- 3. 为组件添加 ref 引用 --> <TemplateRefTwo ref="comRef" /> </div> </template>
// 1. 创建一个组件的 ref 引用 const comRef = ref(null) // 5. 展示子组件中 count 的值 const showNumber = () => { console.log(comRef.value.count) } // 2. 把创建的引用 return 出去 return { comRef, showNumber }
- 子组件
-
<template> <div> <h5>子组件 --- {{count}}</h5> <!-- 3. 点击按钮,让 count 值自增 +1 --> <button @click="count+=1">+1</button> </div> </template>
// 1. 定义响应式的数据 const count = ref(0) // 2. 把响应式数据 return 给 Template 使用 return { count }
createComponent
- 这个函数不是必须的,除非你想要完美结合 TypeScript 提供的类型推断来进行项目的开发。
-
这个函数仅仅提供了类型推断,方便在结合 TypeScript 书写代码时,能为 setup() 中的 props 提供完整的类型推断。
import { createComponent } from 'vue' export default createComponent({ props: { foo: String }, setup(props) { props.foo // <- type: string } })
递归监听与非递归监听
- 要点
-
1. 递归监听存在的问题 如果数据量比较大,非常消耗性能 2. 非递归监听 shallowRef / shallowReactive 3. 如何触发非递归监听属性更新界面? 如果是shallowRef类型数据,可以通过triggerRef来触发 4. 应用场景 —般情况下我们使用ref和reactive即可 只有在需要监听的数据量比较大的时候,我们才使用shallowRef/shallowReactive
- 使用 shallowRef 或 shallowReactive 监听的数据结构
-
let obj = { a:'1', b:{ b1:'2', b2:{ b21:'3', b22:{ b223:'4'} } } }
- shallowReactive 的监听
-
obj.a = 9 obj.b = {b1:'10',b2:{b21:'11',b22:{b223:'12'}}} //如果以上第一层不改动,那么一下改的值将不被监听并且数据还是之前的数据 obj.b.b1 = 10 obj.b.b2.b21 = 11 obj.b.b2.b22.b223 = 12
- shallowRef 的监听
-
//因为 ref 对象自动添加一个 key 为value,导致监听对象为:value:obj obj.value = {a:'1',b:{b1:'10',b2:{b21:'11',b22:{b223:'12'}}}} //如果以上第一层不改动,那么一下改的值将不被监听并且数据还是之前的数据 obj.value.b.b1 = 10 obj.value.b.b2.b21 = 11 obj.value.b.b2.b22.b223 = 12 //但是官网给了 shallowRef 监听一个 triggerRef 方法让非遍历监听对象响应其他层级的数据修改 //使用如下: obj.value.b.b2.b21 = 11 triggerRef(obj)
Vue3与Vue2 的核心
Vue3与Vue2的响应式数据本质
- 在Vue2.x中是通过defineProperty来实现响应式数据的
- 在Vue3.x中是通过Proxy来实现响应式数据的
- Vue3和Vue2 的详情,点击进入
Vue3 的Proxy 使用
let obj = {name:'Lnj1', age:18};
let state = new Proxy(obj, i
get(obj, key){
console.log(obj, key);
return obj[key];
},
set(obj, key, value){
console.log(obj, key, value);
obj[key] = value;
console.log('你设置新值了');
return true
}
}〕
手写 shallowRef 和 shallowReactive
function shallowRef(val) {
return shallowReactive( obj: {value:val});
function shallowReactive(obj) {
return new Proxy(obj, handler: {
get(obj, key){
return obj[key];
},
set(obj, key, val){
obj[key] = val;
return true;
})
}
手写 ref 和 reactive
function ref(val) {
return reactive( obj: {value:val});
function reactive(obj) {
if(typeof obj === 'object'){
if(obj instanceof Array){
//如果是一个数组,那么取出数组中的毎一个元素,
//判断毎个一个对象是否是个对象,如果不是个对象,那么也需要包装成 Proxy
obj.forEach((item, index) =>{
if(typeof item === 'object'){
obj[index] = reactive(item);
}
})
}else{
//如果是一个对象,那么取出对象属性的取值,
//判断对象属性的取值是否又是一个对象,如果不是一个对象,那么也需要包装成Proxy
for(let key in obj){
let item = obj[key];
if(typeof item === 'object'){
obj[key] = reactive(item);
}
}
}
return new Proxy(obj, handler: {
get(obj, key){
return obj[key];
},
set(obj, key, val){
obj[key] = val;
return true;
})
}else{
console.log('所传的必须为对象')
}
}