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