Vue 3.0 学习理解

1. Vue 3.0 开篇

1.1 Vue 3.0 六大亮点

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

1.2 Vue3.0是如何变快的

  • diff 算法优化
  • hoistStatic 静态提升
  • cacheHandlers 事件侦听器缓存
  • ssr 渲染

2. Vue 3.0 diff 算法

2.1 diff 算法优化

  • Vue2 中的虚拟dom是进行全量的对比

在这里插入图片描述

  • Vue3 新增了静态标记(PatchFlag)
    • 与上次虚拟节点进行对比时候只对比带有patch flag的节点
    • 并且可以通过 flag 的信息得知当前节点要对比的具体内容

在这里插入图片描述

<div>
    <p>张三</p>
    <p>张三</p>
    <p>张三</p>
    <p>{
    
    {
    
    msg}}}</p>
</div>

export function render(_ctx, _cache, $props, $setup, $data, $options) {
    
    
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "张三"),
    _createVNode("p", null, "张三"),
    _createVNode("p", null, "张三"),
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}

2.2 静态标记值

  • PatchFlags
export const enum PatchFlags {
    
    
  TEXT = 1,// 动态文本节点
  CLASS = 1 << 1, // 2  // 动态 class
  STYLE = 1 << 2, // 4 // 动态 style
  PROPS = 1 << 3, // 8 // 动态属性,但不包含类名和样式
  FULL_PROPS = 1 << 4, // 16 // 具有动态 key 属性,当 key 改变时,需要进行完整的 diff 比较。
  HYDRATE_EVENTS = 1 << 5, // 32 // 带有监听事件的节点
  STABLE_FRAGMENT = 1 << 6, // 64 // 一个不会改变子节点顺序的 fragment
  KEYED_FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或部分子字节有 key
  UNKEYED_FRAGMENT = 1 << 8, // 256 // 子节点没有 key 的 fragment
  NEED_PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比较
  DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot
  HOISTED = -1, // 静态节点
  // 指示在 diff 过程应该要退出优化模式
  BAIL = -2
}

3. Vue 3.0 静态提升和监听缓存

3.1 hoistStatic 静态提升

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

================================================================================

静态提升之前:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
    
    
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "张三"),
    _createVNode("p", null, "张三"),
    _createVNode("p", null, "张三"),
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}

================================================================================

静态提升之后:

const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "张三", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "张三", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "张三", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
    
    
  return (_openBlock(), _createBlock("div", null, [
    _hoisted_1,
    _hoisted_2,
    _hoisted_3,
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}

3.2 cacheHandlers 事件侦听器缓存

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

<div>
  <button @click="onClick">按钮</button>
</div>

================================================================================

开启事件监听缓存之前:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
    
    
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", {
    
     onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"])
  ]))
}

================================================================================

开启事件监听缓存之后:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
    
    
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", {
    
    
      onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
    }, "按钮")
  ]))
}

4. Vue 3.0 快速上手

4.1 创建Vue 3.0 的3种方式

  • Vue-CLI
  • Webpack
  • Vite

4.2 Vue-CLI 创建

npm install -g @vue/cli
vue create projectName
cd projectName
vue add vue-next
npm run serve

4.3 Webpack 创建

git clone https://github.com/vuejs/vue-next-webpack-preview.git projectName
cd projectName
npm install
npm run dev

4.4 Vite 创建

4.4.1 什么是 Vite
  • Vite 是 Vue 作者开发的一款意图取代 webpack 的工具
4.4.2 Vite 实现原理
  • 利用 ES6import发送请求去加载文件的特性
  • 拦截请求, 做一些预编译, 省去 webpack 冗长的打包时间
4.4.3 安装 Vite
  • npm install -g create-vite-app
4.4.4 利用 Vite 创建 Vue 3.0 项目
  • create-vite-app projectName
4.4.5 安装依赖运行项目
  • cd projectName
  • npm install
  • npm run dev

4.5 Vue 3.0 兼容 Vue 2.x

4.5.1 Vue 2.x 存在的问题
  • 数据与逻辑处理分开
  • 耗性能,维护不方便
