1. Vue3.0六大两点

  • Performance:性能比Vue2.x快1.2~2倍
  • Tree shaking support:按需编译,体积比Vue2.x更小
  • Composition API:组合API(类似React Hooks)
  • Better TypeScript support:更好的 Ts 支持
  • Custom Renderer API:暴露了自定义渲染API
  • Fragment,Teleport(Protal),Suspense:更先进的组件

2. Vue3.0 是如何变快的?

diff方法优化:http://vue-next-template-explorer.netlify.app/

  • Vue2 中的虚拟dom是进行全量的对比
  • Vue3 新增了静态标记(PatchFlag)

在与上次虚拟节点进行对比时候,只对比带有 patch flag 的节点

并且可以通过 flag 的信息得知当前节点要对比的具体内容

在创建虚拟dom的时候,会根据DOM中的内容会不会发生变化,添加静态标记

hoistStatic 静态提升

  • Vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染
  • Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可

cacheHandlers 事件侦听缓存

  • 默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化
  • 但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可

ssr渲染

  • 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个Buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dom来渲染快上很多。
  • 当静态内容达到一定量级时候,会使用_createStaticVNode方法在客户端dom来渲染一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

什么是 Vite?

  • Vite是Vue作者开发的一款意图取代Webpack的工具
  • 其实现原理是利用 ES6 的 import 会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间

3. Composition API

setup 函数是组合api的入口函数

setup() {
    // 定义了一个count变量,初始值为0
    // 该变量发生改变后,Vue会自动更新UI
    let count = ref(0)
    // 在组合api中,如果定义方法,不需要定义到methods,直接定义即可
    function myFun() {
    	count.value += 1
	}
 // * 注意点:
 // * 在组合api中定义的变量/方法,要想在外界使用,必须通过return { xxx, xxx } 暴露出去
	return { count, myFun }
}

数据和业务逻辑一体化

<template>
	<div>
		<ul>
			<li v-for="(stu, index) in state.stus" :key="stu.id" @click="removeStu(index)">
			{{ stu.name }}: {{ stu.age }}
			</li>
		</ul>
	</div>
</template>
<script>
import { reactive } from 'vue'
export default {
	name: 'App',
    setup() {
	// ref 函数注意点:
	// ref函数只能监听简单类型的变化,不能监听复杂类型的变化(对象,数组)
	let { state, removeStu } = useRemoveStudent()
	return { state, removeStu }  
	}
}
function useRemoveStudent() {
	// 数据和业务逻辑一体化,解决了 Vue2.x 中数据和业务逻辑分离的问题
	let state = reactive({
		stus: [
			{ id: 1, name: '张三', age: 10 },
			{ id: 2, name: '李四', age: 20 },
			{ id: 3, name: '王五', age: 30 }
		]
	})
	function removeStu(index) {
		state.stus = state.stus.filter((stu, idx) => idx !== index)
	}
	return { state, removeStu }
}
</script>

模块化(业务逻辑可以抽离成单独的模块)

<template>
	<div>
        <form action="">
			<input type="text" v-model="state2.stu.id">
			<input type="text" v-model="state2.stu.name">
			<input type="text" v-model="state2.stu.age">
			<input type="submit" @click="addStu">
		</form>
		<ul>
			<li v-for="(stu, index) in state.stus" :key="stu.id" @click="removeStu(index)">
			{{ stu.name }}: {{ stu.age }}
			</li>
		</ul>
    </div>
</template>
<script>
import useRemoveStudent from './remove'
import useAddStudent from './add'
export default {
 name: 'App',
	setup() {
		// ref 函数注意点:
		// ref函数只能监听简单类型的变化,不能监听复杂类型的变化(对象,数组)
		let { state, removeStu } = useRemoveStudent()
		let { state2, addStu } = useAddStudent(state)
		return { state, removeStu, state2, addStu }  
	}
}
</script>
import { reactive } from 'vue'
function useAddStudent(state) {
	let state2 = reactive({
	stu: {
		id: '',
		name: '',
		age: ''
	}
	})
	function addStu(e) {
		e.preventDefault()
		const stu = Object.assign({}, state2.stu)
		state.stus.push(stu)
		state2.stu.id = ''
		state2.stu.name = ''
		state2.stu.age = ''
	}
	return { state2, addStu }
}
export default useAddStudent
import { reactive } from 'vue'
function useRemoveStudent() {
	/**
	 * 数据和业务逻辑一体化,解决了 Vue2.x 中数据和业务逻辑分离的问题
	 */
	let state = reactive({
		stus: [
			{ id: 1, name: '张三', age: 10 },
			{ id: 2, name: '李四', age: 20 },
			{ id: 3, name: '王五', age: 30 }
		]
	})
	function removeStu(index) {
		state.stus = state.stus.filter((stu, idx) => idx !== index)
	}
	return { state, removeStu }
}
export default useRemoveStudent

