Vue 3 快速上手学习笔记(续)

112次阅读
没有评论

共计 11585 个字符,预计需要花费 29 分钟才能阅读完成。

学习之前内容:Vue 3 快速上手学习笔记

pinia

搭建 pinia 环境

第一步:pnpm install pinia

第二步:修改 src/main.ts

import {createApp} from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')

存储 + 读取 + 修改数据

Store 是一个保存 状态、业务逻辑 的实体,每个组件都可以读取、写入它。它有三个概念:stategetteraction,相当于组件中的:datacomputedmethods

新建 src/store/sum.ts

import {defineStore} from 'pinia'

// 定义并暴露一个 store
export const useSumStore = defineStore('sum', {
  // 状态
  state() {
    return {
      sum: 9,
      school: 'Hello',
    }
  },
  // 计算
  getters: {},

  // 里面放方法,用于响应组件中的“动作”actions: {increment(value: number) {if (this.sum < 10) {this.sum += value // this 就是当前的 store}
    },
  },
})

修改 src\App.vue

<template>
  <Sum />
</template>

<script setup lang="ts" name="App">
import Sum from './components/Sum.vue'
</script>

新建 src\components\Sum.vue

<template>
  <div class="sum">
    <h2>sum:{{sumStore.sum}}</h2>
    <h3>school:{{school}}</h3>
    <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="Count">
import {ref} from 'vue'
import {useSumStore} from '@/store/sum'

const sumStore = useSumStore()

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

// 方法
function add() {
  // 第一种修改方式
  // sumStore.sum += 1 // sumStore.$state.sum 也能拿到 state 中的数据

  // 第二种修改方式:批量修改
  // sumStore.$patch({
  //   sum: 666,
  //   school: 'abc',
  // })

  // 第三种修改方式
  sumStore.increment(n.value)
}

function minus() {sumStore.sum -= n.value}
</script>

<style scoped>
.sum {
  background-color: skyblue;
  padding: 10px;
  border-radius: 10px;
  box-shadow: 0 0 10px;
}
select,
button {
  margin: 0 5px;
  height: 25px;
}
</style>

storeToRefs

借助 storeToRefs 将 store 中的数据转为 ref 对象,方便在模板中使用。注意 pinia 提供的 storeToRefs 只会将数据做转换,而 Vue 的 toRefs 会转换 store 中所有数据(包括方法)。

修改 src\components\Sum.vue

<template>
  <div class="sum">
    <h2>sum:{{sum}}</h2>
    <h3>school:{{school}}</h3>
    <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="Count">
import {ref} from 'vue'
import {storeToRefs} from 'pinia'
import {useSumStore} from '@/store/sum'

const sumStore = useSumStore()
// storeToRefs 只关注 sotre 中数据,不会对方法进行 ref 包裹
const {sum, school} = storeToRefs(sumStore)

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

function add() {sumStore.increment(n.value)
}

function minus() {sumStore.sum -= n.value}
</script>

getters

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

修改 getters 配置 src\store\sum.ts

getters: {bigSum: (state): number => state.sum * 10,
  upperSchool(): string {return this.school.toUpperCase()
  },
},

修改 src\components\Sum.vue

<template>
  <div class="sum">
    <h2>sum:{{sum}},放大 10 倍:{{bigSum}}</h2>
    <h3>school:{{school}},大写:{{upperSchool}}</h3>
    <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="Count">
import {ref} from 'vue'
import {storeToRefs} from 'pinia'
import {useSumStore} from '@/store/sum'

const sumStore = useSumStore()
const {sum, school, bigSum, upperSchool} = storeToRefs(sumStore)

let n = ref(1)

function add() {sumStore.increment(n.value)
}

function minus() {sumStore.sum -= n.value}
</script>

$subscribe

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

修改 src\components\Sum.vue

sumStore.$subscribe((mutate, state) => {console.log('sumStore', mutate, state)
})

store 组合式写法

新建 src\store\talkList.ts

import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'

export const useTalkStore = defineStore('talkList', () => {const talkList = reactive(JSON.parse(localStorage.getItem('talkList') as string) || [])

  // getATalk 函数相当于 action
  async function getATalk() {
    let {data: { content: title}, // 连续解构赋值 + 重命名
    } = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')

    let obj = {id: nanoid(), title } // 把请求回来的字符串包装成对象

    talkList.unshift(obj) // 放入数组中
  }

  return {talkList, getATalk}
})

修改 src\App.vue

<template>
  <TalkList />
</template>

<script setup lang="ts" name="App">
import TalkList from './components/TalkList.vue'
</script>

新建 src\components\TalkList.vue

<template>
  <div class="talk">
    <button @click="getLoveTalk"> 获取一句土味情话 </button>
    <ul>
      <li v-for="talk in talkList" :key="talk.id">{{talk.title}}</li>
    </ul>
  </div>
</template>

<script setup lang="ts" name="LoveTalk">
import {useTalkStore} from '@/store/talkList'
import {storeToRefs} from 'pinia'

const talkStore = useTalkStore()
const {talkList} = storeToRefs(talkStore)

