Vue 食用指南

Vue3 食用指南

本文记录了Yukiii在学习y总Vue课程的过程中个人心得和学到内容,方便以后开发时查阅,如果发现有误欢迎指正,请多多指教~

可以通过文章目录快速定位到想查看的内容,每个部分的内容概述我都列在了目录中 :3

项目创建

vue ui

之后在图形化界面中选择 项目路径 及初始化配置。

之后安装插件 routervuex,安装依赖 bootstrap

router.js: 路由,实现页面之间的转换

vuex: store,实现多个组件之间维护同一个数据。

bootstrap: 帮助程序员完成美工工作,包装好一系列的 div

说明

文件夹说明

  • router/index.js 记录各个页面的路由

createWebHashHistory 改成 createWebHistory 可以让路由不包含 /#/

  • views/ 文件夹用来保存各个页面

  • components/ 文件夹用来保存各个组件

  • 整个程序的入口是 main.js 文件,createApp(App).use(router).mount('#app'),程序挂载到 index.html 中的 #app

文件说明

每个页面都是个 .vue 文件,由三个部分组成: html, javascript, css

不需要考虑不同组件之间的 css 会不会互相影响到,因为 <style scoped> 会给每个样式返回一个随机值。

<template>
</template>

<script>
</script>

<style scoped>
</style>

导入 bootstrap

main.js 中:

import 'bootstrapp/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap'

网页开发思想

一般开发网页的想法是从上往下开发,从而不用重构代码。每个页面由若干个组件构成。

在这里插入图片描述

Vue使用指南

组件的使用及 export default 对象的属性

每个 vue 组件都会导出一个对象 export default,该组件要用到的子组件都需要在 components 中引入:

export default {
	name: 'xxx',
	components: {
		// 包含的所有该页面用到组件
	}
}

其中 export default 对象的属性:

  • name: 组件的名称
  • components: 存储 <template> 中用到的所有组件
  • props: 存储父组件传递给子组件的数据
  • computed: 动态计算某个数据
  • setup(props, context): 定义初始化变量、函数
    • ref 定义变量可以用.value属性重新赋值或获取值
    • reactive 定义对象不可重新赋值,当该类型变量值改变的时候会改变所有用到该变量的组件
    • props 存储父组件传递过来的数据
    • context.emit() 触发父组件绑定的函数
    • return<template> 需要用到的变量,事件,函数都需要 return
Card 组件的引入

在设计网页时,我们倾向于给每个网页先设置container, card,将主要内容显示在卡片中。

对于这种每个页面基本都会用到的部分,推荐写一个 component,这样以后要改配置的时候只需要修改该 component 即可。

<slot></slot> 存放父组件传过来的 children

ContBase.vue:

<template>
    <div class = "container">
        <div class = "card">
            <div class = "card-body">
                <slot></slot>
    		</div>
    	</div>
    </div>
</template>

<script>
export default {
    name: "ContentBase",
}
</script>

<style scoped>
.container {
    margin-top: 20px;
}
</style>

HomeView.vue:

<template>
	<ContentBase>
        首页
    </ContentBase>
</template>

<script>
import ContentBase from '../components/ContentBase';
    
export default {
    name: "HomeView",
    components: {
        ContentBase,
    }
}
</script>

<style scoped>
</style>

路由的使用

route/index.js

当不存在 routes 前面的路径时,要重定向到 404 页面。

所有链接养成好习惯,以 / 结尾。

