• Vue3学习笔记(二)——组合式API(Composition API)


    一、常用 Composition API

    官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

    组合式 API (Composition API) 是一系列 API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它是一个概括性的术语,涵盖了以下方面的 API:

    • 响应式 API:例如 ref() 和 reactive(),使我们可以直接创建响应式状态、计算属性和侦听器。

    • 生命周期钩子:例如 onMounted() 和 onUnmounted(),使我们可以在组件各个生命周期阶段添加逻辑。

    • 依赖注入:例如 provide() 和 inject(),使我们可以在使用响应式 API 时,利用 Vue 的依赖注入系统。

    组合式 API 是 Vue 3 及 Vue 2.7 的内置功能。对于更老的 Vue 2 版本,可以使用官方维护的插件 @vue/composition-api。在 Vue 3 中,组合式 API 基本上都会配合 <script setup> 语法在单文件组件中使用。下面是一个使用组合式 API 的组件示例:

    <script setup>
    import { ref, onMounted } from 'vue'
    
    // 响应式状态
    const count = ref(0)
    
    // 更改状态、触发更新的函数
    function increment() {
      count.value++
    }
    
    // 生命周期钩子
    onMounted(() => {
      console.log(`计数器初始值为 ${count.value}。`)
    })
    </script>
    
    <template>
      <button @click="increment">点击了:{{ count }} 次</button>
    </template>

    虽然这套 API 的风格是基于函数的组合,但组合式 API 并不是函数式编程。组合式 API 是以 Vue 中数据可变的、细粒度的响应性系统为基础的,而函数式编程通常强调数据不可变。

    如果你对如何通过组合式 API 使用 Vue 感兴趣,可以通过页面左侧边栏上方的开关将 API 偏好切换到组合式 API,然后重新从头阅读指引。

    1.1.setup

    1. 理解:Vue3.0中一个新的配置项,值为一个函数。

    2. setup是所有Composition API(组合API)“ 表演的舞台 ”

    3. 组件中所用到的:数据、方法等等,均要配置在setup中。

    4. setup函数的两种返回值:

      1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)

      2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)

    5. 注意点:

      1. 尽量不要与Vue2.x配置混用

        • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。

        • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。

        • 如果有重名, setup优先。

      2. setup一般不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

    简化的setup函数:

    <template>
      <h2>{{ username }}</h2>
      <button @click="update">update</button>
    </template>
    
    <script lang="ts" setup>
    import { h } from "vue";
    let username = "tom";
    let update = () => {
      console.log("update2");
    };
    </script>
    <style></style>

    返回一个渲染函数,注意会替换页面中的内容,很少使用,慎用。

    <template>
      <h2>123</h2>
      <button>update</button>
    </template>
    
    <script lang="ts">
    import { h } from "vue";
    export default {
      setup() {
        let username = "tom";
        let update = () => {
          console.log("update");
        };
        return () => h("h2", "Hello Setup!");
      },
    };
    </script>
    
    <style></style>

    文本与按钮被替换了。

    Vue3兼容多数Vue2的内容

    <template>
      <h2>{{ user }}</h2>
      <button @click="change">更新</button>
    </template>
    
    <script lang="ts">
    import { h } from "vue";
    export default {
      data() {
        return {
          user: "tom",
        };
      },
      methods: {
        change() {
          this.user += "!";
        },
      },
    };
    </script>
    
    <style></style>

    可以混用,但不建议这么做

    <template>
      <h2>{{ user }}</h2>
      <button @click="change">更新姓名</button>
      <hr />
      <h2>{{ age }}</h2>
      <button @click="update">更新年龄</button>
    </template>
    
    <script lang="ts">
    import { ref } from "vue";
    export default {
      data() {
        return {
          user: "tom",
        };
      },
      methods: {
        change() {
          this.user += "!";
        },
      },
      setup() {
        let age = ref(18);
        let update = () => {
          age.value++;
        };
        return { age, update };
      },
    };
    </script>
    
    <style></style>

     Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。

    <template>
      <h2>{{ user }}</h2>
      <button @click="change">methods中的事件,访问setup中的成员</button>
    </template>
    
    <script lang="ts">
    import { ref } from "vue";
    export default {
      data() {
        return {
          user: "tom",
        };
      },
      methods: {
        change() {
          this.user += this.age;
        },
      },
      setup() {
        let age = 18;
        return { age };
      },
    };
    </script>
    
    <style></style>

     但在setup中不能访问到Vue2.x配置(data、methos、computed...)。

    <template>
      <h2>{{ user }}</h2>
      <button @click="update">setup中的事件,访问data中的数据</button>
    </template>
    
    <script lang="ts">
    import { ref } from "vue";
    export default {
      data() {
        return {
          user: "tom",
        };
      },
      setup() {
        function update(this: any) {
          this.user += "!!!";
          console.log(this.user);
        }
        return { update };
      },
    };
    </script>
    
    <style></style>

     

     如果有重名, setup优先。

    <template>
      <h2>{{ user }}</h2>
    </template>
    
    <script lang="ts">
    import { ref } from "vue";
    export default {
      data() {
        return {
          user: "tom",
        };
      },
      setup() {
        return { user: "jack" };
      },
    };
    </script>
    
    <style></style>

     setup一般不能是一个async函数

    <template>
      <h2>{{ user }}</h2>
    </template>
    
    <script lang="ts">
    import { ref } from "vue";
    export default {
      data() {
        return {
          user: "tom",
        };
      },
      setup() {
        async function f1() {
          return { a: 100 };
        }
    
        console.log(f1());
    
        return { user: "jack" };
      },
    };
    </script>
    
    <style></style>

    1.2.ref函数

    • 作用: 定义一个响应式的数据

    • 语法: const xxx = ref(initValue)

      • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)

      • JS中操作数据: xxx.value

      • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>

      • ref对应的接口是interface Ref<T>
    • 备注:

      • 接收的数据可以是:基本类型、也可以是对象类型,不建议使用对象数据类型。

      • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。

      • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

      • isRef 判断是不是一个ref对象

      • import { ref, Ref,isRef } from 'vue'
        let message: Ref<string | number> = ref("我是message")
        let notRef:number = 123
        const changeMsg = () => {
          message.value = "change msg"
          console.log(isRef(message)); //true
          console.log(isRef(notRef)); //false
          
        }

    下面的示例运行时没有更新姓名

    <template>
      <h2>姓名:{{ user }}</h2>
      <button @click="update">更新</button>
    </template>
    
    <script lang="ts">
    export default {
      setup() {
        let user = "tom";
        let update = () => {
          user += "!";
        };
        return { user, update };
      },
    };
    </script>
    
    <style></style>

    其实user的值是修改了,但是没有响应式的更新视图

     原因是此时的user是一个非响应式对象,使用ref可以解决问题

    <template>
      <h2>姓名:{{ user }}</h2>
      <button @click="update">更新</button>
    </template>
    
    <script lang="ts">
    import { ref } from "vue";
    
    export default {
      setup() {
        let user = ref("tom");
        let update = () => {
          user.value += "!";
          console.log(user);
        };
        return { user, update };
      },
    };
    </script>
    
    <style></style>

    1.3.reactive函数

    • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

    • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)

    • reactive定义的响应式数据是“深层次的”。

    • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

    基本使用:

    <template>
      <h2>{{ user.name }}</h2>
      <h2>{{ user.age }}</h2>
    </template>
    
    <script lang="ts" setup>
    import { reactive } from "vue";
    let user = reactive({
      name: "tom",
      age: 19,
    });
    //let name=reactive("abc");  错误的
    console.log(user);
    </script>
    
    <style></style>

    reactive定义的响应式数据是“深层次的”。

    <template>
      <h2>{{ user.name }}</h2>
      <h2>{{ user.age }}</h2>
      <h2>{{ user.address.city }}</h2>
      <button @click="update">更新用户的地址</button>
    </template>
    
    <script lang="ts" setup>
    import { reactive } from "vue";
    let user = reactive({
      name: "tom",
      age: 19,
      address: {
        city: "New York",
        country: "US",
      },
    });
    
    let update = () => {
      user.address.city += " city ";
    };
    </script>
    
    <style></style>

    1.4.Vue中的响应式原理

    1.4.1.vue2.x的响应式

    使用 Object 构造函数上的 defineProperty() 实现。

    1、vue2存在的问题

    新增属性、删除属性,界面不会更新。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <h2>{{user}}</h2>
          <button @click="attrOperation">新增属性、删除属性,界面不会更新</button>
        </div>
        <script src="./js/vue2.js"></script>
        <script>
          //新增属性、删除属性,界面不会更新。
          //直接通过下标修改数组,界面不会自动更新。
          var app = new Vue({
            el: "#app",
            data: function () {
              return {
                user: { name: "tom", age: 19, address: { city: "zhuhai" } },
              };
            },
            methods: {
              attrOperation() {
                //添加一个新属性
                this.user.address.province = "中国";
                //删除属性
                delete this.user.address.city;
                console.log(JSON.stringify(this.user));
              },
            },
          });
        </script>
      </body>
    </html>
    View Code

    直接通过下标修改数组,界面不会自动更新。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <h2>{{user}}</h2>
          <button @click="attrOperation">新增属性、删除属性,界面不会更新</button>
          <hr />
          <h2>{{array}}</h2>
          <button @click="arrayOperation">
            直接通过下标修改数组,界面不会自动更新。
          </button>
        </div>
        <script src="./js/vue2.js"></script>
        <script>
          //新增属性、删除属性,界面不会更新。
          //直接通过下标修改数组,界面不会自动更新。
          var app = new Vue({
            el: "#app",
            data: function () {
              return {
                user: { name: "tom", age: 19, address: { city: "zhuhai" } },
                array: [1, 2, 3],
              };
            },
            methods: {
              attrOperation() {
                //添加一个新属性
                this.user.address.province = "中国";
                //删除属性
                delete this.user.address.city;
                console.log(JSON.stringify(this.user));
              },
              arrayOperation() {
                this.array[0] = 100;
                console.log(this.array);
              },
            },
          });
        </script>
      </body>
    </html>
    View Code

    注意:如果要解决上述问题

    • ① 使用 vue 实例对象上的 $set 进行添加或修改,$delete 进行删除。
    • ② 使用 Vue.set 进行添加或修改,Vue.delete 进行删除。
    • ③ 使用 vue 实例对象上的 $nextTick 进行页面更新。
    • ④ 使用数组的一些方法对数组进行操作(如 splice() )。

    2、实现响应式的原理

    对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。

    数组类型:通过重写更新数组的方法来实现拦截(对数组的变更方法进行了包裹)。

    (1)Object.defineProperty() 的基本使用

    Object.defineProperty("对象", "属性", {
        value: 0, // 属性值
        enumerable: true, // 属性是否可被枚举,默认 false
        writable: true, // 属性是否可被修改,默认 false
        configurable: true, // 属性是否可被删除,默认 false
        get() {}, // 获取属性值时调用,此函数需返回属性的属性值
        set(value) {}, // 修改属性值时调用,value为修改后的值
    })
          var user = {};
    
          //定义属性
          Object.defineProperty(user, "name", {
            value: "tom",
          });
    
          console.log(user);

    使用defineProperty重新定义属性name,访问时更新界面

          var user = {};
          var name = "";
    
          //定义属性
          Object.defineProperty(user, "name", {
            get() {
              console.log("更新界面,get");
              return name;
            },
            set(value) {
              console.log("更新界面,set");
              name = value;
            },
          });
    
          user.name = "mark";
          console.log(user.name);

    使用闭包调用参数中的value,不再定义外部变量

          var user = { name: "jack" };
    
          //定义属性
          function defineProp(target, key, value) {
            Object.defineProperty(target, key, {
              get() {
                console.log("更新界面,get");
                return value;
              },
              set(newValue) {
                console.log("更新界面,set");
                value = newValue;
              },
            });
          }
    
          //重新定义name
          defineProp(user, "name", user.name);
    
          user.name = "mark";
          console.log(user.name);

    上面的代码还是有缺陷,如果变量比较多,则需要一个个定义,麻烦,可以使用循环 

         var user = { name: "jack", age: 19, sex: "男" };
    
          //定义属性
          function defineProp(target, key, value) {
            Object.defineProperty(target, key, {
              get() {
                console.log("更新界面,get", target, key, value);
                return value;
              },
              set(newValue) {
                console.log("更新界面,set", target, key, value);
                value = newValue;
              },
            });
          }
    
          function observer(target) {
            if (typeof target !== "object" || target === null) {
              return target;
            }
            //遍历对象中所有的key
            for (let key in target) {
              defineProp(target, key, target[key]); //重新定义属性
            }
          }
    
          observer(user);
    
          user.name = "rose";
          user.age = 17;
          user.sex = "female";
          console.log(user.name);
          console.log(user.age);
          console.log(user.sex);

    上面的实现还是有问题,不能深层次的对象进入监听,例如:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <script>
          var user = {
            name: "jack",
            age: 19,
            sex: "男",
            address: { city: "zhuhai" },
          };
    
          //定义属性
          function defineProp(target, key, value) {
            Object.defineProperty(target, key, {
              get() {
                console.log("更新界面,get", target, key, value);
                return value;
              },
              set(newValue) {
                console.log("更新界面,set", target, key, value);
                value = newValue;
              },
            });
          }
    
          function observer(target) {
            if (typeof target !== "object" || target === null) {
              return target;
            }
            //遍历对象中所有的key
            for (let key in target) {
              console.log(key);
              defineProp(target, key, target[key]); //重新定义属性
            }
          }
    
          observer(user);
          user.address.city = "珠海";
          console.log(user.address.city);
        </script>
      </body>
    </html>
    View Code

     

    没有重新定义city,set时没有被拦截,使用递归可以解决该问题,完整代码

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <script>
          //重新定义对象
          function observer(target) {
            //如果被观察的对象不是object类型或为null则直接返回,不能监听
            if (typeof target !== "object" || target === null) {
              return target;
            }
            //遍历对象中所有的key
            for (let key in target) {
              console.log(key);
              defineProp(target, key, target[key]); //重新定义属性
            }
          }
    
          //定义属性
          function defineProp(target, key, value) {
            //如果value仍然是object类型,则继续拆开监听,递归
            if (typeof value === "object") {
              observer(value);
            } else {
              Object.defineProperty(target, key, {
                get() {
                  console.log("更新界面,get", target, key, value);
                  return value;
                },
                set(newValue) {
                  console.log("更新界面,set", target, key, value, newValue);
                  value = newValue;
                },
              });
            }
          }
    
          //原始对象
          var user = {
            name: "jack",
            age: 19,
            sex: "男",
            address: { city: "zhuhai" },
          };
          //重新定义对象中的所有属性,拦截get与set操作,插入要执行的用户逻辑
          observer(user);
    
          user.address.city = "珠海";
          console.log(user.address.city);
        </script>
      </body>
    </html>

    (2)实现代码

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <script src="./js/vue3.js"></script>
        <script>
          function updateView(target, key, newValue) {
            console.log("视图更新了!", target, key, newValue);
          }
    
          function observer(target) {
            //如果被观察的对象不是对象类,或为null,则直接返回原对象
            if (typeof target !== "object" || target === null) {
              return target;
            }
            //遍历出对象的所有的key
            for (let key in target) {
              //定义属性,覆盖原有属性
              defineReactive(target, key, target[key]);
            }
          }
    
          //在目标对象上定义属性,目标对象,属性名,值
          function defineReactive(target, key, value) {
            //如果被定义的值是对象类型则继续观察
            if (typeof value === "object") {
              observer(value);
            } else {
              //value在这里起到了闭包保留值的作用
              Object.defineProperty(target, key, {
                get() {
                  return value;
                },
                set(newValue) {
                  if (newValue !== value) {
                    updateView(target, key, newValue);
                    value = newValue;
                  }
                },
              });
            }
          }
    
          let person = {
            name: "张果",
            address: {
              province: "广东",
              city: "珠海",
            },
          };
          observer(person);
          person.name = "张三";
          person.address.city = "深圳";
          console.log(person);
        </script>
      </body>
    </html>

    1.4.2.Vue3响应式原理

    (1)、反射 Reflect

    Reflect是ES6中新增加的一个对象,并非构造器,该对象中含有多个可完成"元编程(对编程语言进行编程)"功能的静态函数,能方便的对对象进行操作,也可以结合Proxy实现拦截功能

    https://www.cnblogs.com/best/p/16291079.html#_lab2_4_0

    (2)、代理Proxy

    浅层代理:

          //定义被代理的对象
          var shape = {  100 };
    
          //定义代理代理
          let proxy = new Proxy(shape, {
            get: function (target, key, receiver) {
              console.log("get");
              //输出被代理的目标对象,属性名称,receiver为getter调用时的this值(当前对象)
              console.log(target, key, receiver);
              //使用get方法从目标对象中获取值,把取得的值加100
              return Reflect.get(target, key, receiver) + 100;
            },
    
            set: function (target, key, value, receiver) {
              console.log("set");
              //输出被代理的目标对象,属性名称,值,receiver为getter调用时的this值(当前对象)
              console.log(target, key, value, receiver);
    
              //在目标对象上设置属性值,设置值时将值加100
              return Reflect.set(target, key, value + 100, receiver);
            },
            deleteProperty: function (target, key, receiver) {
              console.log("delete");
              console.log(target, key);
              return Reflect.deleteProperty(target, key, receiver);
            },
          });
    
          proxy.width = 101;
          console.log(proxy.width);
          delete proxy.width;

    深层代理 

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <script>
          //定义被代理的对象
          var shape = {  100, location: { x: 100, y: 200 } };
    
          function reactive(target) {
            return createReactiveObject(target);
          }
    
          function isObject(target) {
            return typeof target === "object" && target !== null;
          }
    
          function createReactiveObject(target) {
            if (!isObject(target)) return target;
            return new Proxy(target, {
              get: function (target, key, receiver) {
                console.log("get");
                //输出被代理的目标对象,属性名称,receiver为getter调用时的this值(当前对象)
                console.log(target, key, receiver);
                //使用get方法从目标对象中获取值
                let result = Reflect.get(target, key, receiver);
                //递归多层代理,相比于vue2的优势是,vue2默认递归,而vue3中,只要不使用就不会递归。
                return isObject(result) ? reactive(result) : result;
              },
    
              set: function (target, key, value, receiver) {
                console.log("set");
                //输出被代理的目标对象,属性名称,值,receiver为getter调用时的this值(当前对象)
                console.log(target, key, value, receiver);
    
                //在目标对象上设置属性值
                return Reflect.set(target, key, value, receiver);
              },
              deleteProperty: function (target, key, receiver) {
                console.log("delete");
                console.log(target, key);
                return Reflect.deleteProperty(target, key, receiver);
              },
            });
          }
    
          let proxyShape = reactive(shape);
          proxyShape.location.x = 500;
        </script>
      </body>
    </html>

    Vue3中的响应式方式解决Vue2中没有解决的问题:

          //新增加属性是响应式的
          userProxy.sex = "女";
          //删除属性是响应式的
          delete userProxy.sex;
          //修改数组中的指定元素是响应式的
          userProxy.friends = ["a", "b", "c"];
          userProxy.friends[0] = "A";

    总结:

    • vue2使用Object.defineProperty()实现响应式原理,而vue3使用Proxy()实现。
    • 虽然vue2,vue3面对对象嵌套,都需要递归,但vue2是对对象的所有属性进行递归,vue3是按需递归,如果没有使用到内部对象的属性,就不需要递归,性能更好。
    • vue2中,对象不存在的属性是不能被拦截的。而vue3可以。
    • vue2对数组的实现是重写数组的所有方法,并改变,vue2中,数组的原型来实现,而Proxy则可以轻松实现。而且vue2中改变数组的长度是无效的,无法做到响应式,但vue3可以。

    1.5.reactive对比ref

    • 从定义数据角度对比:

      • ref用来定义:基本类型数据

      • reactive用来定义:对象(或数组)类型数据

      • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象

    • 从原理角度对比:

      • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。

      • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。

    • 从使用角度对比:

      • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value

      • reactive定义的数据:操作数据与读取数据:均不需要.value

    1.6.setup的两个注意点

    • setup执行的时机

      • 在beforeCreate之前执行一次,this是undefined。

    • setup的参数

      • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。

      • context:上下文对象

        • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs

        • slots: 收到的插槽内容, 相当于 this.$slots

        • emit: 分发自定义事件的函数, 相当于 this.$emit

    1.7.计算属性与监视

    1.7.1.computed函数

    • 模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。用计算属性来描述依赖响应式状态的复杂逻辑
    • 与Vue2.x中computed配置功能一致

    • 写法

      import {computed} from 'vue'

      setup(){
         ...
      //计算属性——简写
         let fullName = computed(()=>{
             return person.firstName + '-' + person.lastName
        })
         //计算属性——完整
         let fullName = computed({
             get(){
                 return person.firstName + '-' + person.lastName
            },
             set(value){
                 const nameArr = value.split('-')
                 person.firstName = nameArr[0]
                 person.lastName = nameArr[1]
            }
        })
      }
    <template>
      <p>姓氏:<input v-model="user.lastname" /></p>
      <p>名字:<input v-model="user.firstname" /></p>
      <p>姓名:<input v-model="fullname1" /></p>
      <p>姓名:<input v-model="fullname2" /></p>
    </template>
    
    <script lang="ts">
    import { reactive, ref, computed } from "vue";
    
    export default {
      setup() {
        let user = reactive({
          firstname: "jack",
          lastname: "ma",
        });
        let fullname1 = computed(() => {
          return user.firstname + " " + user.lastname;
        });
    
        let fullname2 = computed({
          get() {
            let result = user.firstname;
            if (user.lastname) {
              result += " " + user.lastname;
            }
            return result;
          },
          set(value) {
            if (value) {
              let users = value.trim().split(" ");
              user.firstname = users[0];
              user.lastname = users[1] || "";
            } else {
              user.firstname = "";
              user.lastname = "";
            }
          },
        });
    
        return { user, fullname1, fullname2 };
      },
    };
    </script>
    
    <style></style>

    运行效果

    计算属性的第3种写法(类似方法)

    <template>
      <h2>
        {{ formatPrice(money, 2) }}
      </h2>
      <h2>
        {{ myPrice(money, 2) }}
      </h2>
    </template>
    
    <script lang="ts">
    import { ref, computed } from "vue";
    
    export default {
      setup() {
        let money = ref(1995.376);
    
        function formatPrice(money: number, length: number) {
          return "¥" + money.toFixed(length);
        }
    
        let myPrice = computed(() => {
          return (money: number, length: number) => {
            return "¥" + money.toFixed(length);
          };
        });
    
        return { money, formatPrice, myPrice };
      },
    };
    </script>
    <style></style>

    若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。

    1.7.2.watch函数

    • 与Vue2.x中watch配置功能一致

    • 两个小“坑”:

      • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。

      • 监视reactive定义的响应式数据中某个属性时:deep配置有效。

      //情况一:监视ref定义的响应式数据
      watch(sum,(newValue,oldValue)=>{
      console.log('sum变化了',newValue,oldValue)
      },{immediate:true})

      //情况二:监视多个ref定义的响应式数据
      watch([sum,msg],(newValue,oldValue)=>{
      console.log('sum或msg变化了',newValue,oldValue)
      })

      /* 情况三:监视reactive定义的响应式数据
      若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
      若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
      */
      watch(person,(newValue,oldValue)=>{
      console.log('person变化了',newValue,oldValue)
      },{immediate:true,deep:false}) //此处的deep配置不再奏效

      //情况四:监视reactive定义的响应式数据中的某个属性
      watch(()=>person.job,(newValue,oldValue)=>{
      console.log('person的job变化了',newValue,oldValue)
      },{immediate:true,deep:true})

      //情况五:监视reactive定义的响应式数据中的某些属性
      watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
      console.log('person的job变化了',newValue,oldValue)
      },{immediate:true,deep:true})

      //特殊情况
      watch(()=>person.job,(newValue,oldValue)=>{
         console.log('person的job变化了',newValue,oldValue)
      },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效

    1.7.3.watchEffect函数

    • watch的套路是:既要指明监视的属性,也要指明监视的回调。

    • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

    • watchEffect有点像computed:

      • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。

      • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

      //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
      watchEffect(()=>{
         const x1 = sum.value
         const x2 = person.age
         console.log('watchEffect配置的回调执行了')
      })
    •     let stop = watchEffect(() => {
            console.log(a.value);
            console.log("a修改了");
          });
      调用stop方法可以停止监视

    1.7.4、比较

    四者的区别:

    计算属性computed:计算属性只能当作属性用,也能带参数(不建议用),有缓存,效率高,,可以直接与v-model绑定。

    方法:方法可以直接调用,可带参数,没有缓存,每次调用都会执行,效率不如计算属性高,不可与v-model绑定。

    watch:不能显式调用(被监视的对象变化时被动调用),可以对变化的控制更加具体,但应用复杂,可以间接与v-model绑定。

    watchEffect:不需要指定监视对象,自动推断出要监视的成员,默认立即监视一次

    <template>
      <button @click="msg = msg ? 'ok' : 'no'">
        {{ (msg + "!!!").split("").reverse().join("") }}
      </button>
    
      <h2>
        {{ `消息内容是:${msg}` }}
      </h2>
    
      <input v-model="msg" />
    
      <h2>
        {{ showInfo() }}
      </h2>
    
      <h2>
        {{ showInfoComputed }}
      </h2>
    </template>
    
    <script lang="ts">
    import { computed } from "@vue/reactivity";
    import { ref, getCurrentInstance } from "vue";
    export default {
      setup() {
        let msg = ref("这是一个按钮");
        function showInfo() {
          console.log("showInfo被调用了");
          return "showinfo的返回值";
        }
        let showInfoComputed = computed(() => {
          console.log("showInfoComputed被调用了");
          return "showInfoComputed的返回值";
        });
        return { msg, showInfo, showInfoComputed };
      },
    };
    </script>

    1.8.toRef

    • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。

    • 语法:const name = toRef(person,'name')

    • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。

    • 直接取出值,是非响应式的:
    • <template>
        <h2>姓名:{{ name }}</h2>
        <h2>薪水:{{ salary }}</h2>
        <button @click="name += '~'">改名</button>
        <button @click="salary++">加薪</button>
      </template>
      
      <script lang="ts">
      import { ref, reactive, getCurrentInstance, watchEffect } from "vue";
      
      export default {
        setup() {
          let user = reactive({ name: "tom", job: { salary: 20 } });
          return { name: user.name, salary: user.job.salary };
        },
      };
      </script>
      <style></style>

      再次包装虽然是响应式的,但已不再是同一个对象

    • <template>
        <h4>{{ user }}</h4>
        <h2>姓名:{{ name }}</h2>
        <h2>薪水:{{ salary }}</h2>
        <button @click="name += '~'">改名</button>
        <button @click="salary++">加薪</button>
      </template>
      
      <script lang="ts">
      import { ref, reactive, getCurrentInstance, watchEffect } from "vue";
      
      export default {
        setup() {
          let user = reactive({ name: "tom", job: { salary: 20 } });
          return { user, name: ref(user.name), salary: ref(user.job.salary) };
        },
      };
      </script>
      <style></style>

    • <template>
        <h4>{{ user }}</h4>
        <h2>姓名:{{ name }}</h2>
        <h2>薪水:{{ salary }}</h2>
        <button @click="name += '~'">改名</button>
        <button @click="salary++">加薪</button>
      </template>
      
      <script lang="ts">
      import { ref, reactive, getCurrentInstance, watchEffect, toRef } from "vue";
      
      export default {
        setup() {
          let user = reactive({ name: "tom", job: { salary: 20 } });
          return {
            user,
            name: toRef(user, "name"),
            salary: toRef(user.job, "salary"),
          };
        },
      };
      </script>
      <style></style>

    1.9.toRefs

    • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

    • <template>
        <h4>{{ user }}</h4>
        <h2>姓名:{{ name }}</h2>
        <h2>薪水:{{ job.salary }}</h2>
        <button @click="name += '~'">改名</button>
        <button @click="job.salary++">加薪</button>
      </template>
      
      <script lang="ts">
      import {
        ref,
        reactive,
        getCurrentInstance,
        watchEffect,
        toRef,
        toRefs,
      } from "vue";
      
      export default {
        setup() {
          let user = reactive({ name: "tom", job: { salary: 20 } });
          return { user, ...toRefs(user) };
        },
      };
      </script>
      <style></style>

    二、其它 Composition API

    2.1.shallowReactive 与 shallowRef

    • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

    • <template>
        <div>
          <h1>姓名:{{name}}</h1>
          <h2>年龄:{{age}}</h2>
          <h3>喜欢的水果:{{likeFood.fruits.apple}}</h3>
          <button @click="name += '~'">修改姓名</button>
          <button @click="age++">修改年龄</button>
          <button @click="likeFood.fruits.apple += '!'">修改水果</button>
        </div>
      </template>
       
      <script>
      import {reactive, toRefs, shallowReactive} from 'vue'
      export default {
        name: "App",
        setup() {
          // 定义了一段数据
          let person = shallowReactive({    // 只将第一层数据做了响应式处理 
            name: '张三',
            age: 18,
            likeFood: {
              fruits:{
                apple: '苹果'               // 深层次的数据将会是一个普通的对象
              }
            }
          })
          // 将数据返回出去
          return {
            ...toRefs(person)
          }
        }
      };
      </script>
    • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

    • ...
        <div>
          <h1>姓名:{{ sum.n}}</h1>
          <button @click="sum.n++">点击+</button>
        </div>
      ...
      setup() {
          // 定义了一段数据
          let sum = shallowRef({      // sum将不在是一个响应式对象 
            n: 0
          });
          // 将数据返回出去
          return {
            sum,
          };
        },
      ...
    • 什么时候使用?

      • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。

      • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

    2.2.readonly 与 shallowReadonly

    • readonly: 让一个响应式数据变为只读的(深只读)。

    • shallowReadonly:让一个响应式数据变为只读的(浅只读)。

    • 应用场景: 不希望数据被修改时。

    • <template>
          <h4>当前的sum值是:{{sum}}</h4>
          <button @click="sum++">点我++</button>
          <hr>
          <h2>姓名:{{name}}</h2>
          <h2>年龄:{{age}}</h2>
          <h2>薪资:{{job.j1.salary}}</h2>
          <button @click="person.name+='~'">修改姓名</button>
          <button @click="person.age++">增长年龄</button>
          <button @click="person.job.j1.salary++">涨薪</button>
      </template>
      
      <script>
          import {reactive,ref,toRef,toRefs,readonly,shallowReadonly} from 'vue'
          export default { 
              name: 'Demo',
              setup(){
                  // 数据
                  let sum=ref(0)
                  let person=reactive({
                      name:'张三',
                      age:18,
                      job:{
                          j1:{
                              salary:20
                          }
                      }
                  })
                  sum=readonly(sum) 
                  // person=readonly(person) //所有的只读的,包括深层次的
                  person=shallowReadonly(person) //只有第一层的不让改,深层次的还可以改
                  // 返回一个对象(常用)  
                  return {
                      person,
                      sum,
                      ...toRefs(person)
                  }
              }
          }
      </script>

    2.3.toRaw 与 markRaw

    • toRaw:

      • 作用:将一个由reactive生成的响应式对象转为普通对象

      • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。

      • <template>
          <h3>{{ user }}</h3>
          <h2>姓名:{{ user.name }}</h2>
          <h2>薪水:{{ user.job.salary }}K</h2>
          <hr />
          <h2>姓名:{{ rawUser.name }}</h2>
          <h2>薪水:{{ rawUser.job.salary }}K</h2>
        
          <button @click="user.name += '@'">改名</button>
          <button @click="user.job.salary++">加薪</button>
        
          <button @click="updateRaw">rawUser改名</button>
          <button @click="rawUser.job.salary++">rawUser加薪</button>
        </template>
        
        <script lang="ts">
        import { reactive, toRaw } from "vue";
        
        export default {
          setup() {
            let user = reactive({ name: "tom", job: { salary: 20 } });
            let rawUser = toRaw(user);
        
            console.log("响应式对象", user);
            console.log("原始对象", rawUser);
        
            function updateRaw() {
              rawUser.name += "@";
              console.log(rawUser);
              console.log(rawUser.name === user.name);
            }
        
            return {
              user,
              rawUser,
              updateRaw,
            };
          },
        };
        </script>
        <style></style>
      • <template>
          <h2>n={{ n }}</h2>
          <button @click="n++">n++</button>
          <h2>姓名:{{ user.name }}</h2>
          <h2>年龄:{{ user.age }}</h2>
          <h2>薪水:{{ user.job.salary }}K</h2>
          <button @click="updateName">修改姓名</button>
          <button @click="user.age++">修改年龄</button>
          <button @click="user.job.salary++">修改薪水</button>
          <hr />
          <button @click="updateRawObject">修改raw对象</button>
        </template>
        
        <script lang="ts">
        import { reactive, ref, toRaw } from "vue";
        export default {
          setup() {
            let n = ref(0);
            let user = reactive({
              name: "rose",
              age: 99,
              job: {
                salary: 30,
              },
            });
        
            //toRaw可以将响应式对象变成普通对象
            //普通对象与响应式对象是关联关系,非深拷贝
            //当更新普通对象时,响应时对象也会更新,但不会更新界面
            //当更新响应式对象时,普通对象也会更新,却可以更新界面
            let u2 = toRaw(user);
            console.log(user, u2);
        
            function updateName() {
              user.name += "&";
              console.log(user, u2);
            }
        
            function updateRawObject() {
              u2.name += "$";
              console.log(user, u2);
            }
        
            return { user, n, updateName, updateRawObject };
          },
        };
        </script>

    • markRaw:

      • 作用:标记一个对象,使其永远不会再成为响应式对象。

      • 应用场景:

        1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。

        2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

      • <template>
          <h2>n={{ n }}</h2>
          <button @click="n++">n++</button>
          <h2>姓名:{{ user.name }}</h2>
          <h2>年龄:{{ user.age }}</h2>
          <h2>薪水:{{ user.job.salary }}K</h2>
          <button @click="user.name += '~'">修改姓名</button>
          <button @click="user.age++">修改年龄</button>
          <button @click="user.job.salary++">修改薪水</button>
          <div style="margin: 30px; background: #ddeeff">
            <fieldset v-if="user.position?.city">
              <h3>省:{{ user.position.province }}</h3>
              <h3>市:{{ user.position.city }}</h3>
              <button @click="user.position!.province += '#'">修改省</button>
              <button @click="user.position!.city += '#'">修改市</button>
            </fieldset>
          </div>
          <hr />
          <button @click="addPosition">添加位置信息</button>
        </template>
        <script lang="ts">
        //定义用户类型
        type UserInfo = {
          name: string;
          age: number;
          job: {
            salary: number;
          };
          position?: {
            province: string;
            city: string;
          };
        };
        
        import { reactive, ref, toRaw, markRaw } from "vue";
        export default {
          setup() {
            let n = ref(0);
            let user: UserInfo = reactive({
              name: "rose",
              age: 99,
              job: {
                salary: 30,
              },
            });
        
            function addPosition() {
              //添加一个新的对象position,将该对象标记为非响应式对象
              user.position = markRaw({
                province: "广东",
                city: "珠海",
              });
              console.log(user);
            }
        
            return { n, user, addPosition };
          },
        };
        </script>

    2.4、限流与防抖

    2.4.1、限流(节流)(规定时间内 只触发一次)

    在JS中,如果一个事件频繁触发(比如用户疯狂点击按钮)并且处理函数处理耗时还比较长,那么就容易造成性能问题。

    限流函数是针对这类问题的优化方式之一,它要求两次事件处理必须大于某个间隔时间,简而言之就是加了一层判断。

    限流函数(throttle:节流阀)的核心在于内部维护了一个“上次执行时间点”,通过比较当前执行时间与上次执行时间的差值判断是否“频繁”,是否执行。限流函数本身是一个装饰函数,修饰了事件处理器之后返回一个新的闭包函数。经过限流函数处理之后,事件触发的频率就被限制为预先传入的interval之上了。

    没有限流的一段登录代码,模拟登录:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <button id="btnLogin">登录</button>
        <script>
          document.getElementById("btnLogin").addEventListener(
            "click",
            (event) => {
              console.log("向服务器请求登录");
            },
            false
          );
        </script>
      </body>
    </html>

    写一个限流函数解决限流问题:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <button id="btnLogin">登录</button>
        <script>
          //节流函数,在interval时间内将要执行的fn函数
          function throttle(fn, interval) {
            //上一次函数被调用的时间
            let last = 0;
            return function () {
              //上下文
              let ctx = this;
              //参数
              let args = arguments;
    
              //当前时间
              let now = new Date();
              if (now - last > interval) {
                fn.apply(ctx, args); //执行函数
                last = now; //重要计时
              }
            };
          }
    
          document.getElementById("btnLogin").addEventListener(
            "click",
            throttle((event) => {
              console.log("向服务器请求登录");
            }, 2000),
            false
          );
        </script>
      </body>
    </html>

    连接点击数十下的效果:

    节流策略(throttle),控制事件发生的频率,如控制为1s发生一次,甚至1分钟发生一次。与服务端(server)及网关(gateway)控制的限流 (Rate Limit) 类似。

    作用: 高频率触发的事件,在指定的单位时间内,只响应第一次。

    节流的应用场景
    鼠标连续不断地触发某事件(如点击),单位时间内只触发一次;

    监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断。例如:懒加载;

    浏览器播放事件,每个一秒计算一次进度信息等

    2.4.2、防抖(多次触发,只执行最后一次)

    防抖函数也是一种限流函数,但要特殊一些。最典型的场景是表单输入,如果我们要在表单中监听input事件(比如远程搜索),那用户在输入的时候也会频繁触发,但这里使用throttle函数不行,因为我们需要等待用户停止输入一段时间后才能确认用户输入的值,所以要定义一个新的限流函数,叫做防抖函数。

    防抖(防反跳)函数的核心是内部使用定时器并维护定时器返回的ID值,如果持续触发则不断clearTimeout()并重新发起setTimeout(),通过这种方式等待事件触发完毕,然后进行延时处理。

    没有防抖带来的问题:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        用户名:<input id="username" />
        <script>
          document.getElementById("username").addEventListener(
            "keyup",
            (event) => {
              console.log(
                "用户名" + ["存在", "不存在"][new Date().getMilliseconds() % 2]
              );
            },
            false
          );
        </script>
      </body>
    </html>

     防抖函数:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <button id="btnLogin">登录</button>
        <script>
          //节流函数,在interval时间内将要执行的fn函数
          function throttle(fn, interval) {
            //上一次函数被调用的时间
            let last = 0;
            return function () {
              //上下文
              let ctx = this;
              //参数
              let args = arguments;
    
              //当前时间
              let now = new Date();
              if (now - last > interval) {
                fn.apply(ctx, args); //执行函数
                last = now; //重要计时
              }
            };
          }
    
          document.getElementById("btnLogin").addEventListener(
            "click",
            throttle((event) => {
              console.log("向服务器请求登录");
            }, 2000),
            false
          );
        </script>
      </body>
    </html>

    防抖策略(debounce)是当事件被触发后,延迟n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。

    作用: 高频率触发的事件,在指定的单位时间内,只响应最后一次,如果在指定的时间内再次触发,则重新计算时间。

    防抖的应用场景
    登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖

    调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖

    文本编辑器实时保存,当无任何更改操作一秒后进行保存

    2.5.customRef

    • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

    •  customRef 接收一个函数作为参数,这个函数接收两个函数作为参数  track (通知vue需要追踪后续内容的变化) 和  trigger (通知vue重新解析模板)。
    • <template>
        <input v-model="num" />
        {{ num }}
      </template>
      
      <script lang="ts">
      import { customRef } from "vue";
      
      export default {
        setup() {
          let myRef = function (value: any) {
            return customRef((track, trigger) => {
              return {
                get() {
                  track(); //告诉vue这个数据是要被追踪的
                  return value;
                },
                set(newValue) {
                  value = newValue;
                  trigger(); //通知vue更新界面
                },
              };
            });
          };
      
          let num = myRef(100);
      
          return { num };
        },
      };
      </script>
      
      <style scoped></style>
    • 实现防抖效果:

    • <template>
        卡号:<input v-model="msg" />
        <hr />
        {{ msg }}
      </template>
      <script lang="ts">
      import { customRef } from "vue";
      export default {
        setup() {
          //带防抖功能的自定义响应式函数
          let myDebounceRef = function (value: any, delay: number) {
            let timer: number = 0;
      
            return customRef((track, trigger) => {
              return {
                get() {
                  track(); //通知vue追踪该变量
                  return value;
                },
                set(newValue) {
                  if (timer) {
                    //如果上一个时钟存在,则清除,重新计时
                    clearTimeout(timer);
                  }
                  //延时赋值
                  timer = setTimeout(() => {
                    value = newValue;
                    trigger(); //更新视图
                    console.log("向服务器发送处理请求");
                  }, delay);
                },
              };
            });
          };
      
          let msg = myDebounceRef("", 1000);
          return { msg };
        },
      };
      </script>

    2.6.provide 与 inject

    • 作用:实现祖与后代组件间通信

    • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

    • 具体写法:

      1. 祖组件中:

        setup(){
        ......
           let car = reactive({name:'奔驰',price:'40万'})
           provide('car',car)
           ......
        }
      2. 后代组件中:

        setup(props,context){
        ......
           const car = inject('car')
           return {car}
        ......
        }

    2.7.响应式数据的判断

    • isRef: 检查一个值是否为一个 ref 对象

    • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理

    • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理

    • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

    三、Composition API 的优势

    3.1.Options API 存在的问题

    使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

    3.2.Composition API 的优势

    我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

    ​3.3、折叠代码

    折叠代码可以让代码更加有序,方便查找与管理

    <template></template>
    <script lang="ts" setup>
    import { customRef } from "vue";
    
    //#region 功能部分
      //#region 功能1
      //功能1的数据
      //功能1的方法
      //功能1的计算属性
      //#endregion
    
      //#region 功能2
      //功能2的数据
      //功能2的方法
      //功能2的计算属性
      //#endregion
    
      //#region 功能3
      //功能3的数据
      //功能3的方法
      //功能3的计算属性
      //#endregion
    //#endregion
    
    </script>

    七、示例下载

    https://gitee.com/zhangguo5/vue3_-chapter1.git

    八、视频

    【Vue3 + Vuex + Pinia + TypeScript + Router】 https://www.bilibili.com/video/BV1at4y1F75D?share_source=copy_web&vd_source=475a31f3c5d6353a782007cd4c638a8a

    九、作业

    1、使用方法,computed,watch,watchEffect实现两个数运算的效果,要求4种方法分开完成,加减乘除。

    2、完成每一个示例

    3、使用React完成每一个示例

    4、定义一个对象,如下:

    let u1={
          name: "rose",
          age: 99,
          job: {
            salary: 30,
          }
    }
    请使用两种以上的方法实现深拷贝。
    5、在u1对象中添加1个函数,并实现深拷贝
    6、请自定义一个cardRef,实现限制用户的输入的卡号长度为16位,每隔4位用空格自动隔开
    7、cardRef实现防抖功能,2000毫秒内只能向服务器发送一次查询卡号是否激活的状态,要求提供后台服务Spring Boot,模拟检查。
  • 相关阅读:
    搜索引擎ElasticSearch系列(四): ElasticSearch2.4.4 sql插件安装
    搜索引擎ElasticSearch系列(三): ElasticSearch2.4.4 bigdesk插件安装
    搜索引擎ElasticSearch系列(二): ElasticSearch2.4.4 Head插件安装
    搜索引擎ElasticSearch系列(一): ElasticSearch2.4.4环境搭建
    LumiSoft.Net 收发邮件
    搜索引擎Solr6.2.1 索引富文本(word/pdf/txt/html)
    用异或提取出数组中的单独数
    买卖股票的最佳时机 II
    二叉树的路径总和
    SpringBeanUtils的部分方法类
  • 原文地址:https://www.cnblogs.com/best/p/16798724.html
Copyright © 2020-2023  润新知