Vue3学习笔记(二)

目录

一:路由以及基本切换

二:路由器工作模式

三:to的三种写法

四:路由传参

五:push & replace

六:编程式路由 - 重点

七:路由重定向 - redirect

八:pinia

九:pinia - state / actions

十: pinia - storeToRefs()

十一: pinia - getter

十二: pinia - $subscribe

十三: pinia - store的组合式写法

十四:组件之间传值

(1): props

(2): 自定义事件

(3): mitt

(4): v-model

(5): $attrs

(6): $refs & $parent

(7): provide-inject

(8): pinia

(9): slot - 默认插槽

(10): slot - 具名插槽

(11): slot - 作用域插槽

一:路由以及基本切换

路由的安装: npm install vue-router

注意⚠️:

1. 路由组件通常存放在pages 或 views文件夹, 一般组件通常存放在components文件夹。

2. 通过点击导航, 视觉效果上“消失” 了的路由组件, 默认是被卸载掉的, 需要时再去挂载。

1: 路由的配置文件

//文件:router/index.ts
/**router:路由器 route:路由
  *关系:路由器管理多个路由(routes)
  */

//第一步:引入createRouter路由器
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../components/theHome.vue'
import About from '../components/theAbout.vue'

//第二步:创建路由器
const router = createRouter({
  history: createWebHistory(),//vue3必须引入路由器的工作模式
  routes: [
    //一个一个的路由规则
    {
      path: '/home',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      component: About
    }
  ]
})

//第三步:暴露router
export default router

2:main.ts

//main.ts

import './assets/main.css'

import { createApp } from 'vue' //引入creatApp来创建应用(花盆)
import App from './App.vue' //引入APP根组件(花的根)
import router from './router' //引入路由器

const app = createApp(App) //创建一个应用(花插进花盆里)

app.use(router)//使用路由器

app.mount('#app') //花摆放的位置-载入到index.html的#app容器中

3: APP.vue

//APP.vue
<template>
  <div class="navigate">
    <!-- 跳转到哪个路由,样式是怎样的 -->
    <RouterLink to="/home" active-class="active">首页</RouterLink>
    <RouterLink to="/about" active-class="active">关于</RouterLink>
  </div>
  <!-- 路由内容的展示区域-路由视图 -->
  <RouterView></RouterView>

</template>

<script setup lang="ts">
import { RouterView, RouterLink } from 'vue-router';

</script>

二:路由器工作模式

history模式

优点: URL更加美观, 不带有#, 更接近传统的网站URL。

缺点: 后期项目上线, 需要服务端配合处理路径问题, 否则刷新会有404错误。

应用场景:ToB较多

const router = createRouter({
    history:createWebHistory(), //history模式
})

hash模式

优点: 兼容性更好, 因为不需要服务器端处理路径。

缺点: URL带有#不太美观, 且在SEO优化方面相对较差。

应用场景:ToC较多

const router = createRouter({
    history:createWebHashHistory(), //hash模式
})

三:to的三种写法

<!-- to的第一种写法: 对象-path -->
<RouterLink :to="{path:'/theComputed'}" active-class="active">计算属性</RouterLink>

<!-- to的第二种写法: 对象-name -->
<RouterLink :to="{name:'watch_watch'}" active-class="active">监听</RouterLink>

<!-- to的第三种写法: 字符串 -->
<RouterLink to="/watchEffect" active-class="active">自动监视</RouterLink>

四:路由传参

有三种传递方式: query / params / props

query / params 需要引入useRoute 获取参数

import { useRoute } from 'vue-router';

1: query

//router/index.ts

  {
      path: '/theNews',
      name: 'theNews',
      component: TheNews,
      children:[
        {
          path:'detail',
          name:'detail',
          component: Detail,
        }
      ]
    },
//theNews.vue

<template>
  <ul>
    <li v-for="news in newsList" :key="news.id">
      <!-- query传参第一种写法  字符串拼接-->
        <!-- <RouterLink :to="`/theNews/detail?id=${news.id}&title=${news.title}&content=${news.content}`">{{news.title}}</RouterLink> -->

      <!-- query传参第二种写法  对象-->
      <RouterLink :to="{
        // 使用path or name
        // path: '/theNews/detail', 
        name: 'detail',
        query:{
          id:news.id,
          title:news.title,
          content:news.content
        }
      }">
        {{news.title}}
      </RouterLink>  
    </li>
  </ul>
  <!-- 展示区 -->
  <div class="news-content">
    <RouterView></RouterView>
  </div>
