Vue3 学习——vue中使用vuex(超详细)

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)注册功能实现

猜你喜欢

转载自blog.csdn.net/qq_46201146/article/details/125805058