Vue3 学习——vue中使用vuex(超详细)
回顾:
Vue——基础知识学习与项目实操
Vue——配置环境与创建项目
文章目录
一、vuex 介绍
通过
vuex
可方便各个组件间信息传递,
vuex
类似react中的redux。
全局维护一个对象,该对象是一个状态
store
树,包含
state
与
操作
。所有组件之间实现交互时,不需要直接交互,而是分别对维护的全局state进行交互即可。
在store文件夹,默认有
index.js
文件。
index.js
中各部分解释如下:
import { createStore } from 'vuex'
// 类似 Redux 中的建立状态树
export default createStore({
// 1、 存储所有全局数据
state: {
},
// 2、 需要通过计算获取state里的内容获取的数据
// 只能读取不可修改
getters: {
},
// 3、定义对state的各种操作
// why不直接实现在mutation里需要写到action里?
// mtations不能执行异步操作。aq:从云端获取信息-->(等待云端反馈)更新到state异步操作
// 因此说:对于异步操作需要放到action里,简单的直接赋值操作可以直接放到mutation里
mutations: {
updataUser(state,user){
state.user.username = user.username;
}
},
// 3、定义对state的各种操作
// actions无法直接修改state,需要在mutations里更新
// mutation不支持异步,所以需要在action里写api到url
actions: {
// 比说action定义了更新state的操作
// 但是不可对其直接修改
// 所有的修改操作必须放到mutations里
},
// state中信息过长时
// 用来将state进行分割
// 如下,将state树分割出一个user state。
modules: {
user: ModuleUser,
}
})
state中信息过长时,
modules
用来将
state
进行分割。
如下,将
state
树分割出一个
user
state。同样的user下也有这几部分内容:
state、getters、mutation、actions、modules
。在新建的
user. js
文件中写入如下代码:
// user. js
const ModuleUser = {
state: {
id: "",
username: "",
photo: "",
},
getters: {
},
mutations: {
updateUser(state,user){
state.id = user.id;
state.username = user.username;
},
},
actions: {
}
modules: {
}
};
export default ModuleUser;
注意:
调用
mutation
里的方法用
context.commit
(store里)或
store.commit
(store外)
调用
action
里的方法用
context.dispatch
(store)里 或
store.dispatch
(store外)
补充:
context的话就相当于state的父亲,上一级,包含着state中的所有属性
context:{
state, 等同于store. $ state,若在模块中则为局部状态
rootState, 等同于store. $ state,只存在模块中
commit, 等同于store. $ commit
dispatch, 等同于store. $ dispatch
getters 等同于store.$ getters
}
常规写法调用的时候会使用context.commit,
二、vuex 应用
全局存储用户信息,通过vuex维护实现登录功能与用户信息的访问。
登录操作时,如何向服务器验证。
1、补充
(1.1)登录验证方式
传统用户登录验证方式:(用户信息存在数据库里)
登录时客户端向服务器发送用户名和密码进行验证,然后服务器返回给客户端一个session_id,并将session_id存储到数据库里。
当客户端未来再访问服务器的链接时,都会默认将session_id带上,当服务器接收到客服端请求时,会将session_id取出,去数据库中查看是否存在,若存在,将id对应infomation传递给用户。
session_id 存在cookie 里,跨域(本地当前域名访问域名不同的API)很难处理。
jwt 用户登录验证方式:(用户信息存在jwt中)
登录时客户端向服务器发送用户名和密码进行验证,然后服务器返回给客户端一个jwt,jwt不会存储到数据库里。
当客户端未来再访问服务器请求时,如果请求需要验证,则需要附加上jwt。服务器端可以验证jwt是否合法。
如何验证jwt?
将用户信息info 加上私钥加密后生成签名,将info 与 签名 作为公钥传给用户。未来每次验证用户发送info与签名。服务器将info+私所存钥经过加密算法后如果生成签名,则验证通过。
(1.2)用ajax从url中获取数据并显示
格式如下:
$.ajax({
url:" 链接地址 ",
type: " 方法:POST、GET、DELETE等",
data: { 需要输入的参数 },
headers: {
'Authorization':"Bearer " + access, // 是否验证jwt:
},
success(resp){
成功时的回调函数
}
});
示例如下:
// 从 api 中获取数据
$.ajax({
url: 'https://app165.acapp.acwing.com.cn/myspace/userlist/',
type: 'get',
// resp 为api中的用户列表数据
success(resp) {
users.value = resp;
}
})
// API:输入user_id获取获取某个用户的信息,需要jwt验证
$.ajax({
url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/",
type: "GET",
data: {
user_id: access_obj.user_id,
},
// jwt验证,授权
headers: {
'Authorization':"Bearer " + access,
},
// 这里的resp为当前user_id下的用户信息
success(resp) {
...
}
})
},
error(){
...
}
});
2、vuex 如何维护 state
实现登录页面,向服务器发送用户名和密码,服务器返回
jwt
。验证登录后显示用户列表信息。实现退出页面,退出时将所获取的
jwt
删除即可。
(2.1)store 的构建
import $ from "jquery";
import jwt_decode from "jwt-decode";
// 装解码包
const ModuleUser = {
// 1、state 中存储属性信息
state: {
id: "",
username: "",
photo: "",
followerCount: 0,
access: "",
refresh: "",
is_login:false,
},
getters: {
},
// 2、 mutation 中写入需要更改state的操作或者无需异步的操作
// actions无法直接修改state,需要在mutations里更新
// mutation不支持异步,所以需要在action里写api到url
mutations: {
updateUser(state,user){
state.id = user.id;
state.username = user.username;
state.photo = user.photo;
state.followerCount = user.followerCount;
state.access = user.access;
state.refresh = user.refresh;
state.is_login = user.is_login;
},
updateAccess(state,user){
state.access = user.access;
},
// 退出操作:清空jwt即可
// 同步操作,可直接写到mutation
logout(state){
state.id = "";
state.username = "";
state.photo = "";
state.followerCount = 0;
state.access = "";
state.refresh = "";
state.is_login = false;
}
},
// 3、 action 中写入异步操作:用ajax从url中获取数据
// 模仿登录验证时向服务器发送请求,并接收jwt获取信息的过程
// 根据发送的username和password得到access里的userid(和其它信息),然后根据userid得到单个用户的信息
actions: {
// context传API,data 自定义传
login: (context, data) => {
$.ajax({
// API:传入用户名和密码,获取token:得到access和refresh
url: "https://app165.acapp.acwing.com.cn/api/token/",
type: "POST",
data: {
username: data.username,
password: data.password,
},
// 成功的回调函数
success(resp) {
// resp为获取的refresh 对象,此时获得的resp是access和refresh的BS6码,需要解码出来
// 获取refresh对象后解析数出来
const {access,refresh} = resp;
// 解码,需要装解码包,npm i jwt-decode。
const access_obj = jwt_decode(access); //解码后access值包含user_id
// 每隔4.5分钟获取一次,mutation中更新一下access
setInterval(() => {
$.ajax({
// 刷新的JWT的API
url:"https://app165.acapp.acwing.com.cn/api/token/refresh/",
type: "POST",
data: {
refresh,
},
// 无需jwt验证
success(resp){
// 调用mutation中的更新操作(action中不能直接对state进行操作)
context.commit("updateAccess",resp.access);
}
});
},4.5*60*1000)
$.ajax({
// API:输入user_id获取获取某个用户的信息,需要jwt验证
url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/",
type: "GET",
data: {
user_id: access_obj.user_id,
},
// jwt验证,授权
headers: {
'Authorization':"Bearer " + access,
},
// 这里的resp为当前user_id下的用户信息
success(resp) {
// API :第一个参数为mutation里的方法名称字符串。第二个参数为user信息
context.commit("updateUser",{
...resp,
access:access,
refresh:refresh,
is_login:true,
});
// 调用login里的success
data.success();
}
})
},
error(){
// 调用login里的error
data.error();
}
});
}
},
modules: {
}
};
export default ModuleUser;
(2.2)组件访问store(实现组件间交互)
- 点击Navbar中的登录按钮路由跳转登录组件页面,登录组件访问store中内容。登录组件如下:
// LoginView.vue
<template>
<ContentBase>
<div class="row justify-content-md-center">
<div class="col-3">
<!-- 绑定事件并阻止默认发生的事件 -->
<form @submit.prevent="login">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input v-model="username" class="form-control" id="username">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input v-model="password" type="password" class="form-control" id="password">
</div>
<div class="error-massage">{{error_massage}}</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from '@/components/ContentBase.vue'
import { ref } from 'vue';
// 调用store树中内容需要引入
import { useStore } from 'vuex';
import router from '@/router/index';
export default {
name: 'LoginView',
components: {
ContentBase
},
setup(){
// 获取store
const store = useStore();
let username = ref('');
let password = ref('');
let error_massage = ref('');
const login = () => {
error_massage.value = "";
// 如果想在外面调用action里的某函数,需要用dispatch这个api。(motation时用commit)
// 将action里的函数名作为字符串传入,然后任意传入需要的参数对象(字典存储)
// 访问ref值时,需要加.value
store.dispatch("login", {
username: username.value,
password: password.value,
success(){
// 对应navbar里的跳转
router.push({name:'userlist'});
},
error(){
error_massage.value = "用户名或密码错误"
}
});
}
return{
username,
password,
error_massage,
login
}
}
}
</script>
<style scoped>
...
</style>
- 点击Navbar中的退出按钮,退出访问store中内容,删除jwt。如下:
// NavBar.vue
<template>
......
<!-- 判断store里的is_login来确认已经登录,是否显示 -->
<ul class="navbar-nav" v-if="!$store.state.user.is_login"><!-- state 下的 user 这个module的state中的is_login属性 -->
<li class="nav-item">
<router-link class="nav-link " :to="{name:'login'}">登录</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name:'register'}">注册</router-link>
</li>
</ul>
<!-- 已登录显示的内容 -->
<ul class="navbar-nav" v-else>
<li class="nav-item">
<router-link class="nav-link " :to="{name:'userprofile', params:{userId: 1}}">
{{$store.state.user.username}}
</router-link>
</li>
<li class="nav-item">
<!-- 退出只需把获取的jwt删除即可 -->
<a class="nav-link" @click="logout" style="cursor:pointer">退出</a>
</li>
</ul>
.......
<template>
<script>
import { useStore } from 'vuex';
export default {
name: "NavBar",
setup: () => {
const store = useStore();
const logout = () => {
// 在外面调用mutation里的函数用commit
// 删除所获得的jwt即可
store.commit("logout");
};
return {
logout,
}
}
}
</script>
<style scoped>
...
</style>
- 实现登录状态下点击用户列表,打开用户动态页面,且不同的用户列表打开用户动态页面时url中的id不同。
// UserListView.vue
<template>
<ContentBase>
用户列表
<hr>
// 为 div 单机事件加带参函数。(react中需要自己写匿名函数调用带参函数,vue不用,vue自身写好了)
<div class="card" v-for="user in users" :key="user.id" @click="open_user_profile(user.id)">
<div class="card-body">
<div class="row">
<div class="col-1">
<img class="img-fluid" :src="user.photo" alt="">
</div>
<div class="col-11">
<div class="username">{{user.username}}</div>
<div class="reputation">22 <span>关注</span> {{user.followerCount}} <span>粉丝</span> 2 <span>获赞</span></div>
</div>
</div>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from '../components/ContentBase.vue';
import $ from 'jquery';
import { ref } from 'vue';
import {useStore} from 'vuex';
import router from '@/router/index';
export default {
name: 'UserListView',
components: {
ContentBase,
},
setup(){
let users = ref([]);
const store = useStore();
// 从 api 中获取数据
$.ajax({
url: 'https://app165.acapp.acwing.com.cn/myspace/userlist/',
type: 'get',
// resp 为api中的用户列表数据
success(resp) {
users.value = resp;
}
})
// 如果是登录状态,打开任意用户列表显示用户动态,并且url中参数不同
// 如果是未登录状态,跳转至登录页面
const open_user_profile = (userId) => {
if(store.state.user.is_login){
// name对应navbar里 router里的to,params传入单击打开userId(可在url中显示)
router.push({
name: "userprofile",
params: {
userId,
}
}
)
}else{
router.push({
name:"login"
});
}
}
return {
users,
open_user_profile,
}
}
}
</script>
<style scoped>
....
</style>
效果如下(此时的用户动态是写死的,只显示写的内容。但url地址是传递的真正的userid。后续会将用户动态页面从云端动态获取):
(2.3) ajax 实现用户动态页面从云端动态获取
用ajax从url中获取数据并显示。
UserProfileView
用户动态组件(包含
UserProfileInfo
用户信息组件和
UserProfilePost
历史发帖组件)。将写死的信息改为动态ajax获取,代码如下:
// UserProfile.vue
<template>
<ContentBase>
<div class="row">
<div class="col-4">
<UserProfileInfo @follow="follow" @unfollow="unfollow" v-bind:user="user" />
<UserProfileWrite @submit="submit" />
</div>
<div class="col-8">
<UserProfilePosts :posts="posts"/>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from '../components/ContentBase.vue';
......
export default {
name: 'UserListView',
components: {
ContentBase,
UserProfileInfo,
UserProfilePosts,
UserProfileWrite,
},
setup: ()=>{
const store = useStore();
const route = useRoute();
// 根据路由中的id查找信息
const userId = route.params.userId;
// 写死改成从云端动态拉取
const user = reactive({});
// 写死改成从云端动态拉取而非写死
const posts = reactive({count: 3,});
// 动态获取用户信息
$.ajax({
// 获取单个用户信息, 需验证, 需输入路由中的参数user_id
url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/",
type: "GET",
data: {
user_id: userId,
},
headers: {
'Authorization':"Bearer " + store.state.user.access, // 验证jwt:
},
success(resp) {
user.id = resp.id;
user.username = resp.username;
user.photo = resp.photo;
user.followerCount = resp.followerCount;
user.is_followed = resp.is_followed;
}
});
// 动态获取某个用户的历史发帖
$.ajax({
// 获取用户列表, 需验证, 需输入路由中的参数user_id
url: "https://app165.acapp.acwing.com.cn/myspace/post/",
type: "GET",
data: {
user_id: userId,
},
headers: {
'Authorization':"Bearer " + store.state.user.access, // 验证jwt:
},
// resp即为所有帖子
success(resp) {
console.log(resp);
posts.posts = resp;
}
});
// UserProfileInfo 组件所需函数
const follow = () => {
...
};
const unfollow = () => {
...
};
// UserProfilePost 组件所需函数
const submit = (content) => {
posts.count ++,
// 在数组最后加是push,最前加是unshift
posts.posts.unshift(
{
id: posts.count,
userId: 1,
content: content,
},
)
}
return {
user: user,
follow,
unfollow,
posts,
submit,
is_me
}
}
}
</script>
<style scoped>
</style>
用户动态中用户信息组件与历史提交组件动态传递:
效果如下:
实现访问其它用户列表时不显示 UserProfileWrile 发布帖子组件。
首先
UserProfileView
中判断当前打开的id是不是登录的id
只有是当前登录的id才会显示 UserProfileWrile 组件
(2.4)ajax 实现当前登录用户动态发帖
删除以前写死发帖的函数,增加ajax访问url动态发帖函数:
(2.5)ajax 实现当前登录用户动态删帖
父组件中写删除函数绑定给子组件:
子组件调用:
ajax
调用后端
Api
将后端内容真正删除
(2.6)注册功能实现