</template>

<script setup lang="ts" name="theAbout">
import { reactive } from 'vue';
import {RouterView,RouterLink} from 'vue-router'

const newsList = reactive([
    {id:'001',title:'很好的抗癌食物',content:'西蓝花'},
    {id:'002',title:'如何一夜暴富',content:'学IT'},
    {id:'003',title:'震惊,万万没想到',content:'这周六天班'},
    {id:'004',title:'好消息!好消息!',content:'还有一天放假'}
])

</script>

//theDetail.vue

<template>
 <ul class="news-list">
    <li>编号:{{ query.id }}</li>
    <li>标题:{{ query.title }}</li>
    <li>内容:{{ query.content }}</li>
  </ul>
</template>

<script setup lang="ts" name="theNews">
import { useRoute } from 'vue-router';
import { toRefs } from 'vue';

let route = useRoute();
let { query } = toRefs(route)
console.log(route)

</script>

2:params

注意⚠️:此时只能使用to的name属性,不可以使用path属性

//router/index.ts

{
  path: '/theNews',
  name: 'theNews',
  component: TheNews,
  children:[
    {
      //第一种写法 字符串拼接
      //需要再路径中进行占位. ?代表content可选,可以没有这个属性
      path:'detail/:id/:title/:content?',
      name:'detail',
      component: Detail,
    }
  ]
},
//theNews.vue

<!-- params传参第一种写法  字符串拼接-->
<RouterLink :to="`/theNews/detail/${news.id}/${news.title}/${news.content}`">{{news.title}}</RouterLink>

<!-- params传参第二种写法  对象-->
<!-- 只能使用name属性传参 -->
<RouterLink :to="{
  name: 'detail',
  params:{
    id:news.id,
    title:news.title,
    content:news.content
  }
}">
  {{news.title}}
</RouterLink> 
//theDetail.vue

<template>
 <ul class="news-list">
    <li>编号:{{ params.id }}</li>
    <li>标题:{{ params.title }}</li>
    <li>内容:{{ params.content }}</li>
  </ul>
</template>

<script setup lang="ts" name="theNews">
import { useRoute } from 'vue-router';
import { toRefs } from 'vue';

let route = useRoute();
let { params } = toRefs(route)
console.log(route)

</script>

3:props

三种方式,重点掌握第三种

可以直接拿到传递的参数,标签中不需要在进行 . 了

使用宏函数 defineProps() 接收参数

//router/index.ts

{
  path: '/theNews',
  name: 'theNews',
  component: TheNews,
  children:[
    {
      path:'detail',
      name:'detail',
      component: Detail,
      // 第一种:对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
      props:{a:1,b:2,c:3}, 

      // 第二种:布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
      //注意只能是params参数才能使用
      props:true
      
      //!!!!!重点掌握
      // 第三种:函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
      props(route){
         //传参的route  可以拿到所有的本路由信息
         return route.query
      }
    }
  ]
}
<template>
 <ul class="news-list">
    <li>编号:{{ id }}</li>
    <li>标题:{{ title }}</li>
    <li>内容:{{ content }}</li>
  </ul>
</template>

<script setup lang="ts" name="theNews">

//配合路由props使用,直接拿到路由传参的内容
defineProps(['id','title','content'])

</script>

五:push & replace

1. 作用: 控制路由跳转时操作浏览器历史记录的模式。

2. 浏览器的历史记录有两种写入方式: 分别为push和replace:

        push: 追加历史记录(默认值)。

        replace: 替换当前记录, 无痕浏览, 不能返回 。

3. 开启replace模式:  RouterLink中加入replace即可

<RouterLink replace to="/home" active-class="active">首页</RouterLink>

六:编程式路由 - 重点

第一步:引入useRouter

import { useRouter } from 'vue-router'

第二步:调用

const router = useRouter();

第三步:使用,push()或者replace()。

注意⚠️:push和replace里。和路由中的to写法一样,可以字符串也可以对象格式。

router.push({
    name: 'detail',
    params:{
      id:news.id,
      title:news.title,
      content:news.content
    }
})

应用场景:

1:符合某些条件进行路由跳转 -- 比如登录成功跳首页

