Vue3 Day7-全局组件、指令以及pinia

7.1 全局组件

App.vue

html 复制代码
<template>
  <div>
    <h2>我是父组件,下面是全局组件的内容</h2>
    <HelloWorld></HelloWorld>
  </div>
</template>
​
<script setup>
​
</script>
<style scoped></style>

全局组件HelloWorld.vue

html 复制代码
<template>
  <div>
    <h2>我是全局组件</h2>
  </div>
</template>
​
<script setup>
​
</script>
<style scoped></style>

main.js

javascript 复制代码
// 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

html 复制代码
<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

javascript 复制代码
// 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

javascript 复制代码
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

javascript 复制代码
// 导入
import router from './router'
// router是插件,需要use才能用
app.use(router).mount('#app')

App.vue

html 复制代码
<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

html 复制代码
<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

html 复制代码
<template>
  <div>
    <h2>HomeView</h2>
  </div>
</template>
​
<script setup>
import { ref, reactive, toRefs, onMounted} from 'vue'
​
</script>
<style scoped>
</style>

7.4 动画

html 复制代码
<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的使用

  1. 安装

    pnpm install pinia

    安装完成后我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程序,简单来说就是创建一个存储数据的数据桶,放到应用程序中去。

    修改main.js,引入pinia提供的createPinia方法,创建根存储。

    html 复制代码
    import { createPinia } from "pinia";
    const pinia = createPinia();
    app.use(pinia).mount('#app')
  2. 创建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

