vue3.0--初识vue3.0、组合式API、EventBus、代码组织能力、Teleport、VueX中如何使用

 目录

一、初识vue3.0

1.1 vue3.0 的优点

1.2 vite 的基本使用

1.3 如何创建一个Vue3.0的项目

二、组合API

2.1 什么是选项API 和组合PAI

2.2 setup 函数

2.3 生命周期函数

2.4 reactive 函数

2.5 toRef 函数

2.6 toRefs 函数

2.7 ref 函数

2.8 demo

2.9 computed 函数

2.10 watch 函数

2.11 ref  属性

2.12 父子通讯

2.13 依赖注入

2.14 v-model 语法糖

三、EventBus

四、代码组织能力

五、Teleport

六、VueX中如何使用 

6.1 同步方法改变数据

6.2 异步方法改变数据

 


 

一、初识vue3.0

现在主流组件库都已经发布了支持vue3.0的版本,其他生态也在不断地完善中,这是趋势。

1.1 vue3.0 的优点

  • 最火框架,它是国内最火的前端框架之一,官方文档 (opens new window)中文文档(opens new window)
  • 性能提升,运行速度事vue2.x的1.5倍左右
  • 体积更小,按需编译体积比vue2.x要更小
  • 类型推断,更好的支持Ts(typescript)这个也是趋势
  • 高级给予,暴露了更底层的API和提供更先进的内置组件
  • ★组合API (composition api) ,能够更好的组织逻辑,封装逻辑,复用逻辑
  • 重写了虚拟Dom的实现(且保证了兼容性,脱离模版的渲染需求旺盛)
  • 编译模板的优化
  • 更高效的组件初始化
  • update性能提高1.3~2倍
  • SSR速度提高了2~3倍
  • 可以将无用模块“剪辑”,仅打包需要的(比如v-model,<transition>,用不到就不会打包)。
  • 一个简单“HelloWorld”大小仅为:13.5kb
  • 包含运行时完整功能:22.5kb,拥有更多的功能,却比Vue 2更迷你。 很多时候,我们并不需要 vue提供的所有功能,在 vue 2 并没有方式排除掉,但是 3.0 都可能做成了按需引入
  • ......


1.2 vite 的基本使用

vite是什么:

官方文档(opens new window)https://cn.vitejs.dev/

  • 它是一个更加轻量(热更新速度快,打包构建速度快)的vue项目脚手架工具
  • 相对于vue-cli它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置
  • 所以: 在单纯学习vue3语法会使用它,后面做项目的时候我们还是使用vue-cli

vite基本使用:

  • 创建项目 npm init vite-app 项目名称 或者 yarn create vite-app 项目名称
  • 安装依赖 npm i 或者 yarn
  • 启动项目 npm run dev 或者 yarn dev

总结: vite是什么?

  • 使用vite创建项目学习vue3语法,使用vue-cli创建项目正式开发。


1.3 如何创建一个Vue3.0的项目

基本步骤:

  • 在main.js中导入createApp函数
  • 定义App.vue组件,导入main.js
  • 使用createApp函数基于App.vue组件创建应用实例
  • 挂载至index.html的#app容器

 1.安装最新版本的Vue-cli工具,最新版本工具已经提供Vue3-preview

npm install -g @vue/cli
# OR
yarn global add @vue/cli

2.创建项目选择

图片替换文本

图片替换文本

3.根实例初始化:

在2.x中,通过new Vue()的方法来初始化

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

在3.x中Vue不再是一个构造函数,通过createApp方法初始化

createApp(App).use(store).use(router).mount('#app')


二、组合API

2.1 什么是选项API 和组合PAI

选项API:

在 Vue2.x 中使用的都是选项API,比如要定义数据就应该在 data 选项下、要定义方法就应该在 methods 选项下、要定义计算属性就应该在 computed 选项下...,这其实就是选项API,也叫 Options API

优点:易于学习和使用,写代码的位置已经约定好

缺点:代码组织性差,相似的逻辑代码不便于复用,逻辑复杂代码多了不好阅读

补充:虽然提供mixins用来封装逻辑,但是出现数据函数覆盖的概率很大,不好维护

组合API:

在 Vue3.x  中使用的就是组合API

代码风格:一个功能逻辑的代码组织在一起(包含数据,函数...)

优点:功能逻辑复杂繁多情况下,各个功能逻辑代码组织再一起,便于阅读和维护

缺点:需要有良好的代码组织能力和拆分逻辑能力

