官方文档

拉开序幕的setup

setup是Vue3中的一个配置项,它的值是一个函数。setup是所有Composition API(组合式API)的开始,在Vue2中使用的数据、方法等,都要配置在setup中,setup函数有两种返回值:返回一个对象,对象中的属性、方法、在模板中可以直接使用;返回一个渲染函数,可以自定义渲染内容。
在Vue3中,尽量不要与Vue2混用。
Vue2中的配置 (data、methods、computed等)可以访问到setup中的属性、方法,但是在Vue3中的setup不能访问到Vue2中的配置(data、methods、computed等)。
setup不能是一个async函数,因为返回值不再是return对象,而是一个promise,模板看不到return对象中的属性,后期可以放回一个Promise实例,但是需要Suspense和异步组件配合。

<template>
  <h1>一个人的信息</h1>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h2>性别:{{ sex }}</h2>
  <h2>a的值是:{{ a }}</h2>
  <button @click="sayHello">说话(Vue3所配置的——sayHello)</button>
  <br>
  <br>
  <button @click="sayWelcome">说话(Vue2所配置的——sayWelcome)</button>
  <br>
  <br>
  <button @click="test1">测试一下在Vue2的配置中去读取Vue3中的数据、方法</button>
  <br>
  <br>
  <button @click="test2">测试一下在Vue3的setup配置中去读取Vue2中的数据、方法</button>
