浅谈vue3.0

    科技2024-11-23  25

    目录

    vue3.0的六大亮点优化了diff算法标记的类型静态提升事件监听缓存vue3.0项目的创建Vue3.0是兼容vue2.x版本的组合逻辑API组合API的使用添加用户 setup的注意点reactive方法什么是ref函数ref 和 reactive 的区别递归监听toRaw函数markRawtoRef函数toRefs自定义一个ref?ref获取页面的元素

    vue3.0的六大亮点

    Performance:性能比vue2.x块1.2倍到2.2倍Tree shaking support:按需编译,体积比vue2.x还小Composition API:组合API(类似于React中的 Hook )Better TypeScript Support:更好的支持TsCustom Renderer API:暴露了自定义的渲染APIFragment,Teleport(Prptal),Suspense:更先进的组件

    优化了diff算法

    vue2.0对于虚拟DOM是进行全量比较的 所谓全量比较:就是对每一个标签进行前后比较 vue3.0新增了静态标记(PatchFlag) 会根据DOM节点是否发生变化,添加静态标记 -> {{ }} 类似这种,给添加标记!然后对这些静态标记进行比较 怎么添加静态标记?

    标记的类型

    text = 1 -> 动态文本class // 2 -> 动态classstyle // 4 动态styleprops // 8 动态属性,但不包含类名和样式FULL_PROPS // 16 具有动态的key属性

    静态提升

    vue2.x无论元素是否参与更新,每次都会重新创建,然后渲染vue3.0中对于不参与更新的元素,会做静态提升,之后被创建一次,在渲染的时候直接复用即可 <div> Hello World! <p>{{ msg }}</p> </div> //静态提升之前 export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createTextVNode(" Hello World! "), _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */) ])) } //静态提升之后 勾选 -> hoistStatic const _hoisted_1 = /*#__PURE__*/_createTextVNode(" Hello World! ") export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _hoisted_1, _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */) ])) } //注意点:静态提升之后,把这个变量 Hello World! 存放在全局之中,只创建一次,然后每一次渲染都是拿全局的!

    事件监听缓存

    默认情况下onClick会被视为动态绑定,所以每次都会缓存起来复用即可但是因为是同一个函数,所以没有追踪它的变化 <div> <button @click="add">按钮</button> </div> //事件监听缓存之前 export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _ctx.add }, "按钮", 8 /* PROPS */, ["onClick"]) ])) } //事件监听缓存之后 勾选-> cacheHandlers export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.add(...args))) }, "按钮") ])) } //事件监听缓存之后,静态标记取消,因此不再追踪,提高了性能!

    vue3.0项目的创建

    Vite || Vue-Cli || Webpack

    什么是Vite?

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

    安装:

    npm i -g create-vite-app

    使用:

    npm i 安装依赖npm run dev 启动项目

    Vue3.0是兼容vue2.x版本的

    依旧可以使用 data数据中的绑定方式 vue2.x版本的增删 <template> <div> <p>{{msg}}</p> <input type="text" v-model="stuObj.id" /> <input type="text" v-model="stuObj.name" /> <input type="submit" value="提交" @click="submit" /> <ul> <li v-for="item in stu" :key="item.id" @click="removeItem(item.id)">{{ item.name }}</li> </ul> </div> </template> <script> export default { name: "App", data() { return { msg: "ppp", stuObj: { id: "", name: "", }, stu: [ { id: 1, name: "ppp", }, { id: 2, name: "www", }, { id: 3, name: "qqq", }, ], }; }, methods: { removeItem(id) { this.stu = this.stu.filter((item) => item.id !== id); }, submit() { const stu = [...this.stu]; stu.push({ id: this.stuObj.id, name: this.stuObj.name, }); this.stu = stu; this.stuObj.id = ""; this.stuObj.name = ""; }, }, }; </script>

    组合逻辑API

    setup() 是组合API的入口函数 这个入口函数内,定义的变量或者方法都需要向外暴露出去,使用return {}需要引入{ref} 对变量的定义初始值 <template> <div> <p>{{ count}}</p> <button @click="add">+1按钮</button> </div> </template> <script> import { ref } from "vue"; export default { name: "App", setup() { //定义一个变量 count 初始值为0 let count = ref(0); function add() { count.value++ } return { count, add } } }; </script>

    ref函数的注意点

    ref函数只能监听简单数据类型的变化,不能监听复杂数据类型的变化

    reactive函数

    用于监听复杂数据类型的变化

    组合API的使用

    <template> <div> <ul> <li v-for="item in state.stu" :key="item.id" @click="removeItem(item.id)">{{ item.name }}</li> </ul> </div> </template> <script> import { reactive } from "vue"; export default { name: "App", setup() { let { state, removeItem } = useRemove() return { state, removeItem } }, }; //关于 用户删除的 数据 与 逻辑 组合api //使用的时候 在setup之中直接结构出 这边方法的导入的数据 function useRemove() { let state = reactive({ stu: [{ id: 1, name: "ppp", }, { id: 2, name: "www", }, ] }) function removeItem(id) { state.stu = state.stu.filter(item => item.id !== id) } return { state, removeItem } } </script>

    添加用户

    注意点:就是关于 用户添加 用户删除的话 可以这这两个抽离出来 然后使用 import方式导入,使得每一个功能为一个单独的模块! <template> <div> <input type="text" v-model="stuObj.id" /> <input type="text" v-model="stuObj.name" /> <input type="submit" value="提交" @click="addItem" /> <ul> <li v-for="item in state.stu" :key="item.id" @click="removeItem(item.id)">{{ item.name }}</li> </ul> </div> </template> <script> import { reactive } from "vue"; export default { name: "App", setup() { let { state, removeItem } = useRemove() let { stuObj, addItem, } = useAdd(state) return { state, removeItem, stuObj, addItem } }, }; //关于 用户删除的 数据 与 逻辑 组合api //使用的时候 在setup之中直接结构出 这边方法的导入的数据 function useRemove() { let state = reactive({ stu: [{ id: 1, name: "ppp", }, { id: 2, name: "www", }, ] }) function removeItem(id) { state.stu = state.stu.filter(item => item.id !== id) } return { state, removeItem } } //添加 function useAdd(state) { let stuObj = reactive({ id: "", name: "" }) function addItem() { state.stu.push({ id: this.stuObj.id, name: this.stuObj.name }) this.stuObj.id = ""; this.stuObj.name = "" } return { stuObj, addItem, } } </script>

    setup的注意点

    就是setup() 函数是介于beforeCreate() 之前 此时还没有初始化好数据 无法拿到vue实例中的methods data但是vue为了避免我们错误使用,他直接把this修改为underfinesetup()只能是同步的,不能是异步的!

    reactive方法

    作用:实现复杂数据的响应式方法 而在vue2.x版本之中使用的是,Object.defineProperty()数据的劫持而vue3.0之中使用的proxy()来实现的 注意点: reactive的参数必须为一个对象(json/arr)如果给reactive传递了其他对象 默认情况下修改对象,界面不会自动刷新如果想更新,可以通过重新赋值的方式 <template> <div> <p>{{state.count }}</p> <button @click="add">+1按钮</button> <p>{{ state.time }}</p> <button @click="addTime">时间按钮</button> </div> </template> <script> import { reactive } from "vue"; export default { name: "App", setup() { //创建一个响应式数据 //传入一个对象,这个对象转化为proxy对象 // let state = reactive(123)//由于传递是 非对象形式,无法作为响应式数据 因此 add() 触发的时候,不会修改视图 let state = reactive({ count: 1, time: new Date() }) function add() { state.count++ } function addTime() { //传入了 其他对象 非 json arr 的 这需重新给这个数据赋值,否则不是响应式数据 let newTime = new Date(); newTime.setDate(state.time.getDate() + 1) state.time = newTime } return { state, add, addTime } } } </script>

    什么是ref函数

    作用:就是把简单数据类型转化为响应式数据本质:其实还是ref函数传递一个值之后,ref函数底层会自动将ref转化成reactive ref(18) -> reacyive({ value:18 })因此在修改的时候,需要使用 xxx.value = XXX在template中使用的使用的时候,不需要使用.value 直接使用 xxx即可 <template> <div> <p>{{age }}</p> <button @click="changeAge">按钮</button> </div> </template> <script> import { ref } from "vue"; export default { name: "App", setup() { let age = ref(1) function changeAge() { age.value = 123 } return { age, changeAge } } } </script>

    ref 和 reactive 的区别

    如果在template之中使用ref数据的时候,会自动帮我们添加.value

    如果在template之中使用reactive数据时候,不会自动帮我们添加.value

    Vue是如何决定是否添加.value的呢?

    Vue在解析数据之前,会先判断这个数据是否属于ref类型,是的话就添加.value,不是的话就不添加

    Vue怎么判断数据是ref类型?

    是通过当前数据的__V_ref来判断的如果有这个私有属性,并且取值为true,那么判断为ref类型,就添加.value自我判断 -> isRef isReactive 可以判断类型 isRef(XXX)isReactive(XXX)

    递归监听

    1:递归监听 默认情况下,不管是ref还是reactive都是递归监听 2:递归监听存在的问题 如果数据量比较大,非常消耗性能 因为每一次都是包装为proxy对象的形式 递归监听 <template> <div> <p>{{state.gf.a }}</p> <p>{{state.gf.f.b }}</p> <p>{{state.gf.f.s.c }}</p> <button @click="changeBtn">按钮</button> </div> </template> <script> import { reactive } from "vue"; export default { name: "App", setup() { let state = reactive({ gf: { a: "a", f: { b: "b", s: { c: "c" } } } }) function changeBtn() { state.gf.a = "1"; // state.value.gf.a = "1"; state.gf.f.b = "2"; state.gf.f.s.c = "3"; } return { state, changeBtn } } } </script> 3:非递归监听 只能监听第一层数据,不能监听内层的数据 <template> <div> <p>{{state.z }}</p> <p>{{state.gf.a }}</p> <p>{{state.gf.f.b }}</p> <p>{{state.gf.f.s.c }}</p> <button @click="changeBtn">按钮</button> </div> </template> <script> import { // reactive // shallowReactive shallowRef, triggerRef } from "vue"; export default { name: "App", setup() { // let state = reactive({ // let state = shallowReactive({ let state = shallowRef({ z: "z", gf: { a: "a", f: { b: "b", s: { c: "c" } } } }) function changeBtn() { // state.z = 0 // shallowReactive 非递归方式 监听 只有最外层不修改,那么后面就算数据修改了,监听不了,更新不了视图 // state.gf.a = "1"; // 这是ref方式修改值 state.value.gf.a = "1"; // state.gf.f.b = "2"; // state.gf.f.s.c = "3"; // console.log(state.gf); // shallowRef //注意点:如果是通过shallowRef创建的数据 //那么Vue监听的是 .value的变化,并不是第一层的变化 因此不会修改数据 // state.value.z = 0 // state.value.gf.a = "1"; // state.value.gf.f.b = "2"; // state.value.gf.f.s.c = "3"; // 监听 .value数据的变化,因此可以修改state数据 更新视图 // state.value = { // z: "0", // gf: { // a: "1", // f: { // b: "2", // s: { // c: "3" // } // } // } // } //triggerRef() 主动修改 第几层的数据后 主动调用数据 修改数据并且更新视图 state.value.gf.f.s.c = "33"; triggerRef(state) console.log(state); console.log(state.value.gf); } return { state, changeBtn } } } </script>

    toRaw函数

    作用:拿到原始数据,对原始数据进行修改 这样的话就不会被追踪了,也不会更新UI界面,使得性能更好! <template> <div> </div> </template> <script> import { ref, toRaw, reactive } from "vue"; export default { name: "App", setup() { let obj = { name: "ppp", age: 12 } //原始数据 let state = ref(obj) //响应式数据 let obj2 = toRaw(state) //获取到响应式数据的原始数据 console.log(obj === state); console.log(obj); //原始数据 {name: "ppp", age: 12 } console.log(state); //响应式数据 RefImpl {_rawValue: {…}, _shallow: false, __v_isRef: true, _value: Proxy} // 注意点:就是使用 toRaw拿到 ref类型的原始数据(创建时传入的那个数据) 那么需要告诉toRaw这个要拿的值是 .value console.log(obj2.value); //从响应式数据中拿到 原始数据 {name: "ppp", age: 12 } return { state, } } } </script>

    markRaw

    作用:就是实现数据不被追踪,数据修改后,但是界面不刷新! <template> <div> <p>{{state.name }}</p> <button @click="change">按钮</button> </div> </template> <script> import { reactive, markRaw } from "vue"; export default { name: "App", setup() { let obj = { name: "ppp", age: 12 } //原始数据 //设置 obj 这个数据 不被追踪 obj = markRaw(obj) let state = reactive(obj) function change() { state.name = "lll" console.log(state); //{name: "lll", age: 12, __v_skip: true} 但是由于设置数据不追踪,界面不刷新 还是之前的 ppp } return { state, change } } } </script>

    toRef函数

    作用:用来创建响应式数据的 <template> <div> <p>{{state }}</p> <button @click="change">按钮</button> </div> </template> <script> import { ref, reactive, toRef } from "vue"; export default { name: "App", setup() { let obj = { name: "ppp", age: 12 } //原始数据 //把name属性 设置为响应式数据 // let state = ref(obj.name) //使用toRef 修改为响应式数据 let state = toRef(obj, 'name') function change() { //结论:如果使用ref将一个对象中的属性变成响应式的数据 我们修改响应式的数据是不会影响到原始数据的! // state.value = "lll" //点击后 界面更新为 lll // console.log(obj); //{name: "ppp", age: 12} // console.log(state); //RefImpl {_rawValue: "lll", _shallow: false, __v_isRef: true, _value: "lll"} //结论:使用toRef方式来设置响应式数据,那么我们修改的响应式数据是会影响原始数据的 但是不会更新UI //应用场景:就是修改响应式数据之后,不让UI发送变化,那么就使用toRaw了 state.value = "lll" //点击后 界面更新为 lll console.log(obj); //{name: "lll", age: 12} console.log(state); //{ ... _object: {name: "lll", age: 12} } } return { state, change } } } </script>

    toRefs

    作用:把一个对象中的多个属性设置为响应式属性 <template> <div> <p>{{state }}</p> <button @click="change">按钮</button> </div> </template> <script> import { ref, reactive, toRefs } from "vue"; export default { name: "App", setup() { let obj = { name: "ppp", age: 12 } //原始数据 // 把obj中的设置为响应式属性 let state = toRefs(obj) function change() { state.value = "lll" //点击后 界面更新为 lll state.age = 30 console.log(obj); //{name: "ppp", age: 12} console.log(state); // {name: ObjectRefImpl, age: 30, value: "lll"} } return { state, change } } } </script>

    自定义一个ref

    <template> <div> <p>{{age }}</p> <button @click="change">按钮</button> </div> </template> <script> import { customRef, ref, } from "vue"; export default { name: "App", setup() { function myRef(value) { return customRef((track, trigger) => { return { get() { track() // 告诉Vue数据是需要追踪的 return value }, set(newValue) { trigger() //告诉Vue触发界面更新 value = newValue } } }) } let age = myRef(18); function change() { age.value++ } return { age, change } } } </script>

    ref获取页面的元素

    <template> <div> <div ref="box">我是</div> </div> </template> <script> import { ref, onMounted } from "vue"; export default { name: "App", setup() { let box = ref() //需要在生命周期中的 onMounted 拿到 onMounted(() => { console.log("onMounted", box.value); // onMounted <div ref="box">我是</div> }) return { box, } } } </script>
    Processed: 0.013, SQL: 8