补充:vue3.0也支持vue2.x选项API写法


2.2 setup 函数

使用细节:

setup 是一个新的组件选项,作为组件中使用组合API的起点

从组件生命周期来看,它的执行在组件实例创建之前(vue2.x的beforeCreate)执行

这就意味着setup函数中 this 还不是组件实例,this 此时是 undefined,因此在setup中不能使用this

在模版中需要使用的数据和函数,需要在 setup 返回

App.vue,

<template>
  <div class="container">
    <h3>{{msg}}</h3>
    <button @click="say">点击</button>
  </div>
</template>

<script>

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    console.log(this); //  undefined

    // 数据
    const msg = 'hello vue3'

    // 函数
    const say = ()  => {
      console.log('hi, i am vue3.0');
    }

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { msg, say }
  }
}
</script>


2.3 生命周期函数

 

回顾vue2.x生命周期钩子函数:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed

认识vue3.0生命周期钩子函数:

  • setup 创建实例前
  • onBeforeMount 挂载DOM前
  • onMounted 挂载DOM后
  • onBeforeUpdate 更新组件前
  • onUpdated 更新组件后
  • onBeforeUnmount 卸载销毁前
  • onUnmounted 卸载销毁后

App.vue,

<template>
  <div class="container">
    根组件
  </div>
</template>

<script>
import { onBeforeMount, onMounted } from 'vue'

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    // 1.  DOM 渲染前的钩子函数
    onBeforeMount(() => {
      console.log('DOM 渲染前的钩子函数', document.querySelector('.container'));
    })
    // 2.  DOM 渲染后的钩子函数
    onMounted(() => {
      console.log('DOM 渲染后的钩子函数1', document.querySelector('.container'));
    })

    // 可以定义多个相同的钩子函数,从而去实现不同的逻辑
    onMounted(() => {
      console.log('DOM 渲染后的钩子函数2', document.querySelector('.container'));
    })

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return {  }
  }
}
</script>

总结: 组合API的生命周期钩子有7个,可以多次使用同一个钩子,执行顺序和书写顺序相同


2.4 reactive 函数

定义响应式数据

reactive是一个函数,它可以定义一个复杂数据类型(对象、数组),成为响应式数据

总结: 通常是用来定义响应式对象数据

App.vue,

<template>
  <div class="container">
    <p>{{obj.name}}</p>
    <p>{{obj.age}}</p>
    <button @click="updatedName">修改名字</button>
  </div>
</template>

<script>
import { reactive } from 'vue'

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const obj = reactive({
      name: 'ls',
      age: 18
    })

    // 修改名字
    const updatedName = () => {
      obj.name = 'zs'
    }

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { obj, updatedName }
  }
}
</script>


2.5 toRef 函数

定义响应式数据

toRef是函数,转换响应式对象某个属性为单独响应式数据,并且值是关联的

使用场景:有一个响应式对象数据,但是模版中只需要使用其中一项数据

App.vue,

<template>
  <div class="container">
    <p>{{obj.name}}</p>
    <p>{{obj.age}}</p>
    <button @click="updatedName">修改名字</button>
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    // 响应式数据对象
    const obj = reactive({
      name: 'ls',
      age: 18
    })

    // 2.模板中只想使用 name 属性
    // 注意:从响应式数据对象中解构出来的属性数据,不再是响应式数据
    // let { name } = obj // 解构出来的是一个普通数据,并不是响应式的数据
    const name = toRef(obj, 'name')
    const updatedName = () => {
      // toRef  转换响应式数据包装成对象,value 是存放值的位置
      name.value = 'zs'
    }

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { obj, updatedName }
  }
}
</script>


2.6 toRefs 函数

定义响应式数据

toRefs是函数,转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的

使用场景:剥离响应式对象(解构|展开),想使用响应式对象中的多个或者所有属性做为响应式数据

App.vue,

<template>
  <div class="container">
    <p>{{name}}</p>
    <p>{{age}}</p>
    <button @click="updateName">修改数据</button>
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    // 响应式数据对象
    const obj = reactive({
      name: 'ls',
      age: 18
    })
    // 解构或者展开响应式数据对象,后不再是响应式数据
    // const { name, age}  = obj
    // const obj2  = {...obj}

    const obj2 = toRefs(obj)
    console.log(obj2);

    const updateName = () => {
      // obj2.name.value = 'zs' // 或如下写法
      obj.name = 'zs'
    }

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { ...obj2, updateName }
  }
}
</script>