Composition API 和 Option API 的混合使用

  • Composition API本质 (组合API/注入API)【本例:setup函数中,将数据(age)注入data,将方法(myFun2)注入 methods】
<template>
	<div>
		<p>{{ name }}</p>
		<button @click="myFun">按钮</button>
		<p>{{ age }}</p>
		<button @click="myFun2">按钮</button>
	</div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'App',
	data () {
		return {
			name: 'wh'
		}
	},
	methods: {
		myFun() {
			alert('123')
		}
	},
	setup() {
		let age = ref(18)
		function myFun2() {
			age.value += 1
		}
		return {
			age,
			myFun2
		}
	}
}

</script>

setup执行时机 & setup注意点

setup执行时机:beforeCreate -> setup -> created
  • beforeCreate: 表示组件刚刚创建出来,组件的 data 和 methods 还没有初始化好
  • created: 表示组件刚刚被创建出来,组件的 data 和 methods 已经初始化好
官方文档原文:
	Because setup is run around the beforeCreate and created lifecycle hooks, you do not need to explicitly define them. 
	In other words, any code that would be written inside those hooks should be written directly in the setup function.

意思是:
	因为 setup 是围绕 beforeCreate 和 created 的生命周期钩子运行的,所以不需要显式地去定义 beforeCreate 和 created 
	换句话说,在 beforeCreate 和 created 钩子中编写的任何代码都应该直接写在 setup 函数中
setup注意点
  • 由于在执行setup函数的时候,还没有执行 created 生命周期方法,所以在 setup 函数中,是无法使用 data 和 methods
  • 由于不能再 setup 函数中使用 data 和 methods,所以 Vue 为了避免我们的错误使用,它直接将 setup 函数中的 this 修改成了 undefined
  • setup 函数只能是同步的,不能是异步的
<template>
	<div>
		<p>{{ name }}</p>
		<button @click="myFun">按钮</button>
		<p>{{ age }}</p>
		<button @click="myFun2">按钮</button>
	</div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'App',
	data () {
		return {
			name: 'wh'
		}
	},
	methods: {
		myFun() {
			alert('123')
		}
	},
	// async setup() {} 错误使用
	setup() {
		let age = ref(18)
		function myFun2() {
			age.value += 1
		}
		console.log(this)
		return {
			age,
			myFun2
		}
	}
}

</script>

reactive 函数

  1. 什么是 reactive?
  • reactive 是 Vue3 中提供的实现响应式数据的方法
  • 在 Vue2 中响应式数据是通过 defineProperty 来实现的,而在 Vue3 中响应式数据是通过 ES6 的 Proxy 来实现的
  1. reactive 注意点
  • reactive 参数必须是对象 (json/array)
  • 如果给 reactive 传递了其他对象
  • 默认情况下修改对象,界面不会自动更新
  • 如果想更新,可以通过重新赋值的方式
<template>
  <div>
  	<!-- <p>{{ state.age }}</p> -->
  	<!-- <p>{{ state }}</p> -->
  	<p>{{ state.time }}</p>
  	<button @click="myFun">按钮</button>
  </div>	
</template>

<script>
import { reactive } from 'vue'
export default {
name: 'App',
  setup() {
  	// 创建一个响应式数据
  	// 本质:就是将一个传入的数据包装成一个 Proxy对象
  	// let state = reactive(123)
  	// let state = reactive({
  	// age: 123
  	// })
  	// let state = reactive([1, 3, 5])
  	let state = reactive({
  		time: new Date() // 给 reactive 传递了其他对象, 默认情况下修改对象,界面不会自动更新
  	})
  	function myFun() {
  		// state = 666 // 由于在创建响应式数据的时候传递的不是一个对象,所以无法实现响应式
  		// state.age = 666 
  		// state[0] = 666
  		// 直接修改以前的,界面不会自动更新
  		// 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.age)
  		console.log(state)
  	}
  	return { state, myFun }
  }
}
</script>

ref 函数

  1. 什么是 ref?
  • ref 和 reactive 一样,也是用来实现响应式数据的方法
  • 由于 reactive 必须传递一个对象,所以导致在企业开发中,如果我们只想让某个变量实现响应式的时候会非常麻烦,
    所以 Vue3 就给我们提供了 ref 方法,实现对简单值的监听
  1. ref 本质
  • ref 底层的本质其实还是 reactive,系统会自动根据我们给 ref 传入的值将它转换成 ref(xx) -> reactive({value: xx})
  1. ref 注意点
  • 在 Vue 中使用 ref 的值不用通过 value 获取
  • 在 JS 中使用 ref 的值必须通过 value 获取