2:鼠标滑过内容进行路由跳转 -- 比如导航栏 

<template>
  <ul>
    <li v-for="news in newsList" :key="news.id">
      <RouterLink :to="{
        name: 'detail',
        params:{
          id:news.id,
          title:news.title,
          content:news.content
        }
      }">
        {{news.title}}
      </RouterLink>  

      <button @click="showDeatil(news)">查看详情</button>

    </li>
  </ul>
  <!-- 展示区 -->
  <div class="news-content">
    <RouterView></RouterView>
  </div>
</template>

<script setup lang="ts" name="theAbout">
import { reactive } from 'vue';
import {RouterView, RouterLink, useRouter} from 'vue-router'

const newsList = reactive([
    {id:'001',title:'很好的抗癌食物',content:'西蓝花'},
    {id:'002',title:'如何一夜暴富',content:'学IT'},
    {id:'003',title:'震惊,万万没想到',content:'这周六天班'},
    {id:'004',title:'好消息!好消息!'}
])

const router = useRouter();
//定义一个NewsInter接口 进行news类型检查
interface NewsInter {
  id:string,
  title:string,
  content:string
}
//不写类型会导致报any的错误,可以直接写any类型,也可以写一个interface接口
//作用:限制参数类型
function showDeatil(news:NewsInter){
  //也可以使用router.replace();
  router.push({
    name: 'detail',
    params:{
      id:news.id,
      title:news.title,
      content:news.content
    }
  })
}

</script>

七:路由重定向 - redirect

重定向 redirect,将特定的路径,重新定向到已有路由。

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      redirect:'/home' //重定向
    }
  ]
})

八:pinia

pinia:vue3的状态管理库

安装:npm i pinia

Store是一个保存状态、业务逻辑的实体,每个组件都可以读取、写入它。

它有三个概念: state、getter、actions, 相当于组件中的: data、 computed 和 methods。

在目录src/store文件下,声明对应的store库文件(ts)。

//theCount.js

// store第一步 - 引入 defineStore(小仓库)
import { defineStore } from 'pinia'

/* defineStore()
第一个参数:theCount 相当于文件id
第二个参数:配置对象 */

// store第二步 - 暴露出去
export const useCountStore = defineStore('theCount',{
    /* state必须写成一个函数
    state:真正存储数据的地方 */
    state() {
        return {
            sum:2
        }
    },
})
//theCount.vue

<template>
  <div class="count">
    <h2>当前求和为:{{ countSrore.sum }}</h2>
    <!-- .number 字符串转为number类型 -->
    <select v-model.number="n">
      <option value="1"> 1 </option>
      <option value="2"> 2 </option>
      <option value="3"> 3 </option>
    </select>
    <button @click="add"> 加 </button>
    <button @click="minus"> 减 </button>
  </div>
</template>

<script setup lang="ts" name="theCount">
  import { ref } from "vue";
  // store第三步 - 引入store文件
  import { useCountStore } from '../store/theCount.ts'

  // store第四步 - 调用
  let countSrore = useCountStore();
  // 以下两种方式都可以拿到store中的数据
  console.log(countSrore.sum);
  console.log(countSrore.$state.sum);

  let n = ref(1) // 用户选择的数字

  // 方法
  function add(){
    countSrore.sum += n.value;
  }
  function minus(){
    countSrore.sum -= n.value;
  }
</script>

九:pinia - state / actions

修改数据有三种方式:

1: 拿到数据直接修改,少量数据

// 修改数据第一种 -  修改少量数据
countSrore.sum += n.value;

2: 借用$patch

// 修改数据第二种 -  修改数据碎片
countSrore.$patch({
  type:'化学',
  place: '净化海洋',
  sum: n.value
})

3: 借用 store中的 actions

// 修改数据第三种 -  修改数据,并有复杂的逻辑判断。
countSrore.changeInfo(n.value)
export const useCountStore = defineStore('theCount',{
    //actions里面放置的是一个一个的方法,用于响应组件中的“动作”
    actions:{
        changeInfo(n:any){
            //方法内可以进行数据修改和逻辑判断
            console.log(n);
            //actions中使用state中的数据,只需要用this就可以拿到
            this.sum += n;
        }
    },
    state() {
        return {
            type:'前端',
            place: '改变世界',
            sum:2
        }
    },
})

十: pinia - storeToRefs()