2.7 ref 函数

定义响应式数据

ref函数,常用于将简单数据类型定义为响应式数据,但是也可以定义复杂数据类型的响应式数据

  • 在修改值,获取值的时候,需要.value
  • 在模板中使用ref申明的响应式数据,可以省略.value

当得到的数据类型未知时,可以用 ref

const data = ref(null)
setTimeout(() => {
    data.value = res.data
 }, 1000)

使用场景:

  • 当你明确知道需要的是一个响应式数据 对象 那么就使用 reactive 即可
  • 其他情况使用ref

App.vue,

<template>
  <div class="container">
    <p>{{name}}</p>
    <p>{{age}}</p>
    <button @click="updateName">修改数据</button>
  </div>
</template>

<script>
import { ref } from 'vue'

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const name = ref('ls')

    const age = ref(20)

    const updateName  = () => {
      name.value = 'zs'
    }

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { name, age, updateName }
  }
}
</script>


2.8 demo

App.vue,

<template>
  <div class="container">
    <p>x:{{x}}</p>
    <p>y:{{y}}</p>
    <hr>
    <p>{{count}}</p>
    <button @click="add">+1</button>
  </div>
</template>

<script>
import { onMounted, onUnmounted, reactive, toRefs, ref } from 'vue'
const useMouse = () => {
      // 1.记录鼠标坐标
    // 1.1
    const mouse = reactive({
      x: 0,
      y: 0
    })

  // 1.3 修改响应式数据
    const move = (e) => {
      mouse.x = e.pageX
      mouse.y = e.pageY
    }

    // 1.2 等 DOM 渲染完毕,去监听事件
    onMounted (() => {
      document.addEventListener('mousemove', move)
    })

    // 1.4 组件销毁时,删除事件
    onUnmounted(() => {
      document.removeEventListener('mousemove')
    })

    return mouse
}

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const  mouse = useMouse()
    // 2.累加
    const count = ref(0)
    const add = () => {
      count.value ++
    }
   
    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { ...toRefs(mouse), count, add }
  }
}
</script>


2.9 computed 函数

定义计算属性:当需要依赖现有的响应式数据,根据一定逻辑得到一个新的数据

computed函数,是用来定义计算属性的,计算属性不能修改

目的:让计算属性支持双向数据绑定

总结:计算属性两种用法:

  • 给computed传入函数,返回值就是计算属性的值
  • 给computed传入对象,get获取计算属性的值,set监听计算属性改变

i. 基本用法:

App.vue,

<template>
  <div class="container">
    <p>今年:{{age}}岁</p>
    <p>后年:{{newAge}}岁</p>
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const age = ref(18)
    const newAge= computed(()  => {
      // 得到后年的值
      // 该函数的返回值就是计算属性的值
      return age.value + 2
    })

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { age, newAge }
  }
}
</script>

 

ii. 高级用法:

App.vue,

<template>
  <div class="container">
    <p>今年:{{age}}岁</p>
    <p>后年:{{newAge}}岁</p>
    <hr>
    <p>使用  v-model 绑定计算属性</p>
    <input type="text" v-model="newAge">
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const age = ref(18)

    // 2.计算属性高级用法
    const newAge = computed({
      //  get 获取计算属性的值
      get(){
        return age.value + 2
      },

      // set 给计算属性设置值
      set(value){
        age.value = value - 2
      }
    })

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { age, newAge }
  }
}
</script>

 


2.10 watch 函数

定义计算属性

watch函数,是用来定义侦听器的:

  • 监听ref定义的响应式数据

App.vue

<template>
  <div class="container">
    <p>count的值:{{count}}</p>
    <button @click="add">修改数据</button>
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const count = ref(8)
    const add =  () => {
      count.value++ 
    }

    //  需要监听数据的变化
    /**
     * 第一个参数:需要监听的目标
     * 第二个参数:改变后触发的函数
     */
    watch(count, (newValue, oldValue) => {
      console.log('newvalue' + newValue);
      console.log('oldValue' + oldValue);
    })

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { count, add }
  }
}
</script>

 

  • 监听多个响应式数据数据

App.vue