import HomeView from '../views/HomeView.vue'
import RegisterView from '../views/RegisterView';

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/userprofile/',
    name: 'userprofile',
    component: UserProfileView
  },
  {
    path: '/register/',
    name: 'register',
    component: RegisterView
  },
  {
    path: '/404/',
    name: '404',
    component: NotFoundView
  },
  {
    path: '/:catchAll(.*)',
    redirect: "/404/"
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
导航栏实现页面跳转
前端渲染方式

vue 提供 router-link 实现前端渲染方式,表现为页面跳转时不会刷新。

其中:v-bind: 的缩写, :to = "{name: 'xxxx'}",表示绑定哪个页面,{}框住一个对象,其属性中的 name 需要和 router.js 中的 name 保存一致。

<li class = "nav-item">
    <router-link class = "nav-link" :to = "{name: 'userlist'}">好友列表</router-link>
</li>
服务器请求方式

这种方式每次都会刷新一下,不倾向于使用这种方式。

<li class = "nav-item">
    <a class = "nav-link" href = "/userlist">好友列表</a>
</li>

实现具体页面的思路

对页面进行分块

利用 bootstrap 中的 grid 可以对页面进行划分,比如要划分成下图所示的两大部分。

<template>
	<ContentBase>
        <div class = "row">
            <div class = "col-3">
                <UserProfileInfo />
    		</div>
            <div class = "col-9">帖子列表</div>
    	</div>
    </ContentBase>
</template>

在这里插入图片描述

划分成多个组件分别实现

以左上角的组件为例,显示头像,用户名,粉丝数及关注按钮,通过给 div 设置 class 从而在 css 中修改样式:

<template>
	<div class = "card">
        <div class = "card-body">
            <div class = "row">
                <div class = "col-3">
                    <img class = "img-fluid" src = "<图片地址>" alt = "">
    			</div>
                <div class = "col-9">
                    <div class = "username">Yukiii</div>
                    <div class = "fans">粉丝:xxx</div>
                    <button type = "button" class = "btn btn-secondary btn-sm">+关注</button>
    			</div>
    		</div>
    	</div>
    </div>
</template>

<style scoped>
img {
    border-radius: 50%;
}
    
.username {
    font-weight: bold;
}
    
.fans {
    font-size: 12px;
    color: grey;
}
    
button {
    padding: 2px 4px;
    font-size: 12px;
}
</style>
为什么要存储数据,如何存储数据,组件之间如何传递信息

以头像,粉丝数,用户名为例,每个用户都有各自的信息,这些数据通常会在顶层的组件中保存,之后传递给各个子组件

在我们的例子中会把数据存在 UserProfileView.vue(父组件) 中,所有在 <template> 中用到的数据都需要 return

其中 returnuser 其实是 user: user, 的缩写。

<script>
import { reactive } from 'vue';
    
export default {
    setup() {
        const user = reactive({
            id: 1,
            username: "yukiii",
            lastName: "Minato",
            firstName: "yukiii",
            followerCount: 0,
            is_followed: false,
        });
        
        return {
            user,
        }
    }
}
</script>

之后,我们需要在不同的组件之间传递信息,父组件传给子组件通常使用 props 的方式,而子组件传给父组件使用 emit 的方式。

父组件传给子组件

其中 "" 引起来的不是普通字符串,而是一个表达式,这里把定义的 user 对象传递给 UserProfileInfo 子组件。

<UserProfileInfo :user="user" />

在子组件UserProfileInfo中:

props 存储父组件传递给子组件的数据,其中传入的 user 中的 required 表示必须传入该参数。

之后可以在<template>中直接使用该变量{{ var.attr }}

<template>
	<div class = "username">{{ user.username }}</div>
	<div class = "fans">粉丝: {{ user.followerCount }}</div>
</template>

<script>
export default {
    name: "UserProfileInfo",
    props: {
        user: {
            type: Object,
            required: true,
		},
    }
}
</script>

如果希望显示的数值是动态计算出来的:

<template>
	<div class = "username">{{ fullName }}</div>
</template>

<script>
import { computed } from 'vue';
    
setup(props) {
    let fullName = computed(() => props.user.lastName + ' ' + props.user.firstName);
    
    return {
        fullName,
	}
}
</script>
子组件传给父组件

通常状态在哪定义的,我们就在哪修改。比如已经关注了用户,则 is_followed 的值就要修改为 true。而关注按钮在子组件中,user 在父组件中。因此我们需要子组件传递信息给父组件。

子组件传给父组件信息是通过子组件触发父组件绑定的事件的方式进行 @事件名称="绑定的事件"

子组件在触发事件的同时也可以传递参数给父组件,方法在 获取 textarea 中的信息 中有提到。

父组件中:

<template>
	<div class = "col-3">
        <UserProfileInfo @follow = "follow" @unfollow = "unfollow" :user = "user" />
    </div>
</template>

<script>
export default {
    setup(props) {
        const follow = () => {
        	if (user.is_followed) return;
        	user.is_followed = true;
            user.followerCount ++;
        };
        
        const unfollow = () => {
            if (!user.is_followed) return;
        	user.is_followed = false;
            user.followerCount --;
        }
        
        return {
            user,
            follow,
            unfollow,
        }
    }
}
</script>

子组件中:

其中绑定了按钮触发 follow 函数,具体见 为按钮绑定函数

<script>
export default {
    setup(props, context) {
		const follow = () => {
            context.emit("follow");
        }
        
        const unfollow = () => {
            context.emit("unfollow");
        }
        
        return {
            follow,
            unfollow,
		}
    }
}
</script>

为按钮绑定函数

setup() 中定义函数事件,并且为按钮绑定上该事件,其中 @clickv-on:click 的缩写,用于绑定事件。

<template>
	<div>
        <bottom @click = "follow" type = "button">关注</bottom>
    </div>
</template>

<script>
export default {
    setup(props) {
        const follow = () => {
          console.log("follow");  
        };
        
        return {
            follow,
        }
    }
}
</script>

Vue中用循环和判断来显示内容

v-if 判断

v-if 判断如果成立则显示该标签,否则不显示。

<template>
	<button v-if = "!user.is_followed" type = "button" class = "btn btn-secondary btn-sm">+关注</button>
	<button v-if = "user.is_followed" type = "button" class = "btn btn-secondary btn-sm">取消关注</button>
</template>
v-for 循环

v-for 对于每个 posts 中的 post 我们都构造一个 div,所有循环的对象都需要绑定一个保证唯一的 key 属性,通常是其id

在下面的例子中用到的 posts 对象包含 countposts 属性,其中 posts 是一个数组,存放各个 post 对象。

const posts = reactive({
	count: 1,
	posts: [
		{
			id: 1,
			userId: 1,
			content: "xxxx",
		}
	]
});
<div v-for = "post in posts.posts" :key = "post.id">
    <div class = "card">
        <div class = "card-body">
            {{ post.content }}
        </div>
    </div>
</div>

获取textarea中的信息

通过v-model="绑定的变量"content变量 和编辑区中的内容绑定起来,从而实时获取编辑内容。

下面例子中绑定了个按钮触发 post_a_post 函数。

post_a_post 函数中触发父组件中 post_a_post 事件,并且传入参数 content.value,由于 content 变量是用 ref类型 修饰的,因此在修改或调用它的值的时候需要加上 .value

<template>
	<textarea v-model = "content" class = "form-control" id = "edit-post" rows = "3"></textarea>
</template>

<script>
import { ref } from 'vue';
    
export default {
    name: "UserProfileWrite",
    setup(props, context) {
        let content = ref('');
        
        const post_a_post = () => {
            context.emit('post_a_post', content.value);
            content.value = "";
        }
        
        return {
            content,
            post_a_post,
        }
	}
}
</script>

在父组件中接收到 post_a_post 触发后调用 post_a_post 函数,之后由于 postsreactive类型变量,因此它修改后会重新渲染所有用到它的组件。

unshift 表示在数组前面加上一个元素。

<template>
	<ContentBase>
        <div class = "row">    
        	<div class = "col -3">
                <UserProfileInfo @follow = "follow" @unfollow = "unfollow" :user = "user" />
                <UserProfileWrite @post_a_post = "post_a_post" />
    		</div>
            <div class = "col-9">
                <UserProfilePosts :posts = "posts" />
    		</div>
    	</div>
    </ContentBase>
</template>

<script>
export default {
  	setup () {
		const post_a_post = (content) => {
            posts.count ++;
            posts.posts.unshift({
                id: posts.count,
                userId: 1,
                content: content;
            })
        }
        
        return {
            post_a_post,
        }
    }
}
</script>

获取后端url信息

在下面的例子中已经提供了一个后端 url 用于获取所有用户信息,返回一个 json 对象到 users 变量中。

之后可以在 <template> 中用该变量信息,见 通过 v-bind: 实现变量取值

url: 要调用的 apiurl 地址。

type: 方法的类型。

data: 传入的相关参数。

<script>
import $ from 'jquery';
import { ref } from 'vue';
    
export default {
    name: 'UserList',
    components: {
        ContentBase,
    },
    setup() {
        let users = ref([]);
        
        $.ajax({
            url: '<后端url>',
            type: "get",
            success(resp) {
                users.value = resp;
			}
        });
        
        return {
            users,
        }
    }
}
</script>

全局变量的使用

可以发现很多地方都需要用到用户的信息,导航栏显示用户名,其他页面也要获取当前用户等等,此时我们就需要维护一个用户信息作为全局变量。

vuex 可以用来维护全局变量,它维护一个 state 状态树,当两个非父子组件需要交互的时候可以通过分别和 state 树交互的方式实现组件之间的交互。vuex 创建的全局唯一的对象在 store/index.js 中。

下面以一个用户登录状态验证的实现来说明如何使用。

用户登录状态的验证
传统方式

在用户第一次访问的时候,浏览器会将 username, password 信息传给 Server,此时 Server 会生成一个 session_id 传给 Client,浏览器会把 session_id 存放在 Cookie 之中,同时 Server 还会把 user, sesson_id 放到数据库之中。

当用户下次访问的时候会带上 session_id 进行访问,Server 会先查看数据库中是否存在传来的 session_id,如果存在则找到该 user,否则就需要重新登陆。

然而 Cookie 为了保证安全,不允许 js 进行访问,因此用 ajax 跨域访问则访问不到,很难维护登录状态。

跨域: 在当前的域名下访问另一个域名的 API

在这里插入图片描述

jwt 方式 json web token

jwt 不仅仅用来维护登录状态,它还可以传很多信息。

在用户第一次登录时,会将用户名和密码传给 Server,之后 Server 会传给 Client 一个 jwt

之后用户如果发送的请求需要验证的话,则需要带上 jwtjwt 就是一个字符串,也可以理解为 json 对象,用户信息就存在 jwt 中。Server 虽然没有保存 jwt,但可以验证 jwt 是否合法。

在这里插入图片描述

简单介绍一下 jwt 的验证原理: 首先会把一些信息存下来(比如 jwt过期时间,user_id等等),再和 Server 中的随机字符串(称为私钥)进行拼接,之后通过某种加密算法生成一个新的字符串(称为签名)。Server 之后会把存下的信息 info签名 拼接成公钥 jwt

之后用户请求验证的时候,把 jwt 传给ServerServer 会把 info签名 提取出来,之后重复生成的过程,看生成的 签名jwt签名 是否相同,如果相同则说明验证成功。

在这里插入图片描述

全局变量的定义与使用

这里我们使用 jwt 方式来实现登录状态的验证。

首先在登录页面提交表单,把 username, password 传给 user.js

user.js 是在 store 文件夹下定义的存储用户全局信息的文件,最后会在 store/index.jsmodules 属性中引入。

下面的代码是登录页面的部分代码,其中 username, password 和表单的内容绑定在了一起,在提交表单之后调用 login 函数,该函数会调用 store 中的 login 函数。

<script>
import { useStore } from 'vuex';
import router from '@/router/index'
    
export default {
    setup() {
        const store = useStore();
        let username = ref('');
        let password = ref('');
        let error_message = ref('');
        
        const login = () => {
            error_message.value = "";
            store.dispatch("login", {
                username: username.value,
                password: password.value,
                success() {
                    router.push({name: 'namelist'});
                },
                error() {
                    error_message.value = "用户名或密码错误";
                }
            })
		}
	}
}
</script>

store/user.js具体代码如下,简单介绍一下下面的用法:

state 存储用户的具体字段信息,后面要调用就用 store.state.user.is_login(在 html 中用要在前面加上 $)。

mutations 中用于改变 state 中的字段数据,只有在 mutations 可以修改。

actions 定义用到或修改 state 中信息的函数,在上面的用户登录页面提交表单后触发其 login 函数,在 login 函数中调用 store 中的 login,传入参数 context, data,其中 context 用来调用其他的 api,比如例子中调用了 mutations 中的 updateUser 函数,另一个参数 data 用来接收传入的参数,比如例子中接收到 username, password, success(), error()

resp 通常是个 json 对象,可以通过 key 来获取 value

import $ from 'jquery';
import jwt_decode from 'jwt-decode';

const ModuleUser = {
  state: {
    id: "",
    username: "",
    photo: "",
    followerCount: 0,
    access: "",
    refresh: "",
    is_login: false,
  },
  getters: {
  },
  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;
      }
  },
  actions: {
      login(context, data) {
          $.ajax({
              url: "<获取jwt>",
              type: "POST",
              data: {
                  username: data.username,
                  password: data.password,
              },
              success(resp) {
                  const {access, refresh} = resp;
                  const access_obj = jwt_decode(access);
                  $.ajax({
                      url: "<获取用户信息>",
                      type: "GET",
                      data: {
                          user_id: access_obj.user_id,
					  },
                      headers: {
                          'Authorization': "Bearer " + access,
                      },
                      success(resp) {
                          context.commit("updateUser", {
                              ...resp,
                              access: access,
                              refresh: refresh,
                              is_login: true,
                          });
                          data.success();
                      },
                  });
              },
              error() {
                  data.error();
              }
          });
      }
  },
  modules: {
  }
}