talkStore.$subscribe((mutate, state) => {localStorage.setItem('talkList', JSON.stringify(state.talkList))
})

// 方法
function getLoveTalk() {talkStore.getATalk()
}
</script>

<style scoped>
.talk {
  background-color: orange;
  padding: 10px;
  border-radius: 10px;
  box-shadow: 0 0 10px;
}
</style>

组件通信

props

props 是使用频率最高的一种通信方式,常用于父子通信:

  • 父传子:属性值非函数
  • 子传父:属性值是函数

修改 src\App.vue

<template>
  <Father />
</template>

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

新建 src\components\Father.vue

<template>
  <div class="father">
    <h3> 父组件 </h3>
    <h4> 汽车:{{car}}</h4>
    <h4 v-show="toy"> 子给的玩具:{{toy}}</h4>
    <Child :car="car" :sendToy="getToy" />
  </div>
</template>

<script setup lang="ts" name="Father">
import Child from '@/components/Child.vue'
import {ref} from 'vue'
// 数据
let car = ref('奔驰')
let toy = ref('')
// 方法
function getToy(value: string) {toy.value = value}
</script>

<style scoped>
.father {background-color: rgb(165, 164, 164);
  padding: 20px;
  border-radius: 10px;
}
</style>

新建 src\components\Child.vue

<template>
  <div class="child">
    <h3> 子组件 </h3>
    <h4> 玩具:{{toy}}</h4>
    <h4> 父给的车:{{car}}</h4>
    <button @click="sendToy(toy)"> 把玩具给父亲 </button>
  </div>
</template>

<script setup lang="ts" name="Child">
import {ref} from 'vue'
// 数据
let toy = ref('奥特曼')
// 声明接收 props
defineProps(['car', 'sendToy'])
</script>

<style scoped>
.child {
  background-color: skyblue;
  padding: 10px;
  box-shadow: 0 0 10px black;
  border-radius: 10px;
}
</style>

自定义事件

常用于子传父,注意区分原生事件、自定义事件:

  • 原生事件:

    • 事件名是特定的(click、mosueenter 等)
    • 事件对象 $event: 包含事件相关信息的对象(pageX、pageY、target、keyCode)
  • 自定义事件:

    • 事件名可以任意名称
    • 事件对象 $event: 是调用 emit 时所提供的数据,可以是任意类型

修改 src\components\Father.vue

<template>
  <div class="father">
    <h3> 父组件 </h3>
    <h4 v-show="toy"> 子给的玩具:{{toy}}</h4>
    <!-- 给子组件绑定事件 -->
    <Child @send-toy="saveToy" />
  </div>
</template>

<script setup lang="ts" name="Father">
import Child from '@/components/Child.vue'
import {ref} from 'vue'
// 数据
let toy = ref('')
// 用于保存传递过来的玩具
function saveToy(value: string) {console.log('saveToy', value)
  toy.value = value
}
</script>

修改 src\components\Child.vue

<template>
  <div class="child">
    <h3> 子组件 </h3>
    <h4> 玩具:{{toy}}</h4>
    <button @click="emit('send-toy', toy)"> 测试 </button>
  </div>
</template>

<script setup lang="ts" name="Child">
import {ref} from 'vue'
// 数据
let toy = ref('奥特曼')
// 声明事件
const emit = defineEmits(['send-toy'])
</script>

$event 到底是啥?啥时候能.target:

  • 原生事件,$event 就是事件对象,能 .target
  • 自定义事件,$event 就是触发事件时,所传递的数据,不能 .target

mitt

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

安装 mitt:pnpm i mitt

新建 src\utils\emitter.ts

import mitt from 'mitt'

const emitter = mitt()
// emitter.on() 绑定事件
// emitter.off() 解绑事件
// emitter.emit() 触发事件
// emitter.all.clear() 清理事件

export default emitter

在 main.ts 引入 emitter

import {createApp} from 'vue'
import App from './App.vue'
import emitter from './utils/emitter'

createApp(App).mount('#app')

src\App.vue

<template>
  <Father />
</template>

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

src\components\Father.vue

<template>
  <div class="father">
    <h3> 父组件 </h3>
    <Child1 />
    <Child2 />
  </div>
</template>

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

<style scoped>
.father {background-color: rgb(165, 164, 164);
  padding: 20px;
  border-radius: 10px;
}
.father button {margin-left: 5px;}
</style>

提供数据组件 src\components\Child1.vue,在合适时候触发事件。

<template>
  <div class="child1">
    <h3> 子组件 1 </h3>
    <h4> 玩具:{{toy}}</h4>
    <button @click="emitter.emit('send-toy', toy)"> 玩具给弟弟 </button>
  </div>
</template>

<script setup lang="ts" name="Child1">
import {ref} from 'vue'
import emitter from '@/utils/emitter'

let toy = ref('奥特曼')
</script>

<style scoped>
.child1 {
  margin-top: 50px;
  background-color: skyblue;
  padding: 10px;
  box-shadow: 0 0 10px black;
  border-radius: 10px;
}
.child1 button {margin-right: 10px;}
</style>