<template>
  <div>
    <form>
      <input type="text" v-model="stu.id">
      <input type="text" v-model="stu.name">
      <input type="text" v-model="stu.age">
      <input type="submit" @click="addStu">
    </form>
    <ul>
      <li v-for="(stu, index) in stus"
      :key="stu.id"
      @click="remStu(index)">
        {
   
   {stu.name}} -- {
   
   {stu.age}}
      </li>
    </ul>
  </div>
</template>

<script>

export default {
     
     
  name: 'App',
  data:function () {
     
     
    return {
     
     
      stus:[
        {
     
     id:1, name:'zs', age:10},
        {
     
     id:2, name:'ls', age:20},
        {
     
     id:3, name:'ww', age:30},
      ],
      stu:{
     
     
        id:'',
        name:'',
        age:''
      }
      // 新增功能1的数据
      // 新增功能2的数据
    }
  },
  methods:{
     
     
    remStu(index){
     
     
      this.stus = this.stus.filter((stu, idx) => idx !== index);
    },
    addStu(e){
     
     
      e.preventDefault();
      const stu = Object.assign({
     
     }, this.stu);
      this.stus.push(stu);
      this.stu.id='';
      this.stu.name='';
      this.stu.age='';
    }
    // 新增功能1的业务逻辑
    // 新增功能2的业务逻辑
  },
  computed:{
     
     
    // 新增功能1的业务逻辑
    // 新增功能2的业务逻辑
  },
  watch:{
     
     
    // 新增功能1的业务逻辑
    // 新增功能2的业务逻辑
  }
}
</script>

<style>

</style>

5. Vue 3.0 组合 API

5.1 Composition API 基本用法

<template>
  <div>
    <p>{
   
   {count}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
     ref} from 'vue';
export default {
     
     
  name: 'App',
  // setup函数是组合API的入口函数
  setup(){
     
     
    // let count = 0;
    // 定义了一个名称叫做count变量, 这个变量的初始值是0
    // 这个变量发生改变之后, Vue会自动更新UI
    let count = ref(0);
    // 在组合API中, 如果想定义方法, 不用定义到methods中, 直接定义即可
    function myFn() {
     
     
      // alert(123);
      // console.log(count.value);
      count.value += 1;
    }
    // 注意点:
    // 在组合API中定义的变量/方法, 要想在外界使用, 必须通过return {xxx, xxx}暴露出去
    return{
     
     count, myFn}
  }
}
</script>

<style>

</style>

5.2 Composition API 抽取

<template>
  <div>
    <ul>
      <li v-for="(stu, index) in state.stus"
          :key="stu.id"
          @click="remStu(index)">
        {
   
   {stu.name}} - {
   
   {stu.age}}
      </li>
    </ul>
  </div>
</template>

<script>
import {
     
     reactive} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
    /*
    // ref函数注意点:
    // ref函数只能监听简单类型的变化, 不能监听复杂类型的变化(对象/数组)
    let state = reactive({
      stus:[
        {id:1, name:'zs', age:10},
        {id:2, name:'ls', age:20},
        {id:3, name:'ww', age:30},
      ]
    });
    function remStu(index) {
      state.stus = state.stus.filter((stu, idx) => idx !== index);
    }
     */
    let {
     
     state, remStu} = useRemoveStudent();

    return {
     
     state1, remStu}
  }
}
function useRemoveStudent() {
     
     
  // 将数据与逻辑处理放在一起 
  let state = reactive({
     
     
    stus:[
      {
     
     id:1, name:'zs', age:10},
      {
     
     id:2, name:'ls', age:20},
      {
     
     id:3, name:'ww', age:30},
    ]
  });
  function remStu(index) {
     
     
    state.stus = state.stus.filter((stu, idx) => idx !== index);
  }
  return {
     
     state, remStu};
}
</script>

<style>

</style>

5.3 Composition API 组合

  • App.vue
<template>
  <div>
    <form>
      <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="remStu(index)">
        {
   
   {stu.name}} - {
   
   {stu.age}}
      </li>
    </ul>
  </div>