<template>
<div>
	<!-- 
	注意点:
	如果是通过 ref 创建的数据,那么在 template 中使用的时候不用通过 .value 来获取
	因为 Vue 会自动给我们添加 .value
	 -->
	<p>{{ age }}</p>
	<button @click="myFun">按钮</button>
	</div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'App',
	setup() {
		let age = ref(18)
		function myFun() {
			// age = 666
			age.value += 2
		}
		return {
			age,
			myFun
		}
	}
}

</script>

ref 和 reactive 的区别

  • 如果在 template 里使用的是 ref 类型的数据,那么 Vue 会自动帮我们添加 .value
  • 如果在 template 里使用的是 reactive 类型的数据,那么 Vue 不会自动帮我们添加 .value
  • Vue 是如何决定是否需要添加 .value 的?
  • Vue 在解析数据之前,会自动判断这个数据是否是 ref 类型的,如果是就自动添加 .value,如果不是就不自动添加 .value
  • Vue 是如何判断当前数据是否是 ref 类型的?
  • 通过当前数据的 v_isRef 来判断的
  • 如果存在这个私有的属性,并且取值为 true,那么就代表是一个 ref 类型的数据
  • 因为是私有属性,所以我们是访问不到 v_isRef 属性的值
  • Vue 提供了 isRef 和 isReactive 方法来判断一个数据是 ref 类型还是 reactive 类型
<template>
<div>
	<p>{{ age }}</p>
	<button @click="myFun">按钮</button>
</div>
</template>

<script>
/**

*/
import { ref, reactive, isRef, isReactive } from 'vue'
export default {
  name: 'App',
	setup() {
		let age = ref(18)
		// let age = reactive({
		// value: 18
		// })
		function myFun() {
			// age = 666
			/**
			 * ref -> {"_rawValue":18,"_shallow":false,"__v_isRef":true,"_value":18}
			 * reactive -> {"value":18}
			 */
			console.log(isReactive(age))
			console.log(isRef(age))
			age.value += 2
		}
		return {
			age,
			myFun
		}
	}
}

</script>

递归监听 & 非递归监听

  1. 递归监听
  1. 默认情况下,无论是通过 ref 还是 reactive 都是递归监听
  2. 每一层都包装成了一个 proxy 对象
  1. 递归监听存在的问题
  1. 如果数据量比较大,非常消耗性能
  1. 非递归监听
  1. shallowReactive
  1. 非递归监听下,第一层被包装成了 proxy
  2. 这意味着:只有第一层的数据发生改变,才会触发 UI界面 的更新
  1. shallowRef
  1. 如果是通过 shallowRef 创建数据,那么 Vue监听的是 .value 的变化,并不是第一层的变化
  2. 如果想在修改其内部数据后触发界面的更新,可以调用 triggerRef 方法
  1. 应用场景
  1. 一般情况下 使用 ref 和 reactive 即可
  2. 只有在需要监听的数据量比较大的时候,我们才使用 shallowRef / shallowReactive

reactive

<template>
  <div>
	<p>{{ state.a }}</p>
	<p>{{ state.gf.b }}</p>
	<p>{{ state.gf.f.c }}</p>
	<p>{{ state.gf.f.s.d }}</p>
	<button @click="myFun">按钮</button>
  </div>
</template>

<script>
import { ref, reactive, isRef, isReactive } from 'vue'
export default {
  name: 'App',
	setup() {
		let state = reactive({
			a: 'a',
			gf: {
				b: 'b',
				f: {
					c: 'c',
					s: {
						d: 'd'
					}
				}
			}
		})

		function myFun() {
			state.a = 1
			state.gf.b = 2
			state.gf.f.c = 3
			state.gf.s.f.d = 4
		}

		return {
			state,
			myFun
		}
	}
}

</script>
<template>
  <div>
		<p>{{ state.a }}</p>
		<p>{{ state.gf.b }}</p>
		<p>{{ state.gf.f.c }}</p>
		<p>{{ state.gf.f.s.d }}</p>
		<button @click="myFun">按钮</button>
  </div>
</template>

<script>
import { reactive, shallowReactive } from 'vue'
export default {
  name: 'App',
	setup() {
		let state = shallowReactive({
			a: 'a',
			gf: {
				b: 'b',
				f: {
					c: 'c',
					s: {
						d: 'd'
					}
				}
			}
		})

		function myFun() {
			/**
			 * 非递归监听下,该层被包装成了 proxy
			 * 这意味着:只有该层的数据发生改变,才会触发 UI 的更新
			 * 即:如果 state.a = 1 不执行,那么 界面是不会发生改变的
			 */
			// state.a = 1
			state.gf.b = 2
			state.gf.f.c = 3
			state.gf.f.s.d = 4
			console.log(state) // 只有这一层包装成了 proxy
			console.log(state.gf)
			console.log(state.gf.f)
			console.log(state.gf.f.s)
		}

		return {
			state,
			myFun
		}
	}
}