接收数据组件 src\components\Child2.vue:绑定事件、同时在卸载前解绑事件。

<template>
  <div class="child2">
    <h3> 子组件 2 </h3>
    <h4> 电脑:{{computer}}</h4>
    <h4> 哥哥给的玩具:{{toy}}</h4>
  </div>
</template>

<script setup lang="ts" name="Child2">
import {ref, onUnmounted} from 'vue'
import emitter from '@/utils/emitter'

let computer = ref('联想')
let toy = ref('')

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

<style scoped>
.child2 {
  margin-top: 50px;
  background-color: orange;
  padding: 10px;
  box-shadow: 0 0 10px black;
  border-radius: 10px;
}
</style>

v-model

v-model 用在 HTML 标签上:<input type="text" v-model="userName" />,本质上是:<input type="text" :value="userName" @input="userName = (<HTMLInputElement>$event.target).value" />

v-model 用在组件标签上,本质上是::moldeValue + update:modelValue 事件。

<!-- 组件标签上使用 v -model 指令 -->
<AInput v-model="userName" />

<!-- 本质上 -->
<AInput :modelValue="userName" @update:modelValue="userName = $event" />

可以在组件标签上多次使用 v -model:<AInput v-model:usr="userName" v-model:pwd="password" />

$attrs

用于实现当前组件的父组件,向当前组件的子组件通信,即祖传孙。

$attrs 是一个对象,包含所有父组件传入的标签属性。$attrs 会自动排除 props 中声明的属性(可认为声明过的 props 被子组件自己“消费”了)。

父组件 src\components\Father.vue:

<template>
  <div class="father">
    <h3> 父组件 </h3>
    <h4>a:{{a}}</h4>
    <h4>b:{{b}}</h4>
    <Child :a="a" :b="b" v-bind="{x: 100, y: 200}" :updateA="updateA" />
  </div>
</template>

<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 += value}
</script>

子组件 src\components\Child.vue:

<template>
    <div class="child">
        <h3> 子组件 </h3>
        <GrandChild v-bind="$attrs"/>
    </div>
</template>

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

孙组件:

<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(3)"> 点我更新爷爷那的 a </button>
  </div>
</template>

<script setup lang="ts" name="GrandChild">
defineProps(['a', 'b', 'x', 'y', 'updateA'])
</script>

$refs 和 $parent

$refs 用于:父 → 子,$parent 用于:子 → 父。

  • $refs 值为对象,包含所有被 ref 属性标识的 DOM 元素或组件实例
  • $parent 值为对象,当前组件的父组件实例对象

src\components\Father.vue

<template>
  <div class="father">
    <h3> 父组件 </h3>
    <h4> 房产:{{house}}</h4>
    <button @click="changeToy"> 修改孩子的玩具 </button>
    <button @click="getAllChild($refs)"> 让孩子的书变多 </button>
    <Child ref="c" />
  </div>
</template>

<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref, reactive} from 'vue'
let c = ref()

let house = ref(4)

function changeToy() {c.value.toy = '小猪佩奇'}

function getAllChild(refs: { [key: string]: any }) {console.log(refs)
  for (let key in refs) {refs[key].book += 3
  }
}
// 向外部提供数据
defineExpose({house})
</script>

src\components\Child.vue

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

<script setup lang="ts" name="Child">
import {ref} from 'vue'
// 数据
let toy = ref('奥特曼')
let book = ref(3)
// 方法
function minusHouse(parent: any) {parent.house -= 1}
// 把数据交给外部
defineExpose({toy, book})
</script>

provide 和 inject

实现祖孙组件直接通信,具体使用:

  • 在祖先组件中通过 provide 配置向后代组件提供数据
  • 在后代组件中通过 inject 配置来声明接收数据

src\components\Father.vue

<template>
  <div class="father">
    <h3> 父组件 </h3>
    <h4> 钱:{{money}}万元 </h4>
    <h4> 车:一辆 {{car.brand}},价值{{car.price}} 万 </h4>
    <Child />
  </div>
</template>

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

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

// 向后代提供数据
provide('moneyContext', { money, updateMoney})
provide('car', car)
</script>

src\components\Child.vue

<template>
  <div class="child">
    <h3> 子组件 </h3>
    <GrandChild />
  </div>
</template>

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

src\components\GrandChild.vue

<template>
  <div class="grand-child">
    <h3> 孙组件 </h3>
    <h4> 钱:{{money}}万元 </h4>
    <h4> 车:一辆 {{car.brand}},价值{{car.price}} 万 </h4>
    <button @click="updateMoney(3)"> 花爷爷的钱 </button>
  </div>
</template>

<script setup lang="ts" name="GrandChild">
import {inject} from 'vue'

let {money, updateMoney} = inject('moneyContext', { money: 0, updateMoney: (param: number) => {}})
let car = inject('car', { brand: '未知', price: 0})
</script>

正文完
 0
三毛笔记
版权声明:本站原创文章,由 三毛笔记 于2024-09-25发表,共计11585字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)