当然,在其他组件中也可以调用 store 中的函数,调用 actions 就用 dispatch,调用 mutations 就用 commit

下面以导航栏中的 logout 退出为例,调用了 mutations 中定义的 logout 函数,具体代码就不展示了,只是把 state 中变量清空为初始状态。

<script>
export default {
    name: "NavBar",
    setup() {
       const store = useStore();
       const logout = () => {
           store.commit('logout');
       };
        
       return {
           logout,
       }
	}
}
</script>

小技巧

头像设置圆形和自适应大小

<tmplate>
    <div class = "col-3">
        <img class = "img-fluid" src = "<图片路径>" alt = "">
    </div>
</tmplate>

<style scoped>
img {
    border-radius: 50%;
}
</style>

定义卡片并修改样式

div.card>div.card-body

<template>
    <div class = "card single-post">
        <div class = "card-body">
            {{ post.content }}
        </div>
    </div>
</template>

<style scoped>
.single-post {
    margin-bottom: 10px;
}
</style>

通过 v-bind: 实现变量取值

在下面的例子中,通过 :srcuser 对象中的 photo 属性取出来。

<template>
    <div class = "card" v-for = "user in users" :key = "user.id">
        <div class = "card-body">
            <img :src = "user.photo" alt = "">
        </div>
    </div>
