Vue3筑基—上篇

142 阅读10分钟

1、初识setup

  • 组件中所用到的:数据、方法等等,均要配置在setup中。
  • setup函数的两种返回值:
  1. 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点注意!!)
<template>
  <div>姓名:{{ name }} 年龄:{{ age }}</div>
  <button @click="sayHello" >点一点我试试</button>
</template>

<script>
export default {
  name: 'App',
  setup() {
    let name = '呆瓜'
    let age = 18
    function sayHello() {
      console.log(`大家好,我叫${name},我今年刚满${age}岁啦`)
    }
    return {
      name,
      age,
      sayHello
    }
  },
}
</script>
  1. 若返回一个渲染函数:则可以自定义渲染内容。
<template>
  <!-- 模板中内容不起效果-->
  <div>姓名:{{ name }} 年龄:{{ age }}</div>
  <button @click="sayHello" >点一点我试试</button>
</template>

<script>
import {h} from 'vue'
export default {
  name: 'App',
  setup() {
    let name = '呆瓜'
    let age = 18
    function sayHello() {
      console.log(`大家好,我叫${name},我今年刚满${age}岁啦`)
    }
    //模板写的东西不会起效果,而是以渲染函数的内容为主,页面只有-我今年刚满18啦
      return () => h('h1','我今年刚满18啦')
  },
}
</script>

备注:

  • 不要与vue2混用,Vue2.x配置(data、method、computed...)中可以访问setup中的属性、方法;
  • 但在setup中不能访问到Vue2.x配置(data、methods、computed...);
  • 如果有重名,谁写在后面,值就是谁的;
  • setup不能是一个async函数,因为返回值不再是return对象,而是promise,模板看不到return对象中的属性。(返回一个promise实例,需要Suspense和异步组件。(Vue3筑基—下篇 - 掘金 (juejin.cn))里面有Suspense的示例用法)

注意点

  • setup执行的事件,在beforeCreate之前执行一次,this是undefined
  • setup的参数
    props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    context:上下文对象
    {
        attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于vue2的this.$attrs
        slots:收到的插槽内容,相当于vue2的this.$slots
        emit:分发自定义事件的函数,相当于vue2的this.$emit
    }
//App.vue
<template>
  <Demo @hello="sayHello" msg="你好啊" name="小明" >
    <template  v-slot:qwe >
      <span>修仙进行中</span>
    </template>
  </Demo>
</template>

<script>
import Demo from './components/Demo.vue'
export default {
  name: 'App',
  components:{
    Demo
  },
  setup(){
    function sayHello(value){
      console.log(`你好,触发了hello事件,传递的参数是${value}`);
    }
    return {
      sayHello
    }
  }
}
//Demo.vue
<template>
    <div>
        <h3>姓名:{{ name }} 年龄:{{ person.age }}</h3>
        <h3>{{ name }} {{ msg }}</h3>
        <slot name="qwe" ></slot>
        <button @click="test" >测试出发demo组件的hello事件</button>
    </div>
    
  </template>
  
  <script>
  export default {
    // eslint-disable-next-line vue/multi-word-component-names
    name: 'Demo',
    props:['msg','name'],
    // emits:['hello'],
    setup(props,context) {
        //主要观察props和context的组成部分
        console.log(props,context);
      //数据
      let person = {
        age: 19,
        hobby:'唱跳rap篮球'
      }
      function test(){
        context.emit('hello',88888)
      }
      return {
        person,
        test
      }
    },
  }
  </script>
  

2、ref的基本使用

  • 作用:定义一个响应式数据
  • 语法:const xxx = ref(initValue)
    创建一个包含响应式数据的引用对象(reference对象)
    js中操作数据:xxx.value
    模板中读取数据:不需要value,直接:<div>{{xxx}}</div>
  • 备注:
    接收的数据可以式:基本类型、也可以式对象类型
    基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的
    对象类型的数据:内容“求助”了vue3.0中的一个新函数-reactive
