目录
- Single Page Application 单页面应用
- HTML5 History API:在不刷新页面的前提下,动态改变URL地址,修改页面内容
- history.pushState(state, title, url) 方法 :添加一条历史记录,不刷新页面参数
- state : 指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数中,不需要可以填 null
- title : 新页面的标题,所有浏览器目前都忽略这个值,可以填 null
- 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 组件跳转的方式
- 对象类型: :to="{ name: 'column', params: { id: column.id }}"
- 模板字符串类型: :to="`/column/${column.id}`"
- useRouter() :调用后进行跳转,是 vue-router 的钩子函数
- router.push 中接受两种参数:
- 对象类型: :to="{ name: 'column', params: { id: column.id }}"
- 模板字符串类型: :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(小众化)
- 设计理念:
- 有一个类似 object 的全局数据结构,称之为 store,其中的数据是 响应式的
- 只能用 特定方法【显式地提交 (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) }, }
Vue3 仿知乎项目实战(三) —— SPA、Vue Router、Vuex
最新推荐文章于 2024-09-25 14:00:27 发布