<template>
  <div class="container">
    <p>count的值:{{count}}</p>
    <button @click="add">修改数据</button>
    <hr>
    <p>{{obj.name}}</p>
    <p>{{obj.age}}</p>
    <button @click="updataName">修改数据</button>
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const count = ref(8)
    const add =  () => {
      count.value++ 
    }

    const obj = reactive({
      name: 'zs',
      age: 15
    })
    const updataName = () => {
      obj.name = 'ls'
    }
    

    // 监听多个数据的变化
    /**
     * 第一个参数:需要监听的目标
     * 第二个参数:改变后触发的函数
     */
    watch([count, obj], () => {
      console.log('监听多个数据的改变');
    })

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { count, add, obj, updataName }
  }
}
</script>

  • 监听reactive定义的响应式数据

App.vue

<template>
  <div class="container">
    <p>{{obj.name}}</p>
    <p>{{obj.age}}</p>
    <button @click="updataName">修改数据</button>
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    //  需要监听数据的变化
    /**
     * 第一个参数:需要监听的目标
     * 第二个参数:改变后触发的函数
     */
    const obj = reactive({
      name: 'zs',
      age: 15
    })
    const updataName = () => {
      obj.name = 'ls'
    }
    // 监听 reactive 数据
    watch(obj, (newValue, oldValue) => {
      console.log('newvalue' + newValue);
      console.log('oldValue' + oldValue);
    })

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { obj, updataName }
  }
}
</script>

  • 监听reactive定义的响应式数据,某一个属性

需要写成函数返回该属性的方式才能监听到

App.vue

<template>
  <div class="container">
    <p>count的值:{{count}}</p>
    <button @click="add">修改数据</button>
    <hr>
    <p>{{obj.name}}</p>
    <p>{{obj.age}}</p>
    <button @click="updataName">修改数据</button>
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const count = ref(8)
    const add =  () => {
      count.value++ 
    }

    const obj = reactive({
      name: 'zs',
      age: 15
    })
    const updataName = () => {
      obj.name = 'ls'
    }
    
    // 只监听 obj 里的 name -- 监听对象中某一个属性的变化
    /**
     * 第一个参数:需要监听的目标
     * 第二个参数:改变后触发的函数
     */
    watch(() => obj.name, () => {
      console.log('监听到了 name 的变化');
    })

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { count, add, obj, updataName }
  }
}
</script>

  • 深度监听
  • 默认执行

App.vue

<template>
  <div class="container">
    <p>count的值:{{count}}</p>
    <button @click="add">修改数据</button>
    <hr>
    <p>{{obj.name}}</p>
    <p>{{obj.age}}</p>
    <button @click="updataName">修改数据</button>
    <hr>
     <p>{{obj.brand.name}}</p>
    <button @click="updataBrandName">修改名字</button>
  </div>
</template>

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

export default {
  name: 'App',
  // 组合API的起点
  setup () {
    const count = ref(8)
    const add =  () => {
      count.value++ 
    }

    const obj = reactive({
      name: 'zs',
      age: 15,
      brand: {
        id: 1,
        name: 'lv'
       }
    })
    
    const updataName = () => {
      obj.name = 'ls'
    }

    const updataBrandName = () => {
      obj.brand.name = 'll'
    }

    //  需要监听数据的变化
    /**
     * 第一个参数:需要监听的目标
     * 第二个参数:改变后触发的函数
     */
    watch(() => obj.brand, () => {
      console.log('brand数据改变了');
    },{
      // 深度监听
      deep: true,
      // 默认执行
      immediate: true
    })

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { count, add, obj, updataName, updataBrandName }
  }
}
</script>


2.11 ref  属性

获取DOM或者组件实例可以使用ref属性,写法和vue2.0需要区分开

i. 获取单个DOM或者组件:

App.vue,

<template>
  <div class="container">
    <!-- vue2.x 获取div元素:
    1. 通过 ref 属性绑定该元素
    <div ref="div">我是box</div>
    再由 this.$refs.div 获取元素
     -->
    <!-- <div>我是box</div> -->

    <!-- vue2.x 获取 v-for 遍历的多个元素
     2. 通过 ref 属性绑定被遍历的元素
    <li v-for="i in 4" :key="i" ref="li">{{i}}</li>
    通过 this.$refs.li 获取所有遍历的元素
    -->
    <!-- <ul>
      <li v-for="i in 4" :key="i" ref="li">{{i}}</li>
    </ul> -->

    <!-- 单个元素或组件 -->
    <div ref="box">我是box</div>

    <!-- 被遍历的元素或组件 -->
  </div>
</template>