<template>
  <!-- vue3组件模板中可以没有根标签 -->
  <div>姓名:{{ name }} 年龄:{{ age }}</div>
  <h4>工作种类:{{ job.type }}</h4>
  <h4>薪水:{{ job.salary }}</h4>
  <button @click="sayHello" >修改数据</button>
</template>

<script>
// import {h} from 'vue'
import { ref } from 'vue'
export default {
  name: 'App',
  setup() {
    //数据
    let name = ref('呆瓜') //RefImpl{get/set value}
    let age = ref(18) //RefImpl{get/set value}
    let job = ref({ 
      type:'筑基导师',
      salary:'10000灵石'
    }) //RefImpl{proxy value}
    //方法
    function sayHello() {
      console.log(name);
      name.value = '玉面狐狸'
      age.value = 28
      job.value.type='摆烂专家'
      console.log(job);
      job.value.salary = '20000灵石'
    }
    return {
      name,
      age,
      job,
      sayHello
    }
  },
}
</script>

3、reactive函数

  • 作用:定义一个对象类型的响应式数据(基本类型不要用它,用ref函数)
  • 语法:const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(proxy对象)
  • reactive定义的响应式数据是“深层次的”
  • 内部基于es6的Proxy实现,通过代理对象操作源对象内部数据进行操作。

<template>
  <!-- vue3组件模板中可以没有根标签 -->
  <div>姓名:{{ person.name }} 年龄:{{ person.age }}</div>
  <h4>工作种类:{{ person.job.type }}</h4>
  <h4>薪水:{{ person.job.salary }}</h4>
  <h4>爱好:{{ person.hobby }}</h4>
  <button @click="sayHello" >修改数据</button>
</template>

<script>
// import {h} from 'vue'
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    //数据
    let person = reactive({
      name:'呆瓜',
      age:18,
      job:{
        type:'筑基导师',
        salary:'10000灵石'
      },
      hobby:['吃饭','睡觉','打豆豆'],
    })
   
    //方法
    function sayHello() {
      person.name = '玉面狐狸'
      person.age = 28
      person.job.type='摆烂专家'
      person.job.salary = '20000灵石'
      person.hobby[0]='学习'
    }
    return {
      person,
      sayHello
    }
  },
}
</script>

4、Vue中的响应式原理

vue2.x的响应式

实现原理:
对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        let person = {
            name: '张三',
            age: '18'
        }
        let p = {}
        //模拟vue2响应式
        Object.defineProperty(p, 'name', {
            get() {
                //读取数据
                return person.name
            },
            set(value) {
                console.log(value);
                //修改数据
                console.log('修改数据,更新页面');
                person.name = value
            }
        })
        Object.defineProperty(p, 'age', {
            configurable: true,
            get() {
                //读取数据
                return person.age
            },
            set(value) {
                console.log(value);
                //修改数据
                console.log('修改数据,更新页面');
                person.age = value
            }
        })

    </script>
</body>

</html>

存在问题:
   新增属性、删除属性,界面不会更新。需要通过this.$set()才能进行新增。或Vue.set();删除通过this.$delete()或者Vue.delete()
   直接通过下标修改数组,界面不会自动更新。