</script>

ref

<template>
  <div>
		<p>{{ state.a }}</p>
		<p>{{ state.gf.b }}</p>
		<p>{{ state.gf.f.c }}</p>
		<p>{{ state.gf.f.s.d }}</p>
		<button @click="myFun">按钮</button>
  </div>
</template>

<script>
import { ref, reactive, isRef, isReactive } from 'vue'
export default {
  name: 'App',
  setup() {
		let state = ref({
			a: 'a',
			gf: {
				b: 'b',
				f: {
					c: 'c',
					s: {
						d: 'd'
					}
				}
			}
		})

		function myFun() {
			state.value.a = 1
			state.value.gf.b = 2
			state.value.gf.f.c = 3
			state.value.gf.f.s.d = 4
		}

		return {
			state,
			myFun
		}
	}
}

</script>
<template>
  <div>
	<p>{{ state.a }}</p>
	<p>{{ state.gf.b }}</p>
	<p>{{ state.gf.f.c }}</p>
	<p>{{ state.gf.f.s.d }}</p>
	<button @click="myFun">按钮</button>
  </div>
</template>

<script>
import { ref, shallowRef, triggerRef } from 'vue'
export default {
  name: 'App',
  setup() {
    let state = shallowRef({
	  a: 'a',
	  gf: {
	    b: 'b',
	    f: {
				c: 'c',
				s: {
					d: 'd'
				}
	    }
	  }
    })
    
    function myFun() {
	  // state.value.a = 1
	  // state.value.gf.b = 2
	  // state.value.gf.f.c = 3
	  state.value.gf.f.s.d = 4 // 如果想在修改该数据时,触发界面的更新,可以调用 triggerRef 方法主动触发
	  /**
	   * 注意点:
	   *  + Vue3 只提供了 triggerRef 方法,没有提供 triggerReactive 方法
	   *  + 所以如果是 reactive 类型的数据,那么是无法主动触发界面更新的
	   */
	  triggerRef(state)
	  /**
	   * 注意点:
	   *  + 如果是通过 shallowRef 创建数据,那么 Vue监听的是 .value 的变化,并不是第一层的变化
	   */
	  // state.value = {
	  //   a: '1',
	  //   gf: {
	  //     b: '2',
	  //     f: {
	  //       c: '3',
	  //       s: {
	  //         d: '4'
	  //       }
	  //     }
	  //   }
	  // }
	  console.log(state)
	  console.log(state.value.gf)
	  console.log(state.value.gf.f)
	  console.log(state.value.gf.f.s)
    }
    
    return {
	  state,
	  myFun
    }
  }
}

</script>

shallowRef & shallowReative

  • shallowRef 本质上还是 shallowReative
  • 所以如果是通过 shallowRef 创建的数据,它监听的是 .value 的变化
<template>
  <div>
    <p>{{ state.a }}</p>
    <p>{{ state.gf.b }}</p>
    <p>{{ state.gf.f.c }}</p>
    <p>{{ state.gf.f.s.d }}</p>
    <button @click="myFun">按钮</button>
  </div>
</template>

<script>
import { ref, shallowRef, triggerRef } from 'vue'
export default {
  name: 'App',
	setup() {
      // shallowRef 本质上还是 shallowReative
      // shallowRef(10) -> shallowReactive({ value: 10 })
      // 所以如果是通过 shallowRef 创建的数据,它监听的是 .value 的变化
      let state = shallowRef({
        a: 'a',
        gf: {
          b: 'b',
          f: {
            c: 'c',
            s: {
              d: 'd'
            }
          }
        }
      })

      function myFun() {
        // state.value.a = 1
        // state.value.gf.b = 2
        // state.value.gf.f.c = 3
        state.value.gf.f.s.d = 4 // 如果想在修改该数据时,触发界面的更新,可以调用 triggerRef 方法主动触发
        /**
         * 注意点:
         *  + Vue3 只提供了 triggerRef 方法,没有提供 triggerReactive 方法
         *  + 所以如果是 reactive 类型的数据,那么是无法主动触发界面更新的
         */
        triggerRef(state)
        /**
         * 注意点:
         *  + 如果是通过 shallowRef 创建数据,那么 Vue监听的是 .value 的变化,并不是第一层的变化
         */
        // state.value = {
        //   a: '1',
        //   gf: {
        //     b: '2',
        //     f: {
        //       c: '3',
        //       s: {
        //         d: '4'
        //       }
        //     }
        //   }
        // }
        console.log(state)
        console.log(state.value.gf)
        console.log(state.value.gf.f)
        console.log(state.value.gf.f.s)
      }

      return {
        state,
        myFun
      }
	}
}