</template>

<script>
import useRemoveStudent from './rem';
import useAddStudent from './add';
export default {
     
     
  name: 'App',
  setup() {
     
     
    let {
     
     state, remStu} = useRemoveStudent();
    let {
     
     state2, addStu} = useAddStudent(state);
    return {
     
     state, remStu, state2, addStu}
  }
}
/*
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}
}
function useRemoveStudent() {
  let state = reactive({
    stus:[
      {id:1, name:'zs', age:10},
      {id:2, name:'ls', age:20},
      {id:3, name:'ww', age:30},
    ]
  });
  function remStu(index) {
    state.stus = state.stus.filter((stu, idx) => idx !== index);
  }
  return {state, remStu};
}
*/
</script>

<style>

</style>
  • rem.js
import {
    
    reactive} from 'vue';
function useRemoveStudent() {
    
    
    let state = reactive({
    
    
        stus:[
            {
    
    id:1, name:'zs', age:10},
            {
    
    id:2, name:'ls', age:20},
            {
    
    id:3, name:'ww', age:30},
        ]
    });
    function remStu(index) {
    
    
        state.stus = state.stus.filter((stu, idx) => idx !== index);
    }
    return {
    
    state, remStu};
}
export default useRemoveStudent;
  • add.js
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;
  • 图解组合 API

在这里插入图片描述

5.4 Composition API 本质

  • Composition APIOption API 混合使用
  • Composition API 本质 (组合API/注入API)
    • 将暴露的数据注入到 data 中
    • 将暴露的方法注入到 methods 中
<template>
  <div>
    <p>{
   
   {name}}</p>
    <button @click="myFn1">按钮</button>
    <p>{
   
   {age}}</p>
    <button @click="myFn2">按钮</button>
  </div>
</template>

<script>
import {
     
     ref} from 'vue';
export default {
     
     
  name: 'App',
  data: function(){
     
     
    return {
     
     
      name: 'lnj',
      // age: 18
    }
  },
  methods:{
     
     
    myFn1(){
     
     
      alert('abc');
    },
    // myFn2() {
     
     
    //   alert('www.it666.com');
    // }
  },
  setup() {
     
     
    let age = ref(18);
    function myFn2() {
     
     
      alert('www.it666.com');
    }
    return {
     
     age, myFn2}
  }
}
</script>

<style>

</style>

6. setup 执行时机及注意点

6.1 setup 执行时机

  • setup
  • beforeCreate: 表示组件刚刚被创建出来, 组件的data和methods还没有初始化好
  • Created : 表示组件刚刚被创建出来, 并且组件的data和methods已经初始化好

6.2 setup 注意点

  • 由于在执行 setup 函数的时候, 还没有执行 Created 生命周期方法
    • 所以在 setup 函数中,是无法使用 data 和 methods
  • 由于不能在 setup 函数中使用 data 和 methods
    • 所以 Vue 为了避免错误的使用, 它直接将 setup 函数中 this 修改成了 undefined
  • setup 函数只能是同步的不能是异步的
<template>
  <div>
    <p>{
   
   {name}}</p>
    <button @click="myFn1">按钮</button>
    <p>{
   
   {age}}</p>
    <button @click="myFn2">按钮</button>
  </div>
</template>

<script>
import {
     
     ref} from 'vue';
export default {
     
     
  name: 'App',
  data: function(){
     
     
    return {
     
     
      name: 'lnj',
    }
  },
  methods:{
     
     
    myFn1(){
     
     
      alert('abc');
    },
  },
  // async setup() {
     
     
  setup() {
     
     
    let age = ref(18);
    function myFn2() {
     
     
      alert('www.it666.com');
    }

    // console.log(this); // undefined
    // console.log(this.name);
    // this.myFn1();
    return {
     
     age, myFn2}
  }
}
</script>

<style>

</style>

7. reactive 理解

7.1 什么是reactive?

  • reactive 是 Vue3 中提供的实现响应式数据的方法
  • Vue2 中响应式数据是通过 defineProperty 来实现
  • 在 Vue3 中响应式数据是通过 ES6 的 Proxy 来实现