</template>
<script>
// import {h} from 'vue'
export default {
  name: 'App',
  data() {
    return {
      sex: '男',
      // a: 100
    }
  },
  methods: {
    sayWelcome() {
      alert('欢迎来到尚硅谷学习')
    },
    test1() {
      console.log(this.sex)
      console.log(this.name)
      console.log(this.age)
      console.log(this.sayHello)
    }
  },
  // 此处只是测试一下setup,暂时不考虑响应式的问题
  // 不要把setup修饰成async的
  setup() {
    // 数据
    let name = '张三'
    let age = 18
    let a = 200

    // 方法
    function sayHello() {
      alert(`我叫${name},我${age}岁了,你好啊!`)
    }

    function test2() {
      console.log(name)
      console.log(age)
      console.log(sayHello)
      console.log(this.sex)
      console.log(this.sayWelcome)
    }

    // 返回一个对象(常用)
    return {
      name,
      age,
      sayHello,
      test2,
      a
    }

    // 返回一个函数(渲染函数)
    // return ()=> h('h1','尚硅谷')
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.

ref函数

用于定义一个响应式的数据,就要用到ref函数。

// 创建一个包含响应式数据的引用对象,reference对象,简称ref对象
const xxx = ref(value);
// 修改响应式对象的值
xxx.value = yyy;
  • 1.
  • 2.
  • 3.
  • 4.
<!--模板中读取数据不需要.value,Vue识别到变量是引用对象的时候,会自动调用.value-->
<div>{{xxx}}</div>
  • 1.
  • 2.

ref函数的参数,可以是基本类型,也可以是对象类型。
对于基本数据类型,响应式依赖的是Object.defineProperty()getset完成的
对于对象数据类型,响应式依赖的是一个新的函数:reactive()

<template>
  <h1>一个人的信息</h1>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h3>工作种类:{{ job.type }}</h3>
  <h3>工作薪水:{{ job.salary }}</h3>
  <button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {ref} from 'vue'

export default {
  name: 'App',
  setup() {
    // 数据
    let name = ref('张三')
    let age = ref(18)
    let job = ref({
      type: '前端工程师',
      salary: '30K'
    });

    // 方法
    function changeInfo() {
      name.value = '李四';// ref函数包含的是基本数据类型,修改值
      age.value = 48;// ref函数包含的是基本数据类型,修改值
      console.log(job.value)
      job.value.type = 'UI设计师';// ref函数包含的是对象数据类型,修改值
      job.value.salary = '60K';// ref函数包含的是对象数据类型,修改值
      job.value = {
        type: 'UI设计师',
        salary: '60K'
      }// ref函数包含的是对象数据类型,修改值
      console.log(name, age)
    }

    // 返回一个对象(常用)
    return {
      name,
      age,
      job,
      changeInfo
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.

reactive函数

用于定义一个对象类型的响应式数据,但是基本数据类型不要使用,基本数据类型,请使用ref()函数。

// 定义对象(源对象可以是一个对象,也可以是一个数组)
const 代理对象 = reactive(源对象);
  • 1.
  • 2.

内部是基于ES6的Proxy实现,通过代理对象操作源对象。reactive()定义的响应式数据是深层次的。

<template>
  <h1>一个人的信息</h1>
  <h2>姓名:{{ person.name }}</h2>
  <h2>年龄:{{ person.age }}</h2>
  <h3>工作种类:{{ person.job.type }}</h3>
  <h3>工作薪水:{{ person.job.salary }}</h3>
  <h3>爱好:{{ person.hobby }}</h3>
  <h3>测试的数据c:{{ person.job.a.b.c }}</h3>
  <button @click="changeInfo">修改人的信息</button>
</template>

<script>
import {reactive} from 'vue'

export default {
  name: 'App',
  setup() {
    // 数据
    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        type: '前端工程师',
        salary: '30K',
        a: {
          b: {
            c: 666
          }
        }
      },
      hobby: ['抽烟', '喝酒', '烫头']
    });

    // 方法
    function changeInfo() {
      person.name = '李四'
      person.age = 48
      person.job.type = 'UI设计师'
      person.job.salary = '60K'
      person.job.a.b.c = 999
      person.hobby[0] = '学习'
    }

    // 返回一个对象(常用)
    return {
      person,
      changeInfo
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.

Vue3中的响应式原理

在Vue2中,对象的响应式的实现原理是通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)实现的,数组的响应式是通过重写更新数组的一系列方法来实现拦截的,对数组的变更方法进行了拦截。

Object.defineProperty(data, 'field', {
	get() {},
	set() {}
});
  • 1.
  • 2.
  • 3.
  • 4.

存在的问题:

  • 新增属性、删除属性,界面不会更新
  • 直接铜鼓哦数组下标修改数组,页面不会更新

回顾一下Vue2的响应式,新建Vue2项目。

<template>
  <div>
    <h1 v-show="person.name">姓名:{{ person.name }}</h1>
    <h1>年龄:{{ person.age }}</h1>
    <h1 v-show="person.sex">性别:{{ person.sex }}</h1>
    <h1>爱好:{{ person.hobby }}</h1>
    <button @click="addSex">添加性别属性</button>
    <button @click="deleteName">删除姓名属性</button>
    <button @click="updateHobby">修改爱好</button>
  </div>
</template>
<script type="text/javascript">
import Vue from 'vue'

export default {
  name: 'App',
  data() {
    return {
      person: {
        name: '张三',
        age: 18,
        hobby: ['听歌', '追剧']
      }
    }
  },
  methods: {
    addSex() {
      // console.log(this);
      // this.$set(this.person, 'sex', '男');// 添加属性
      // Vue.set(this.person, 'sex', '男');// 添加属性
    },
    deleteName() {
      // console.log(this);
      // this.$delete(this.person, 'name');// 删除属性
      // Vue.delete(this.person, 'name');// 删除属性
    },
    updateHobby() {
      // console.log(this);
      // this.$set(this.person.hobby, '0', '逛街');// 修改数组属性
      // Vue.set(this.person.hobby, '0', '逛街');// 修改数组属性
      // this.person.hobby.splice(0, 1, '逛街');// 修改数组属性
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

再来看Vue3的响应式。

<template>
  <div>
    <h1 v-show="person.name">姓名:{{ person.name }}</h1>
    <h1>年龄:{{ person.age }}</h1>
    <h1 v-show="person.sex">性别:{{ person.sex }}</h1>
    <h1>爱好:{{ person.hobby }}</h1>
    <button @click="addSex">添加性别属性</button>
    <button @click="deleteName">删除姓名属性</button>
  </div>
</template>
<script>
import {reactive} from 'vue'

export default {
  name: "App",
  setup() {
    // 数据
    let person = reactive({
      name: '张三',
      age: '18',
      hobby: ['听歌', '追剧']
    });

    // 方法
    function addSex() {
      person.sex = '男';
    }

    function deleteName() {
      delete person.name;
    }

    // 模拟Vue3实现响应式,通过js方法,修改p的值,会发现调用了下面的get、set、deleteProperty方法
    const p = new Proxy(person, {
      //有人读取p的某个属性时调用
      get(target, propName) {
        console.log(`有人读取了p身上的${propName}属性`)
        return Reflect.get(target, propName)
      },
      //有人修改p的某个属性、或给p追加某个属性时调用
      set(target, propName, value) {
        console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
        Reflect.set(target, propName, value)
      },
      //有人删除p的某个属性时调用
      deleteProperty(target, propName) {
        console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
        return Reflect.deleteProperty(target, propName)
      }
    });
    return {
      p,
      person,
      addSex,
      deleteName
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.

原理:
通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射):对源对象的属性进行操作。
MDN文档中对ProxyReflect的描述:
 Proxy Reflect

reactive对比ref

  • 从定义数据角度对比:
  • ref用来定义:基本类型数据。
  • reactive用来定义:对象(或数组)类型数据。
  • 备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。
  • 从原理角度对比:
  • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
  • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
  • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
  • reactive定义的数据:操作数据与读取数据:均不需要.value

setup的两个注意点

setup的执行时机:在beforeCreate之前执行一次,this的值是undefined。
setup的参数:

  • props:值为对象,包含:组件外部传递过来,在组件内部声明接收的属性
  • context:上下文对象
  • attrs:值为对象,包含:组件外部传递过来,但是没有在props配置里声明的属性,相当于this.$atrrs
  • slots:收到的插槽内容,相当于this.$slots
  • emit:分发自定义事件函数:相当于this.$emit

App.vue

<template>
  <Demo @hello="showHelloMsg" msg="你好啊" school="尚硅谷">
    <template v-slot:qwe>
      <span>尚硅谷1</span>
    </template>
    <template v-slot:asd>
      <span>尚硅谷2</span>
    </template>
  </Demo>
</template>

<script>
import Demo from './components/Demo'

export default {
  name: 'App',
  components: {Demo},
  setup() {
    function showHelloMsg(value) {
      alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`)
    }

    return {
      showHelloMsg
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

Demo.vue

<template>
	<h1>一个人的信息</h1>
	<h2>姓名:{{person.name}}</h2>
	<h2>年龄:{{person.age}}</h2>
	<button @click="test">测试触发一下Demo组件的Hello事件</button>
  <slot name="qwe"></slot>
  <slot name="asd"></slot>
</template>

<script>
	import {reactive} from 'vue'
	export default {
		name: 'Demo',
		props:['msg','school'],
		emits:['hello'],
		setup(props,context){
			// console.log('---setup---',props)
			// console.log('---setup---',context)
			// console.log('---setup---',context.attrs) //相当与Vue2中的$attrs
			// console.log('---setup---',context.emit) //触发自定义事件的。
			console.log('---setup---',context.slots) //插槽
			//数据
			let person = reactive({
				name:'张三',
				age:18
			})

			//方法
			function test(){
				context.emit('hello',666)
			}

			//返回一个对象(常用)
			return {
				person,
				test
			}
		}
	}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

计算属性与监视

computed函数

<template>
  <div>
    一个人的信息<br>
    姓:<input type="text" v-model="person.lastName"><br>
    名:<input type="text" v-model="person.firstName"><br>
    全名:<input type="text" v-model="person.fullName"><br>
  </div>
</template>

<script>
import {reactive, computed} from "vue";

export default {
  name: 'App',
  setup() {
    let person = reactive({
      lastName: '王',
      firstName: '劭阳'
    });
    // 计算属性,简写(只考虑读的情况)
    // person.fullName = computed(() => {
    //   return person.lastName + " " + person.firstName;
    // });
    // 计算属性,完整写法(考虑读和谐)
    person.fullName = computed({
      get() {
        return person.lastName + " " + person.firstName;
      },
      set(value) {
        const array = value.split(" ");
        person.lastName = array[0];
        person.firstName = array[1];
      }
    });
    return {
      person
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

watch函数

<template>
  <div>
    当前的值为:{{ sum }}<br>
    <button @click="sum++">点我+1</button>
    <br>
    当前信息为:{{ message }}<br>
    <button @click="message+='!'">更新信息</button>
    <br>
    姓名:{{ person.name }}<br>
    年龄:{{ person.age }}<br>
    薪资:{{ person.job.j1.salary }}<br>
    <button @click="person.name+='~'">修改姓名</button>
    <button @click="person.age++">修改年龄</button>
    <button @click="person.job.j1.salary++">涨薪</button>
  </div>
</template>

<script>
import {reactive, ref, watch} from "vue";

export default {
  name: 'App',
  setup() {
    let sum = ref(0);
    let message = ref("hello world");
    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    });
    // vue3中的watch
    // 对于vue2中的immediate、deep配置,可以写在watch的第三个参数上
    // 情况1:监视ref定义的一个响应式数据
    watch(sum, (newValue, oldValue) => {
      console.log("sum的值变化了", oldValue, newValue);
    }, {
      immediate: true
    });
    // 情况2:监视ref定义的多个响应式数据
    watch([sum, message], (newValue, oldValue) => {
      console.log("sum或message的值变化了", oldValue, newValue);
    });
    // 情况3:监视reactive定义的对象响应式数据,此处有一个坑:用reactive定义的对象无法获取到oldValue,强制开启deep配置,无法关闭
    watch(person, (newValue, oldValue) => {
      console.log("person的值变化了", oldValue, newValue);
    });
    // 情况4:监视reactive定义的对象响应式数据中的一个属性(只监听age变化)
    watch(() => person.age, (newValue, oldValue) => {
      console.log("person的age值变化了", oldValue, newValue);
    });
    // 情况5:监视reactive定义的对象响应式数据中的多个属性
    watch([() => person.age, () => person.name], (newValue, oldValue) => {
      console.log("person的age或name值变化了", oldValue, newValue);
    });
    // 特殊情况:因为watch监视的对象是person.job,但是person.job不是被reactive直接修饰的,而且person.job是对象类型,所以需要加deep: true
    watch([() => person.job], (newValue, oldValue) => {
      console.log("person的job值变化了", oldValue, newValue);
    }, {deep: true});
    return {
      person,
      sum,
      message
    }
  },
  // vue2中的watch
  // watch : {
  //   // sum(newValue, oldValue) {
  //   //   console.log("sum的值变化了", oldValue, newValue);
  //   // }
  //   sum : {
  //     immediate: true,
  //     deep: true,
  //     handler (newValue, oldValue) {
  //       console.log("sum的值变化了", oldValue, newValue);
  //     }
  //   }
  // }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.

watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。
  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
  • watchEffect有点像computed
  • computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
  • watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
<template>
  <div>
    当前的值为:{{ sum }}<br>
    <button @click="sum++">点我+1</button>
    <br>
    当前信息为:{{ message }}<br>
    <button @click="message+='!'">更新信息</button>
    <br>
    姓名:{{ person.name }}<br>
    年龄:{{ person.age }}<br>
    薪资:{{ person.job.j1.salary }}<br>
    <button @click="person.name+='~'">修改姓名</button>
    <button @click="person.age++">修改年龄</button>
    <button @click="person.job.j1.salary++">涨薪</button>
  </div>
</template>

<script>
import {reactive, ref, watchEffect} from "vue";

export default {
  name: 'App',
  setup() {
    let sum = ref(0);
    let message = ref("hello world");
    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    });
    watchEffect(() => {
      console.log(sum.value);
      console.log(person.job.j1.salary);
      console.log("watchEffect配置的回调执行了");
    });
    return {
      person,
      sum,
      message
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.

生命周期

Vue2的生命周期:

Vue笔记09-常用Composition API_数据


Vue3的生命周期:

Vue笔记09-常用Composition API_数组_02


在Vue3中可以继续使用Vue2的生命周期钩子,但是有两个被更名:

beforeDestroy改名为beforeUnmountdestroyed改名为unmounted

Vue3也提供了组合式API形式的生命周期钩子,与Vue2中钩子对应关系如下:

  • beforeCreate=>setup()
  • created=>setup()
  • beforeMount=>onBeforeMount
  • mounted=>onMounted
  • beforeUpdate=>onBeforeUpdate
  • updated=>onUpdated
  • beforeUnmount=>onBeforeUnmount
  • unmounted=>onUnmounted Demo.vue
<template>
  <h1></h1>
  <h1>{{ sum }}</h1>
  <button @click="sum++">加一</button>
</template>
<script>
import {onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref} from 'vue'

export default {
  name: 'Demo',
  setup() {
    let sum = ref(0);

    // 通过组合式API的形式去使用生命周期钩子
    onBeforeMount(() => {
      console.log('---onBeforeMount---')
    });
    onMounted(() => {
      console.log('---onMounted---')
    });
    onBeforeUpdate(() => {
      console.log('---onBeforeUpdate---')
    });
    onUpdated(() => {
      console.log('---onUpdated---')
    });
    onBeforeUnmount(() => {
      console.log('---onBeforeUnmount---')
    });
    onUnmounted(() => {
      console.log('---onUnmounted---')
    });

    //返回一个对象(常用)
    return {
      sum
    }
  },
  // 通过配置项的形式使用生命周期钩子
  beforeCreate() {
    console.log('---beforeCreate---')
  },
  created() {
    console.log('---created---')
  },
  beforeMount() {
    console.log('---beforeMount---')
  },
  mounted() {
    console.log('---mounted---')
  },
  beforeUpdate() {
    console.log('---beforeUpdate---')
  },
  updated() {
    console.log('---updated---')
  },
  beforeUnmount() {
    console.log('---beforeUnmount---')
  },
  unmounted() {
    console.log('---unmounted---')
  },
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.

App.vue

<template>
  <div>
    <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
    <Demo v-if="isShowDemo"/>
  </div>
</template>
<script>
import Demo from "@/components/Demo.vue";
import {ref} from "vue";

export default {
  name: 'App',
  components: {Demo},
  setup() {
    let isShowDemo = ref(true)
    return {
      isShowDemo
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

自定义hook函数

hook函数本质是一个函数,把setup函数中使用的组合式API进行了封装
类似于Vue2中的mixin
自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂
需求:给window绑定一个点击事件,点击一次,在页面输出当前点击的坐标。
Demo.vue

<template>
  <h1></h1>
  <h1>当前点击的坐标是{{ point.x }},{{ point.y}}</h1>
</template>
<script>
import {onBeforeUnmount, onMounted, reactive} from 'vue'

export default {
  name: 'Demo',
  setup() {
    let point = reactive({
      x: 0,
      y: 0
    });

    function savePoint (event) {
      console.log(event.x, event.y);
      point.x = event.clientX;
      point.y = event.clientY;
    }

    // 当组件展示的时候,绑定事件
    onMounted(() => {
      window.addEventListener('click', savePoint);
    });
    // 当组件隐藏的时候,移除事件
    onBeforeUnmount(() => {
      window.removeEventListener('click', savePoint);
    });
    //返回一个对象(常用)
    return {
      point
    }
  },
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

App.vue保持不变。
此时,其他地方,也需要一个打点的功能,方便复用,我们就要用到hook函数,在src目录创建hooks文件夹,创建savePoint.js文件。
savePoint.js

import {onBeforeUnmount, onMounted, reactive} from "vue";

export default function () {
    let point = reactive({
        x: 0,
        y: 0
    });

    function savePoint (event) {
        console.log(event.x, event.y);
        point.x = event.clientX;
        point.y = event.clientY;
    }

    // 当组件展示的时候,绑定事件
    onMounted(() => {
        window.addEventListener('click', savePoint);
    });
    // 当组件隐藏的时候,移除事件
    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint);
    });
    return point;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

Demo.vue

<template>
  <h1></h1>
  <h1>当前点击的坐标是{{ point.x }},{{ point.y }}</h1>
</template>
<script>
import savePoint from '../hooks/savePoint';

export default {
  name: 'Demo',
  setup() {
    const point = savePoint();
    //返回一个对象(常用)
    return {
      point
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

toRef

作用:创建一个ref对象,其value值指向源对象的某个属性,当创建的这个ref对象修改的时候,源对象的属性值也同步修改。
应用:要将响应式对象中某个属性单独提供给外部使用的时候,而且单独修改这个属性的时候,要求同步修改源数据的属性。
扩展:toRefstoRef功能一致,但是可以批量创建多个ref对象。

<template>
  <h4>{{ person }}</h4>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h2>薪资:{{ job.j1.salary }}K</h2>
  <button @click="name+='~'">修改姓名</button>
  <button @click="age++">增长年龄</button>
  <button @click="job.j1.salary++">涨薪</button>
</template>

<script>
import {reactive, toRef, toRefs} from 'vue'

export default {
  name: 'Demo',
  setup() {
    //数据
    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    });

    console.log('******', toRefs(person));
    
    // 返回一个对象(常用)
    return {
      person,
      name: toRef(person, 'name'),
      age: toRef(person, 'age'),
      salary: toRef(person.job.j1, 'salary'),
      // ...是扩展运算符,将对象进行展开
      ...toRefs(person)
    }
  }
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.