Vue3 仿知乎项目实战(三) —— SPA、Vue Router、Vuex

目录

Vue Router 添加路由

Vue Router 获取参数、跳转路由

Vue Router 路由守卫、路由元信息

Vuex 状态管理工具 

Vuex 整合当前应用

CreatePost.vue 创建新文章逻辑 

ColumnDetail.vue 专栏详情逻辑


  • Single Page Application 单页面应用
  • HTML5 History API:在不刷新页面的前提下,动态改变URL地址,修改页面内容
  • history.pushState(state, title, url) 方法 :添加一条历史记录,不刷新页面参数
  1. state : 指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数中,不需要可以填 null
  2. title : 新页面的标题,所有浏览器目前都忽略这个值,可以填 null 
  3. url : 新的网址,必须与前页面处在 同域,浏览器的地址栏将显示它
  • SPA 优点:速度快,第一次下载完成静态文件,跳转不需要再次下载,利于前后端分离

Vue Router 添加路由

  • Vue Router需要 4.0.0 以上的版本,才能支持 vue3
  • npm install vue-router@next
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import Login from './views/Login.vue'

const routerHistory = createWebHistory()
const router = createRouter({
  history: routerHistory,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/login',
      name: 'login',
      component: Login
    }
  ]
})

Vue Router 获取参数、跳转路由

  • useRoute() :调用后返回路由对象,是 vue-router 的钩子函数
<pre>{{route}}</pre>

import { useRoute } from 'vue-router'
// useRoute 是一个函数,调用后返回路由对象
const route = useRoute() 
return {
 route
}
  • params:动态路由,比如 column/12
  • query:查询参数,比如 url 上自带的 ?search=123
  • hash:哈希值,比如 #foo

 

  • router-link 组件跳转的方式 
  1. 对象类型: :to="{ name: 'column', params: { id: column.id }}"
  2. 模板字符串类型: :to="`/column/${column.id}`"

 

  • useRouter() :调用后进行跳转,是 vue-router 的钩子函数
  • router.push 中接受两种参数:
  1. 对象类型: :to="{ name: 'column', params: { id: column.id }}"
  2. 模板字符串类型: :to="`/column/${column.id}`"
import { useRouter } from 'vue-router'
// useRouter 是一个函数,调用后实现跳转
const router = useRouter()

router.push('/login')

Vue Router 路由守卫、路由元信息

  • next() 表示继续向下执行,里面接受跳转地址,或者不接受参数直接跳转,或者false跳转失败
  • 路由元信息,用户登录后,不能访问登录注册页,用户未登录,不能访问创建文章页面等等
  • 这类区分不同路由的信息,被存储在 路由元信息 meta 里,通过 to.meta 获取路由元信息
    {
      path: '/login',
      name: 'login',
      component: Login,
      meta: { redirectAlreadyLogin: true } // 路由元信息
    },
    {
      path: '/create',
      name: 'create',
      component: CreatePost,
      meta: { requiredLogin: true } // 路由元信息
    },

router.beforeEach((to, from, next) => {
  // 获取路由元信息
  console.log(to.meta)
  // 如果路由元信息表示,当前页面必须登录访问,而用户又没有登陆
  if (to.meta.requiredLogin && !store.state.user.isLogin) {
    // 就跳转到登陆页面
    next({ name: 'login' })
  // 如果路由元信息表示,已经登录不能访问当前页面,而用户已经登录
  } else if (to.meta.redirectAlreadyLogin && store.state.user.isLogin) {
    // 就跳转到首页
    next('/')
  // 没有路由元信息
  } else {
    // 就跳转到指定页面
    next()
  }
})

Vuex 状态管理工具 

  • 直接使用 全局对象 的问题:数据并非响应式,可以随意更改,且不确定修改者
  • 常见状态管理工具:Vuex(仅限 vue 使用)、Redux(React 推出)、Mobx(小众化)
  • 设计理念:
  1. 有一个类似 object 的全局数据结构,称之为 store,其中的数据是 响应式的
  2. 只能用 特定方法【显式地提交 (commit) mutation】 修改 store 里面的内容
  • vuex 安装版本大于 4.0.0 才能支持 vue3
  • npm install vuex@next --save
import { createStore } from 'vuex'
// 实例化 store
const store = createStore({
  // 存储全局公共的响应式数据 store.state.count
  state: {
    count: 0
  },  
  // 修改全局共享数据的方法
  mutations: {
    add (state) {
      state.count++
    }
  },
  // 相当于 store中的计算属性
  getters: { },
  //
  acitons: { },
})

