VUE3照本宣科——路由与状态管理器

VUE3照本宣科------路由与状态管理器


前言

👨‍💻👨‍🌾📝记录学习成果,以便温故而知新

"VUE3照本宣科"是指照着中文官网菜鸟教程这两个"本"来学习一下VUE3。以前也学过VUE2,当时只再gitee留下一些代码,却没有记录学习的心得体会,有时也免不了会追忆一下。

以后出现"中文官网"不做特殊说明就是指:https://cn.vuejs.org/;菜鸟教程就是指:https://www.runoob.com/vue3/vue3-tutorial.html


一、路由(router)

Vue.js 的官方路由的内容洋洋洒洒,蔚为大观,内容太多,现在介绍zbxk项目中路由的默认内容及其它常用项。

Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。

所以说路由与Vue是独立的,需要另外安装:

javascript 复制代码
npm install vue-router@4

main.js中安装路由代码:

javascript 复制代码
import router from './router'

app.use(router)

1.createRouter

创建一个可以被 Vue 应用使用的 Router 实例。

在Vue.js 的官方路由文档中找到如下代码,感觉很短小经精悍:

javascript 复制代码
// 1. 定义路由组件.
// 也可以从其他文件导入
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }

// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
// 我们后面再讨论嵌套路由。
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]

// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = VueRouter.createRouter({
  // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
  history: VueRouter.createWebHashHistory(),
  routes, // `routes: routes` 的缩写
})

// 5. 创建并挂载根实例
const app = Vue.createApp({})
//确保 _use_ 路由实例使
//整个应用支持路由。
app.use(router)

app.mount('#app')

// 现在,应用已经启动了!

使用一个自定义组件 router-link 来创建链接。这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。

在Vue.js 的官方路由文档实在是没有找组件 router-link更详细的介绍,而菜鸟教程却更全面一点。

1.to 表示目标路由的链接。 当被点击后,内部会立刻把 to 的值传到 router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象。

2.replace 设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),导航后不会留下 history 记录。

3.append 设置 append 属性后,则在当前 (相对) 路径前添加其路径。例如,我们从 /a 导航到一个相对路径 b,如果没有配置 append,则路径为 /b,如果配了,则为 /a/b

4.tag 有时候想要 <router-link> 渲染成某种标签,例如<li>。 于是我们使用 tag prop 类指定何种标签,同样它还是会监听点击,触发导航。

5.active-class 设置 链接激活时使用的 CSS 类名。可以通过以下代码来替代。

  1. exact-active-class 配置当链接被精确匹配的时候应该激活的 class。可以通过以下代码来替代。

7.event 声明可以用来触发导航的事件。可以是一个字符串或是一个包含字符串的数组。

html 复制代码
<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染结果 -->
<a href="home">Home</a>

<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>

<!-- 不写 v-bind 也可以,就像绑定别的属性一样 -->
<router-link :to="'home'">Home</router-link>

<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>

<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

<!-- 带查询参数,下面的结果为 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>

在代码里使用编程式导航:

javascript 复制代码
// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })

// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })

注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。

3.router-view

将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。

html 复制代码
<div id="app">
  <h1>Hello App!</h1>
  <p>
    <!--使用 router-link 组件进行导航 -->
    <!--通过传递 `to` 来指定链接 -->
    <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
    <router-link to="/">Go to Home</router-link>
    <router-link to="/about">Go to About</router-link>
  </p>
  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>

以上代码也来源于Vue.js 的官方路由文档,说明router-link与router-view的使用。

4.useRoute

返回当前的路由地址。相当于在模板中使用 $route。

地址相关信息:

  • $route.fullPath 路由完整路径
  • $route.matched 路由匹配路径
  • $route.meta 路由元数据
  • $route.name 路由名称
  • $route.params 路由参数,params传参
  • $route.path 路由路径
  • $route.query 路由参数,query传参
    其它相关信息就不一一罗列了。

5.useRouter

返回路由器实例。相当于在模板中使用 $router。

演示代码:

javascript 复制代码
script setup>
import { onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

onMounted(() => {
  console.log(route)
  console.log(router)
})

</script>

终端输出如图:

6.路由守卫

官方文档中称为"导航守卫 "。

可以使用 router.beforeEach 注册一个全局前置守卫:

javascript 复制代码
const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})

接收两个参数:

  • to: 即将要进入的目标 用一种标准化的方式。
    from: 当前导航正要离开的路由 用一种标准化的方式

返回:

  • false: 取消当前的导航。
  • 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: true 或 name: 'home' 之类的配置。

7.嵌套路由

示例代码:

javascript 复制代码
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        // 当 /user/:id/profile 匹配成功
        // UserProfile 将被渲染到 User 的 <router-view> 内部
        path: 'profile',
        component: UserProfile,
      },
      {
        // 当 /user/:id/posts 匹配成功
        // UserPosts 将被渲染到 User 的 <router-view> 内部
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]