<script>
import { onMounted, ref } from 'vue'
export default {
  name: 'App',
  // 组合API的起点
  setup () {
    /**
     * 步骤:
     * 1.先定义一个空的响应式数据ref定义
     * 2.在 setup 中返回该数据,然后在需要获取的 DOM 元素上通过 ref 绑定该数据
     */
    const box = ref(null)
    console.log(box);

    onMounted(() => {
      console.log(box.value);
    })
    
    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { box }
  }
}
</script>

 

ii. 获取v-for遍历的DOM或者组件:

App.vue,

<template>
  <div class="container">
    <!-- vue2.x 获取div元素:
    1. 通过 ref 属性绑定该元素
    <div ref="div">我是box</div>
    再由 this.$refs.div 获取元素
     -->
    <!-- <div>我是box</div> -->

    <!-- vue2.x 获取 v-for 遍历的多个元素
     2. 通过 ref 属性绑定被遍历的元素
    <li v-for="i in 4" :key="i" ref="li">{{i}}</li>
    通过 this.$refs.li 获取所有遍历的元素
    -->
    <!-- <ul>
      <li v-for="i in 4" :key="i" ref="li">{{i}}</li>
    </ul> -->

    <!-- 被遍历的元素或组件 -->
    <ul>
      <li v-for="i in 4" :key="i" :ref="setDom">第{{i}}个li</li>
    </ul>
  </div>
</template>

<script>
import { onMounted, ref } from 'vue'
export default {
  name: 'App',
  // 组合API的起点
  setup () {
    /**
     * 2.获取 v-for 遍历的元素或组件
     * 步骤:
     * 1.定义一个空数组,接收 所有的li
     * 2.定义一个函数,往空数组里 push DOM
     */
    const domList = []
    const setDom = (el) => {
      console.log(el);
      domList.push(el)
    }
    console.log(domList);
    
    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { box, domList, setDom }
  }
}
</script>

 

总结:

  • 单个元素:先申明ref响应式数据,返回给模版使用,通过ref绑定数据

  • 遍历的元素:先定义一个空数组,定一个函数获取元素,返回给模版使用,通过ref绑定这个函数

    • 有一个边界问题:组件更新的时候会重复的设置dom元素给数组:
        // ref获取v-for遍历的DOM元素,需要在组件更新的时候重置接受dom元素的数组。
        onBeforeUpdate(()=>{
          list = []
        })


2.12 父子通讯

i. 父传子:

App.vue

<template>
  <div class="container">
    <h3>父组件</h3>
    <p>{{money}}</p>
    <hr>
    <Son :money="money"></Son>
  </div>
</template>

<script>
import Son from './Son'
import { ref } from 'vue'
export default {
  name: 'App',
  components: {
    Son
  },
  // 组合API的起点
  setup () {
    const money = ref(100)
    
    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { money }
  }
}
</script>

Son.vue

<template>
  <div>
      <h3>子组件</h3>
      <p>{{money}}</p>
  </div>
</template>

<script>
import { onMounted } from '@vue/runtime-core'
export default {
name: 'Son',
// 子组件接收父组件数据使用 props 即可
props: {
    money: {
        type: Number,
        default: 0
    }
},
/**
 * 1.props:外部组件传递过来的内容,响应式的,当传入新的 prop 时,它将被更新
 * 
 * 2.
 */
setup (props) {
    // 若想在这里获取父组件的数据
    onMounted(() => {
        // console.log(this.money); // 报错
    })

    onMounted(() => {
        console.log(props.money); // 100
    })

    console.log(props.money);
}
}
</script>

注意点:

不能使用 ES6 解构,它会消除 prop 的响应性;如果需要解构 prop,可以在 setup 函数中使用toRefs函数来完成此操作:例如,

// MyBook.vue

import { toRefs } from 'vue'

setup(props) {
  const { title } = toRefs(props)

  console.log(title.value)
}

如果 title 是可选的 prop,则传入的 props 中可能没有 title 。在这种情况下,toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef 替代它:例如,

// MyBook.vue
import { toRef } from 'vue'
setup(props) {
  const title = toRef(props, 'title')
  console.log(title.value)
}

 

ii. 子传父:

App.vue

<template>
  <div class="container">
    <h3>父组件</h3>
    <p>{{money}}</p>
    <hr>
    <Son :money="money" @changeMoney="updateMoney"></Son>
  </div>
</template>