// 在组件中修改 store 中的 state,必须通过 commit('mutations中的方法')
store.commit('add')

Vuex 整合当前应用

  • 定义 store 文件:
import { createStore } from 'vuex'
// 导入文章信息类型、专栏信息类型、测试文章数据、测试专栏数据
import { testData, testPosts, ColumnProps, PostProps } from './testData'

// 定义用户信息类型
interface UserProps {
  isLogin: boolean;
  name?: string;
  id?: number;
}
// 定义全局公共数据类类型
export interface GlobalDataProps {
  columns: ColumnProps[];
  posts: PostProps[];
  user: UserProps;
}

// 实例化 store 全局共享信息
const store = createStore<GlobalDataProps>({
  state: {
    columns: testData,
    posts: testPosts,
    user: { isLogin: false }
  },
  // 修改 state 中的信息
  mutations: {
    login(state) { // mutations 中接受的第一个参数就是 state
      state.user = { ...state.user, isLogin: true, name: 'TeaMeow' }
    }
  },
  getters: {
    getPostsByCid: (state) => (id: number) => {
      return state.posts.filter(post => post.columnId === id)
    }
  }
})

export default store
  • 使用 store 文件: 
  • 通过 钩子函数 useStore(),实例化全局 store:const store = useStore<GlobalDataProps>()
  • 通过 计算属性 computed(),获取全局 state:const list = computed(() => store.state.columns)
  • 通过 计算属性 computed(),获取全局 getter:const column = computed(() => store.getters.getColumnById(currentId))
import { useStore } from 'vuex'
import { GlobalDataProps } from '../store'

// 通过钩子函数 useStore(),实例化全局 store
const store = useStore<GlobalDataProps>()
// 通过计算属性 computed 获取全局 store
const list = computed(() => store.state.columns)
// 通过计算属性 computed 获取全局 getter(类似于计算属性)
const column = computed(() => store.getters.getColumnById(currentId))

CreatePost.vue 创建新文章逻辑 

  •  
  • 每个用户对应一个专栏,每个专栏对应多篇文章

 

  • 创建文章页面,整体采用 validate-form / validate-input 组件进行创造
  • 需要改造 validate-input,因为此时只能展示输入框,而不能展示文本域
  • 自定义接受props tag,它只能接受两种字符串:input/textarea,默认值为 input
  • export type TagType = 'input' | 'textarea' 
  •     tag: {
          // 只能输入指定的两种字符串
          type: String as PropType<TagType>,
          // 默认展示输入框
          default: 'input'
        }
  • 通过 v-if 判断 tag值,决定显示输入框,还是显示文本域
    <input
      v-if="tag !== 'textarea'"
      class="form-control"
      :class="{'is-invalid': inputRef.error}"
      @blur="validateInput"
      v-model="inputRef.val"
      v-bind="$attrs"
    >
    <textarea
      v-else
      class="form-control"
      :class="{'is-invalid': inputRef.error}"
      @blur="validateInput"
      v-model="inputRef.val"
      v-bind="$attrs"
    >
    </textarea>

 

  • 文章创建完成后的 表单提交逻辑:
  • 从 store 中获取 当前用户id 及 当前用户专栏id:const { column, _id } = store.state.user
  • 如果用户专栏存在,就创建一篇新文章:
          const newPost: PostProps = {
            title: titleVal.value,
            content: contentVal.value,
            column,
            author: _id
          }
  • store mutations 中添加 增加专栏新文章的方法:
  •     createPost(state, newPost) {
          state.posts.push(newPost)
        },

  • createPost 组件通过  commit 触发 store mutations 中的 createPost 方法,实现增加专栏新文章
  • store.commit('createPost', newPost)

 

  • 文章创建完成后,自动跳转到专栏首页:
  • router.push({ name: 'column', params: { id: column } })

ColumnDetail.vue 专栏详情逻辑

  • 获取当前专栏id:const currentId = route.params.id
  • 通过当前专栏id,获取当前专栏内容:
   const column = computed(() => {
      const selectColumn = store.getters.getColumnById(currentId)
      return selectColumn
    })
  • 通过当前专栏id,获取当前专栏文章列表内容:const list = computed(() => store.getters.getPostsByCid(currentId))
  • 在 store.ts 中书写 筛选专栏信息及专栏文章的方法:
  // 相当于 store中的计算属性
  getters: {
    // 获取当前专栏的文章列表,cid 是接受的参数,表示当前专栏id,
    getPostsByCid: (state) => (cid: string) => {
      // 筛选出文章列表中 专栏id = 当前专栏id 的文章
      return state.posts.filter(post => post.column === cid)
    },
  }

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyrelion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值