使用pinia中的 storeToRefs 进行store数据美化。只对store数据进行ref包裹

  import { storeToRefs } from 'pinia';


  let countSrore = useCountStore();
  
  // storeToRefs只会关注sotre中数据,不会对方法进行ref包裹
  let {sum, place, type} = storeToRefs(countSrore);  
  console.log('!!!!!',storeToRefs(countSrore))  

  // toRefs会对countSrore的所有内容进行ref包裹,包括数据、方法
  // let {sum, place, type} = toRefs(countSrore);
  // console.log('!!!!!',toRefs(countSrore))  

十一: pinia - getter

当state中的数据, 需要经过处理后再使用时, 可以使用getters配置。

export const useCountStore = defineStore('theCount',{
    state() {
        return {
            type:'web',
            place: '改变世界',
            sum:2
        }
    },
    getters:{
        //一:可以直接传参state
        bigNum:state => state.sum * 10,
        //二:可以使用this拿数据
        typeUpper():string{
            return this.type.toUpperCase();
        }
    }
})
<template>
  <div class="count">
    <h2>学习{{ type }},{{ place }},大写字母:{{ typeUpper }}</h2>
    <h2>当前求和为:{{ sum }},放大十倍后{{ bigNum }}</h2>
  </div>
</template>

<script setup lang="ts" name="theCount">
  import { useCountStore } from '../store/theCount.ts'
  import { storeToRefs } from 'pinia';

  let countSrore = useCountStore();
  let {sum, place, type, bigNum, typeUpper} = storeToRefs(countSrore);  

</script>

十二: pinia - $subscribe

// 通过 store 的 $subscribe() 方法侦听 state 及其变化

talkStore.$subscribe((mutate,state)=>{

    console.log('talkStore里面保存的数据发生了变化',mutate,state)
    localStorage.setItem('talkList',JSON.stringify(state.talkList))

})

十三: pinia - store的组合式写法