<script>
import Son from './Son'
import { ref } from 'vue'
export default {
  name: 'App',
  components: {
    Son
  },
  // 组合API的起点
  setup () {
    const money = ref(100)

    const updateMoney = (newMoney) => {
      // console.log(newMoney);
      money.value  = newMoney
    }
    
    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { money, updateMoney }
  }
}
</script>

Son.vue

<template>
  <div>
      <h3>子组件</h3>
      <p>{{money}}</p>
      <button @click="changeMoney">花50元</button>
  </div>
</template>

<script>
import { onMounted } from '@vue/runtime-core'
export default {
name: 'Son',
// 子组件接收父组件数据使用 props 即可
props: {
    money: {
        type: Number,
        default: 0
    }
},
/**
 * 1.props:外部组件传递过来的内容,响应式的,当传入新的 prop 时,它将被更新
 * 
 * 2.context:一个普通 JavaScript 对象,暴露了其它可能在 setup 中有用的值
 * context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,
 * 这意味着你可以安全地对 context 使用 ES6 解构
 */
setup (props, {emit}) {
    // 若想在这里获取父组件的数据
    onMounted(() => {
        // console.log(this.money); // 报错
    })

    onMounted(() => {
        //console.log(props.money); // 100
    })

    //console.log(props.money);

    // 子传父
    const changeMoney = () => {
        // 消费了50
        // 通知父组件,money 需要变成 50
        emit('changeMoney', 50)
    }

    return { changeMoney }
}
}
</script>

注意:

context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。例如:

// MyBook.vue
export default {
  setup(props, { attrs, slots, emit, expose }) {
    ...
  }
}

iii. 扩展:

  • 在vue2.x的时候 .sync 除去v-model实现双向数据绑定的另一种方式
<!-- <Son :money='money' @update:money="fn"  /> -->
<Son :money.sync='money'  />
  • 在vue3.0的时候,使用 v-model:money="money" 即可
    <!-- <Son :money="money" @update:money="updateMoney" /> -->
    <Son v-model:money="money" />

