为了复习之前学习的前端知识还有快速学习一下express,我跟着B站上的一个教程做了个全栈的博客相关功能实现的全栈项目(这是链接)。做完项目后对自己学习或者复习到的一些内容做点整理

后端

后端方面主要就是通过express对数据库的CRUD。在app.js中引入相关的依赖(express,path,multer,数据库相关),实例化一个express对象:const app = express(),设置中间件(跨域请求处理、JSON处理、上传、静态路径、token验证、路由设置),最后在对应端口设置监听:app.listen(port,callback)

路由的设置

路由用于定义处理 HTTP 请求的端点,将路由文件全放入router文件夹下方便统一管理,在路由文件中引入依赖(express、router、数据库相关,以及其他工具函数和实现功能所需要的依赖等)。在路由模块中通过实现诸如router.get('/xx',(req,res)=>xxx)等方法后,在路由模块末尾导出module.exports = router,最后在主文件app.js中使用中间件引入app.use('/xxx',require(routerPath)),访问时在对应端口的/xx/xxx

数据库的连接和使用

这里我使用的是MySQL+(mysql2/promise)包。创建一个存放工具函数的文件夹(可以存放数据库相关和id生成之类的函数),创建一个文件db.js,引入mysql相关依赖,根据该包的文档创建连接对象,最后module.exports={db,genid},在其他需要该模块的文件中引入。

登陆和token的验证

个人觉得该教程中的实现不太好,待我进一步学习。

关于next()

前端

前端是用vue3写的,实践中主要是前面学的东西太久没用忘记了,需要重新捡起来一下

inject和provide

父组件通过 provide 提供数据,子组件使用 inject 获取数据。
例如在本项目中先在入口文件main.js中设置axios:axios.defaults.baseURL="http://localhost:8080",然后通过app.provide("axios",axios)并且在需要使用aixos的文件中const axios = inject("axios")引入,(注意在app实例化后),从而避免在组件或者路由中重新设置。
provide也可以在vue组件中的父组件使用

1
2
3
4
5
6
7
setup() {
const user = reactive({ name: 'Alice', age: 25 });
provide('user', user);

const message = 'Hello from parent!';
provide('greeting', message);
}

然后直接在子组件中使用(provide 数据只能在子组件(包括深层嵌套组件)中被注入)

路由相关

创建专门的router.js文件引入vue-router进行配置,定义路由规则routes,然后const router = createRouter({path:"/xxx",name:xxx,component:()=>import("path/xxx.vue")})并且export {router,routes},在入口文件main.js引入:app.use(router);
路由的视图组件xxx.vue通常放在src/views目录下。在需要的应用组件中(注意必须在根组件(如 App.vue)中包含 ,这是 Vue Router 渲染匹配组件的必要部分。没有它,路由匹配的组件将无法在应用中显示。),使用 <router-link> 来导航不同的路由,并使用 <router-view> 来渲染当前匹配的视图。(创建视图组件时,不需要在每个视图组件中显式引入 router.js。视图组件(XXX.vue)只需定义其内容和逻辑,路由配置已经在 router.js 中完成,并在 main.js 中全局注册。)

1
2
3
4
5
6
7
<div>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view />
</div>

在本项目中由于跳转路由的按钮选项是引入的naiviUI,所以需要为跳转路由写逻辑(使用useRouter().push("/xxx")),而没使用router-link标签。