</script>

toRaw & markRaw 函数

  • ref/reactive 数据类型的特点:
  • 每次修改都会被追踪,都会更新 UI 界面,但是这样是非常消耗性能的
  • 所以如果我们有一些操作不需要追踪,不需要更新 UI 界面,那么这个时候,
    我们就可以通过 toRaw 方法拿到它的原始数据,对数据进行修改,这样就不会被追踪,也就不会去更新 UI 界面,性能就会有所提升
<template>
  <div>
    <p>{{ state }}</p>
    <button @click="myFun">按钮</button>
  </div>
</template>

<script>
/**

 */
import { reactive, toRaw } from 'vue'
export default {
  name: 'App',
	setup() {
      /**
       * ref/reactive 数据类型的特点:
       *    + 每次修改都会被追踪,都会更新 UI 界面,但是这样是非常消耗性能的
       *    + 所以如果我们有一些操作不需要追踪,不需要更新 UI 界面,那么这个时候,
       *      我们就可以通过 toRaw 方法拿到它的原始数据,对数据进行修改,这样就不会被追踪,
       *      也就不会去更新 UI 界面,性能就会有所提升
       */
      let obj = { name: 'wh', age: 18 }
      let state = reactive(obj)
      let obj2 = toRaw(state)
      console.log(obj2)
      // state 和 obj 的关系:
      // + 引用关系,state的本质是 proxy 对象,在这个 proxy 对象中引用了 obj
      function myFun() {
        // state.name = 'zs'
        // 如果直接修改 obj,那么是无法触发界面更新的
        // 只有通过包装之后的对象来修改,才会出发界面的更新
        obj2.name = 'zs'
        console.log(obj2) // {name: "zs", age: 18}
        console.log(state) // Proxy {name: "zs", age: 18}
      }
      return {
        state,
        myFun
      }
	}
}

</script>
<template>
  <div>
    <p>{{ state }}</p>
    <button @click="myFun">按钮</button>
  </div>
</template>

<script>
/**

 */
import { ref, toRaw } from 'vue'
export default {
  name: 'App',
	setup() {
      /**
       * 1. ref本质: reactive
       *    ref(obj) -> reactive({value: obj})
       *
       */
      let obj = { name: 'wh', age: 18 }
      let state = ref(obj)
      // 注意点:如果想通过 toRaw 拿到 ref 类型的原始数据(创建时传入的那个数据)
      //        那么必须明确的告诉 toRaw 方法,要获取的是 .value
      //        因为经过 Vue 处理之后,.value 中保存的才是当初创建的时候传入的那个原始数据
      let obj2 = toRaw(state.value)
      console.log(obj2)
      // state 和 obj 的关系:
      // + 引用关系,state的本质是 proxy 对象,在这个 proxy 对象中引用了 obj
      function myFun() {
        // state.name = 'zs'
        // 如果直接修改 obj,那么是无法触发界面更新的
        // 只有通过包装之后的对象来修改,才会出发界面的更新
        obj2.name = 'zs'
        console.log(obj2) // {name: "zs", age: 18}
        console.log(state) // Proxy {name: "zs", age: 18}
      }
      return {
        state,
        myFun
      }
	}
}

</script>
<template>
  <div>
    <p>{{ state }}</p>
    <button @click="myFun">按钮</button>
  </div>
</template>

<script>
import { reactive, markRaw } from 'vue'
export default {
  name: 'App',
	setup() {
      let obj = { name: 'wh', age: 18 }
      obj = markRaw(obj) // 限制该数据永远无法被追踪
      let state = reactive(obj)
      function myFun() {
        state.name = 'zs'
      }
      return {
        state,
        myFun
      }
	}
}
</script>

toRef & toRefs 函数

  • ref->复制:
  • 修改响应式数据不会影响以前的数据
  • 数据发生改变,界面就会自动更新
  • toRef->引用:
  • 修改响应式数据会影响以前的数据
  • 数据发生改变,界面也不会自动更新
  • toRef 应用场景:
  • 如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新 UI,那么就可以使用 toRef
<template>
  <div>
    <p>{{ state }}</p>
    <button @click="myFun">按钮</button>
  </div>
</template>

<script>
  /**
   *
   */