7.2 reactive注意点

  • reactive 参数必须是对象(json/arr)
  • 如果给 reactive 传递了其它对象
    • 默认情况下修改对象, 界面不会自动更新
    • 如果想更新, 可以通过重新赋值的方式
<template>
  <div>
    <p>{
   
   {state.time}}</p>
    <button @click="myFn">按钮</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()
    });
    function myFn() {
     
     
      // 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.time);
    }
    return {
     
     state, myFn};
  }
}
</script>

<style>

</style>

8. ref 理解

8.1 什么是 ref

  • ref 和 reactive 一样, 也是用来实现响应式数据的方法
  • 由于 reactive 必须传递一个对象
    • 所以导致在企业开发中,如果我们只想让某个变量实现响应式的时候会非常麻烦
    • 所以 Vue3 就给我们提供了 ref 方法, 实现对简单值的监听

8.2 ref 本质

  • ref 底层的本质其实还是 reactive
  • 系统会自动根据我们给 ref 传入的值将它转换成
    • ref(xx) -> reactive({value:xx})

8.3 ref 注意点

  • 在 Vue 中使用 ref 的值不用通过 value 获取
  • 在 JS 中使用 ref 的值必须通过 value 获取
<template>
  <div>
<!--    <p>{
    
    {state.age}}</p>-->
    <!--
    注意点:
    如果是通过ref创建的数据, 那么在template中使用的时候不用通过.value来获取
    因为Vue会自动给我们添加.value
    -->
    <p>{
   
   {age}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
// import {reactive} from 'vue';
import {
     
     ref} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
    // let state = reactive({
     
     
    //   age: 18
    // })
    /*
    ref本质:
    ref本质其实还是reactive
    当我们给ref函数传递一个值之后, ref函数底层会自动将ref转换成reactive
    ref(18) -> reactive({value: 18})
    * */
    let age = ref(18);
    function myFn() {
     
     
      // state.age = 666;
      // age = 666;
      age.value = 666;
      console.log(age);
    }
    return {
     
     age, myFn}
  }
}
</script>

<style>

</style>

9. ref 和 reactive 区别

  • ref 和 reactive 区别

    • 如果在 template 里使用的是 ref 类型的数据, 那么 Vue 会自动帮我们添加 .value
    • 如果在 template 里使用的是 reactive 类型的数据, 那么 Vue 不会自动帮我们添加 .value
  • Vue 是如何决定是否需要自动添加 .value 的

    • Vue 在解析数据之前, 会自动判断这个数据是否是 ref 类型
    • 如果是就自动添加 .value, 如果不是就不自动添加 .value
  • Vue 是如何判断当前的数据是否是 ref 类型的

    • 通过当前数据的私有属性 __v_ref 来判断
    • 如果有这个私有的属性, 并且取值为 true, 那么就代表是一个 ref 类型的数据
  • 我们如何判断数据到底是 ref 还是 reactive

    • 通过 isRef / isReactive 方法
<template>
  <div>
    <p>{
   
   {age}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
  /*
  1.ref和reactive区别
  - 如果是reactive在template中不会自动添加.value

  2.reactive为什么不会自动添加.value?
  - 因为Vue在添加的时候首先会判断当前数据类型是ref还是reactive

  3.Vue如何判断?
  通过包装后的私有属性
  __v-isRef

  4.我们如何判断数据到底是ref还是reactive?
  通过isRef / isReactive 方法
  * */
  import {
     
     isRef, isReactive} from 'vue';
  import {
     
     reactive} from 'vue';
  // import {ref} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
    // ref(18) -> reactive({value: 18})
    // let age = ref(18);
    let age = reactive({
     
     value: 18});
    function myFn() {
     
     
        console.log(isRef(age));
        console.log(isReactive(age));
        age.value = 666;
    }
    return {
     
     age, myFn}
  }
}
</script>

<style>

</style>

10. 递归监听

10.1 递归监听

  • 默认情况下, 无论是通过 ref 还是 reactive 都是递归监听