//二:组合式写法 第二个参数是方法
export const useTalkStore = defineStore('loveTalk', ()=>{
    // talkList就是state
    const talkList = reactive(
        JSON.parse(localStorage.getItem('talkList') as string) || []
    )
    // getLoveTalk函数相当于actions
    async function getLoveTalk(){
        // 发请求
        /*连续解构赋值+重命名 
        ({data:{content}}连续解构 
        content:title 重命名为title) */
        const {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')

        // 把请求回来的字符串,包装成一个对象
        const obj = {id:nanoid(),title}
        // 放到数组中
        talkList.unshift(obj)
    }

    // 必须要return
    return {talkList, getLoveTalk}

})

十四:组件之间传值

(1): props

若 父传子:属性值是非函数。

若 子传父:属性值是函数。

//father.vue

<h4>汽车:{{ car }}</h4>
<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
<Child :car="car" :sendToy="getToy"/>


<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import {ref} from 'vue'

	// 数据
	let car = ref('小米')
	let toy = ref('')

	// 把父组件的方法 传递给子组件
	function getToy(value:string){
		toy.value = value
	}
</script>
//child.vue

<h4>玩具:{{ toy }}</h4>
<h4>父给的车:{{ car }}</h4>
<button @click="sendToy(toy)">把玩具给父亲</button>

<script setup lang="ts" name="Child">
	import {ref} from 'vue'
	// 数据
	let toy = ref('奥特曼')

	// 声明接收props
	defineProps(['car','sendToy'])
</script>
(2): 自定义事件

常用于 子传父

注意⚠️:区分原生事件、自定义事件

原生事件:

        事件名是特定的( click、mosueenter等等 )

        事件对象$event: 就是事件相关信息的对象( target、pageX、keyCode ),可以.target

自定义事件:

        事件名是任意名称

        事件对象$event: 是调用emit时所提供的数据,可以是任意数据类型!!!,不能.target

//child.vue
<button @click="emit('send-toy',toy)">测试</button>

// 数据
let toy = ref('奥特曼')
// 声明自定义事件send-toy
const emit =  defineEmits(['send-toy'])


//father.vue
<Child @send-toy="saveToy"/>
// 用于保存传递过来的玩具
function saveToy(value:string){
    console.log('saveToy',value)
    toy.value = value
}
(3): mitt

与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信。

有四个方法:

  • on  ( 绑定事件 )
  • emit  ( 调用事件 ) 
  • off  ( 关闭某一个事件 )
  • all  ( 关闭所有事件 )

第一步安装: npm install mitt

第二步引入: import emitter from '@/utils/emitter';

第三步创建: const emitter = mitt();

第四步暴露: export default emitter

//emitter.ts
// 引入mitt
import mitt from 'mitt'

// 调用mitt得到emitter,emitter能:绑定事件、触发事件
const emitter = mitt()

// on - 绑定事件(可以N个)
emitter.on('test1',()=>{
  console.log('test1被调用了')
})
emitter.on('test2',()=>{
  console.log('test2被调用了')
})

// emit - 触发事件
setInterval(() => {
  emitter.emit('test1')
  emitter.emit('test2')
}, 1000);

setTimeout(() => {
  // off - 关闭某一个事件
  // emitter.off('test1')
  // emitter.off('test2')
  //all - 关闭所有事件
  emitter.all.clear()
}, 3000);


// 暴露emitter
export default emitter

第一步:在接收数据的组件中:(1):绑定事件 (2):在销毁前解绑事件

//child2.vue  --   收
import {ref,onUnmounted} from 'vue'
import emitter from '@/utils/emitter';

// 给emitter绑定send-toy事件
emitter.on('send-toy',(value:any)=>{
	toy.value = value
})
// 在组件卸载时解绑send-toy事件
onUnmounted(()=>{
	emitter.off('send-toy')
})

第二步:在提供数据的组件,在合适的时候触发事件

//child1.vue  --   发
import emitter from "@/utils/emitter";

<button @click="emitter.emit('send-toy',toy)">玩具给弟弟</button>
(4): v-model

实现 父 ↔ 子 之间 相互通信

组件标签上的v-model的本质::moldeValue + update:modelValue事件。

注意⚠️:HTMLInputElement 制定类型 - html的input输入类型元素

//father.vue


<h4>username:{{ username }}</h4>
<h4>password:{{ password }}</h4>
<!-- v-model用在html标签上  - 可以实现数据双向绑定 -->
<!-- <input type="text" v-model="username"> -->

<!-- input第7行 双向绑定的本质写法
    动态的value值,配合一个绑定事件
    HTMLInputElement 制定类型 - html的input输入类型元素-->
<input type="text" :value="username" 
    @input="username =(<HTMLInputElement>$event.target).value">

<!-- v-model用在组件标签上 -->
<!-- 直接写不能实现双向绑定 -->
<!-- <beautifulInput v-model="username"/> -->

<!-- elementUi 底层实现原理(本质写法)
    :modelValue: 实现数据到页面  等同于:value
    @update:modelValue: vue3的事件名(只不过带了冒号,等同于@input), 叫 更新-modelValue -->
<beautifulInput :modelValue="username" 
    @update:modelValue="username = $event"/> 


<script setup lang="ts" name="Father">
  import { ref } from "vue";
  import beautifulInput from './beautifulInput.vue'
  // 数据
  let username = ref('章三')
  let password = ref('123456')
</script>

//beautifulInput.vue
<template>
  <!-- @input 绑定update:modelValue事件 并传参- input输入框的内容 -->
  <input 
    type="text " 
    :value="modelValue"
    @input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)"
  >
  <br> 

  <input 
    type="text" 
    :value="mingzi"
    @input="emit('update:mingzi',(<HTMLInputElement>$event.target).value)"
  >
</template>

<script setup lang="ts" name="beautifulInput">
  //接收modelValue
  defineProps(['modelValue'])
  //接收事件
  const emit = defineEmits(['update:modelValue'])

</script>

使用组件类型的传值,可以传递多个model

//father.vue
<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>username:{{ username }}</h4>
    <h4>password:{{ password }}</h4>

    <!-- 1:修改modelValue - 重命名为名字
    2:可以写多个model -->
    <beautifulInput v-model:mingzi="username" v-model:mima="password"/>

  </div>
</template>

<script setup lang="ts" name="Father">
  import { ref } from "vue";
  import beautifulInput from './beautifulInput.vue'
  // 数据
  let username = ref('章三')
  let password = ref('123456')
</script>
//beautifulInput.vue
<template>
  <input 
    type="text" 
    :value="mingzi"
    @input="emit('update:mingzi',(<HTMLInputElement>$event.target).value)"
  >
  <br>

  <input 
    type="text" 
    :value="mima"
    @input="emit('update:mima',(<HTMLInputElement>$event.target).value)"
  >