import { ref, toRef } from 'vue'
export default {
  name: 'App',
	setup() {
      let obj = { name: 'wh' }
      // ref:复制
      // let state = ref(obj.name)
      // toRef:引用
      /**
       * ref 和 toRef 区别:
       *  + ref->复制:
       *    - 修改响应式数据不会影响以前的数据
       *    - 数据发生改变,界面就会自动更新
       *  + toRef->引用:
       *    - 修改响应式数据会影响以前的数据
       *    - 数据发生改变,界面也不会自动更新
       *
       * toRef 应用场景:
       *  + 如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新 UI,那么就可以使用 toRef
       */
      let state = toRef(obj, 'name')
      console.log(state)

      function myFun() {
        state.value = "zs"
        /**
         *  结论:如果利用 ref 将某一个对象的属性变为响应式的数据
         *        那么修改响应式数据是不会影响到原始数据的
         */
        /**
         * 结论:如果利用 toRef 将某一个对象中的属性变成响应式的数据
         *      那么修改响应式数据是会影响到原始数据的
         *      但是如果响应式的数据通过 toRef 创建,那么修改了数据并不会触发UI界面的更新
         */
        console.log(obj) // wh
        console.log(state) // zs
      }
      return {
        state,
        myFun
      }
	}
}
</script>
<template>
  <div>
<!--    <p>{{ name }}</p>-->
<!--    <p>{{ age }}</p>-->
    <p>{{state}}</p>
    <button @click="myFun">按钮</button>
  </div>
</template>

<script>
  /**
   *
   */
import { ref, toRef, toRefs } from 'vue'
export default {
  name: 'App',
	setup() {
      let obj = { name: 'wh', age: 18 }
      // ref:复制
      // let state = ref(obj.name)
      // toRef:引用
      /**
       * ref 和 toRef 区别:
       *  + ref->复制:
       *    - 修改响应式数据不会影响以前的数据
       *    - 数据发生改变,界面就会自动更新
       *  + toRef->引用:
       *    - 修改响应式数据会影响以前的数据
       *    - 数据发生改变,界面也不会自动更新
       *
       * toRef 应用场景:
       *  + 如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新 UI,那么就可以使用 toRef
       */
      // let name = toRef(obj, 'name')
      // let age = toRef(obj, 'age')
      let state = toRefs(obj)
      console.log(state)

      function myFun() {
        // name.value = "zs"
        // age.value = "666"
        state.name.value = 'zs'
        state.age.value = 666
        /**
         *  结论:如果利用 ref 将某一个对象的属性变为响应式的数据
         *        那么修改响应式数据是不会影响到原始数据的
         */
        /**
         * 结论:如果利用 toRef 将某一个对象中的属性变成响应式的数据
         *      那么修改响应式数据是会影响到原始数据的
         *      但是如果响应式的数据通过 toRef 创建,那么修改了数据并不会触发UI界面的更新
         */
        console.log('obj', obj) // obj {name: "zs", age: 666}
        console.log('state', state) // state {name: "zs", age: 666}
      }
      return {
        // name,
        // age,
        state,
        myFun
      }
	}
}
</script>

customRef 函数

  • 返回一个 ref 对象,可以显式的控制依赖追踪和触发响应
  • 异步处理服务器数据的时候可能会用到
  • 需要注意的是:不能在 get 方法中发送网络请求,因为这会导致不断发送请求,不断更新界面的无限循环
<template>
 <div>
   <p>{{age}}</p>
   <button @click="myFun">按钮</button>
 </div>
</template>

<script>
 /**
  * 1. custemRef
  *    + 返回一个 ref 对象,可以显式的控制依赖追踪和触发响应
  */
import { ref, customRef } from 'vue'

 /**
  * 自定义 Ref
  * @param value
  * @returns {Ref<*>}
  */
 function myRef(value) {

 return customRef((track, trigger) => {
   // track -> 追踪  trigger -> 触发
   return {
     get() {
       track() // 告诉 Vue 这个数据是需要追踪变化的
       console.log('get', value)
       return value
     },
     set(newValue) {
       console.log('set', newValue)
       value = newValue
       trigger() // 告诉 Vue 触发界面更新
     }
   }
 })
}

export default {
 name: 'App',
   setup() {
     // let age = ref(18) // reactive({value: 18})
     let age = myRef(18)
     function myFun() {
       age.value += 1
     }
     return { age, myFun }
   }
}
</script>
/**
 * data.json
*/
[
  {"id": 1, "name":  "鲁班"},
  {"id": 2, "name":  "虞姬"},
  {"id": 3, "name":  "黄忠"}
]
<template>
  <div>
    <ul>
      <li v-for="item in state" :key="item.id"> {{ item.name }} </li>
    </ul>
  </div>
</template>

<script>
  /**
   * 1. custemRef
   *    + 返回一个 ref 对象,可以显式的控制依赖追踪和触发响应
   */