10.2 递归监听存在的问题

  • 如果数据量比较大, 非常消耗性能
<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="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
     reactive} from 'vue';
// import {ref} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
    let state = reactive({
     
     
    // let state = ref({
     
     
        a:'a',
        gf:{
     
     
            b:'b',
            f:{
     
     
                c:'c',
                s:{
     
     
                    d:'d'
                }
            }
        }
    });
    function myFn() {
     
     
        // state.a = '1';
        // state.gf.b = '2';
        // state.gf.f.c = '3';
        // state.gf.f.s.d = '4';

        // state.value.a = '1';
        // state.value.gf.b = '2';
        // state.value.gf.f.c = '3';
        // state.value.gf.f.s.d = '4';

        console.log(state);
        console.log(state.gf);
        console.log(state.gf.f);
        console.log(state.gf.f.s);
    }
    return {
     
     state, myFn}
  }
}
</script>

<style>

</style>

11. 非递归监听

  • 如何触发非递归监听属性更新界面
    • 如果是 shallowRef 类型数据, 可以通过 triggerRef 来触发

11.1 应用场景

  • 一般情况下我们使用 ref 和 reactive 即可
  • 只有在需要监听的数据量比较大的时候, 我们才使用 shallowRef/shallowReactive
<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="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
     shallowReactive} from 'vue';
import {
     
     shallowRef, triggerRef} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
    // let state = shallowReactive({
     
     
    let state = shallowRef({
     
     
        a:'a',
        gf:{
     
     
            b:'b',
            f:{
     
     
                c:'c',
                s:{
     
     
                    d:'d'
                }
            }
        }
    });
    function myFn() {
     
     
        // state.a = '1';
        // state.gf.b = '2';
        // state.gf.f.c = '3';
        // state.gf.f.s.d = '4';
        //
        // console.log(state);
        // console.log(state.gf);
        // console.log(state.gf.f);
        // console.log(state.gf.f.s);

        // state.value = {
     
     
        //     a:'1',
        //     gf:{
     
     
        //         b:'2',
        //         f:{
     
     
        //             c:'3',
        //             s:{
     
     
        //                 d:'4'
        //             }
        //         }
        //     }
        // }

        // state.value.a = '1';
        // state.value.gf.b = '2';
        // state.value.gf.f.c = '3';
        // state.value.gf.f.s.d = '4';

        state.value.gf.f.s.d = '4';
        // 注意点: Vue3只提供了triggerRef方法, 没有提供triggerReactive方法
        //        所以如果是reactive类型的数据, 那么是无法主动触发界面更新的
        triggerRef(state);

        // 注意点: 如果是通过shallowRef创建数据,
        // 那么Vue监听的是.value的变化, 并不是第一层的变化
        console.log(state);
        console.log(state.value);
        console.log(state.value.gf);
        console.log(state.value.gf.f);
        console.log(state.value.gf.f.s);

    }
    return {
     
     state, myFn}
  }
}
</script>

<style>

</style>

12. shallowRef 本质

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

<script>
import {
     
     shallowReactive} from 'vue';
import {
     
     shallowRef} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
    // ref -> reactive
    // ref(10) ->  reactive({value:10})
    // shallowRef ->  shallowReactive
    // shallowRef(10)  ->  shallowReactive({value: 10})
    // 所以如果是通过shallowRef创建的数据, 它监听的是.value的变化
    // 因为底层本质上value才是第一层
    let state1 = shallowRef({
     
     
        a:'a',
        gf:{
     
     
            b:'b',
            f:{
     
     
                c:'c',
                s:{
     
     
                    d:'d'
                }
            }
        }
    });
    let state2 = shallowReactive({
     
     
          value: {
     
     
              a:'a',
              gf:{
     
     
                  b:'b',
                  f:{
     
     
                      c:'c',
                      s:{
     
     
                          d:'d'
                      }
                  }
              }
          }
      });

    function myFn() {
     
     

    }
    return {
     
     state, myFn}
  }
}
</script>

<style>

</style>