嵌套路由一般可以实现布局。

二、状态管理器(Pinia)

zbxk项目是用的pinia作为状态管理器,pinia官网中说:

Pinia是Vue 的专属状态管理库,它允许你跨组件或页面共享状态。

使用 Pinia可以获得如下功能:

  • Devtools 支持
    • 追踪 actions、mutations 的时间线
    • 在组件中展示它们所用到的 Store
    • 让调试更容易的 Time travel
  • 热更新
    • 不必重载页面即可修改 Store
    • 开发时可保持当前的 State
  • 插件:可通过插件扩展 Pinia 功能
  • 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
  • 支持服务端渲染

Pinia与Vue也是独立的,需要另外安装:

javascript 复制代码
npm install pinia

main.js中安装pinia代码:

javascript 复制代码
import { createPinia } from 'pinia'

app.use(createPinia())

1.定义Store

Store应该是保存状态的容器,但是至今没有看到过官方文档的明确说明,也可能是自己看官方文档不够仔细,搞得Store总是是是而非的。

Store是用 defineStore() 定义的,它的第一个参数被用作 id是必须传入的;第二个参数可接受两类值:Setup 函数或 Option 对象。

(1)Option Store

官方示例代码:

javascript 复制代码
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2,//稍有改动
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

以上介绍是不是熟悉的配方、熟悉的味道?Vue2使用Vuex管理状态时貌似也是这样定义的。

(2)Setup Store

官方示例代码:

javascript 复制代码
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)//这行代码官方示例中没有,zbxk项目默认代码里是有的
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

在 Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

对比Option Store与Setup Store的示例代码,发现它们是殊途同归。

2.State

(1)访问state

示例代码:

javascript 复制代码
const store = useStore()

store.count++

(2)重置state

示例代码:

javascript 复制代码
const store = useStore()

store.$reset()

(3)变更state

调用 $patch方法变更state。

示例代码一:

javascript 复制代码
store.$patch({
  count: store.count + 1,
  age: 120,
  name: 'DIO',
})

示例代码二:

javascript 复制代码
store.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

示例代码二明显要比示例代码一更灵活。

(4)替换state

示例代码:

javascript 复制代码
// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })

这个感觉就是变更,写法上不一样

(5)订阅state

示例代码:

javascript 复制代码
cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // 和 cartStore.$id 一样
  mutation.storeId // 'cart'
  // 只有 mutation.type === 'patch object'的情况下才可用
  mutation.payload // 传递给 cartStore.$patch() 的补丁对象。

  // 每当状态发生变化时,将整个 state 持久化到本地存储。
  localStorage.setItem('cart', JSON.stringify(state))
})

本来以为这就是侦听的,看到pinia官网上所说:

比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次。

才发觉这是订阅的patch。

3.Getter

(1)使用Getter

示例代码:

javascript 复制代码
<script setup>
import { useUserListStore } from './store'
const userList = useUserListStore()
const { getUserById } = storeToRefs(userList)
// 请注意,你需要使用 `getUserById.value` 来访问
// <script setup> 中的函数
</script>

<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>

重点看一下storeToRefs,pinia官网说:

创建一个引用对象,包含 store 的所有 state、 getter 和 plugin 添加的 state 属性。 类似于 toRefs(),但专门为 Pinia store 设计, 所以 method 和非响应式属性会被完全忽略。

4.Action

(1)使用Action

示例代码:

javascript 复制代码
const store = useCounterStore()
// 将 action 作为 store 的方法进行调用
store.randomizeCounter()

其它的应用都是围绕着这段代码的核心思想展开的。

(2)订阅action

通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。

javascript 复制代码
const unsubscribe = someStore.$onAction(
  ({
    name, // action 名称
    store, // store 实例,类似 `someStore`
    args, // 传递给 action 的参数数组
    after, // 在 action 返回或解决后的钩子
    onError, // action 抛出或拒绝的钩子
  }) => {
    // 为这个特定的 action 调用提供一个共享变量
    const startTime = Date.now()
    // 这将在执行 "store "的 action 之前触发。
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // 这将在 action 成功并完全运行后触发。
    // 它等待着任何返回的 promise
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // 如果 action 抛出或返回一个拒绝的 promise,这将触发
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// 手动删除监听器
unsubscribe()

本系列专题组合式外的功能介绍得较少,如mapState与mapWritableState等功能请参看pinia官网https://pinia.vuejs.org/zh/

相关推荐
乘风gg31 分钟前
还在养虾吗?虾王已诞生:微信龙虾 ClawBot
前端·ai编程·claude
小小小小宇1 小时前
LLM 长期记忆构建
前端
lichenyang4531 小时前
从 Express 老项目到 NestJS + Docker:一次车辆管理系统的渐进式重构
前端
Momo__2 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富2 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇2 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇2 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆2 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马2 小时前
Verilog开发常见问题汇总解析
前端
子兮曰3 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端