</template>

<script setup lang="ts" name="beautifulInput">
  //接收modelValue
  defineProps(['mingzi', 'mima'])
  //接收事件
  const emit = defineEmits(['update:mingzi','update:mima'])

</script>
(5): $attrs

作用:当前组件的父组件,向当前组件的子组件通信( 祖→孙 传值)

原理:父传子过程中,子组件已接收的存在props中,未接收的存在attrs中。故子组件不接收的$attrs中的数据也可以直接传给孙组件。

//Father.vue
<Child :a="a" :b="b" v-bind="{x:100,y:200}" :updateA="updateA"/>

<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import {ref} from 'vue'

	let a = ref(1)
	let b = ref(2)

	function updateA(value:number){
		a.value  =  a.value * value
	}
</script>
//Child.vue

<!-- 子组件将未使用的$attr数据直接传递给孙组件 -->
<GrandChild v-bind="$attrs"/>

<script setup lang="ts" name="Child">
	import GrandChild from './GrandChild.vue'
</script>

//GrandChild.vue
<template>
	<div class="grand-child">
		<h3>孙组件</h3>
		<h4>a:{{ a }}</h4>
		<h4>b:{{ b }}</h4>
		<h4>x:{{ x }}</h4>
		<h4>y:{{ y }}</h4>
		<button @click="updateA(10)">点我将爷爷那的a更新</button>
	</div>
</template>

<script setup lang="ts" name="GrandChild">
	//接收数据
	defineProps(['a','b','x','y','updateA'])
</script>
(6): $refs & $parent

$refs用于: 父→子, 值为对象, 包含所有被、ref、属性标识的、DOM、元素或组件实例。

$parent用于: 子→父, 值为对象, 当前组件的父组件实例对象。

注意⚠️:需要传递的参数必须通过宏函数 defineExpose() 向外暴露

//Father.vue

<template>
	<div class="father">
		<h3>父组件</h3>
		<h4>房产:{{ house }}</h4>
		<button @click="changeToy">修改Child1的玩具</button>
		<button @click="changeComputer">修改Child2的电脑</button>
        <!-- 父传子 -->
		<button @click="getAllChild($refs)">让所有孩子的书变多</button>
		<Child1 ref="c1"/>
		<Child2 ref="c2"/>
	</div>
</template>

<script setup lang="ts" name="Father">
	import Child1 from './Child1.vue'
	import Child2 from './Child2.vue'
	import { ref,reactive } from "vue";
	let c1 = ref()
	let c2 = ref()
	
	// 数据
	let house = ref(4)

	//修改child1的玩具
	function changeToy(){
		c1.value.toy = '小猪佩奇'
	}

	//修改所有子组件的书,传的$refs 就会是所有子组件暴露出来的数据
	function getAllChild(refs:{[key:string]:any}){
		console.log('所有子组件====',refs)
		for (let key in refs){
			refs[key].book += 3
		}
	}
	// 向外部提供数据,允许子组件拿到的数据house
	defineExpose({house})
</script>

//Child1.vue 

<template>
  <div class="child1">
    <h3>子组件1</h3>
		<h4>玩具:{{ toy }}</h4>
		<h4>书籍:{{ book }} 本</h4>
        <!-- 子传父 -->
		<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
  </div>
</template>

<script setup lang="ts" name="Child1">
	import { ref } from "vue";
	let toy = ref('奥特曼')
	let book = ref(3)

	function minusHouse(parent:any){
		parent.house -= 1
	}

	// 把数据交给外部,允许父元素操作toy,book
	defineExpose({toy,book})

</script>

//Child2.vue
<template>
  <div class="child2">
    <h3>子组件2</h3>
		<h4>电脑:{{ computer }}</h4>
		<h4>书籍:{{ book }} 本</h4>
  </div>
</template>

<script setup lang="ts" name="Child2">
		import { ref } from "vue";
		let computer = ref('联想')
		let book = ref(6)

		// 把数据交给外部,允许父元素操作computer,book
		defineExpose({computer,book})
</script>

(7): provide-inject

用于祖→孙:隔代传值(不操作子组件,直接实现祖→孙)

父组件:需要引入 provide : import { provide } from "vue";

子组件:需要引入 inject : import { inject } from "vue";

//Father.vue

<Child/>

