hello world
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Vue App</title>
</head>
<body>
<div id="app">{{message}}</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
}
});
</script>
</body>
</html>
dev tool
安装完插件后,如果页面中使用了 vue,插件图标就会变亮,打开开发工具即可看到 vue 的标签。
imagevue CLI
安装
# install with npm
npm i -g @vue/cli @vue/cli-service-global
# install with yarn
yarn global add @vue/cli @vue/cli-service-global
这样是全局安装 vue,在任何目录下都可以使用 vue 命令。
创建 vue 项目:
vue create vue-app
执行完成后,会有提示:
image按提示执行这2个命令:
cd vue-app
npm run serve
执行完成后:
image image生成的项目文件目录结构:
image重要文件:
- public/index.html - 主页面
- src/main.js - 主入口
- src/App.vue - 最基础的 vue 组件
- src/components/HelloWorld.vue - 业务组件
main.js
加载 App.vue
,App.vue
导入 HelloWorld.vue
,main.js
把 App
组件内容渲染到 index.html
中的 <div id="app"></div>
。
Vue 组件文件的结构
vue 组件文件使用 .vue
后缀,内容总会包含3部分:
-
<template>
-
<script>
-
<style>
形式如下:
<template></template>
<script>
export default {
name: 'component-name',
}
</script>
<style scoped></style>
和我们以前开发的思路不同,以前我们会把 HTML、JS、CSS 的内容都分开放入不同的文件,而 vue 组件是把3者放在一起,这样是为了便于维护。
继续学习 vue 之前,先引入一个 CSS 样式库,在 index.html
中添加:
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css" />
创建一个 vue 组件
src/components/EmployeeTable.vue
<template>
<div id="employee-table">
<table>
<thead>
<tr>
<th>Employee name</th>
<th>Employee email</th>
</tr>
</thead>
<tbody>
<tr>
<td>Richard Hendricks</td>
<td>richard@piedpiper.com</td>
</tr>
<tr>
<td>Bertram Gilfoyle</td>
<td>gilfoyle@piedpiper.com</td>
</tr>
<tr>
<td>Dinesh Chugtai</td>
<td>dinesh@piedpiper.com</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'employee-table',
}
</script>
<style scoped></style>
Vue 中的惯例是文件名和导入将使用PascalCase
,例如EmployeeTable
,但是在模板中,将转换为 <employee-table>
。
App.vue
中引入 EmployeeTable
<template>
<div id="app" class="small-container">
<h1>Employees</h1>
<employee-table />
</div>
</template>
<script>
import EmployeeTable from '@/components/EmployeeTable/EmployeeTable.vue'
export default {
name: 'app',
components: {
EmployeeTable,
},
}
</script>
<style>
button {
background: #009435;
border: 1px solid #009435;
}
.small-container {
max-width: 680px;
}
</style>
页面效果:
imageVue 组件动态数据
组件改造
EmployeeTable.vue
中暴露一个属性:
export default {
name: 'employee-table',
props: {
employees: Array,
},
}
EmployeeTable.vue
模板中循环显示数据:
<template>
<div id="employee-table">
<table>
<!-- ...thead... -->
<tbody>
<tr v-for="employee in employees" :key="employee.id">
<td>{{ employee.name }}</td>
<td>{{ employee.email }}</td>
</tr>
</tbody>
</table>
</div>
</template>
App 中引用组件
设置数据:
export default {
name: 'app',
components: {
EmployeeTable,
},
data() {
return {
employees: [
{
id: 1,
name: 'Richard Hendricks',
email: 'richard@piedpiper.com',
},
{
id: 2,
name: 'Bertram Gilfoyle',
email: 'gilfoyle@piedpiper.com',
},
{
id: 3,
name: 'Dinesh Chugtai',
email: 'dinesh@piedpiper.com',
},
],
}
},
}
引用组件时设置属性:
<employee-table :employees="employees" />
页面效果
image表单
创建组件 src/components/EmployeeForm.vue
,使用表单添加 employee 数据
<template>
<div id="employee-form">
<form>
<label>Employee name</label>
<input type="text" />
<label>Employee Email</label>
<input type="text" />
<button>Add Employee</button>
</form>
</div>
</template>
<script>
export default {
name: 'employee-form',
data() {
return {
employee: {
name: '',
email: '',
},
}
},
}
</script>
<style scoped>
form {
margin-bottom: 2rem;
}
</style>
App 中引入此组件
<template>
<div id="app" class="small-container">
<h1>Employees</h1>
<employee-form />
<employee-table :employees="employees"/>
</div>
</template>
<script>
import EmployeeTable from '@/components/EmployeeTable.vue'
import EmployeeForm from '@/components/EmployeeForm.vue'
export default {
name: 'app',
components: {
EmployeeTable,
EmployeeForm,
},
data: {
// ...
}
}
</script>
页面效果:
image表单中填入的数据如何拿到呢?
需要使用v-model
绑定表单输入框与组件中定义的变量:
<template>
<div id="employee-form">
<form>
<label>Employee name</label>
<input v-model="employee.name" type="text" />
<label>Employee Email</label>
<input v-model="employee.email" type="text" />
<button>Add Employee</button>
</form>
</div>
</template>
使用 vue devtool 就可以看到效果,输入框中输入内容后,可以看到组件数据同步变化:
image接下来需要提交表单,把输入的内容放入列表中。
事件监听
EmployeeForm.vue
中为 form 绑定提交处理方法:
<form @submit.prevent="handleSubmit"></form>
添加处理提交的方法:
export default {
name: 'employee-form',
data() {
return {
employee: {
name: '',
email: '',
},
}
},
methods: {
handleSubmit() {
console.log('testing handleSubmit')
},
},
}
handleSubmit
方法中打印了一条测试信息,可以在开发工具中实验。
EmployeeForm
组件已经可以拿到提交的数据了,但如何把数据给 EmployeeTable
组件呢?
向父组件传递事件
EmployeeForm
把自己的数据传递给父组件 App
handleSubmit() {
this.$emit('add:employee', this.employee)
}
第一个参数是事件的名称,第二个参数是要传递的数据。
页面中提交一下表单,devtool 中会看到事件的信息:
image从子组件接收事件
EmployeeForm 已经向 App 发送了事件,那么 App 需要接收到这个事件。
首先,App 中要在 employee-form
标签中声明事件的名称以及处理方法:
<employee-form @add:employee="addEmployee" />
add:employee
是 EmployeeForm 中事件的名称,addEmployee
是处理事件的方法名。
App.vue 中添加一个方法:
methods: {
addEmployee(employee) {
const lastId = this.employees.length > 0
? this.employees[this.employees.length - 1].id : 0;
const id = lastId + 1;
const newEmployee = {...employee, id};
this.employees = [...this.employees, newEmployee];
}
}
效果:
image现在基础的功能已经实现了,但还需要一些辅助的功能,例如:
- 显示成功信息
- 显示错误提示
- 数据不符合规则时,input 高亮
- 表单提交后,input 清空
- 成功提交后,第一个 input 自动聚焦
计算的属性 Computed properties
Computed properties
是在某些变动后自动计算的函数。
这种方式可以避免我们写复杂的逻辑。
下面在 EmployeeForm.vue 中做一个简单的检查:输入框不为空。
computed: {
invalidName() {
return this.employee.name === ''
},
invalidEmail() {
return this.employee.email === ''
},
},
需要添加几个状态变量:
- submitting - 表单是否正在提交的状态
- error - 是否发生错误
- success - 是否成功
data() {
return {
submitting: false,
error: false,
success: false,
employee: {
name: '',
email: '',
}
}
}
提交方法中添加状态的验证逻辑:
methods: {
handleSubmit() {
this.submitting = true
this.clearStatus()
if (this.invalidName || this.invalidEmail) {
this.error = true
return
}
this.$emit('add:employee', this.employee)
this.employee = {
name: '',
email: '',
}
this.error = false
this.success = true
this.submitting = false
},
clearStatus() {
this.success = false
this.error = false
}
}
为提示信息添加样式:
<style scoped>
form {
margin-bottom: 2rem;
}
[class*='-message'] {
font-weight: 500;
}
.error-message {
color: #d33c40;
}
.success-message {
color: #32a95d;
}
</style>
修改表格:
<form @submit.prevent="handleSubmit">
<label>Employee name</label>
<input
type="text"
:class="{ 'has-error': submitting && invalidName }"
v-model="employee.name"
@focus="clearStatus"
@keypress="clearStatus"
/>
<label>Employee Email</label>
<input
type="text"
:class="{ 'has-error': submitting && invalidEmail }"
v-model="employee.email"
@focus="clearStatus"
/>
<p v-if="error && submitting" class="error-message">
❗Please fill out all required fields
</p>
<p v-if="success" class="success-message">
✅ Employee successfully added
</p>
<button>Add Employee</button>
</form>
效果:
image[图片上传失败...(image-18c7f7-1586086511847)]
添加 reference
在提交表单之后,最好将焦点放回第一个条目上,可以通过 refs
实现。
在第一个 input 上添加:
<input ref="first" ... />
在 handleSubmit
方法中添加 focus
:
this.$emit('add:employee', this.employee)
this.$refs.first.focus()
效果:
image删除列表中记录
EmployeeTable.vue
在每条记录后添加“编辑”、“删除”的按钮:
<template>
<div id="employee-table">
<table>
<thead>
<tr>
<th>Employee name</th>
<th>Employee email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="employee in employees" :key="employee.id">
<td>{{ employee.name }}</td>
<td>{{ employee.email }}</td>
<td>
<button>Edit</button>
<button>Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
button {
margin: 0 0.5rem 0 0;
}
</style>
删除按钮上添加点击事件:
<button @click="$emit('delete:employee', employee.id)">Delete</button>
点击后会发出一个事件 delete:employee
,并传递数据 employee.id
。
App.vue 中处理此事件,首先在 employee-table
标签中绑定事件处理方法:
<employee-table :employees="employees" @delete:employee="deleteEmployee" />
然后添加处理方法:
methods: {
...
deleteEmployee(id) {
this.employees = this.employees.filter(
employee => employee.id !== id
)
}
}
EmployeeTable.vue 中判断表格是否为空:
<div id="employee-table">
<p v-if="employees.length < 1" class="empty-table">
No employees
</p>
<table v-else>
...
</table>
</div>
效果:
image编辑列表中的记录
EmployeeTable.vue 中为 "编辑" 按钮添加点击事件:
<button @click="$emit('edit:employee', employee.id)">Edit</button>
App.vue 中处理事件。
绑定处理方法:
<employee-table
:employees="employees"
@delete:employee="deleteEmployee"
@edit:employee="editEmployee"
/>
添加编辑方法:
editEmployee(id, updatedEmployee) {
this.employees = this.employees.map(employee =>
employee.id === id ? updatedEmployee : employee
)
}
效果:
image添加 Cancel
的处理:
editMode(employee) {
this.cachedEmployee = Object.assign({}, employee)
this.editing = employee.id
},
cancelEdit(employee) {
Object.assign(employee, this.cachedEmployee)
this.editing = null;
}
修改 Cancle
按钮:
<button class="muted-button" @click="cancelEdit(employee)">Cancel</button>
修改 Edit
按钮,去掉参数的 .id
:
<button @click="editMode(employee)">Edit</button>
调用 REST API
生命周期方法
把默认数据移除,使用 GET 从接口获取数据,什么时候调用 GET 呢?应该在 mounted
这个生命周期点中。
mounted
告诉组件可以执行一些动作了,因为组件已经插入到 DOM 中了。
App.vue 的 mounted 中获取数据:
export default {
name: 'app',
components: {
EmployeeTable,
EmployeeForm,
},
data() {
return {
employees: [],
}
},
mounted() {
this.getEmployees()
},
}
GET
async getEmployees() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await response.json()
this.employees = data
} catch (error) {
console.error(error)
}
}
POST
async addEmployee(employee) {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
body: JSON.stringify(employee),
headers: { 'Content-type': 'application/json; charset=UTF-8' },
})
const data = await response.json()
this.employees = [...this.employees, data]
} catch (error) {
console.error(error)
}
}
PUT
async editEmployee(id, updatedEmployee) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'PUT',
body: JSON.stringify(updatedEmployee),
headers: { 'Content-type': 'application/json; charset=UTF-8' },
})
const data = await response.json()
this.employees = this.employees.map(employee => (employee.id === id ? data : employee))
} catch (error) {
console.error(error)
}
}
DELETE
async deleteEmployee(id) {
try {
await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: "DELETE"
});
this.employees = this.employees.filter(employee => employee.id !== id);
} catch (error) {
console.error(error);
}
}
参考资料:
网友评论