vue3.0的响应式
实现原理:

  • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等
  • 通过Reflect(反射):对被代理对象(源对象)的属性进行操作。Reflect提供了很多方法,使用该方式可以更直接统一的管理对象。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        //window.Reflect 是 JavaScript 中的一个内置对象,它提供了一组静态方法,用于对对象进行反射操作。反射是指在运行时检查、获取和修改对象的属性和行为。
        // Reflect 对象的方法可以用于执行常见的对象操作,例如获取属性值、设置属性值、调用函数等。与传统的对象操作方法相比,Reflect 方法提供了一种更统一、更直观的方式来执行这些操作。

        // 以下是一些 Reflect 方法的示例:

        // Reflect.get(target, property):用于获取目标对象的指定属性的值。
        // Reflect.set(target, property, value):用于设置目标对象的指定属性的值。
        // Reflect.has(target, property):用于检查目标对象是否具有指定的属性。
        // Reflect.deleteProperty(target, property):用于删除目标对象的指定属性。
        // Reflect.apply(func, thisArg, args):用于调用函数并指定函数的上下文和参数。
        // Reflect.construct(target, args):用于创建一个目标对象的实例,并传递参数给构造函数。
        let person = {
            name: '张三',
            age: '20',
        }
        const p = new Proxy(person, {
            get(target, key) {
                console.log(`${key}被读取了`);
                // return target[key]
                return Reflect.get(target, key)
            },
            //set修改或者追加某个属性时调用
            //set可以对属性进行拦截、验证、自定义行为等操作,比如你可以去告诉页面,某个属性被修改了然后更新页面。
            set(target, key, value) {
                console.log(`${key}被修改了,更新界面去了`);
                // target[key] = value
                Reflect.set(target, key, value)
            },
            deleteProperty(target, key) {
                console.log(`${key}被删除了,更新界面去了`);
                //    return delete target[key]
                return Reflect.deleteProperty(target, key)

            }
        })
        console.log(p);
    </script>
</body>

</html>

5、reactive对比ref

  1. 数据类型:
  • ref主要用于定义基本数据类型,如数字、字符串等
  • reactive主要定义对象(数组)类型数据,内部会自动通过reactive转为代理对象。
  • 备注:ref也可以通过定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。
  1. 语法:
  • ref定义的数据:操作数据要加.value,从模板读取数据时不需要加。
  • reactive会自动将嵌套的属性转换为响应式对象,所有不需要加.value
  1. 实现原理:
  • ref通过Object.defineProperty()getset实现响应式(数据劫持)
  • reactive通过Proxy来实现响应式(数据劫持),并提供Reflect操作源对象内部的数据。

6、computed函数

跟vue2的computed配置功能一致

<template>
  <div>
    <h2>某个人的信息</h2>
    姓氏:<input type="text" v-model="person.firstName" /><br />
    名字:<input type="text" v-model="person.lastName" /><br />
    全名:{{ fullName }} <br />
    修改全名: <input type="text" v-model="person.fullName" />
  </div>
</template>

<script>
import { computed, reactive } from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  // emits:['hello'],
  setup() {
    let person = reactive({
      firstName: "呆瓜",
      lastName: 20,
    });
    //简写,没有考虑被修改的情况
    // person.fullName = computed(()=>{
    //   return person.firstName+person.lastName
    // })

    //完整写法(读和写)
    person.fullName = computed({
      get() {
        return person.firstName + person.lastName;
      },
      set(value) {
        const arr = value.split("-");
        person.firstName = arr[0] || "";
        person.lastName = arr[1] || "";
      },
    });

    return {
      person,
    };
  },
};
</script>

7、watch函数

  • 与vue2.x中的watch配置功能一致
  • 两个小坑:
  • 1、监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
  • 2、监视reactive定义的响应式数据中某个属性时:deep配置有效。
<template>
  <div>
    <h2>求和:{{ sum }}</h2>
    <h3>{{ msg }}</h3>
    <button @click="sum++" >增加数据</button>
    <button @click="msg +='!'">添加感叹号</button>
    <hr>
    <h3>姓名:{{ person.name }}</h3>
    <h3>年龄:{{ person.age }}</h3>
    <h3>薪资:{{ person.job.salary }}石</h3>
    <button @click="person.name += '~'" >修改姓名</button>
    <button @click="person.age++">修改年龄</button>
    <button @click="person.job.salary++" >修改薪资</button>
  </div>
</template>

<script>
import { reactive, ref,watch } from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  // emits:['hello'],
  setup() {
    let sum = ref(0)
    let msg = ref('不是吧不是吧')
    let person = reactive({
      name:'李四',
      age:20,
      job:{
        salary:1000,
      }
    })

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

    // watch(msg,(newValue,oldValue)=>{
    //   console.log('msg的值变化了',newValue,oldValue)
    //  },{immediate:true})

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

    //情况三:监视reactive所对应的全部响应式数据
    //注意1、此处无法正确获取到oldValue,会跟newValue一致
    //注意2、强制开启了深度监视(deep配置项无效)
    // watch(person,(newValue,oldValue)=>{
    //   console.log('person的值变化了',newValue,oldValue)
    // },{deep:false})//deep配置无效

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

    //情况五:监视reactive所定义的一个对象中的某些属性
    // watch([()=>person.name,()=>person.job.salary],(newValue,oldValue)=>{
    //   console.log('person的值变化了',newValue,oldValue)
    // })

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

    return {
      msg,
      sum,
      person
    };
  },
};
</script>