import { ref, customRef } from 'vue'

  /**
   * 自定义 Ref
   * @param value
   * @returns {Ref<*>}
   */
  function myRef(value) {

  return customRef((track, trigger) => {
    // track -> 追踪  trigger -> 触发
    fetch(value)
        .then(res => res.json())
        .then(data=>{
          console.log(data)
          value = data
          trigger()
        })
        .catch(err => {
          console.log(err);
        })
    return {
      get() {
        track() // 告诉 Vue 这个数据是需要追踪变化的
        console.log('get', value)
        /**
         * 注意点:
         *  + 不能在 get 方法中发送网络请求
         *  + 渲染界面 -> 调用 get -> 发送网络请求
         *  + 保存数据 -> 更新界面 -> 调用 get
         */
        return value
      },
      set(newValue) {
        console.log('set', newValue)
        value = newValue
        trigger() // 告诉 Vue 触发界面更新
      }
    }
  })
}

export default {
  name: 'App',
  // setup函数:只能是一个同步的函数,不能是一个异步的函数
  // async setup() {
    // const data = await fetch('../public/data.json')
  setup() {
    /*
      let state = ref([])
      fetch('../public/data.json')
          .then(res => res.json())
          .then(data=>{
            console.log(data)
            state.value = data
          })
          .catch(err => {
            console.log(err);
          })
    */
    let state = myRef('../public/data.json')
    return { state }
  }
}
</script>

生命周期

<template>
  <div ref="box">我是div</div>
</template>

<script>
  /**
   * 1. 获取元素
   *  + 在 Vue2.x 中我们可以通过给元素添加 ref='xxx',
   *    然后在代码中通过 ref.xxx 的方式来获取元素
   *    在 Vue3.x 中我们也可以通过 ref 来获取元素
   *
   */
import { ref, onMounted } from 'vue'
export default {
  name: 'App',
  setup() {
    // console.log(this.$refs.box) // vue3 中不能这样使用
    let box = ref(null)
    onMounted(()=>{
      console.log('onMounted', box.value) // onMounted <div>我是div</div>
    })
    console.log(box.value) // null
    return { box }
  }
}
</script>

readonly 函数

  • readonly:用于创建一个只读的数据,并且是递归只读
  • shallowReadonly: 用于创建一个只读数据,但是不是递归只读的
  • isReadonly:对于 readonly 和 shallowReadonly 创建的数据,返回结果均为 true
  • const 和 readonly 的区别:
  • const: 赋值保护,不能给变量重新赋值
  • readonly:属性保护,不能给属性重新赋值
<template>
  <div>
    <p>{{ state.name }}</p>
    <p>{{ state.attr.age }}</p>
    <p>{{ state.attr.height }}</p>
    <button @click="myFun()">按钮</button>
  </div>
</template>

<script>
  /**
   * 1. readonly
   *  +
   *
   */
import { readonly, shallowReadonly, isReadonly } from 'vue'
export default {
  name: 'App',
  setup() {
    // readonly:用于创建一个只读的数据,并且是递归只读
    // let state = readonly({name: 'wh', attr: {age: 18, height: 1.88}})
    // shallowReadonly:用于创建一个只读数据,但是不是递归只读的
    let state = shallowReadonly({name: 'wh', attr: {age: 18, height: 1.88}})
    /** const 和 readonly 的区别:
     *    + const: 赋值保护,不能给变量重新赋值
     *    + readonly:属性保护,不能给属性重新赋值
     */
    // const value = 123
    const value = { name: 'zs', age: 123 }
    function myFun() {
      state.name = 'zs'
      state.attr.age = 666
      state.attr.height = 1.222
      value.name = 'ls'
      value.age = '456' // { name: 'ls', age: 456 }
      console.log(state)
      console.log(isReadonly(state)) // true
      console.log(value)
    }
    return { state, myFun }
  }
}
</script>

Proxy 实现数据绑定

对象

/*
    1. Vue3 响应式数据的本质
      + 在 Vue2.x 中是通过 defineProperty 来实现响应式数据的
        详见:手写 vue 全家桶视频
      + 在 Vue3.x 中通过 Proxy 来实现响应式数据的
 */

let obj = { name: 'wh', age: 18 }
let state = new Proxy(obj, {
  get(obj, key) {
    console.log(obj, key) // { name: 'wh', age: 18 } name
    return obj[key]
  },
  set(obj, key, value) {
    console.log(obj, key, value) // { name: 'wh', age: 18 } name zs
    obj[key] = value
    console.log('更新UI界面')
  }
})

// console.log(state.name) // wh
state.name = "zs"
console.log(state) // { name: 'zs', age: 18 }

