1. 前言
在使用Go重构了仿牛客社区的后端的登录模块以后,就想要学习一下前端,把相应的功能在前端实现,因为没有现成的前端素材,重构前端的工作量太大了,所以决定不重构仿牛客社区了,准备在这个登录模块的基础上,写一个其他项目,下面记录一下今天学的Vue相关的内容。
2. Vue官方教程学习
首先,vue是通过将.vue文件解析成js文件,然后渲染成DOM元素,来在浏览器上访问的;
这个部分有13个demo:
2.1 绑定数据
这个demo绑定了名为message的对象,在template中通过双花括号来访问变量的内容(对象)。
<template>
<!--使用双大括号{{ }}将相应的对象绑定到格式中-->
<h1>{{ message }}}</h1>
</template>
<script>
export default {
name: '1_绑定数据',
data() {
return {
message: "message"
}
}
}
</script>
<style scoped>
</style>
2.2 绑定属性
在这个demo中通过v-bind:class绑定了h1的class为titcleClass来改变字体颜色为红色,同时v-bind可以简写为:class。
<template>
<!-- 使用:class(v-bind:class)绑定h1的class为titleClass,绑定属性不需要{{}}-->
<h1 :class="titleClass">Make Me Red</h1>
</template>
<script>
export default {
name: "2_绑定属性",
data() {
return {
titleClass: 'title'
}
}
}
</script>
<style scoped>
.title {
color: red;
}
</style>
2.3 绑定事件
这个demo中,通过@click(或者v-on:click)绑定了按钮的事件increment函数来让count自增。
<template>
<!-- 使用@(v-on:click)绑定按钮对应的函数increment-->
<button @click="increment">count is:{{ count }}</button>
</template>
<script>
export default {
name: "3_绑定事件",
data() {
return {
count: 0,
// 定义自增的函数
increment() {
this.count++
}
}
}
}
</script>
<style scoped>
</style>
2.4 绑定表单
在这个demo中,通过value="text" @input="input"或者直接使用v-model="text"来获取并绑定表单中的数据到text。
<template>
<!-- 使用v-model绑定表单的输入值-->
<input v-model="text" placeholder="Type here">
<input :value="text" @input="input">
<p>{{ text }}</p>
</template>
<script>
export default {
name: "4_绑定表单",
data() {
return {
text: "",
input(e) {
this.text = e
}
}
}
}
</script>
<style scoped>
</style>
2.5 v-if的使用
这个demo使用v-if来控制元素的显示,通过函数toggle来反转awesome的值,控制元素的隐藏与显示。
<script>
export default {
data() {
return {
awesome: true
}
},
methods: {
toggle() {
this.awesome = !this.awesome
}
}
}
</script>
<template>
<button @click="toggle">toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</template>
2.6 遍历数组元素
在这个demo中,通过数组的push函数和filter函数分别实现了添加数组元素和过滤数组元素,实现类似软删除的效果,其中removeTodo(e)函数通过遍历数组找到输入的元素,输出除了该元素的所有元素,addTodo()函数则通过push新的元素到数组中来实现元素列表的添加。
<script>
// 给每个 todo 对象一个唯一的 id
let id = 0
export default {
name: "6_遍历数组元素",
data() {
return {
newTodo: '',
todos: [
{ id: id++, text: 'Learn HTML' },
{ id: id++, text: 'Learn JavaScript' },
{ id: id++, text: 'Learn Vue' }
]
}
},
methods: {
addTodo() {
// 添加一个元素
this.todos.push({ id: id++, text: this.newTodo })
this.newTodo = ''
},
removeTodo(todo) {
// 过滤器,过滤掉选定的元素,生成一个不包含该元素的数组
this.todos = this.todos.filter((t) => t !== todo)
}
}
}
</script>
<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
</template>
2.7 计算组件的使用
在这个demo中,通过使用computed组件来实现选择是否过滤特定的元素,即是否显示checkbox为true的内容或是显示全部内容,可选择使用if-else或是三元表达式实现。
<script>
let id = 0
export default {
data() {
return {
newTodo: '',
hideCompleted: false,
todos: [
{ id: id++, text: 'Learn HTML', done: true },
{ id: id++, text: 'Learn JavaScript', done: true },
{ id: id++, text: 'Learn Vue', done: false }
]
}
},
computed: {
filteredTodos() {
// return this.hideCompleted ? this.todos.filter((t) => !t.done) : this.todos
if (this.hideCompleted === true) {
return this.todos.filter((t) => !t.done)
} else {
return this.todos
}
}
},
methods: {
addTodo() {
this.todos.push({ id: id++, text: this.newTodo, done: false })
this.newTodo = ''
},
removeTodo(todo) {
this.todos = this.todos.filter((t) => t !== todo)
}
}
}
</script>
<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? 'Show all' : 'Hide completed' }}
</button>
</template>
<style>
.done {
text-decoration: line-through;
}
</style>
2.8 生命周期和模板引用
在这个demo中,通过mounted组件编辑refs来修改p和h1元素的内容,并且将相应的修改挂载到DOM自动执行。
<script>
export default {
mounted() {
this.$refs.p.textContent = 'mounted!'
this.$refs.h1.textContent = 'I am H1!'
}
}
</script>
<template>
<p ref="p">hello</p>
<h1 ref="h1">
www
</h1>
</template>
2.9 侦听器
在这个demo中,定义了一个异步函数fetchData来从特定的url获取数据并通过mounted挂载到DOM上展示,然后通过一个button来增加Id,同时设置了一个侦听组件Watch来侦听todoId的变化,一旦变化就自动执行fetchData来刷新数据。
<script>
export default {
data() {
return {
todoId: 1,
todoData: null
}
},
methods: {
// 异步获取
async fetchData() {
this.todoData = null
// 从这个网站通过改变不同的Id获取数据
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${this.todoId}`
)
// await等待把数据转换成json格式
this.todoData = await res.json()
}
},
// 通过mounted挂载函数到dom
mounted() {
this.fetchData()
},
watch: {
todoId() {
this.fetchData()
}
}
}
</script>
<template>
<p>Todo id: {{ todoId }}</p>
<button @click="todoId++">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>
2.10 组件的注册与导入
在这个demo中,通过import组件ChileComp,然后在template中调用来显示ChildComp中的template中的内容。
<script>
import ChildComp from './ChildComp.vue'
export default {
components: {
ChildComp,
}
}
</script>
<template>
<!-- render child component -->
<ChildComp></ChildComp>
</template>
ChildComp.vue:
<template>
<h2>A Child Component!</h2>
</template>
2.11 props组件
在这个demo中,通过在ChildComp.vue中定义props组件来获取父组件中的数据,重载msg中的内容。
<script>
import ChildComp from './ChildComp.vue'
export default {
components: {
ChildComp
},
data() {
return {
greeting: 'Hello from parent'
}
props: {
msg: ""
}
}
}
</script>
<template>
<!-- 重载子组件中的msg为父组件的greeting-->
<ChildComp :msg="greeting" />
</template>
ChildComp.vue:
<script>
export default {
props: {
msg: String
}
}
</script>
<template>
<h2>{{ msg || 'No props passed yet' }}</h2>
</template>
2.12 emits组件
在这个demo中,通过在在ChildComp中定义emits自定义了事件response来向父组件传递数据,将childMsg的值修改为ChildComp的msg的值。
<script>
import ChildComp from './ChildComp.vue'
export default {
components: {
ChildComp
},
data() {
return {
childMsg: 'No child msg yet'
}
}
}
</script>
<template>
<!-- 自定义事件response重载父组件的childMsg为子组件的msg-->
<ChildComp @response="(msg) => childMsg = msg" />
<p>{{ childMsg }}</p>
</template>
ChildComp:
<script>
export default {
emits: ['response'],
created() {
this.$emit('response', 'hello from child')
}
}
</script>
<template>
<h2>Child component</h2>
</template>
2.13 slot的使用
在这个demo中,通过在ChildComp的模板中使用slot来接收父组件的数据。
<script>
import ChildComp from './ChildComp.vue'
export default {
components: {
ChildComp
},
data() {
return {
msg: 'from parent'
}
}
}
</script>
<template>
<ChildComp>Message: {{ msg }}</ChildComp>
</template>
ChildComp:
<template>
<slot>Fallback content</slot>
</template>
3. 实战
在这个部分,我从使用vue渲染了后端接口传递的用户列表数据,并且显示在了页面上。
3.1 UserList.vue的编写
这个组件中,定义了数据user[] 数组、异步的fetchdata()函数来从后端接口获取定义为entity类型的json数据,并使用mounted()自动挂载到了DOM中,使用按钮调用fetchData()来刷新用户列表,并且使用v-for循环输出用户名为li元素(挂载后访问页面就会自己执行this.fetchData(),此时下面的刷新就要替换为获取数据更为恰当了)。
<script>
export default {
inject: ['$backendApi'],
data() {
return {
users: [],
};
},
methods: {
async fetchData() {
try {
const response = await this.$backendApi.get('/community/user/getUserList'); // 发送 GET 请求到后端服务器
this.users = response.data.entity.data; // 更新组件的用户数据
} catch (error) {
console.error(error); // 输出错误消息到控制台
}
},
},
// 挂载后访问页面就会自己执行this.fetchData(),此时下面的刷新就要替换为获取数据更为恰当了
mounted() {
this.fetchData()
},
};
</script>
<template>
<div>
<h1>User List</h1>
<button @click="fetchData">刷新</button>
<ul>
<li v-for="user in users" :key="user.id">{{ user.username }}</li>
</ul>
</div>
</template>
3.2 App.vue的编写
import上面的UserList.vue,在template中调用。
<template>
<img alt="Vue logo" src="./assets/logo.png">
<!-- <HelloWorld msg="Welcome to Your Vue.js App"/>-->
<UserList></UserList>
</template>
<script>
// import HelloWorld from './components/HelloWorld.vue'
import UserList from "@/components/UserList.vue";
export default {
name: 'App',
components: {
// HelloWorld,
UserList
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
3.3 main.js的编写
这里定义了后端运行的端口,并且自动挂载app到DOM。
import { createApp } from 'vue';
import App from './App.vue';
import axios from 'axios';
const app = createApp(App);
// 通过 provide() 将 $backendApi 全局属性注入到应用实例中
app.provide('$backendApi', axios.create({
baseURL: 'http://localhost:8080', // 设置后端服务器地址和端口号
}));
app.mount('#app');
3.3 效果:
添加一个数据,然后点击刷新:
后端的控制台:
4. 总结
今天初步学习了vue框架的基本操作,还初步将前后端结合了一下,体会到了前后端分离的美妙感觉!!!