如果使用ref定义了对象,watch监视也可以这样写

<template>
  <div>
    <h2>求和:{{ sum }}</h2>
    <button @click="sum++" >增加数据</button>
    <hr>
    <h3>姓名:{{ person.name }}</h3>
    <h3>年龄:{{ person.age }}</h3>
    <h3>薪资:{{ person.job.salary }}石</h3>
    <button @click="person.name += '~'" >修改姓名</button>
    <button @click="person.age++">修改年龄</button>
    <button @click="person.job.salary++" >修改薪资</button>
  </div>
</template>

<script>
import { ref,watch } from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  // emits:['hello'],
  setup() {
    let sum = ref(0)
    let person = ref({
      name:'李四',
      age:20,
      job:{
        salary:1000,
      }
    })

    console.log(person);
    //不使用.value进行监视,是因为sum是基本数据类型
    watch(sum,(newValue,oldValue)=>{
      console.log('sum的值变化了',newValue,oldValue)
    })

    //监视对象,注意使用的是ref!!!,可以使用.value进行监视
    watch(person.value,(newValue,oldValue)=>{
      console.log('person的值变化了',newValue,oldValue)
    })
  
    //ref定义的对象 也可以使用deep配置项,同样有效果
    watch(person,(newValue,oldValue)=>{
      console.log('person的值变化了',newValue,oldValue)
    },{deep:true})
  
   
    return {
      sum,
      person
    };
  },
};
</script>

8、watchEffect函数

watchEffect是Vue 3中的一个新特性,它用于监听响应式数据的变化并执行相应的副作用函数。当被监听的响应式数据发生变化时,watchEffect会立即执行传入的副作用函数。
watch方法不同的是,watchEffect没有指定具体要监听的数据,而是会自动追踪响应式数据的依赖,并在依赖变化时重新运行副作用函数。
需要注意的是,watchEffect会在组件渲染时立即执行一次副作用函数,并在组件卸载时自动停止监听。如果需要停止监听,也可以通过返回一个清理函数来实现。

  • watch:既要指明监视的属性,也要指明监视的回调
  • watcheffect:不用指明监视的哪个属性,监视的回调中用到哪个属性,就监视哪个属性
  • watchEffect有点像computed:
    但是computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值。
    而watchEffect更注重的是过程、逻辑(回调函数的函数体),所以不用写返回值。(面试可以聊)
<template>
  <div>
    <h2>求和:{{ sum }}</h2>
    <button @click="sum++" >增加数据</button>
    <hr>
    <h3>姓名:{{ person.name }}</h3>
    <h3>年龄:{{ person.age }}</h3>
    <h3>薪资:{{ person.job.salary }}石</h3>
    <button @click="person.name += '~'" >修改姓名</button>
    <button @click="person.age++">修改年龄</button>
    <button @click="person.job.salary++" >修改薪资</button>
  </div>
</template>

<script>
import { ref,watch,watchEffect } from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  // emits:['hello'],
  setup() {
    let sum = ref(0)
    let person = ref({
      name:'李四',
      age:20,
      job:{
        salary:1000,
      }
    })

    //不使用.value进行监视,是因为sum是基本数据类型
    watch(sum,(newValue,oldValue)=>{
      console.log('sum的值变化了',newValue,oldValue)
    })

    watchEffect(()=>{
      let x = sum.value
      let x2 = person.value.job.salary
      console.log('watchEffect所指定的回调执行了',x,x2);
    })
  
   
    return {
      sum,
      person
    };
  },
};
</script>