javascript 复制代码
  import { defineStore } from 'pinia'
   
   // 第一个参数是应用程序中 store 的唯一 id
   export const useUsersStore = defineStore('users', {
     // 其它配置项
   })
  1. 使用store

    使用store很简单,直接引入我们声明的useUsersStore 方法即可

    App.vue

    html 复制代码
    <template>
        
    </template>
    ​
    <script setup>
    import { useUsersStore } from "../src/store/user";
    const store = useUsersStore();
    console.log(store);
    ​
    </script>
    ​
    <style scoped>
    ​
    </style>
    ​
  2. 添加state

    前面我们利用defineStore函数创建了一个store,该函数第二个参数是一个options配置项,我们需要存放的数据就放在options对象中的state属性内。

    user.js

    javascript 复制代码
    export const useUsersStore = defineStore("users", {
      state: () => {
        return {
          name: "小猪课堂",
          age: 25,
          sex: "男",
        };
      },
    });

    上段代码中我们给配置项添加了state属性,该属性就是用来存储数据的,我们往state中添加了3条数据。需要注意的是,state接收的是一个箭头函数返回的值,它不能直接接收一个对象

  3. 操作state

    1. 读取state数据

      App.vue

      html 复制代码
      <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

      html 复制代码
      import { useUsersStore } from "../src/store/user";
      const store = useUsersStore();
      const { name, age, sex } = store;
    2. 多个组件使用state

      接下来我们新建一个child.vue组件,在该组件内部也使用state数据

      components->child.vue

      html 复制代码
      <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

      html 复制代码
      <child></child>
      import child from './components/child.vue'

      这样我们就实现了多个组件同时使用store中的数据

    3. 修改state数据

      如果我们想要修改store中的数据,可以直接重新赋值即可,我们在App.vue里面添加一个按钮,点击按钮修改store中的某一个数据

      App.vue

      html 复制代码
      <button @click="changeName">更改姓名</button>
      const changeName = () => {
          store.name = "张三";
          console.log(store);
      };

      我们可以看到store中的name确实被修改了,但是页面上似乎没有变化,这说明我们的使用的name不是响应式的。

      很多小伙伴可能会说那可以用监听函数啊,监听store变化,刷新页面...

      其实,pinia提供了方法给我们,让我们获得的name等属性变为响应式的,我们重新修改代码。

      利用pinia的storeToRefs函数,将sstate中的数据变为了响应式的

      App.vue

      html 复制代码
      import { storeToRefs } from 'pinia';
      // 响应式
      const { name, age, sex } = storeToRefs(store);
      child.vue
      
      import { storeToRefs } from 'pinia';
      // 响应式
      const { name, age, sex } = storeToRefs(store);
    4. 重置state

      有时候我们修改了state数据,想要将它还原,此时,我们直接调用store的$reset()方法即可,继续使用我们的例子,添加一个重置按钮

      html 复制代码
      <button @click="reset">重置store</button>
      // 重置store
      const reset = () => {
        store.$reset();
      };
    5. 批量更改state数据

      一次性需要修改很多条数据的话,有更加简便的方法,使用store的$patch方法,修改app.vue代码,添加一个批量更改数据的方法

      html 复制代码
      <button @click="patchStore">批量修改数据</button>
      // 批量修改数据
      const patchStore = () => {
        store.$patch({
          name: "张三",
          age: 100,
          sex: "女",
        });
      };

      假如我们state中有些字段无需更改,但是按照上段代码的写法,我们必须要将state中的所有字段例举出了。

      为了解决该问题,pinia提供的**$patch方法**还可以接收一个回调函数,它的用法有点像我们的数组循环回调函数了

      App.vue

      html 复制代码
      // 修改单个数据
      const patchStore = () => {
          store.$patch((state) => {
              state.name='haha'
              state.hasChanged = true
          })
      };
  4. getters属性

    getters是defineStore参数配置项里面的另一个属性,前面我们讲了state属性。getter属性值是一个对象,该对象里面是各种各样的方法。大家可以把getter想象成Vue中的计算属性,它的作用就是返回一个新的结果,既然它和Vue中的计算属性类似,那么它肯定也是会被缓存的,就和computed一样

    1. 添加getter

      user.js

      javascript 复制代码
      // 第一个参数是应用程序中 store 的唯一 id
      export const useUsersStore = defineStore('users', {
        // 其它配置项
        state: () => {
          return {
            name: "小猪课堂",
            age: 25,
            sex: "男",
          };
        },
        getters: {
          getAddAge: (state) => {
            return state.age + 100;
          },
        },
      })

      我们在配置项参数中添加了getter属性,该属性对象中定义了一个getAddAge方法,该方法会默认接收一个state参数,也就是state对象,然后该方法返回的是一个新的数据

    2. 使用getter

      html 复制代码
      <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等属性也可以以此种方式直接在标签上使用,也可以保持响应式。

      当我们点击批量修改数据按钮时,页面上的新年龄字段也会跟着变化

    3. getter中调用其他getter

      有时候我们需要在这一个getter方法中调用其它getter方法,其实很简单,我们可以直接在getter方法中调用this,this指向的便是store实例,所以理所当然的能够调用到其它getter。

      html 复制代码
      getters: {
          getAddAge: (state) => {
            return state.age + 100;
          },
          getNameAndAge(): string {
            return this.name + this.getAddAge; // 调用其它getter
          },
        },

      上段代码中我们又定义了一个名为getNameAndAge的getter函数,在函数内部直接使用了this来获取state数据以及调用其它getter函数。

      细心的小伙伴可能会发现我们这里没有使用箭头函数的形式,这是因为我们在函数内部使用了this,箭头函数的this指向问题相信大家都知道吧!所以这里我们没有采用箭头函数的形式。

      组件中

      html 复制代码
      <p>调用其它getter:{{ store.getNameAndAge }}</p>
    4. getter传参

      html 复制代码
      getAddAge: (state) => {
               return (num) => state.age + num;
          },

      上段代码中我们getter函数getAddAge接收了一个参数num,这种写法其实有点闭包的概念在里面了,相当于我们整体返回了一个新的函数,并且将state传入了新的函数。

      组件中

      html 复制代码
       <p>新年龄:{{ store.getAddAge(1100) }}</p>
  5. actions属性

    前面我们提到的state和getters属性都主要是数据层面的,并没有具体的业务逻辑代码,它们两个就和我们组件代码中的data数据和computed计算属性一样。

    那么,如果我们有业务代码的话,最好就是卸载actions属性里面,该属性就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。

    actions属性值同样是一个对象,该对象里面也是存储的各种各样的方法,包括同步方法和异步方法。

    1. 添加actions

      html 复制代码
       actions: {
          saveName(name: string) {
            this.name = name;
          },
        },

      上段代码中我们定义了一个非常简单的actions方法,在实际场景中,该方法可以是任何逻辑,比如发送请求、存储token等等。大家把actions方法当作一个普通的方法即可,特殊之处在于该方法内部的this指向的是当前store。

    2. 使用actions

      html 复制代码
      <button @click="saveName">actions修改数据的方法</button>
      const saveName = () => {
        store.saveName("我是小猪");
      };

      pinia内容借鉴保姆级教程:https://zhuanlan.zhihu.com/p/533233367

相关推荐
hackeroink5 分钟前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者2 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-2 小时前
验证码机制
前端·后端
燃先生._.3 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖4 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235244 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240255 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar5 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人6 小时前
前端知识补充—CSS
前端·css