</template>

卡片鼠标悬浮效果和阴影

当卡片中内容可交互时,如果我们把鼠标放到卡片上,鼠标的图样应该发生变化,变成"可点击"的样式,同时为卡片浮现一个阴影动画。

<style scoped>
.card {
    margin-bottom: 20px;
    cursor: pointer;
}
    
.card:hover {
    box-shadow: 2px 2px 10px lightgrey;
    transition: 500ms;
}
</style>

函数中的页面跳转

通过 router 中的 router.push({name: '<页面name>'}) 实现。

<script>
import router from '@/router/index'
    
export default {
    setup() { 
        const login = () => {
            store.dispatch("login", {
                username: username.value,
                password: password.value,
                success() {
                    router.push({name: 'namelist'});
                },
            })
		}
	}
}
</script>

再举一个例子,下面的函数用来判断如果用户处于登录状态,则可以跳转到其他页面,否则跳转到登录页面。

html 中的点击函数传入参数的方法: @click = open(user.id)

<script>
export default {
    setup() { 
        const open = userId => {
            if (store.state.user.is_login) {
                router.push({
                    name: "<跳转页面_name>",
                    params: {
                        userId
                    }
                })
            } else {
                router.push({
                   name: "login" 
                });
            }
		}
	}
}
</script>
  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值