<script setup lang="ts" name="Father">
  import Child from './Child.vue'
  //第一步:引入provide
  import {ref,reactive,provide} from 'vue'

  let money = ref(100)
  let car = reactive({
    brand:'奔驰',
    price:100
  })
  function updateMoney(value:number){
    money.value -= value
  }

/*  向后代提供数据
    第一个参数:数据名称
    第二个参数:数据的值
      可以是ref数据(不需要进行 .value操作, 实现数据响应式)
      也可以是方法(可以实现子传祖) */
  //第二步:调用provide,传参
  provide('moneyContext',{money,updateMoney})
  provide('car',car)

</script>
//GrandChild.vue

<button @click="updateMoney(6)">花爷爷的钱</button>

<script setup lang="ts" name="GrandChild">
  //第三步:引入inject
  import { inject } from "vue";

  //第四步:调用inject,接收参数
  //需要定义参数的类型,也可以设置默认值
  //这里vue3没有自动识别参数类型 - {money:0, updateMoney:(param:number)=>{}})
  let {money,updateMoney} = inject('moneyContext',{money:0, updateMoney:(param:number)=>{}})
  //这里vue3没有自动识别参数类型 - {brand:'未知',price:0}
  let car = inject('car',{brand:'未知',price:0})
</script>
(8): pinia

参考上述的pinia内容即可 : pinia

(9): slot - 默认插槽

默认插槽,在子组件中使用<slot>标签进行占位,父组件中可以传递任何内容

默认插槽只有一个占位(坑)

默认插槽也有名字 name='default'

//Father.vue
<Category>
    <ul>
        <li v-for="g in games" :key="g.id">{{ g.name }}</li>
    </ul>
</Category>

<script setup lang="ts" name="Father">
  import Category from './Category.vue'
  let games = [
    {id:'asgytdfats01',name:'英雄联盟'},
    {id:'asgytdfats02',name:'王者荣耀'},
    {id:'asgytdfats03',name:'吃鸡'},
    {id:'asgytdfats04',name:'斗罗大陆'}
  ]
</script>


//Category.vue
<div class="category">
  <!-- 默认插槽,可以设置默认内容
  进行占位,将<Category></Category> 中间的内容传进来放进插槽中 -->
  <slot>默认内容</slot>
</div>
(10): slot - 具名插槽

简单来说,给插槽起个名字,就可以实现多个占位(坑)

插槽的名字只能放在 <组件标签> 或 <template> 标签上

注意⚠️:有简写形式:v-slot:slot1  ==   #slot1

//Father.vue
<Category title="今日美食城市">
    <!-- v-slot:slot1 可以简写成  #slot1 -->
    <template v-slot:slot1>
        <img :src="imgUrl" alt="">
    </template>  
</Category>


//Category.vue
<!-- 第二种:具名插槽 - 可以设置多个名字,根据名字插入到对应的插槽中 -->
<slot name ='slot1'>默认内容</slot>
(11): slot - 作用域插槽

数据在组件(子组件)本身, 但数据生成的结构需要由组件的使用者(父组件)来决定。
例如:玩具数据在Toy组件中, 但使用数据遍历出来的结构由Father组件决定

数据:可以传递多个(多个数据组成对象形式传递),父组件随意定一个变量来接收,可以解构赋值

//Father.vue

<div class="content">
  <Toy title="玩具列表">
  <!-- 随意定义一个变量用来接收子组件传过来的参数。这里的params,接收的是子传过来的一个对象
    仍然可以使用简写: #toy == v-slot:toy
  -->
    <template #toy="params">
      <ul>
        <!-- 定义数据格式 -->
        <li v-for="item in params.wanju" :key="item.id">
          {{item.name}}
        </li>
      </ul>
    </template>
  </Toy>
</div>

<script setup lang="ts" name="Father">
  import Toy from './Toy.vue'
</script>

//Toy.vue
<template>
  <div class="Toy">
    <!-- 传参:会将所有参数以对象的形式传给父组件 -->
    <slot name="toy" :wanju = toyList x="a" y="b"></slot>
  </div>
</template>

<script setup lang="ts" name="Toy">
  import { reactive } from "vue";
  //定义数据
  let toyList = reactive([
    {id:'001',name:'泡泡玛特'},
    {id:'002',name:'飞机模型'},
    {id:'003',name:'毛绒玩具'}
  ])
</script>

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值