学到的新的东西

  • 路由守卫:如果需要在路由进入或离开时执行一些逻辑,可以使用路由守卫。在main.js中:
    1
    2
    3
    4
    router.beforeEach((to, from, next) => {
    // 这里可以添加逻辑,例如认证检查
    next(); // 必须调用 next() 以继续导航
    });
  • 路由嵌套:在需要添加子路由的视图页面中使用并在配置文件router.js中的路由规则routes中视图页面对应的对象添加一个Children属性即可
  • useRoute()和useRouter():这是vue-router中的两个重要的 Composition API 函数,useRoute() 用于获取当前路由的信息,包括路径、参数、查询字符串等。返回一个响应式对象,包含当前路由的信息。useRouter() 用于访问路由实例,提供了编程式导航和路由控制的方法。如 push(), replace(),go()
    如本项目中跳转页面时添加查询参数和详情页面获取该路由对应的查询参数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //跳转页面时添加查询参数, 使用router-link时的写法:<router-link :to="{ path: '/about', query: { id: 123, name: 'Alice' } }">
    const toDetail = async (blog)=>{
    router.push({path:"/detail",query:{id:blog.id}})
    }
    //详情页面获取该路由对应的查询参数
    const route=useRoute();
    let res = await axios.get("/blog/detail?id="+route.query.id);
    /*
    useRoute() 返回的对象:当你调用 useRoute() 时,它返回的对象是响应式的。这意味着,当路由发生变化(如导航到不同的页面)时,模板会自动更新。
    可直接在模板中使用:
    <p>ID: {{ route.query.id }}</p>
    <p>Name: {{ route.query.name }}</p>

    */

pinia相关

使用pinia实现状态管理。安装依赖后,在入口文件导入createPinia(),然后app.use(createPinia()),创建个文件夹Stores,在该文件夹下创建一个管理状态的js文件。
在该JS文件中引入defineStore,然后export导出,写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {defineStore} from  "pinia"
export const XXXStore = defineStore("xxx",{
state:()=>{
return {
id:0,
account:'',
token:''
}
},
/*
或者是:
state: () => ({
id:0,
account:'',
token:''
}),注意{}外面必须有一对()
*/

actions:{actionFunc1,actionFunc2,...},
getters:{}
})

然后就可以在需要的vue组件或者js(如main.js)中引入状态管理了import {XXXstore} from './Stores/xxx.js'
引入后const xxxStore = XXXstore();
在 web 开发中,状态管理通常在以下情况下使用:

  • 共享状态: 当多个组件需要访问和更新同一份状态时,例如用户登录信息、购物车内容或主题设置。

  • 复杂组件树: 在大型应用中,组件之间的层级关系可能会很复杂。状态管理可以简化状态传递,避免层层传递 props。

  • 全局状态: 需要在不同页面或不同视图中保持一致的状态,例如用户偏好设置、应用配置或认证状态。

  • 异步数据管理: 当需要处理异步请求(如 API 调用)并将数据存储在状态中,状态管理可以帮助跟踪数据加载状态和错误处理。

  • 表单和输入状态: 在复杂表单中,状态管理可以帮助统一管理表单输入的状态和验证逻辑。

  • 状态持久化: 在需要保存用户状态(如主题、语言选择)到本地存储或其他持久化机制时,状态管理库通常提供相关功能。

  • 性能优化: 在需要优化组件渲染性能时,使用状态管理可以避免不必要的渲染,提高应用的响应速度。

如在本项目中的login.vue组件中使用const adminStore=AdminStore();adminStore.token = result.data.data.token;adminStore.account = result.data.data.account;adminStore.id=result.data.data.id; 来储存调用接口后得到的信息。然后可以在其他组件中使用被存储的信息。本项目中是在main.js中设置拦截器来将adminStore存储的token添加到每次请求的请求头中,如下:

1
2
3
4
axios.interceptors.request.use((config)=>{
config.headers.token=adminStore.token;
return config;
})//需要配合provide和inject

pinia除了上述选项式写法外,还有种新的组合式写法,详细参考:https://juejin.cn/post/7271076750351548457https://juejin.cn/post/7057439040911441957

localStorage的使用

也是在本项目的login页面用到。localStorage 是 原生JavaScript 的 API。localStorage.setItem('key', 'value');来存储数据,const value = localStorage.getItem('key');来获取数据,localStorage.removeItem('key');来删除,localStorage.clear();清空所有数据。
注意事项:

  • localStorage 只能存储字符串类型的数据。如果你需要存储对象,可以使用 JSON.stringify() 和 JSON.parse() 来转换:
  • localStorage 的数据是持久化的,即使浏览器关闭后也会保留,直到被删除。

项目结构的改进

  • 在使用pinia时,可以在store文件夹下创建个index.js文件将其他的store文件如xxx.js用export {xxx} from './xxx.js'或者export * from './xxx.js'统一导出。有助于保持项目的组织性和可维护性