Vue3详解

目录:

  1. Vue3快速上手
  2. 创建Vue3.0工程
  3. 常用Composition API
  4. 其他Composition API
  5. Composition API的优势
  6. 新的组件
  7. 其他

1.Vue3快速上手

略~

2.创建Vue3.0工程

官方文档:创建一个项目 | Vue CLI

1.使用 vue-cli 创建:

bash 复制代码
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve

2.使用 vite 创建

官方文档:快速上手 | Vue.js

vite官网:https://vitejs.cn

  • 什么是vite?------ 新一代前端构建工具。(目前的前端构建工具是webpack)

  • 优势如下:

    • 开发环境中,无需打包操作,可快速的冷启动。

    • 轻量快速的热重载(HMR)。

    • 真正的按需编译,不再等待整个应用编译完成。

  • 传统构建 与 vite构建对比图

bash 复制代码
## 创建工程
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue

## 进入工程目录
cd <project-name>

## 安装依赖
npm install (或者直接写 npm i)

## 运行
npm run dev

3.常用Composition API

拉开序幕的setup

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

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

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

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

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

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

bash 复制代码
//1.返回一个对象
setup() {
  let name = '柯南';
  let age = 5;
  function say() {
    alert("真相只有一个!");
  }
  return {
    name,
    age,
    say
  }
}
//2.返回一个渲染函数
import {h} from 'vue'
export default {
  name: 'App',
  setup() {
    return ()=> h('h1','米花小学') //('要在页面放的元素', '要在页面放的内容')
  }
}

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和异步组件的配合)

代码示例:

App.vue

bash 复制代码
<template>
  <h1>我是App组件</h1>
  <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('欢迎学习vue3')
    },
    test1() {
      console.log(this.sex)
      console.log(this.name)
      console.log(this.age)
      console.log(this.sayHello)
      console.log(this.sayWelcome)
    }
  },
  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 () => {
    //   return h('h1', '渲染函数')
    // }

    //写法二
    // return () => h('h1', '渲染函数')

  }
}

</script>

ref函数

  • 作用: 定义一个响应式的数据(让vue3能检测到它的改变)

  • 引入:import {ref} from 'vue'(Vue3的一个特点就是要用啥引入啥)

  • 语法: const xxx = ref(initValue)

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

      ref(xxx)将xxx包装成了RefImpl(reference引用 implement实现)构造函数的实例对象,也就是ref对象

    • JS中操作数据: xxx.value

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

  • 备注:

    • 接收的数据可以是:基本类型、也可以是对象类型。

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

    • 对象类型的数据:内部 " 求助 " 了Vue3.0中的一个新函数------reactive函数。于是xxx.value就是proxy对象

代码示例:

App.vue

bash 复制代码
<template>
  <h1>我是App组件</h1>
  <h1>一个人的信息</h1>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <button @click="changeInfo">修改人的信息</button>
</template>

<script>
import {ref} from 'vue'

export default {
  name: 'App',
  setup() {
    let name = ref('张三')
    let age = ref(18)

    function changeInfo() {
      name.value = '李四'
      age.value = 48
      console.log(name, age)
    }

    return {
      name,
      age,
      changeInfo
    }
  }
}

</script>

升级一下:

bash 复制代码
<template>
  <h1>我是App组件</h1>
  <h1>一个人的信息</h1>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h2>工作种类:{{ job.type }}</h2>
  <h2>工作薪水:{{ job.salary }}</h2>
  <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 = '李四'
      age.value = 48
      job.value.type = 'UI设计师'
      job.value.salary = '60K'
      console.log(name, age)
    }

    return {
      name,
      age,
      changeInfo,
      job
    }
  }
}

</script>

reactive函数

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

  • 引入:import {reactive} from 'vue'

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

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

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

  • 这里数组也能直接通过下标操作数据且vue能监测到,而vue2里不能

代码示例:

App.vue

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

<script>
import {ref, reactive} from 'vue'

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


    function changeInfo() {
      name.value = '李四'
      age.value = 48
      // job.value.type = 'UI设计师'
      // job.value.salary = '60K'
      job.type = 'UI设计师'
      job.salary = '60K'
      job.a.b.c = 999

      hobby[0] = '唱'
      hobby[1] = '跳'
      hobby[2] = 'rap'
      console.log(name, age)
      console.log(job)
    }

    return {
      name,
      age,
      changeInfo,
      job,
      hobby
    }
  }
}

</script>

升级一下:

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

<script>
import {ref, 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] = '唱'
      person.hobby[1] = '跳'
      person.hobby[2] = 'rap'
    }

    return {
      person,
      changeInfo,
    }
  }
}

</script>