13. toRaw 理解

  • toRaw

    • 从 Reactive 或 Ref 中得到原始数据
  • toRaw 作用

    • 做一些不想被监听的事情(提升性能)
<template>
  <div>
      <p>{
   
   {state}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
     reactive, toRaw} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
      let obj = {
     
     name:'lnj', age:18};
      /*
      ref/reactive数据类型的特点:
      每次修改都会被追踪, 都会更新UI界面, 但是这样其实是非常消耗性能的
      所以如果我们有一些操作不需要追踪, 不需要更新UI界面, 那么这个时候,
      我们就可以通过toRaw方法拿到它的原始数据, 对原始数据进行修改
      这样就不会被追踪, 这样就不会更新UI界面, 这样性能就好了
      * */
      let state = reactive(obj);
      let obj2 = toRaw(state);
      // console.log(obj === obj2); // true

      // console.log(obj === state); // false
      // state和obj的关系:
      // 引用关系, state的本质是一个Proxy对象, 在这个Proxy对象中引用了obj

      function myFn() {
     
     
          // 如果直接修改obj, 那么是无法触发界面更新的
          // 只有通过包装之后的对象来修改, 才会触发界面的更新
          obj2.name = 'zs';
          console.log(obj2); // {name: "zs", age: 18}
          console.log(state); // {name: "zs", age: 18}
          // state.name = 'zs';
          // console.log(state);
      }
    return {
     
     state, myFn}
  }
}
</script>

<style>

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

<script>
  /*
  1.markRaw
  将数据标记为永远不能追踪的数据
  一般在编写自己的第三方库时使用
  * */
  import {
     
     reactive, toRaw, ref} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
      let obj = {
     
     name:'lnj', age:18};
      /*
      1.ref本质: reactive
      ref(obj) -> reactive({value: obj})
      * */
      let state = ref(obj);
      // 注意点: 如果想通过toRaw拿到ref类型的原始数据(创建时传入的那个数据)
      //        那么就必须明确的告诉toRaw方法, 要获取的是.value的值
      //        因为经过Vue处理之后, .value中保存的才是当初创建时传入的那个原始数据
      // let obj2 = toRaw(state);
      let obj2 = toRaw(state.value);

      console.log(obj);
      console.log(state);
      console.log(obj2);

      function myFn() {
     
     

      }
    return {
     
     state, myFn}
  }
}
</script>

<style>

</style>

14. markRaw 理解

  • 将数据标记为永远不能追踪的数据
  • 一般在编写自己的第三方库时使用
<template>
  <div>
      <p>{
   
   {state}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
     reactive, markRaw} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
      let obj = {
     
     name: 'lnj', age: 18};
      obj = markRaw(obj);
      let state = reactive(obj);
      function myFn() {
     
     
          state.name = 'zs';
      }
    return {
     
     state, myFn}
  }
}
</script>

<style>

</style>

15. toRef 理解

15.1 toRef

  • 创建一个 ref 类型数据, 并和以前的数据关联

15.2 toRef 和 ref 区别

  • ref
    • 创建出来的数据和以前无关(复制)
    • 数据变化会自动更新界面
  • toRef
    • 创建出来的数据和以前的有关(引用)
    • 数据变化不会自动更新界面
<template>
  <div>
      <p>{
   
   {state}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
     ref, toRef} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
      let obj = {
     
     name:'lnj'};
      /*
      ref(obj.name) -> ref(lnj)
      -> reactive({value:lnj})
      * */
      // ref->复制
      // let state = ref(obj.name);
      // toRef->引用
      /*
      ref和toRef区别:
      ref->复制, 修改响应式数据不会影响以前的数据
      toRef->引用, 修改响应式数据会影响以前的数据
      ref->数据发生改变, 界面就会自动更新
      toRef->数据发生改变, 界面也不会自动更新

      toRef应用场景:
      如果想让响应式数据和以前的数据关联起来, 并且更新响应式数据之后还不想更新UI, 那么就可以使用toRef
      * */
      let state = toRef(obj, 'name');

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

<style>

</style>