总结:

  • 父传子:在setup种使用props数据 setup(props){ // props就是父组件数据 }
  • 子传父:触发自定义事件的时候emit来自 setup(props,{emit}){ // emit 就是触发事件函数 }
  • 在vue3.0中 v-model 和 .sync 已经合并成 v-model 指令


2.13 依赖注入

使用provide函数和inject函数完成后代组件数据通讯

使用场景:有一个父组件,里头有子组件,有孙组件,有很多后代组件,共享父组件数据

Provide / Inject的应用,它们在3.x中得到了增强:

  • provide() 和 inject() 可以实现嵌套组件之间的数据传递。
  • 这两个函数只能在 setup() 函数中使用。
  • 父级组件中使用 provide() 函数向下传递数据。
  • 子级组件中使用 inject() 获取上层传递过来的数据。
  • 不限层级

App.vue,

<template>
  <div class="container">
    <p>我是父组件</p>
    <p>{{money}}</p>
    <button @click="money=1000">发钱</button>
    <hr>
    <Son1></Son1>
  </div>
</template>

<script>
import Son1 from "./Son1"
import { ref, provide } from 'vue'
export default {
  name: 'App',
  components: {
    Son1
  },
  // 组合API的起点
  setup () {
    const money = ref(100)

    const changeMoney = (saleMoney) => {
      console.log('changeMoney');
      money.value = saleMoney
    }

    // 将数据提供给后代组件 provide
    provide('money',  money)

    // 将函数提供给后代组件 provide
    provide('changeMoney', changeMoney)
    
    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { money, changeMoney }
  }
}
</script>

Son1.vue,

<template>
  <div>
      <p>我是儿子组件</p>
      <p>{{money}}</p>
      <hr>
      <GrandSon></GrandSon>
  </div>
</template>

<script>
import { inject } from '@vue/runtime-core'
import GrandSon from './GrandSon'
export default {
name: 'Son',
components: {
  GrandSon
},
setup () {
    // 接收祖先组件提供的数据
    const money = inject('money')

    return { money }
}
}
</script>

GrandSon.vue,

<template>
  <div>
      <p>我是孙子组件</p>
      <p>{{money}}</p>
      <button @click="changeMoneyFn">花费了50</button>
  </div>
</template>

<script>
import { inject } from '@vue/runtime-core'
export default {
    name: 'GrandSon',
    setup () {
        // 接收祖先组件传递过来的数据
        const money = inject('money')

        // 孙组件消费50,通知祖先组件 App.vue进行修改
        // 不能直接在这里修改,需要遵循单项数据流原则--谁定义谁修改
        const changeMoney = inject('changeMoney')

        const changeMoneyFn = () => {
            changeMoney(50)
        }

        return { money,  changeMoney, changeMoneyFn }
    }
}
</script>

 

总结:

  • provide函数提供数据和函数给后代组件使用
  • inject函数给当前组件注入provide提供的数据和函数


2.14 v-model 语法糖

在vue2.0中v-mode语法糖简写的代码:

 <Son :value="msg" @input="msg=$event" />

 

在vue3.0中v-model语法糖有所调整:

<Son :modelValue="msg" @update:modelValue="msg=$event" />

App.vue

<template>
  <div class="container">
    <!-- 
      如果想获取原生事件的事件对象
      如果绑定的是函数 fn fn(e) { // e 就是事件对象 }
      如果绑定的是js表达式,此时提供一个默认的变量 $event
     -->
    <h3 @click="$event.target.style.color='red'">父组件</h3>
    <p>{{count}}</p>
    <hr>
    <!-- 
      如果想获取自定义事件的事件对象
      如果绑定的是函数 fn fn(data) { // data 触发自定义事件的传参 }
      如果绑定的是js表达式,此时 $event 代表的是触发自定义事件的传参
    -->
    <Son2 :modeValue="count" @update:modeValue="count=$event"></Son2>
    <!-- 可以简写成如下形式 -->
    <!-- <Son2 v-model="count"></Son2> -->
  </div>
</template>

<script>
import Son2 from './Son2'
import { ref } from 'vue'
export default {
  name: 'App',
  components: {
    Son2
  },
  // 组合API的起点
  setup () {

    const count = ref(10)

    // 模版中需要使用的数据和函数,需要在 setup 中返回
    return { count }
  }
}
</script>

Son2.vue

<template>
  <div>
      <h3>子组件</h3>
      <p>{{modeValue}}</p>
      <button @click="fn">改变数据</button>
  </div>
</template>

<script>
export default {
    name: 'Son2',
    props: {
        modeValue: {
            type: Number,
            default: 0
        }
    },
    setup (props, {emit}) {
        const fn = () => {
            // 改变数据
            emit('update:modeValue', 100)
        }

        return { fn }
    }
}
</script>

 

总结: vue3.0封装组件支持v-model的时候,父传子:modelValue 子传父 @update:modelValue

补充: vue2.0的 xxx.sync 语法糖解析 父传子 :xxx 子传父 @update:xxx 在vue3.0 使用 v-model:xxx 代替


三、EventBus

 在 vue3.x 中无法使用EventBus

在 src 目录下新建一个名为 utils 的文件夹,并在该文件夹里新建一个名为 mitt.js 的文件

并通过 npm install --save mitt 安装 mitt

在 main.js 里引入(import "./utils/mitt")

在 src/components 目录下新建一个名为 Child 的文件夹,并在该文件夹里新建一个名为 index.vue 的文件

mitt.js,

import mitt from "mitt"

const emitter = mitt();

export default emitter;

src/components/Child/index.vue,

<template>
  <h3>Child页面</h3>
  <p>{{message}}</p>
</template>

<script>
import emitter from "../../utils/mitt"

export default {
    data () {
        return {
            message:""
        }
    },
    mounted () {
        //接收数据
        emitter.on("onCustomEvent",data=>{
            // console.log(data);
            this.message = data;
        });
    }
}
</script>

<style>

</style>

在2.x中通过EventBus的方法来实现组件通信:(Vue2Demo.vue)

var EventBus = new Vue()
Vue.prototype.$EventBus = EventBus
...
this.$EventBus.$on()  this.$EventBus.$emit()

在3.x中移除了$on, $off等方法(参考rfc),而是推荐使用mitt方案来代替(Vue3Demo.vue)

<template>
  <h3>vue3版本</h3>
  <button @click="sendMessageHandle">发送数据给Child</button>
  <Child />
</template>

<script>
import Child from "./Child"
import emitter from "../utils/mitt"

export default {
  setup(){
    const sendMessageHandle = () => {
      //发送数据
      emitter.emit("onCustomEvent",'456123');
    };


    return {
      sendMessageHandle
    }
  },
  components: {
    Child
  }
}
</script>

<style>

</style>


四、代码组织能力

关于代码组织能力,其实很简单,就是将多个组件都需要用到的一些方法等提取出来,单独放在一个文件里,然后分别引用即可


五、Teleport

关于这一部分其实没什么好说的,都比较简单

举个例子来说就是,你在书写代码时可以指定将其加到某一个class/id...属性为xxx的结构里:

<telepoer to="#app">
    <h3>这是一个独立的结构</h3>
</telepoer>

类似于上面的代码,就是将里面书写的 h3 放到一个属性为id=app的结构里


六、VueX中如何使用 

6.1 同步方法改变数据

Home.vue

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <h1>{{name}}</h1>
  </div>
</template>

<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';

export default {
  name: 'Home',
  setup(){
    const store = useStore();
    const {name}=toRefs(store.state);
    return{
      name
    }
  }
}
</script>

About.vue

<template>
  <div class="about">
    <h1 @click="handleClick">This is an about page</h1>
    <h1>{{name}}</h1>
  </div>
</template>

<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';

export default {
  name: 'About', 
  setup(){
    const store = useStore();

    const {name}=toRefs(store.state);

    const handleClick=()=>{
      store.commit('changeName','hello');
    }

    return{
      name,
      handleClick
    }
  }
}
</script>

store/index.js

import { createStore } from 'vuex'

//VueX 数据管理框架
//VueX 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
    // 存放全局的数据
    state: {
        name: 'dell'
    },
    //mutation 里面只允许写同步代码,不允许写异步代码
    //commit和mutation作关联
    mutations: {
        //第四步,对应的 mutation 被执行
        changeName(state, str) {
            //第五步 在 mutation 里修改数据
            // this.state.name = 'lee';
            state.name = str;
        }
    },
    modules: {}
})


6.2 异步方法改变数据

 About.vue

<template>
  <div class="about">
    <h1 @click="handleClick">This is an about page</h1>
    <h1>{{name}}</h1>
  </div>
</template>

<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';

export default {
  name: 'About', 
  setup(){
    const store = useStore();

    const {name}=toRefs(store.state);

    const handleClick=()=>{
      store.dispatch('getData');
    }

    return{
      name,
      handleClick
    }
  }
}
</script>

Home.vue

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <h1>{{name}}</h1>
  </div>
</template>

<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';

export default {
  name: 'Home',
  setup(){
    const store = useStore();
    const {name}=toRefs(store.state);
    return{
      name
    }
  }
}
</script>

index.js

import { createStore } from 'vuex'

//VueX 数据管理框架
//VueX 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
    // 存放全局的数据
    state: {
        name: 'dell'
    },
    //mutation 里面只允许写同步代码,不允许写异步代码
    //commit和mutation作关联
    mutations: {
        //第四步,对应的 mutation 被执行
        changeName(state, str) {
            //第五步 在 mutation 里修改数据
            // this.state.name = 'lee';
            state.name = str;
        }
    },
    // dispatch 和 actions 作关联
    actions: {
        //第二步,store 感知到了你触发了一个叫做 change 的 action
        //执行 change()方法
        getData(store) {
            setTimeout(() => {
                //第三步,提交一个 commit,触发一个 mutation
                store.commit('changeName', 'hello');
            }, 2000)
        }
    },
    modules: {}
})

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Vue3.0的组件高级功能包括保持动态组件状态和引用DOM元素。 保持动态组件状态的方法是使用Vue内置的<keep-alive>组件。通过将动态组件包裹在<keep-alive>标签,可以在切换动态组件时保持组件的状态。例如: ```html <keep-alive> <component :is="comName"></component> </keep-alive> ``` 在这个例子,comName是一个data属性,用于指定当前要渲染的组件的名称。通过在<component>标签使用:is属性,可以动态地指定要渲染的组件。 引用DOM元素的方法是使用ref属性。通过给DOM元素添加ref属性,可以在组件通过$refs属性获取对DOM元素的引用。例如: ```html <template> <h3>MyRef组件</h3> <button @click="getRef">获取$refs引用</button> </template> <script> export default { methods: { getRef() { console.log(this.$refs) // $refs指向一个空对象,需要在DOM元素上添加ref属性 } } } </script> ``` 在这个例子,点击按钮后,调用getRef方法可以在控制台上输出当前组件实例对象this。通过this.$refs可以访问到DOM元素的引用。 以上就是Vue3.0组件高级功能的两个示例,分别是保持动态组件状态和引用DOM元素。希望能对你有所帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Vue3.0----组件高级【下】(第五章)](https://blog.csdn.net/QQ675396947/article/details/127486948)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白小白从不日白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值