Vue3 Day7-全局组件、指令以及pinia
7.1 全局组件
App.vue
<template>
<div>
<h2>我是父组件,下面是全局组件的内容</h2>
<HelloWorld></HelloWorld>
</div>
</template>
<script setup>
</script>
<style scoped></style>
全局组件HelloWorld.vue
<template>
<div>
<h2>我是全局组件</h2>
</div>
</template>
<script setup>
</script>
<style scoped></style>
main.js
// createApp是一个工厂函数
import {
createApp
} from 'vue'
import './style.css'
import App from './App.vue'
let app = createApp(App)
// 注册全局组件
import HelloWorld from './components/HelloWorld.vue'
app.component('HelloWorld', HelloWorld)
app.mount('#app')
// 另一种写法
// createApp(App).mount('#app')
7.2 全局自定义指令
App.vue
<template>
<div>
<button v-has="{ color: '#f60', text: '全局自定义指令' }">{{ btn }}</button>
</div>
</template>
<script setup>
import { ref } from "vue";
let btn = ref('按钮')
</script>
<style scoped></style>
main.js
// createApp是一个工厂函数
import {
createApp
} from 'vue'
import './style.css'
import App from './App.vue'
let app = createApp(App)
// 注册全局组件
import HelloWorld from './components/HelloWorld.vue'
app.component('HelloWorld', HelloWorld)
// 全局自定义指令
// 写法1:
// app.directive('has', {
// beforeMount(el, binding) {
// },
// mounted(el, binding) {
// el.innerHTML = binding.value.text
// el.style.color=binding.value.color
// }
// })
// 写法2:
app.directive('has', function (el, binding) {
el.innerHTML = binding.value.text
el.style.color = binding.value.color
})
app.mount('#app')
// 另一种写法
// createApp(App).mount('#app')
7.3 路由
router->index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// import AboutView from '../views/AboutView' 下面用了懒加载方法,所以这里注释掉
// 定义一些路由
// 每个路由都需要映射到一个组件
const routes = [
{
name: 'home',
path: '/',
component: HomeView
},
{
name: 'about',
path: '/about/:name/:age', // '/about'这个前缀代表了不同的模块或功能区域,避免路由名称冲突
component: () => import('../views/AboutView.vue') // 路由懒加载
},
]
// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
history: createWebHashHistory(),
routes, // `routes: routes` 的缩写
})
export default router
main.js
// 导入
import router from './router'
// router是插件,需要use才能用
app.use(router).mount('#app')
App.vue
<template>
<div>
<!-- 写法1: -->
<!-- <router-view></router-view> -->
<!-- 写法2: -->
<RouterView></RouterView>
<!-- 点击按钮后将本路由数据传给AboutView.vue路由,展示在页面上 -->
<button @click="goHome">home</button>
<button @click="goAbout">about</button>
</div>
</template>
<script setup>
import { reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';
let info = reactive({
name: 'haha',
work: {
job: 'xi',
age: 27
}
})
const router = useRouter() //解构出来
let goHome = () => {
router.push('/')
}
let goAbout = () => {
router.push(
// query传参
// {
// path: '/about',
// query: {
// age: info.work.age,
// name: info.name
// }
// }
// params传参
{
name: 'about',
params: {
name: info.name,
age: info.work.age
}
}
)
}
</script>
<style scoped></style>
AboutView.vue
<template>
<div>
<h2>AboutView</h2>
<!-- <h2>{{ route }}</h2> -->
<!-- <h2>{{ $route }}</h2> -->
<!-- App.vue路由传过来的数据 -->
<!-- <h2>{{ $route.query.age }}</h2> -->
<!-- <h2>{{ $route.query.name }}</h2> -->
<h2>{{ $route.params.name }}</h2>
<h2>{{ $route.params.age }}</h2>
</div>
</template>
<script setup>
import { ref, reactive, toRefs, onMounted } from 'vue'
import { useRoute } from 'vue-router';
const route = useRoute()
console.log(route.params);
</script>
<style scoped lang="less"></style>
HomeView.vue
<template>
<div>
<h2>HomeView</h2>
</div>
</template>
<script setup>
import { ref, reactive, toRefs, onMounted} from 'vue'
</script>
<style scoped>
</style>
7.4 动画
<template>
<div>
<button @click="show = !show">电我吧</button>
<transition name="fade">
<p v-show="show" :style="styleobj">看我变身</p>
</transition>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue';
let show = ref(true)
let styleobj = reactive({
fontSize: '30px',
color: 'red'
})
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 2s;
}
/* vue3这里的fade-enter后面加了一个from,比vue2的动画离开效果更生动了 */
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
7.5 pinia
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。pinia和Vuex的作用是一样的,它也充当的是一个存储数据的作用,存储在pinia的数据允许我们在各个组件中使用
优点:
-
Vue2和Vue3都支持,这让我们同时使用Vue2和Vue3的小伙伴都能很快上手。
-
pinia中只有state、getter、action,抛弃了Vuex中的Mutation,Vuex中mutation一直都不太受小伙伴们的待见,pinia直接抛弃它了,这无疑减少了我们工作量。
-
pinia中action支持同步和异步,Vuex不支持
-
良好的Typescript支持,毕竟我们Vue3都推荐使用TS来编写,这个时候使用pinia就非常合适了
-
无需再创建各个模块嵌套了,Vuex中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响。
-
体积非常小,只有1KB左右。
-
pinia支持插件来扩展自身功能。
-
支持服务端渲染。
pinia的使用
-
安装
pnpm install pinia
安装完成后我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程序,简单来说就是创建一个存储数据的数据桶,放到应用程序中去。
修改main.js,引入pinia提供的createPinia方法,创建根存储。
import { createPinia } from "pinia"; const pinia = createPinia(); app.use(pinia).mount('#app')
-
创建store
可以理解为一个公共组件,只不过该公共组件只存放数据,这些数据我们其它所有的组件都能够访问且可以修改,使用pinia提供的defineStore()方法来创建一个store,该store用来存放我们需要全局使用的数据,首先在项目src目录下新建store文件夹,用来存放我们创建的各种store,然后在该目录下新建user.js文件,主要用来存放与user相关的store。
defineStore函数,接收两个参数:
name:一个字符串,必传项,该store的唯一id。
options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
我们可以定义任意数量的store,因为一个store就是一个函数,这也是pinia的好处之一,让我们的代码扁平化了,这和Vue3的实现思想是一样的。
user.js
import { defineStore } from 'pinia'
// 第一个参数是应用程序中 store 的唯一 id
export const useUsersStore = defineStore('users', {
// 其它配置项
})
-
使用store
使用store很简单,直接引入我们声明的useUsersStore 方法即可
App.vue
<template> </template> <script setup> import { useUsersStore } from "../src/store/user"; const store = useUsersStore(); console.log(store); </script> <style scoped> </style>
-
添加state
前面我们利用defineStore函数创建了一个store,该函数第二个参数是一个options配置项,我们需要存放的数据就放在options对象中的state属性内。
user.js
export const useUsersStore = defineStore("users", { state: () => { return { name: "小猪课堂", age: 25, sex: "男", }; }, });
上段代码中我们给配置项添加了state属性,该属性就是用来存储数据的,我们往state中添加了3条数据。需要注意的是,state接收的是一个箭头函数返回的值,它不能直接接收一个对象。
-
操作state
-
读取state数据
App.vue
<template> <p>姓名:{{ name }}</p> <p>年龄:{{ age }}</p> <p>性别:{{ sex }}</p> </template> <script setup> import {ref} from 'vue' import { useUsersStore } from "../src/store/user"; const store = useUsersStore(); console.log(store); const name = ref(store.name); const age = ref(store.age); const sex = ref(store.sex); </script> <style scoped> </style>
简单方法:用解构的方法获取数据
App.vue
import { useUsersStore } from "../src/store/user"; const store = useUsersStore(); const { name, age, sex } = store;
-
多个组件使用state
接下来我们新建一个child.vue组件,在该组件内部也使用state数据
components->child.vue
<template> <h1>我是child组件</h1> <p>姓名:{{ name }}</p> <p>年龄:{{ age }}</p> <p>性别:{{ sex }}</p> </template> <script setup> import { ref, reactive, toRefs, onMounted } from 'vue' import { useUsersStore } from "../store/user"; const store = useUsersStore(); const { name, age, sex } = store; </script> <style scoped></style>
App.vue
<child></child> import child from './components/child.vue'
这样我们就实现了多个组件同时使用store中的数据
-
修改state数据
如果我们想要修改store中的数据,可以直接重新赋值即可,我们在App.vue里面添加一个按钮,点击按钮修改store中的某一个数据
App.vue
<button @click="changeName">更改姓名</button> const changeName = () => { store.name = "张三"; console.log(store); };
我们可以看到store中的name确实被修改了,但是页面上似乎没有变化,这说明我们的使用的name不是响应式的。
很多小伙伴可能会说那可以用监听函数啊,监听store变化,刷新页面...
其实,pinia提供了方法给我们,让我们获得的name等属性变为响应式的,我们重新修改代码。
利用pinia的storeToRefs函数,将sstate中的数据变为了响应式的
App.vue
import { storeToRefs } from 'pinia'; // 响应式 const { name, age, sex } = storeToRefs(store); child.vue import { storeToRefs } from 'pinia'; // 响应式 const { name, age, sex } = storeToRefs(store);
-
重置state
有时候我们修改了state数据,想要将它还原,此时,我们直接调用store的$reset()方法即可,继续使用我们的例子,添加一个重置按钮
<button @click="reset">重置store</button> // 重置store const reset = () => { store.$reset(); };
-
批量更改state数据
一次性需要修改很多条数据的话,有更加简便的方法,使用store的$patch方法,修改app.vue代码,添加一个批量更改数据的方法
<button @click="patchStore">批量修改数据</button> // 批量修改数据 const patchStore = () => { store.$patch({ name: "张三", age: 100, sex: "女", }); };
假如我们state中有些字段无需更改,但是按照上段代码的写法,我们必须要将state中的所有字段例举出了。
为了解决该问题,pinia提供的$patch方法还可以接收一个回调函数,它的用法有点像我们的数组循环回调函数了
App.vue
// 修改单个数据 const patchStore = () => { store.$patch((state) => { state.name='haha' state.hasChanged = true }) };
-
-
getters属性
getters是defineStore参数配置项里面的另一个属性,前面我们讲了state属性。getter属性值是一个对象,该对象里面是各种各样的方法。大家可以把getter想象成Vue中的计算属性,它的作用就是返回一个新的结果,既然它和Vue中的计算属性类似,那么它肯定也是会被缓存的,就和computed一样
-
添加getter
user.js
// 第一个参数是应用程序中 store 的唯一 id export const useUsersStore = defineStore('users', { // 其它配置项 state: () => { return { name: "小猪课堂", age: 25, sex: "男", }; }, getters: { getAddAge: (state) => { return state.age + 100; }, }, })
我们在配置项参数中添加了getter属性,该属性对象中定义了一个getAddAge方法,该方法会默认接收一个state参数,也就是state对象,然后该方法返回的是一个新的数据
-
使用getter
<template> <p>新年龄:{{ store.getAddAge }}</p> <button @click="patchStore">批量修改数据</button> </template> <script setup lang="ts"> import { useUsersStore } from "../src/store/user"; const store = useUsersStore(); // 批量修改数据 const patchStore = () => { store.$patch({ name: "张三", age: 100, sex: "女", }); }; </script>
上段代码中我们直接在标签上使用了store.gettAddAge方法,这样可以保证响应式,其实我们state中的name等属性也可以以此种方式直接在标签上使用,也可以保持响应式。
当我们点击批量修改数据按钮时,页面上的新年龄字段也会跟着变化
-
getter中调用其他getter
有时候我们需要在这一个getter方法中调用其它getter方法,其实很简单,我们可以直接在getter方法中调用this,this指向的便是store实例,所以理所当然的能够调用到其它getter。
getters: { getAddAge: (state) => { return state.age + 100; }, getNameAndAge(): string { return this.name + this.getAddAge; // 调用其它getter }, },
上段代码中我们又定义了一个名为getNameAndAge的getter函数,在函数内部直接使用了this来获取state数据以及调用其它getter函数。
细心的小伙伴可能会发现我们这里没有使用箭头函数的形式,这是因为我们在函数内部使用了this,箭头函数的this指向问题相信大家都知道吧!所以这里我们没有采用箭头函数的形式。
组件中
<p>调用其它getter:{{ store.getNameAndAge }}</p>
-
getter传参
getAddAge: (state) => { return (num) => state.age + num; },
上段代码中我们getter函数getAddAge接收了一个参数num,这种写法其实有点闭包的概念在里面了,相当于我们整体返回了一个新的函数,并且将state传入了新的函数。
组件中
<p>新年龄:{{ store.getAddAge(1100) }}</p>
-
-
actions属性
前面我们提到的state和getters属性都主要是数据层面的,并没有具体的业务逻辑代码,它们两个就和我们组件代码中的data数据和computed计算属性一样。
那么,如果我们有业务代码的话,最好就是卸载actions属性里面,该属性就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。
actions属性值同样是一个对象,该对象里面也是存储的各种各样的方法,包括同步方法和异步方法。
-
添加actions
actions: { saveName(name: string) { this.name = name; }, },
上段代码中我们定义了一个非常简单的actions方法,在实际场景中,该方法可以是任何逻辑,比如发送请求、存储token等等。大家把actions方法当作一个普通的方法即可,特殊之处在于该方法内部的this指向的是当前store。
-
使用actions
<button @click="saveName">actions修改数据的方法</button> const saveName = () => { store.saveName("我是小猪"); };
pinia内容借鉴保姆级教程:https://zhuanlan.zhihu.com/p/533233367
-