16. toRefs 理解

16.1 toRefs

  • 批量创建 ref 类型数据, 并和以前数据关联
<template>
  <div>
      <p>{
   
   {state}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
     ref, toRef, toRefs} from 'vue';
export default {
     
     
  name: 'App',
  setup() {
     
     
      let obj = {
     
     name:'lnj', age:18};
      // let name = toRef(obj, 'name');
      // let age = toRef(obj, 'age');
      let state = toRefs(obj);

      function myFn() {
     
     
          // name.value = 'zs';
          // age.value = 666;
          state.name.value = 'zs';
          state.age.value = 666;
          console.log(obj);
          console.log(state);
          // console.log(name);
          // console.log(age);
      }
    return {
     
     state, myFn}
  }
}
</script>

<style>

</style>

17. customRef 理解

  • customRef
    • 返回一个 ref 对象, 可以显式地控制依赖追踪和触发响应
<template>
  <div>
      <p>{
   
   {age}}</p>
      <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
      ref, customRef } from 'vue';
function myRef(value) {
     
     
	return customRef((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 myFn() {
     
     
      	age.value += 1;
      }
      return {
     
     age, myFn}
  }
}
</script>

<style>

</style>
<template>
  <div>
      <p>{
   
   {age}}</p>
      <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {
     
      ref, customRef } from 'vue';
function myRef(value) {
     
     
	return customRef((track, trigger)=>{
     
     
		fetch(value)
		      		.then((res)=>{
     
     
						return 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 函数: 只能是一个同步的函数,不能是一个异步的函数
  setup() {
     
     
  	  /*
  	  let state = ref([]);
      fetch('../public/data.json')
      		.then((res)=>{
				return res.json();
		    })
			.then((data)=>{
				console.log(data);
				state.value = data;
			})
			.catch((err)=>{
				console.log(err);
			})
		*/
	  let state = myRef('../public/data.json');
	  return {
     
     state};	
  }
}
</script>

<style>

</style>

18. ref 获取元素

18.1 获取元素

  • 在 Vue2.x 中我们可以通过给元素添加 ref='xxx'
  • 然后在代码中通过 refs.xxx 的方式来获取元素
  • 在 Vue3.0 中我们也可以通过 ref 来获取元素
<template>
  <div ref="box">我是div</div>
</template>

<script>
import {
     
      ref, onMounted } from 'vue';
export default {
     
     
  name: 'App',
  /*
  beforeCreate
  setup
  created
  */
  setup() {
     
     
  	// console.log(this.$refs.box);
  	let box = ref(null); // reactive({value: null})
  	
	onMounted(()=>{
     
     
		console.log('onMounted',box.value);
	})
	
	console.log(box.value); // null
  	return {
     
     box};
  }
}
</script>

<style>

</style>

19. readonly 家族

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

<script>
import {
     
      readonly, isReadonly, shallowReadonly } from 'vue'
export default {
     
     
  name: 'App',
  setup() {
     
     
  	// let state = readonly({name: 'lnj', attr:{age:18, height: 1.88}});
  	// let state = shallowReadonly({name:'lnj', attr:{age:18,height: 1.88}});
  	// const value = 345; 只读
  	const value = {
     
     name: 'zs', age:123};
  	function myFn(){
     
     
		state.name = 'zx';
		state.attr.age = 123;
		state.attr.height = 1.78;
		console.log(state);
		consloe.log(isReadonly(state));
		// value = 456;
		// console.log(value);
		value.name = 'ls';
		value.age = 456;
		console.log(value); // 能改变值
	}
  	return {
     
     state, myFn};
  }
}
</script>

<style>

</style>

20. Vue 3.0 响应式数据本质

20.1 Vue 3.0 响应式数据本质

  • Vue 2.x 中是通过 defineProperty 来实现响应式数据的
  • Vue 3.0 中是通过 Proxy 来实现响应式数据的
let obj = {
    
    name: 'lnj', age: 18};
let state = new Proxy(obj, {
    
    
	get(obj, key){
    
    
		console.log(obj, key); // { name: 'lnj', age: 18 } name
		return obj[key];
	},
	set(obj, key, value){
    
    
		console.log(obj, key, value ); // { name: 'lnj', age: 18 } name zx
		obj[key] = value;
		console.log('更新UI界面');
	}
});
// console.log(state.name); // lnj
state.name = 'zx';
console.log(state);

20.2 Proxy 注意点

  • set 方法必须通过返回值告诉 Proxy 此次操作是否成功
let arr = [1, 3, 5]; // [1, 3, 5, 7]
let state = new Proxy(arr, {
    
    
	get(obj, key){
    
    
		console.log(obj, key); // [1, 3, 5 ] 1
		return obj[key];
	},
	set(obj, key, value){
    
    
		// [1, 3, 5 ] 3 7
		// [1, 3, 5, 7 ] length 4
		console.log(obj, key, value ); 
		obj[key] = value;
		console.log('更新UI界面');
		return true;
	}
});

console.log(state[1]);
state.push(7);

21. 手写 shallowReactive——shallowRef 理解

  • shallowReactive
function shallowReactive(obj){
    
    
	return new Proxy(obj, {
    
    
		get(obj, key){
    
    
			return obj[key];
		},
		set(obj, key, val){
    
    
			obj[key] = val;
			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';
  • shallowRef
function shallowRef(val){
    
    
	return shallowReactive({
    
    value:val});
}

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

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

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
			}
		}
	}
}

22. 手写 reactive-ref 理解

  • reactive
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, {
    
    
			get(obj, key){
    
    
				return obj[key];
			},
			set(obj, key, val){
    
    
				obj[key] = val;
				console.log('更新UI界面');
				return true;
			}
		})
	}else{
    
    
		console.warn(`${
      
      obj} is not object`);
	}
}
/*
let obj = {
	a: 'a',
	gf:{
		b:'b',
		f;{
			c:'c',
			s:{
				d:'d'
			}
		}
	}
}

let state = reactive(obj)

state.a = 1;
state.gf.b = 2;
state.gf.f.c = 3;
state.gf.f.s.d = 4;
*/
let arr = [{
    
    id:1, name:'鲁班', attr:{
    
    age:18}}, {
    
    id:2, name:'虞姬'}]
let state = reactive(arr)
state[0].name='zx'
state[0].attr.age = 666
state[1].id = 3
  • ref
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);
				}
			}
		}

		return new Proxy(obj, {
    
    
			get(obj, key){
    
    
				return obj[key];
			},
			set(obj, key, val){
    
    
				obj[key] = val;
				console.log('更新UI界面');
				return true;
			}
		})
	}else{
    
    
		console.warn(`${
      
      obj} is not object`);
	}
}
/*
let obj = {
	a: 'a',
	gf:{
		b:'b',
		f;{
			c:'c',
			s:{
				d:'d'
			}
		}
	}
}

let state = reactive(obj)

state.a = 1;
state.gf.b = 2;
state.gf.f.c = 3;
state.gf.f.s.d = 4;
*/
let arr = [{
    
    id:1, name:'鲁班', attr:{
    
    age:18}}, {
    
    id:2, name:'虞姬'}]
let state = reactive(arr)
state[0].name='zx'
state[0].attr.age = 666
state[1].id = 3

23. 手写 readonly-shallowReadonly 理解

  • shallowReadonly
function shallowReadonly(obj){
    
    
	return new Proxy(obj, {
    
    
		get(obj, key){
    
    
			return obj[key];
		},
		set(obj, key, val){
    
    
			// obj[key] = val;
			// console.log('更新UI界面');
			// return true;
			console.warn(`${
      
      key}是只读的,不能赋值`);
		}
	})
}
let obj = {
    
    
	a: 'a',
	gf:{
    
    
		b:'b',
		f;{
    
    
			c:'c',
			s:{
    
    
				d:'d'
			}
		}
	}
}
let state = shallowReadonly(obj)
state.a = 1
state.gf.b = 2

猜你喜欢

转载自blog.csdn.net/qq_43645678/article/details/108805413