vue2.x的响应式

  • 实现原理:

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

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

      复制代码
      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
  • 存在问题:

    • 新增属性(.)、删除属性(delete), 界面不会更新。(需用$set/$deleteVue.set/Vue.delete

    • 直接通过下标修改数组, 界面不会自动更新。(需用$setVue.setsplice())

代码示例:

bash 复制代码
<template>
  <div>
    <h1>我是Vue2写的效果</h1>
    <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">添加一个sex属性</button>
    <button @click="deleteName">删除name属性</button>
    <button @click="updateHobby">修改第一个爱好的名字</button>
  </div>
</template>

<script>
import Vue from 'vue'

export default {
  name: 'App',
  data() {
    return {
      person: {
        name: '张三',
        age: 18,
        hobby: ['学习', '吃饭']
      }
    }
  },
  methods: {
    addSex() {
      this.person.sex = '女'
      // this.$set(this.person, 'sex', '女')
      // Vue.set(this.person, 'sex', '女')
    },
    deleteName() {
      delete this.person.name
      // this.$delete(this.person, 'name')
      // Vue.delete(this.person, 'name', '女')
    },
    updateHobby() {
      this.person.hobby[0] = '逛街'
      // this.$set(this.person.hobby, 0, '逛街')
      // this.person.hobby.splice(0, 1, '逛街')
    }
  }
}

</script>

vue2的响应式原理:

bash 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript">
    let person = {
        name: '张三',
        age: 18
    }

    let p = {}

    Object.defineProperty(p, 'name', {
        configurable: true,
        get() {
            return person.name
        },
        set(value) {
            console.log('有人修改了name属性,我发现了,我要去更新界面!')
            person.name = value
        }
    })

    Object.defineProperty(p, 'age', {
        configurable: true,
        get() {
            return person.age
        },
        set(value) {
            console.log('有人修改了age属性,我发现了,我要去更新界面!')
            person.age = value
        }
    })
</script>
</body>
</html>

运行结果:

Vue3.0的响应式

  • 实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射): 对源对象的属性进行操作。

    • MDN文档中描述的Proxy与Reflect:

代码示例:

App.vue

bash 复制代码
<template>
  <div>
    <h1>我是Vue3写的效果</h1>
    <h1 v-show="person.name">姓名:{{ person.name }}</h1>
    <h1>年龄:{{ person.age }}</h1>
    <h1 v-show="person.sex">性别:{{ person.sex }}</h1>
    <h1>工作种类:{{ person.job.type }}</h1>
    <h1>工作薪水:{{ person.job.salary }}</h1>
    <h1>爱好:{{ person.hobby }}</h1>
    <h1>测试的数据c:{{ person.job.a.b.c }}</h1>
    <button @click="changeInfo">修改人的信息</button>
    <button @click="addSex">添加一个sex属性</button>
    <button @click="deleteName">删除name属性</button>
  </div>
</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] = '唱'
      person.hobby[1] = '跳'
      person.hobby[2] = 'rap'
    }
    
    function addSex() {
      person.sex = '男'
    }

    function deleteName() {
      delete person.name
    }

    return {
      person,
      changeInfo,
      addSex,
      deleteName,
    }
  }
}

</script>

vue3的响应式原理:

