前言

最近 mx-Space 多了一个新项目:nai-vue

A simple website. Written by Vue 3.

Visit: http://shizuri.net/

Anther version written in React & NextJS, follow nai-react

使用的是Vite构建工具,是我还算是了解点的东西

开始分析项目

万物之源:App.vue

经常的,所有代码都会在src文件夹中(基本),翻找一下,打开App.vue

<template>
  <router-view v-slot="{ Component }">
    <keep-alive :exclude="['note']">
      <component :is="Component" />
    </keep-alive>
  </router-view>
</template>

<script lang="ts">
import { defineComponent, onActivated } from '@vue/runtime-core'
import { configs } from '../configs'

export default defineComponent({
  setup() {
    onActivated(() => {
      document.title = `${configs.title} | ${configs.subtitle}`
    })
  },
})
</script>

首先,在export default defineComponent中使用了setup()函数,这是Vue3的新增函数,代替了data()等函数,封装了大多数组件代码,并处理了响应式,生命周期钩子函数等。

其次,在setup()中还是用到了一个onActivated()钩子函数,对于这个需要讲一下

<keep-alive>包裹的动态组件会被缓存,它是一个抽象组件,它自身不会渲染一个dom元素,也不会出现在父组件链中。当组件在 <keep-alive> 内被切换,它的 onactivated 和 ondeactivated 这两个生命周期钩子函数将会被对应执行。

<keep-alive>包裹两个组件:组件A和组件B。当第一次切换到组件A时,组件A的created和onactivated生命周期函数都会被执行,这时通过点击事件改变组件A的文字的颜色,在切换到组件B,这时组件A的ondeactivated的生命周期函数会被触发;在切换回组件A,组件A的activated生命周期函数会被触发,但是它的created生命周期函数不会被触发了,而且A组件的文字颜色也是我们之前设置过的。

顺带说下,Vue2 -> Vue3的函数名字改变了,基本上都是旧函数前面加个on就变成Vue3可以用的了

configs.ts

export const configs = {
  title: '森',
  subtitle: '寻的碎碎念',
  website: 'https://innei.ren',
  apiBase: import.meta.env.VUE_APP_APIBASE ?? 'https://api.innei.ren/graphql',
}

这个我就不说了吧?这个要是不懂的话那就算了~

核心main.ts

打开一下main.ts,就是一堆的import

import { createApp } from 'vue'
import App from './App.vue'
import './assets/tailwind.css'
import 'normalize.css'
import router from './router'
import './assets/main.css'
createApp(App).use(router).mount('#app')

估计最后一行mount()就不需要讲了吧?大约意思就是挂载在#app上咯

可以看到,import只有一个似乎挺重要的:router.ts

router.ts

import QProgress from 'qier-progress'
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import { configs } from '../configs'
import { NoteContentView } from './views/content'
import { HomeView } from './views/home'
import { AboutView } from './views/page/about'

在router.ts,他引入了一样东西:Qprogress,项目地址在这:https://github.com/vortesnail/qier-progress,就是一个进度条的东西(用于缓解用户焦虑的进度条)

接着都是应该都懂的了,vue-router:路由,configs:就是config.ts,xxxView的就是页面咯

在开头使用const qprogress = new QProgress()来新建一个Qprogress实例

使用routes: RouteRecordRaw[]来设置路由:

const routes: RouteRecordRaw[] = [
  {
    name: 'root',
    path: '/',
    component: () => import('./App.vue'),
    children: [
      {
        path: '/',
        component: HomeView,
        meta: {
          title: configs.title + ' | ' + configs.subtitle,
          fulltitle: true,
        },
        name: 'home',
      },

      {
        path: '/notes/:id',
        props: true,
        component: NoteContentView,
        meta: { title: '' },
        name: 'note',
      },
      {
        path: '/about',
        component: AboutView,
        meta: { title: '关于' },
      },
    ],
  },
]

都是使用一个个对象的,在component就使用在开头import的三个view了

之后一个createRouter()创建路由,并使用createWebHashHistory()创建HashHistory路由,实际上我没有用过createWebHashHistory()这玩意,一般我都直接createrouter就算了,或许有他另外的用途吧~

接下来就很简单的函数辽

router.beforeEach((before, to, next) => {
  qprogress.start()
  if (to.meta.title) {
    document.title = to.meta.fulltitle
      ? to.meta.title
      : to.meta.title + ' | ' + configs.title
  }
  next()
})
router.afterEach(() => {
  qprogress.finish()
})
router.onError(() => {
  qprogress.finish()
})

router.beforeEach():在跳转之前执行的代码

router.afterEach():在跳转之后判断

router.onError():不想说的屑(

具体可以在这里看到:https://router.vuejs.org/zh/api/#router-%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95

最后一句export default router完事

App.ts

说了这么多突然想起我还没看app.ts的说(

import { defineComponent, KeepAlive } from 'vue'
import { RouterView } from 'vue-router'
import { configs } from '../configs'

export default defineComponent({
  setup() {
    document.title = `${configs.title} | ${configs.subtitle}`

    // FIXME: there is a way to use KeepAlive with RouterView in JSX??
    // how to make v-slot working in JSX
    return () => (
      <KeepAlive exclude={['note']}>
        <RouterView>
          <KeepAlive></KeepAlive>
        </RouterView>
      </KeepAlive>
    )
  },
})

KeepAlive我在App.vue中讲过,又不知道的话可以翻上去再看一次

<router-view>是用于渲染路径匹配到的视图组件。 渲染的组件还可以内嵌自己的,根据嵌套路径,渲染嵌套组件。

因为它也是个组件,所以可以配合 <transition><keep-alive> 使用。如果两个结合一起用,要确保在内层使用<keep-alive>

views/home/index.tsx

import { getNoteList } from 'api'
import { NoteListPayload, Pager } from 'api/types'
import { NoteList } from 'components/list'
import BaseLayout from 'layouts/base.vue'
import router from 'router'
import clsx from 'clsx'
import { defineComponent, onMounted, reactive, ref } from 'vue'
import { useRoute } from 'vue-router'
import './index.css'

这波啊,引入了很多东西,我们来分析一些没见过的

getNoteList, NoteListPayload, Pager,都在api文件夹里,这些就是一些接口、一些获取gql的东西

BaseLayout:基本框架

router:路由

clsx:用于className有条件地构造字符串

之后使用defineComponent()创建,但是我却看不懂里面的东西了。。

先是const loading = ref(true)决定了是否load?之后创建data对象 const data = reactive({})

reactive()似乎对于处理复杂的数据会更好,也是用于处理响应式数据的,ref()相对来说或许不太好

之后一堆奇奇怪怪我有点难理解的东西

layouts/base.vue

在上面,我们有看到来自于这里的BaseLayout,代码是这样的

import { defineComponent } from 'vue'
import { Header } from '../components/header'
import { Footer } from '../components/footer'
export default defineComponent({
  components: {
    Header: Header,
    Footer,
  },
})

其中有两句又是关于组件的:import { Header } from '../components/header' import { Footer } from '../components/footer'

components/header.tsx

在里面,先是引入了router,之后使用setup()返回内容

export const Header = defineComponent({
  setup() {
    return () => (
      <header class={'header-wrapper'}>
        <RouterLink to={'/'}>
          <h1>
            {configs.title}
            <small>{configs.subtitle}</small>
          </h1>
        </RouterLink>

        <div class={'links'}>
          <RouterLink to={'/about'}>About</RouterLink>
          <a href={configs.website} target={'_blank'}>
            Website
          </a>
        </div>
      </header>
    )
  },
})

其实这个最后出来的就是这个部件了