数组

/*
    1. Vue3 响应式数据的本质
      + 在 Vue2.x 中是通过 defineProperty 来实现响应式数据的
        详见:手写 vue 全家桶视频
      + 在 Vue3.x 中通过 Proxy 来实现响应式数据的
 */

let arr = [ 1, 3, 5 ]
let state = new Proxy(arr, {
  get(obj, key) {
    console.log(obj, key) // [ 1, 3, 5 ] 1
    return obj[key]
  },
  set(obj, key, value) {
    // 第1次 [ 1, 3, 5 ] 3 7
    // 第2次 [ 1, 3, 5, 7 ] length 4
    console.log(obj, key, value)
    obj[key] = value
    console.log('更新UI界面')
    return true // 必须要返回 true 才会继续更新 length
  }
})

// console.log(state[1]) // 3
state.push(7) // 先追加 7 ,再更新 length

手写 shallowReactive, shallowRef

/*
    1. shallowReactive, shallowRef
    2. shallowReadonly
    3. reactive, ref
    4. readonly
 */

function shallowRef(val) {
  return shallowReactive({ value: val })
}

function shallowReactive(obj) {
  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key, value) {
      obj[key] = value
      console.log('更新UI界面')
      return true
    }
  })
}

let obj = {
  a: 'a',
  gf: {
    b: 'b',
    f: {
      c: 'c',
      s: {
        d: 'd'
      }
    }
  }
}
/*
let state = shallowReactive(obj)
// state.a = 1 // 更新UI界面
state.gf.b = 2
state.gf.f.c = 3
state.gf.f.s.d = 4
*/
let state = shallowRef(obj)
// state.value.a = 1
// state.value.gf.b = 2
// state.value.gf.f.c = 3
// state.value.gf.f.s.d = 4
state.value = {
  a: '1',
  gf: {
    b: '2',
    f: {
      c: '3',
      s: {
        d: '4'
      }
    }
  }
}

手写 reactive, ref

/*
    1. shallowReactive, shallowRef
    2. shallowReadonly
    3. reactive, ref
    4. readonly
 */

// let arr = [{id: 1, name: '鲁班'}, {id: 2, name: '虞姬'}]
// let obj = {a:{id: 1, name: '鲁班'}, b:{id: 2, name: '虞姬'}}

function ref(val) {
  return reactive({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)
        }
      }
    }
  } else {
    console.warn(`${JSON.stringify(obj)} is not object`)
  }
  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key, value) {
      obj[key] = value
      console.log('更新UI界面')
      return true
    }
  })
}

let obj = {
  a: 'a',
  gf: {
    b: 'b',
    f: {
      c: 'c',
      s: {
        d: 'd'
      }
    }
  }
}

// let state = reactive(obj)
// state.a = 1 // 更新UI界面
// state.gf.b = 2 // 更新UI界面
// state.gf.f.c = 3 // 更新UI界面
// state.gf.f.s.d = 4 // 更新UI界面

let arr = [{id: 1, name: '鲁班'}, {id: 2, name: '虞姬'}]
let state = reactive(arr)
state[0].name = '张三' // 更新UI界面
state[0].age = 666 // 更新UI界面
state[1].id = 3 // 更新UI界面

手写 readonly, shallowReadonly

/*
    1. shallowReactive, shallowRef
    2. shallowReadonly
    3. reactive, ref
    4. readonly
 */

function readonly(obj) {
  if(typeof obj === 'object') {
    if (obj instanceof Array) {
      // 如果是一个数据,那么取出数组中的每一个元素
      // 判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy
      obj.forEach((item, index) => {
        if(typeof item === 'object') {
          obj[index] = rereadonlyactive(item)
        }
      })
    } else {
      // 如果是一个对象,那么去除对象属性的取值
      // 判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy
      for (let key in  obj) {
        let item = obj[key]
        if (typeof item === 'object') {
          obj[key] = readonly(item)
        }
      }
    }
  } else {
    console.warn(`${JSON.stringify(obj)} is not object`)
  }
  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key, value) {
      console.warn(`${key}是只读的,不能赋值`)
      return true
    }
  })
}

function shallowReadonly(obj) {

  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key, value) {
      console.warn(`${key}是只读的,不能赋值`)
    }
  })
}

let obj = {
  a: 'a',
  gf: {
    b: 'b',
    f: {
      c: 'c',
      s: {
        d: 'd'
      }
    }
  }
}

// let state = shallowReadonly(obj)
// state.a = 1 // a是只读的,不能赋值
// state.gf.b = 2 // 空行
let state1 = readonly(obj)
state1.a = 1 // a是只读的,不能赋值

state1.gf.b = 2 // b是只读的,不能赋值