bash 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript">
    let person = {
        name: '张三',
        age: 18
    }

    const p = new Proxy(person, {
        get(target, propName) {
            console.log(`有人读取了p身上的${propName}属性,我要去更新界面了`, target, propName)
            return target[propName]
        },
        set(target, propName, value) {
            console.log(`有人设置了p身上的${propName}属性,我要去更新界面了`, target, propName, value)
            return target[propName] = value
        },
        deleteProperty(target, propName) {
            console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`, target, propName)
            return delete target[propName]
        }
    })
</script>
</body>
</html>

运行结果:

vue3的响应式原理改进:

bash 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript">
    let person = {
        name: '张三',
        age: 18
    }

    let obj = {a: 1, b: 2}

    // Object.defineProperty(obj, 'c', {
    //     get() {
    //         return 3
    //     }
    // })
    // Object.defineProperty(obj, 'c', {
    //     get() {
    //         return 4
    //     }
    // })
    //
    // console.log('@@@')

    const x1 = Reflect.defineProperty(obj, 'c', {
        get() {
            return 3
        }
    })

    console.log(x1)

    const x2 = Reflect.defineProperty(obj, 'c', {
        get() {
            return 4
        }
    })
    if (x2) {
        console.log(x2)
        console.log('某某某某某操作成功了!')
    } else {
        console.log(x2)
        console.log('某某某某某操作失败了!')
    }
    
    console.log('@@@')

</script>
</body>
</html>
bash 复制代码
new Proxy(data, {
	// 拦截读取属性值
    get (target, prop) {
        // return target[prop]
    	return Reflect.get(target, prop)
    },
    // 拦截设置属性值或添加新属性
    set (target, prop, value) {
        // return target[prop] = value
    	return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
    deleteProperty (target, prop) {
        // return delete target[prop]
    	return Reflect.deleteProperty(target, prop)
    }
})

proxy.name = 'tom'   

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.$attrs

      • slots: 收到的插槽内容, 相当于 this.$slots。vue3中尽量用v-slot

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

回忆vue2的props的用法:

App.vue

bash 复制代码
<template>
  <div class="app">
    <h1>我是Vue2写的效果</h1>
    <Demo msg="你好啊" school="尚硅谷"></Demo>
  </div>
</template>

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

export default {
  name: 'App',
  components: {Demo}
}
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>

Demo.vue

bash 复制代码
<template>
  <div class="demo">
    <h2>我是Demo组件</h2>
    <!--方法一 使用props接收-->
    <!--    {{ msg }}-->
    <!--    {{ school }}-->


    <!--    方法二:直接使用-->
    {{ $attrs.msg }}
    {{ $attrs.school }}
  </div>
</template>

<script>
export default {
  name: "Demo",
  // 方法一:使用props接收
  // props: ['msg', 'school'],
  mounted() {
    console.log(this)
  }
}
</script>

<style scoped>
.demo {
  background-color: orange;
  padding: 10px;
}
</style>

回忆vue2的slots的用法:

App.vue

bash 复制代码
<template>
  <div class="app">
    <h1>我是Vue2写的效果</h1>
    <Demo>
      <span>尚硅谷</span>
      <span>尚硅谷!!!</span>
    </Demo>
  </div>
</template>

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

export default {
  name: 'App',
  components: {Demo}
}
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>

Demo.vue

bash 复制代码
<template>
  <div class="demo">
    <h2>我是Demo组件</h2>
    <slot></slot>
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "Demo",
  mounted() {
    console.log(this)
  }
}
</script>

<style scoped>
.demo {
  background-color: orange;
  padding: 10px;
}
</style>

回忆vue2的slots带名字的用法:

Demo.vue

bash 复制代码
<template>
  <div class="demo">
    <h2>我是Demo组件</h2>
    <slot name="test1"></slot>
    <slot name="test2"></slot>
  </div>
</template>

<script>
export default {
  name: "Demo",
  mounted() {
    console.log(this)
  }
}
</script>

<style scoped>
.demo {
  background-color: orange;
  padding: 10px;
}
</style>

App.vue

bash 复制代码
<template>
  <div class="app">
    <h1>我是Vue2写的效果</h1>

    <Demo>
      <template slot="test1">
        <span>尚硅谷</span>
      </template>

      <template slot="test2">
        <span>尚硅谷!!!</span>
      </template>
    </Demo>
  </div>
</template>

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

export default {
  name: 'App',
  components: {Demo}
}
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>

vue3的setup函数的俩个注意点:

App.vue

bash 复制代码
<template>
  <Demo @hello="showHelloMsg" msg="你好啊" school="尚硅谷">
    <template v-slot:qwe>
      <span>尚硅谷</span>
    </template>
    <template v-slot:www>
      <span>尚硅谷</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>

Demo.vue

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

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

export default {
  name: "Demo",
  props: ['msg', 'school'],
  emits: ['hello'],

  setup(props, context) {

    console.log('---setup---', props, context)
    console.log('---setup---', props, context.attrs)
    console.log('---setup---', props, context.emit)
    console.log('---setup---', props, context.slots)

    let person = reactive({
      name: '张三',
      age: 18
    })

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

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

computed函数

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

  • 写法

    bash 复制代码
    import {computed} from 'vue' // 引入computed(vue里把计算属性变成了一个组合式的api)
    
    setup(){
        ...
    	//计算属性------简写
        let fullName = computed(()=>{ // vue3中没有this,所以用普通函数也行,箭头函数也行
            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]
            }
        })
    }

代码示例:

App.vue

bash 复制代码
<template>
  <Demo></Demo>
</template>

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

export default {
  name: 'App',
  components: {Demo},
}
</script>

Demo.vue

bash 复制代码
<template>
  <h1>一个人的信息</h1>
  姓:<input type="text" v-model="person.firstName">
  <br>
  名:<input type="text" v-model="person.lastName">
  <br>
  <!--  <span>全名:{{ fullName }}</span>-->
  <span>全名:{{ person.fullName }}</span><br>

  全名:<input type="text" v-model="person.fullName">
</template>

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

export default {
  name: "Demo",
  // vue2中的计算属性
  // computed: {
  //   fullName() {
  //     return this.person.firstName + "  -  " + this.person.lastName
  //   }
  // },
  setup() {
    let person = reactive({
      firstName: '张',
      lastName: '三'
    })

    //计算属性-简写(没有考虑计算属性被修改的情况)
    // person.fullName = computed(() => {
    //   return person.firstName + '  -  ' + person.lastName
    // })

    //计算属性-完整
    person.fullName = computed({
      get() {
        return person.firstName + '-' + person.lastName
      },
      set(value) {
        const nameArr = value.split('-')
        person.firstName = nameArr[0]
        person.lastName = nameArr[1]
      }
    })


    return {
      person
    }
  }
}
</script>

watch函数

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

  • 两个小"坑":

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

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

python 复制代码
//import {watch} from 'vue' // 引入watch(vue里把监视属性变成了一个组合式的api)

let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
	name:'张三',
	age:18,
	job:{
		j1:{
			salary:20
		}
	}
})
//情况一:监视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定义的响应式数据,则强制开启了深度监视(能捕获数据变化)
	若一定要获取某个属性的oldValue,则不要把它放进对象里,像sum,msg一样放在外面就好(或者按情况四)
*/
watch(person,(newValue,oldValue)=>{ // 一般开发中直接用这个就行,实在要用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})

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

代码示例:

App.vue

bash 复制代码
<template>
  <Demo></Demo>
</template>

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

export default {
  name: 'App',
  components: {Demo},
}
</script>

Demo.vue

bash 复制代码
<template>
  <h2>当前的求和为:{{ sum }}</h2>
  <button @click="sum++">点我+1</button>
  <hr>
  <h2>当前的信息为:{{ msg }}</h2>
  <button @click="msg+='!'">修改信息</button>
  <hr>
  <h2>姓名:{{ person.name }}</h2>
  <h2>年龄:{{ person.age }}</h2>
  <h2>薪资:{{ person.job.j1.salary }}</h2>
  <button @click="person.name+='~'">修改姓名</button>
  <button @click="person.age++">增长年龄</button>
  <button @click="person.job.j1.salary++">涨薪</button>
</template>

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

export default {
  name: "Demo",
  //vue2的写法
  // watch: {
  //   //简写
  //   // sum(newValue, oldValue) {
  //   //   console.log('sum的值变化了', newValue, oldValue)
  //   // }
  //
  //   //一般写法
  //   sum: {
  //     immediate: true,
  //     deep: true,
  //     handler(newValue, oldValue) {
  //       console.log('sum的值变化了', newValue, oldValue)
  //     }
  //   }
  // },
  setup() {
    let sum = ref(0)
    let msg = ref('你好啊')

    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })

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

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

    //情况二优化
    watch([sum, msg], (newValue, oldValue) => {
      console.log('msg或sum变了', newValue, oldValue)
    }, {immediate: true})

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

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

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

    // 特殊情况
    watch(() => person.job, (newValue, oldValue) => {
      console.log('person的salary变化了', newValue, oldValue)
    }, {deep: true})
    return {
      sum,
      msg,
      person
    }
  }
}
</script>
  • 监视ref定义的数据加不加.value的问题

    • 如果是ref定义的基本数据类型,则不用.value,否则就是直接把那个值给watch监视(会报错),但watch要监视的应该是一个结构而不是一个具体的值

    • 如果是ref定义的复杂数据类型,则要用.value,否则就是监视一个对象的地址,因为ref定义的复杂数据类型里的value是一个proxy对象,要监视的应该是这个对象里的数据,而不是这个对象的地址或者开启深度监视(deep:true),这样对象里的数据变了也能监测到

代码示例:

App.vue

bash 复制代码
<template>
  <Demo></Demo>
</template>

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

export default {
  name: 'App',
  components: {Demo},
}
</script>

Demo.vue

bash 复制代码
<template>
  <h2>当前的求和为:{{ sum }}</h2>
  <button @click="sum++">点我+1</button>
  <hr>
  <h2>当前的信息为:{{ msg }}</h2>
  <button @click="msg+='!'">修改信息</button>
  <hr>
  <h2>姓名:{{ person.name }}</h2>
  <h2>年龄:{{ person.age }}</h2>
  <h2>薪资:{{ person.job.j1.salary + "K" }}</h2>
  <button @click="person.name+='~'">修改姓名</button>
  <button @click="person.age++">增长年龄</button>
  <button @click="person.job.j1.salary++">涨薪</button>
</template>

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

export default {
  name: "Demo",
  setup() {
    let sum = ref(0)
    let msg = ref('你好啊')

    let person = ref({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })

    watch(sum, (newValue, oldValue) => {
      console.log('sum的值变化了', newValue, oldValue)
    })

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

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

watchEffect函数

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

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

  • watchEffect有点像computed:(所依赖的数据变了就重新执行一次)

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

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

代码示例:

App.vue

bash 复制代码
<template>
  <Demo></Demo>
</template>

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

export default {
  name: 'App',
  components: {Demo},
}
</script>

Demo.vue

bash 复制代码
<template>
  <h2>当前的求和为:{{ sum }}</h2>
  <button @click="sum++">点我+1</button>
  <hr>
  <h2>当前的信息为:{{ msg }}</h2>
  <button @click="msg+='!'">修改信息</button>
  <hr>
  <h2>姓名:{{ person.name }}</h2>
  <h2>年龄:{{ person.age }}</h2>
  <h2>薪资:{{ person.job.j1.salary + "K" }}</h2>
  <button @click="person.name+='~'">修改姓名</button>
  <button @click="person.age++">增长年龄</button>
  <button @click="person.job.j1.salary++">涨薪</button>
</template>

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

export default {
  name: "Demo",
  setup() {
    let sum = ref(0)
    let msg = ref('你好啊')

    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })

    watchEffect(() => {
      const x1 = sum.value
      const x2 = person.job.j1.salary.value
      console.log('watchEffect所指定的回调执行了')
    })

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

生命周期

vue2的生命周期:

vue3的生命周期

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

    • beforeDestroy改名为 beforeUnmount(卸载前)

    • destroyed改名为 unmounted(卸载后)

  • Vue3.0也提供了 Composition API(组合式API,即往setup写) 形式的生命周期钩子,与Vue3.x中钩子对应关系如下:

    • beforeCreate===>setup()

    • created===>setup()(beforeCreate和created就相当于setup(),所以没有这两个组合式API,写不进去)

    • beforeMount ===>onBeforeMount

    • mounted=======>onMounted

    • beforeUpdate===>onBeforeUpdate

    • updated =======>onUpdated

    • beforeUnmount ==>onBeforeUnmount

    • unmounted =====>onUnmounted

  • 组合式API形式的生命周期钩子的执行时机比配置项形式的生命周期钩子更快些,但一般不两种写法混用,且使用组合式API时,须先引入import {onBeforeMount,onMounted,...} from 'vue'

  • 通过组合式API的形式去使用生命周期钩子

    bash 复制代码
    setup(){
        onBeforeMount(()=>{
            console.log('---onBeforeMount---')
        })
    }
  • Vue3.0中少了一个判断环节

    • Vue2.x中即使没有vm.$mount(el)也可以走两个钩子(beforeCreate和created),之后再判断是否传入了el参数

    • Vue3.0中必须在一开始app.mount(el)之后才能再往下走,则无须判断是否传入了el参

代码示例:

App.vue

bash 复制代码
<template>
  <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
  <Demo v-if="isShowDemo"></Demo>
</template>

<script>
import Demo from './components/Demo'
import {ref} from "vue";

export default {
  name: 'App',
  components: {Demo},
  setup() {
    let isShowDemo = ref(true)

    return {
      isShowDemo
    }
  }
}
</script>

Demo.vue

bash 复制代码
<template>
  <h2>当前的求和为:{{ sum }}</h2>
  <button @click="sum++">点我+1</button>
</template>

<script>
import {ref} from "vue";

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

    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---')
  },
  beforeDestroy() {
    console.log('---beforeDestroy---')
  },
  beforeUnmount() {
    console.log('---beforeUnmount---')
  },
  destroyed() {
    console.log('---destroyed---')
  },
  unmounted() {
    console.log('---unmounted---')
  }
}
</script>

组合式api:

Demo.vue

bash 复制代码
<template>
  <h2>当前的求和为:{{ sum }}</h2>
  <button @click="sum++">点我+1</button>
</template>

<script>
import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from "vue";

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

    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---')
  },
  beforeDestroy() {
    console.log('---beforeDestroy---')
  },
  beforeUnmount() {
    console.log('---beforeUnmount---')
  },
  destroyed() {
    console.log('---destroyed---')
  },
  unmounted() {
    console.log('---unmounted---')
  }
}
</script>

自定义hook函数

  • 什么是hook?------ 本质是一个函数,把setup函数中使用的Composition API进行了封装。

  • 类似于vue2.x中的mixin。

  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

  • 封装:src - hooks - xxx.js(里面写功能函数),eg: usePoint.js

bash 复制代码
import {reactive,onMounted,onBeforeUnmount} from 'vue'
export default function (){
	//实现鼠标"打点"相关的数据
	let point = reactive({
		x:0,
		y:0
	})

	//实现鼠标"打点"相关的方法
	function savePoint(event){
		point.x = event.pageX
		point.y = event.pageY
		console.log(event.pageX,event.pageY)
	}

	//实现鼠标"打点"相关的生命周期钩子
	onMounted(()=>{
		window.addEventListener('click',savePoint)
	})

	onBeforeUnmount(()=>{
		window.removeEventListener('click',savePoint)
	})

	return point
}
  • 使用:
bash 复制代码
import usePoint from '../hooks/usePoint'
export default {
	name:'Test',
	setup(){
		const point = usePoint()
		return {point}
	}
}

代码示例:

Demo.vue

bash 复制代码
<template>
  <h2>当前的求和为:{{ sum }}</h2>
  <button @click="sum++">点我+1</button>
  <hr>
  <h2>当前点击鼠标的坐标为:x:{{ point.x }},y:{{ point.y }}</h2>
</template>

<script>
import {onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from "vue";

export default {
  name: "Demo",
  setup() {
    let sum = ref(0)
    let point = reactive({
      x: 0,
      y: 0
    })

    function savePoint(event) {
      point.x = event.pageX
      point.y = event.pageY
      console.log(event.pageX, event.pageY)
    }

    onMounted(() => {
      window.addEventListener('click', savePoint)
    })

    onBeforeUnmount(() => {
      window.removeEventListener('click', savePoint)
    })

    return {
      sum,
      point
    }
  }
}
</script>

App.vue

bash 复制代码
<template>
  <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
  <Demo v-if="isShowDemo"></Demo>
</template>

<script>
import Demo from './components/Demo'
import {ref} from "vue";

export default {
  name: 'App',
  components: {Demo},
  setup() {
    let isShowDemo = ref(true)

    return {
      isShowDemo
    }
  }
}
</script>

升级一下:

Demo.vue

bash 复制代码
<template>
  <h2>当前的求和为:{{ sum }}</h2>
  <button @click="sum++">点我+1</button>
  <hr>
  <h2>当前点击鼠标的坐标为:x:{{ point.x }},y:{{ point.y }}</h2>
</template>

<script>
import {ref} from "vue";
import usePoint from "@/hooks/usePoint";

export default {
  name: "Demo",
  setup() {
    let sum = ref(0)
    let point = usePoint()

    return {
      sum,
      point
    }
  }
}
</script>

Test.vue

bash 复制代码
<template>
  <h2>我是Test组件</h2>
  <h2>当前点击鼠标的坐标为:x:{{ point.x }},y:{{ point.y }}</h2>
</template>

<script>
import usePoint from "@/hooks/usePoint";

export default {
  name: "Test",
  setup() {
    const point = usePoint()

    return {point}
  }
}
</script>

<style scoped>

</style>

App.vue

bash 复制代码
<template>
  <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
  <Demo v-if="isShowDemo"></Demo>
  <hr>
  <Test></Test>
</template>

<script>
import Demo from './components/Demo'
import {ref} from "vue";
import Test from "@/components/Test";

export default {
  name: 'App',
  components: {Test, Demo},
  setup() {
    let isShowDemo = ref(true)

    return {
      isShowDemo
    }
  }
}
</script>

usePoint.js

bash 复制代码
import {onBeforeUnmount, onMounted, reactive} from "vue";

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

    function savePoint(event) {
        point.x = event.pageX
        point.y = event.pageY
        console.log(event.pageX, event.pageY)
    }

    onMounted(() => {
        window.addEventListener('click', savePoint)
    })

    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint)
    })

    return point
}

运行结果:

toRef

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

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

    bash 复制代码
    return {
    	person,
    	// name:toRef(person,'name'),
    	// age:toRef(person,'age'),
    	// salary:toRef(person.job.j1,'salary'),
    	...toRefs(person) //解构赋值(只解析第一层的,若里面还有对象,则还得用.写一长串)
    }
  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。这样模板中使用就不用写一长串了person.name → name

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

代码示例:

Demo.vue

bash 复制代码
<template>
  <h2>{{ person }}</h2>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <!--  <h2>薪资:{{ salary }}K</h2>-->
  <h2>薪资:{{ job.j1.salary }}K</h2>

  <button @click="name+='~'">修改姓名</button>
  <button @click="age++">增长年龄</button>
  <!--  <button @click="salary++">涨薪</button>-->
  <button @click="job.j1.salary++">涨薪</button>
</template>

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

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

    //反例
    // return {
    //   person,
    //   name: toRef(person.name),
    //   age: toRef(person.age),
    //   salary: toRef(person.job.j1.salary)
    // }

    // 正例
    return {
      person,
      // name: toRef(person, 'name'),
      // age: toRef(person, 'age'),
      // salary: toRef(person.job.j1, 'salary'),
      ...toRefs(person)
    }
  }
}
</script>

App.vue

bash 复制代码
<template>
  <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
  <Demo v-if="isShowDemo"></Demo>
</template>

<script>
import Demo from './components/Demo'
import {ref} from "vue";

export default {
  name: 'App',
  components: {Demo},
  setup() {
    let isShowDemo = ref(true)

    return {
      isShowDemo
    }
  }
}
</script>

4.其他Composition API

shallowReactive 与 shallowRef

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

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?(用法同reactive,ref,也需要import引入)

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

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

代码示例:

Demo.vue

bash 复制代码
<template>
  <!--  <h2>当前的x值是{{ x }}</h2>-->
  <!--  <button @click="x++">点我x+1</button>-->

  <h2>当前的x值是{{ x.y }}</h2>
  <button @click="x.y++">点我x+1</button>
  <hr>
  <h2>{{ person }}</h2>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <!--  <h2>薪资:{{ salary }}K</h2>-->
  <h2>薪资:{{ job.j1.salary }}K</h2>

  <button @click="name+='~'">修改姓名</button>
  <button @click="age++">增长年龄</button>
  <!--  <button @click="salary++">涨薪</button>-->
  <button @click="job.j1.salary++">涨薪</button>
</template>

<script>
import {toRef, toRefs, reactive, shallowReactive, shallowRef, ref} from "vue";

export default {
  name: "Demo",
  setup() {
    // let person = shallowReactive({ //只考虑第一层数据的响应式
    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })

    // let x = shallowRef(0)
    // let x = ref({
    //   y: 0
    // })

    let x = shallowRef({
      y: 0
    })

    return {
      person,
      x,
      ...toRefs(person)
    }
  }
}
</script>

App.vue

bash 复制代码
<template>
  <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
  <Demo v-if="isShowDemo"></Demo>
</template>

<script>
import Demo from './components/Demo'
import {ref} from "vue";

export default {
  name: 'App',
  components: {Demo},
  setup() {
    let isShowDemo = ref(true)

    return {
      isShowDemo
    }
  }
}
</script>

readonly 与 shallowReadonly

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

  • shallowReadonly:让一个响应式数据变为只读的(浅只读)--- 深层数据仍可修改。

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

  • 用法:person = readonly(person)

代码示例:

Demo.vue

bash 复制代码
<template>
  <h2>当前求和为:{{ sum }}</h2>
  <button @click="sum++">点我++</button>
  <hr>
  <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 {toRefs, reactive, ref, readonly, shallowReadonly, shallowRef} from "vue";

export default {
  name: "Demo",
  setup() {
    let sum = ref(0)
    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })

    // person = readonly(person)
    // person = shallowReadonly(person)
    // sum = readonly(sum)
    // sum = shallowReadonly(sum)



    return {
      sum,
      ...toRefs(person)
    }
  }
}
</script>

App.vue

bash 复制代码
<template>
  <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
  <Demo v-if="isShowDemo"></Demo>
</template>

<script>
import Demo from './components/Demo'
import {ref} from "vue";

export default {
  name: 'App',
  components: {Demo},
  setup() {
    let isShowDemo = ref(true)

    return {
      isShowDemo
    }
  }
}
</script>

toRaw 与 markRaw

  • toRaw:

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

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

  • markRaw:

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

    • 应用场景:(其使用场景高于toRow,可以提高性能)

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

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

代码示例:

Demo.vue

bash 复制代码
<template>
  <h2>当前求和为:{{ sum }}</h2>
  <button @click="sum++">点我++</button>
  <hr>
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h2>薪资:{{ job.j1.salary }}K</h2>
  <h2 v-show="person.car">座驾信息:{{ person.car }}</h2>

  <button @click="name+='~'">修改姓名</button>
  <button @click="age++">增长年龄</button>
  <button @click="job.j1.salary++">涨薪</button>

  <button @click="showRawPerson">输出最原始的person</button>
  <button @click="addCar">给人添加一台车</button>
  <button @click="person.car.name+='!'">换车名</button>
  <button @click="changePrice">换价格</button>


</template>

<script>
import {toRefs, reactive, ref, toRaw, markRaw} from "vue";

export default {
  name: "Demo",
  setup() {
    let sum = ref(0)
    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })

    function showRawPerson() {
      const p = toRaw(person)
      p.age++
      console.log(p)

      //ref不行,reactive行
      // const sum = toRaw(sum)
      // console.log(sum)
    }

    function addCar() {
      let car = {name: '奔驰', price: 40}
      // person.car = car
      person.car = markRaw(car)
    }

    function changePrice(){
      person.car.price++
      console.log(person.car.price)
    }


    return {
      sum,
      person,
      ...toRefs(person),
      showRawPerson,
      addCar,
      changePrice
    }
  }
}
</script>

App.vue

bash 复制代码
<template>
  <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
  <Demo v-if="isShowDemo"></Demo>
</template>

<script>
import Demo from './components/Demo'
import {ref} from "vue";

export default {
  name: 'App',
  components: {Demo},
  setup() {
    let isShowDemo = ref(true)

    return {
      isShowDemo
    }
  }
}
</script>

customRef

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

  • 实现防抖效果:

    bash 复制代码
    <template>
    	<input type="text" v-model="keyword">
    	<h3>{{keyword}}</h3>
    </template>
    
    <script>
    	import {ref,customRef} from 'vue'
    	export default {
    		name:'Demo',
    		setup(){
    			// let keyword = ref('hello') //使用Vue准备好的内置ref
    			//自定义一个myRef
    			function myRef(value,delay){
    				let timer
    				//通过customRef去实现自定义
    				return customRef((track,trigger)=>{
    					return{
    						get(){
    							track() //告诉Vue这个value值是需要被"追踪"的
    							return value
    						},
    						set(newValue){
    							clearTimeout(timer) //防抖-防止开启多个定时器而未及时关闭
    							timer = setTimeout(()=>{
    								value = newValue
    								trigger() //告诉Vue去更新界面
    							},delay)
    						}
    					}
    				})
    			}
    			let keyword = myRef('hello',500) //使用程序员自定义的ref
    			return {
    				keyword
    			}
    		}
    	}
    </script>

代码示例:

App.vue

bash 复制代码
<template>
  <input type="text" v-model="keyWord">
  <h3>{{ keyWord }}</h3>
</template>

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

export default {
  name: 'App',
  setup() {
    function myRef(value,delay) {
      let timmer
      return customRef((track, trigger) => {
        return {
          get() {
            console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
            track()//通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
            return value
          },
          set(newValue) {
            console.log(`有人把myRef这个容器中的数据改为了:${value}`)
            clearTimeout(timmer)
            timmer = setTimeout(()=>{
              value = newValue
              trigger()//通知Vue去重新解析模板
            },delay)
          }
        }
      })
      return x
    }

    // let keyWord = ref('hello') //使用Vue提供的ref
    let keyWord = myRef('hello',500) //使用程序员自定义的ref

    return {
      keyWord
    }
  }
}
</script>

provide 与 inject

  • 作用:实现祖与后代组件间通信(跨级组件)(其实父子组件之间也能用,但没必要,用props就好)

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

  • 具体写法:(须先引入import {provide} from 'vue' import {inject} from 'vue')

    1. 祖组件中:

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

      setup(props,context){

      ......

      const car = inject('car')

      return {car}

      ......

      }

代码示例:

App.vue

bash 复制代码
<template>
  <div class="app">
    <h3>我是App组件(祖),{{ name }}--{{ price }}</h3>
    <Child></Child>
  </div>
</template>

<script>

import Child from "@/components/Child";
import {reactive, toRefs, provide} from "vue";

export default {
  name: 'App',
  components: {Child},
  setup() {
    let car = reactive({name: '奔驰', price: '40W'})
    provide('car', car)//给自己的后代组件传递数据
    return {...toRefs(car)}
  }
}
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>

Child.vue

bash 复制代码
<template>
  <div class="child">
    <h3>我是Child组件(儿)--{{ car.name }} --- {{ car.price }}</h3>
    <Son></Son>
  </div>
</template>

<script>
import Son from "@/components/Son";
import {inject} from "vue";

export default {
  name: "Child",
  components: {Son},
  setup() {
    let car = inject('car')
    console.log('child----')

    return {car}
  }
}
</script>

<style scoped>
.child {
  background-color: skyblue;
  padding: 10px;
}
</style>

Son.vue

bash 复制代码
<template>
  <div class="son">
    <h3>我是son组件(孙),{{ car.name }} -- {{ car.price }}</h3>
  </div>
</template>

<script>
import {inject} from "vue";

export default {
  name: "Son",
  setup() {
    let car = inject('car')
    return {car}
  }
}
</script>

<style scoped>
.son {
  background-color: orange;
  padding: 10px;
}
</style>

响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象 (isRef(sum),是返回true,否返回false)

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

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

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

使用前都需import引入import {isRef,isReactive,isReadonly,isProxy } from 'vue'

代码示例:

App.vue

bash 复制代码
<template>
  <h3>我是App组件</h3>
</template>

<script>

import {ref, reactive, readonly, toRefs, isRef, isReactive, isReadonly, isProxy} from "vue";

export default {
  name: 'App',
  setup() {
    let car = reactive({name: '奔驰', price: '40W'})
    let sum = ref(0)
    let car2 = readonly(car)

    console.log(isRef(sum))
    console.log(isReactive(car))
    console.log(isReadonly(car2))
    console.log(isProxy(car))
    console.log(isProxy(car2))
    console.log(isProxy(sum))

    return {
      ...toRefs(car)
    }
  }
}
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>

5.Composition API的优势

Options API 存在的问题 (Vue2中的配置式API):使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

Composition API 的优势 (Vue3中的组合式API):我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。(hook函数)

  • 在Vue2中: 组件必须有一个根标签

  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中

  • 好处: 减少标签层级, 减小内存占用

6.新的组件

Fragment

  • 在Vue2中:组件必须有一个根标签
  • 在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中。
  • 好处:减少标签层级,减小内存占用

Teleport

  • 什么是Teleport?------ Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

代码示例:

App.vue

bash 复制代码
<template>
  <Child></Child>
</template>

<script>

import Child from "@/components/Child";

export default {
  name: 'App',
  components: {Child},
}
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>

Child.vue

bash 复制代码
<template>
  <div class="child">
    <h3>我是Child组件</h3>
    <Son></Son>
  </div>
</template>

<script>
import Son from "@/components/Son";

export default {
  name: "Child",
  components: {Son},
}
</script>

<style scoped>
.child {
  background-color: skyblue;
  padding: 10px;
}
</style>

Dialog.vue

bash 复制代码
<template>
  <div>
    <button @click="isShow=true">点我弹个窗</button>

    <teleport to="body">
      <div v-if="isShow" class="mask">
        <div v-if="isShow" class="dialog">
          <h3>我是一个弹窗</h3>
          <h4>一些内容</h4>
          <h4>一些内容</h4>
          <h4>一些内容</h4>
          <button @click="isShow=false">关闭弹窗</button>
        </div>
      </div>
    </teleport>
  </div>
</template>

<script>
import {ref} from "vue";

export default {
  name: "Dialog",
  setup() {
    let isShow = ref(false)

    return {
      isShow
    }
  }
}
</script>

<style scoped>
.dialog {
  width: 300px;
  height: 300px;
  background-color: green;
  text-align: center;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.mask {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgb(0, 0, 0, 0.5);
}
</style>

Son.vue

bash 复制代码
<template>
  <div class="son">
    <h3>我是son组件</h3>
    <Dialog></Dialog>
  </div>
</template>

<script>
import Dialog from "@/components/Dialog";
export default {
  name: "Son",
  components: {Dialog},
}
</script>

<style scoped>
.son {
  background-color: orange;
  padding: 10px;
}
</style>

Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

      bash 复制代码
      // import Child from './components/Child' //静态引入
      import {defineAsyncComponent} from 'vue' //defineAsyncComponent即定义异步组件
      const Child = defineAsyncComponent(()=>import('./components/Child.vue'))//动态(异步)引入
    • 使用Suspense包裹组件,并配置好defaultfallback

      bash 复制代码
      <template>
      	<div class="app">
      		<h3>我是App组件</h3>
      		<Suspense> <!-- Suspense无须引入,是内置的可直接使用 -->
      			<template v-slot:default> <!-- 默认展示的内容 -->
      				<Child/>
      			</template>
      			<template v-slot:fallback> <!-- 默认内容未加载完成时展示的内容(退路) -->
      				<h3>加载中.....</h3>
      			</template>
      		</Suspense>
      	</div>
      </template>
    • 返回一个Promise实例

      bash 复制代码
      async setup(){
      	let sum = ref(0)
      	/* return new Promise((resolve, reject)=>{
      		setTimeout(() => {
      			resolve({sum})
      		}, 3000);
      	}) */
      	let p = new Promise((resolve,reject)=>{
      		setTimeout(()=>{
      			resolve({sum})
      		},3000) 
      	})
      	return await p
      }

代码示例:

App.vue

bash 复制代码
<template>
  <div class="app">
    <h3>我是App组件</h3>
    <Suspense>
      <template v-slot:default>
        <Child></Child>
      </template>
      <template v-slot:fallback>
        <h3>稍等,加载中。。。</h3>
      </template>
    </Suspense>
  </div>

</template>

<script>
  // import Child from "@/components/Child";//静态引入

  import {defineAsyncComponent} from "vue";
  const Child = defineAsyncComponent(() => import('./components/Child'))//异步引入

  export default {
    name: 'App',
    components: {Child},
  }
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>

Child.vue

bash 复制代码
<template>
  <div class="child">
    <h3>我是Child组件</h3>
    {{ sum }}
  </div>
</template>

<script>
import {ref} from "vue";

export default {
  name: "Child",
  async setup() {
    let sum = ref(0)

    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({sum})
      }, 1000)
    })

    return await p
  }
}
</script>

<style scoped>
.child {
  background-color: skyblue;
  padding: 10px;
}
</style>

7.其他

全局API的转移

Vue 2.x 有许多全局 API 和配置。

  • 例如:注册全局组件、注册全局指令等。

    bash 复制代码
    //注册全局组件
    Vue.component('MyButton', {
      data: () => ({
        count: 0
      }),
      template: '<button @click="count++">Clicked {{ count }} times.</button>'
    })
    
    //注册全局指令
    Vue.directive('focus', {
      inserted: el => el.focus()
    }

Vue3.0中对这些API做出了调整:

  • 将全局的API,即:Vue.xxx调整到应用实例(app)上
2.x 全局 API(Vue 3.x 实例 API (app)
Vue.config.xxxx app.config.xxxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      bash 复制代码
      .v-enter,
      .v-leave-to {
        opacity: 0;
      }
      .v-leave,
      .v-enter-to {
        opacity: 1;
      }
    • Vue3.x写法(更语义化)

      bash 复制代码
      .v-enter-from,
      .v-leave-to {
        opacity: 0;
      }
      
      .v-leave-from,
      .v-enter-to {
        opacity: 1;
      }
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes(定义别名按键)(因为keyCodes兼容性太差)

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      bash 复制代码
      <my-component
        v-on:close="handleComponentEvent"
        v-on:click="handleNativeClickEvent"
      />
    • 子组件中声明自定义事件

      bash 复制代码
      <script>
        export default {
          emits: ['close'] //未声明的click默认为原生事件
        }
      </script>
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 "只是 JavaScript" 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax