前端新手入门:从 0 到 1 搭建第一个 Vue 项目,全程跟着敲就行
本文目标:你不需要提前懂任何东西,打开电脑,跟着一步步做,最终能搭出一个带接口请求、能用 Element Plus 组件库的前端项目。
一、先搞清楚我们要用什么
前端发展到现在,工具链已经比较成熟了。本文选的是一套最适合新手、也是国内企业用得最多的组合:
| 工具 | 干什么的 | 一句话理解 |
|---|---|---|
| Node.js | 前端的运行环境 | 就像手机里的操作系统,没有它什么都跑不了 |
| Vue 3 | 前端框架 | 帮你用数据驱动页面,不用自己操作 DOM |
| Vite | 构建工具 | 把代码打包、热更新,开发体验极快 |
| Element Plus | UI 组件库 | 提供现成的按钮、表格、表单,不用自己写 CSS |
| Axios | HTTP 请求库 | 负责和后台接口打交道 |
一个比喻帮你记住这五样东西的关系:
你要开一家餐厅。
Node.js = 厨房(运行环境)
Vue = 你做菜的流程规范(框架)
Vite = 你的厨具套装(构建工具,开火就能用)
Element Plus = 半成品食材包(组件库,拿来就用)
Axios = 外卖配送员(负责把菜送到客人手里 / 从供应商取货)
二、装 Node.js(第一步,也是最容易卡住的一步)
2.1 下载安装
去 Node.js 官网:Node.js — Run JavaScript Everywhere
你会看到两个版本:
-
LTS(长期支持版):稳定,推荐新手选这个
-
Current(最新版):功能新但可能有坑
直接下 LTS 版本,双击安装,一路点"下一步"就行。
2.2 验证装好了没有
打开终端(Win 按 Win+R 输入 cmd,Mac 打开"终端"),输入:
node -v
如果出来一串版本号(比如 v20.11.0),说明装好了。
再输入:
npm -v
出来版本号(比如 10.2.4),说明 npm(Node 自带的包管理器)也 OK 了。
npm 是什么? 你可以把它理解成"应用商店"——你需要什么库(比如 Vue、Axios),敲一行命令它就帮你下载好。
三、创建你的第一个项目
3.1 用 Vite 一键生成项目
在终端里 cd 到你放代码的目录,然后执行:
npm create vite@latest my-first-project -- --template vue
解释一下这行命令:
-
npm create vite@latest:用最新版 Vite 创建项目 -
my-first-project:项目文件夹名字,可以随便改 -
-- --template vue:指定用 Vue 模板
执行后会问你几个问题,一路回车就行。
3.2 进项目、装依赖
cd my-first-project npm install
npm install 会根据 package.json 文件里记录的清单,把所有需要的库下载到 node_modules 文件夹里。
这步容易遇到的问题: 如果下载很慢或者失败,是因为 npm 默认从国外服务器下载。换成国内镜像:
npm config set registry https://registry.npmmirror.com然后再执行
npm install,速度起飞。
3.3 跑起来
npm run dev
终端会出现类似这样的提示:
VITE v5.0.0 ready in 300 ms ➜ Local: http://localhost:5173/
打开浏览器,访问 http://localhost:5173/,看到一个 Vue 的欢迎页面,恭喜你,第一个前端项目跑通了!
3.4 项目结构看一眼就够了
my-first-project/ ├── index.html # 入口 HTML,单页应用就这一个页面 ├── package.json # 项目配置,记录了装了哪些依赖 ├── vite.config.js # Vite 的配置文件 ├── src/ │ ├── main.js # 程序的入口,Vue 应用在这里启动 │ ├── App.vue # 根组件,所有页面都套在这个壳里 │ ├── components/ # 放组件的文件夹 │ ├── views/ # 放页面的文件夹(需要自己建) │ └── assets/ # 放图片、样式等静态资源 └── node_modules/ # 所有下载的依赖包都在这里(别手动改)
四、理解 Vue 的核心:组件和生命周期
4.1 .vue 文件长什么样
每个 .vue 文件就是一个组件。一个组件由三部分组成:
<template>
<!-- HTML:页面上显示什么 -->
<div>
<h1>{{ title }}</h1>
<button @click="handleClick">点我</button>
</div>
</template>
<script setup>
// JavaScript:页面逻辑
import { ref } from 'vue'
const title = ref('你好,世界')
function handleClick() {
title.value = '你点了我一下'
}
</script>
<style scoped>
/* CSS:样式,scoped 表示只在这个组件生效 */
h1 {
color: #409eff;
}
</style>
三个关键点:
-
{{ }}双花括号:把 JS 里的数据渲染到页面上 -
ref():让数据变成"响应式"的——数据一变,页面自动跟着变 -
@click:绑定点击事件
4.2 生命周期是什么
一句话:Vue 组件从"出生"到"销毁"的整个过程,就是生命周期。 你在不同阶段可以插入自己的代码。
Vue 3(<script setup> 写法)常用生命周期
<script setup>
import { onMounted, onUnmounted } from 'vue'
// 组件挂载完成(页面元素已经渲染到浏览器里了)
onMounted(() => {
console.log('组件出现了,可以在这里发请求拿数据')
})
// 组件销毁前(页面切换走、组件移除时)
onUnmounted(() => {
console.log('组件要消失了,可以在这里清定时器、取消请求')
})
</script>
created 是什么?
created 是 Vue 2 Options API 里的一个生命周期钩子,在组件实例被创建之后、页面渲染之前触发。Vue 2 里经常在 created 里发请求拿数据:
// Vue 2 Options API 写法
export default {
data() {
return { userList: [] }
},
created() {
// 组件实例创建好了,可以访问 data、methods,但 DOM 还没渲染
this.fetchUsers()
},
methods: {
async fetchUsers() {
const res = await axios.get('/api/users')
this.userList = res.data
}
}
}
Vue 3 里 created 变成什么了?
Vue 3 的 <script setup> 语法里没有 created 这个钩子,因为 <script setup> 里直接写的代码就是在 created 阶段执行的:
<script setup>
import { ref } from 'vue'
// 直接写在这里的代码,就相当于 Vue 2 的 created
// 组件实例创建好、数据初始化完毕时执行
const count = ref(0)
console.log('这行代码在 created 阶段执行')
// 如果你确实需要在 created 时机做某事,直接写在这里就行
</script>
完整生命周期图(对比版)
| 阶段 | Vue 2 Options API | Vue 3 <script setup> |
能做什么 |
|---|---|---|---|
| 实例创建完成,数据初始化好了,但 DOM 还没渲染 | created() |
直接写在 <script setup> 顶层 |
初始化数据、发请求(此时看不到页面元素) |
| DOM 渲染完成,可以操作页面元素了 | mounted() |
onMounted(() => {}) |
发请求、初始化图表、获取 DOM 元素 |
| 组件要销毁了 | beforeDestroy() |
onUnmounted(() => {}) |
清除定时器、取消未完成的请求 |
一个生活类比:
| 生命周期 | 类比 | 典型用途 |
|---|---|---|
created / <script setup> 顶层 |
你到公司,走进办公室,但还没开电脑 | 准备今天要干什么、检查日程 |
onMounted |
你打开电脑,桌面出现了 | 发请求拉数据、操作页面元素 |
onUnmounted |
你下班,关电脑走人 | 清除定时器、取消未完成的请求 |
新手提示: 如果你学的是 Vue 2 教程,会看到大量
created的用法。如果你用的是 Vue 3 +<script setup>,记得直接把代码写在<script setup>顶层就等价于created。实际开发中,大部分数据请求放在onMounted里就够了,因为早几毫秒拿到数据对用户体验几乎没有影响。
五、向后端发请求(Axios)
5.1 安装 Axios
项目根目录下执行:
npm install axios
5.2 第一次发请求
在一个 .vue 组件里试试:
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const userList = ref([])
// 组件加载完就请求数据
onMounted(async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users')
userList.value = response.data
console.log('拿到的数据:', response.data)
} catch (error) {
console.error('请求失败了:', error)
}
})
</script>
<template>
<div>
<h2>用户列表</h2>
<ul>
<li v-for="user in userList" :key="user.id">
{{ user.name }} —— {{ user.email }}
</li>
</ul>
</div>
</template>
这里有三个新东西:
-
axios.get(url):发一个 GET 请求到指定地址 -
async / await:因为网络请求需要时间,用 await 等结果回来再继续 -
v-for:Vue 的循环指令,把数组里的每一项渲染出来
5.3 这个 API 是干嘛的
jsonplaceholder.typicode.com 是一个免费的假数据 API,专门给前端新手练习用的。不需要注册,直接发请求就能拿到数据,放心用。
5.4 实战推荐:封装一个请求工具
项目稍微大一点,就不能在每个组件里都写 axios.get。通常的做法是新建一个 src/utils/request.js:
import axios from 'axios'
// 创建 axios 实例
const request = axios.create({
baseURL: 'https://your-api-server.com', // 你的后台地址
timeout: 10000, // 10 秒没响应就报超时
})
// 请求拦截器:发请求之前可以做的事(比如加 token)
request.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器:收到响应后统一处理(比如统一报错)
request.interceptors.response.use(
(response) => response.data,
(error) => {
console.error('请求出错', error)
return Promise.reject(error)
}
)
export default request
然后在组件里用:
import request from '@/utils/request'
const data = await request.get('/users')
const result = await request.post('/login', { username: 'admin', password: '123456' })
这样全项目的接口请求都走同一个出口,改后台地址、加 token、统一报错都只改这一个文件。
六、Element Plus 组件库
6.1 安装
npm install element-plus
6.2 在 main.js 中引入
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
6.3 直接用就行了
引完之后,你在任何 .vue 组件里直接用 Element Plus 的标签,不需要额外 import:
<template>
<!-- 一个按钮 -->
<el-button type="primary">主要按钮</el-button>
<!-- 一个输入框 -->
<el-input v-model="inputValue" placeholder="请输入内容" />
<!-- 一个表格 -->
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="age" label="年龄" />
</el-table>
<!-- 一个确认弹窗 -->
<el-button @click="showDialog = true">打开弹窗</el-button>
<el-dialog v-model="showDialog" title="提示">
<p>这是一段内容</p>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
const inputValue = ref('')
const showDialog = ref(false)
const tableData = ref([
{ name: '张三', age: 25 },
{ name: '李四', age: 30 },
])
</script>
6.4 学习 Element Plus 的正确姿势
不要试图把所有组件背下来。 正确的做法是:
需要用什么组件 → 去官网搜 → 复制示例代码 → 改成你自己的数据
Element Plus 官网:一个 Vue 3 UI 框架 | Element Plus
新手上路最常用的五个组件:
| 组件 | 标签 | 什么时候用 |
|---|---|---|
| 按钮 | <el-button> |
几乎所有页面都需要 |
| 输入框 | <el-input> |
表单、搜索 |
| 表格 | <el-table> |
展示列表数据 |
| 弹窗 | <el-dialog> |
确认操作、填写表单 |
| 表单 | <el-form> |
登录、注册、配置页 |
七、全流程串起来:一个完整的页面
下面是一个真实的小页面,把上面所有知识点串在一起:
<template>
<div class="page">
<!-- 顶部的搜索栏 -->
<el-input
v-model="searchKeyword"
placeholder="搜索用户"
style="width: 300px; margin-bottom: 16px"
/>
<el-button type="primary" @click="fetchUsers">搜索</el-button>
<el-button @click="showAddDialog = true">新增用户</el-button>
<!-- 数据表格 -->
<el-table :data="filteredUsers" style="width: 100%; margin-top: 16px">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="姓名" />
<el-table-column prop="email" label="邮箱" />
</el-table>
<!-- 新增用户的弹窗 -->
<el-dialog v-model="showAddDialog" title="新增用户">
<el-form :model="newUser">
<el-form-item label="姓名">
<el-input v-model="newUser.name" />
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="newUser.email" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">取消</el-button>
<el-button type="primary" @click="addUser">确认添加</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'
// ----- 数据 -----
const users = ref([])
const searchKeyword = ref('')
const showAddDialog = ref(false)
const newUser = ref({ name: '', email: '' })
// ----- 计算属性:根据搜索关键词过滤数据 -----
const filteredUsers = computed(() => {
if (!searchKeyword.value) return users.value
return users.value.filter(u => u.name.includes(searchKeyword.value))
})
// ----- 方法 -----
async function fetchUsers() {
const res = await axios.get('https://jsonplaceholder.typicode.com/users')
users.value = res.data
}
async function addUser() {
// 实际项目里这里发 POST 请求
users.value.push({
id: users.value.length + 1,
name: newUser.value.name,
email: newUser.value.email,
})
showAddDialog.value = false
newUser.value = { name: '', email: '' }
}
// ----- 生命周期:页面加载自动拿数据 -----
onMounted(() => {
fetchUsers()
})
</script>
<style scoped>
.page {
padding: 24px;
}
</style>
这个页面实现了:页面加载自动拉数据、表格展示、搜索过滤、弹窗新增。你把 jsonplaceholder 的地址换成自己后台的接口地址,就是一个真实可用的业务页面了。
八、跨域问题(CORS)—— 新手必踩的坑
8.1 什么是跨域?为什么会报错?
你第一次把前端(localhost:5173)连上后台(localhost:8080 或其他域名),浏览器控制台大概率会出现这个报错:
Access to XMLHttpRequest at 'http://localhost:8080/api/users' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
先用人话解释:
浏览器就像一个很严格的门卫。你的前端页面运行在 A 域名(比如
localhost:5173),你想去 B 域名(比如localhost:8080)拿数据。浏览器觉得:"你一个在 A 地盘的人,凭什么去 B 地盘拿东西?万一是恶意攻击呢?" 于是它把你的请求拦了下来。
一句话:跨域不是"你不能发请求",而是"浏览器收到了后台返回的数据,但拒绝把数据交给你的前端代码"。
实际上,请求已经发到了后台,后台也返回了数据,但浏览器的安全策略(同源策略)把数据拦截了。
什么是"同源"? 三个东西必须完全一样才叫同源:
| 前端页面 | 后台接口 | 同源吗? | |
|---|---|---|---|
| 示例 1 | http://localhost:5173 |
http://localhost:8080 |
❌ 端口不同 |
| 示例 2 | http://localhost:3000 |
https://api.example.com |
❌ 域名、协议都不同 |
| 示例 3 | http://www.example.com |
http://www.example.com/api |
✅ 完全同源 |
只要协议、域名、端口有一个不一样,就构成跨域。
8.2 怎么解决?—— 两种方案
方案一:开发阶段用 Vite 代理(前端自己搞定)
这是开发阶段最简单、最常用的方法。原理是:让 Vite 开发服务器冒充中间人,帮你转发请求。 浏览器以为请求发给了和自己同源的 Vite 服务器,实际上 Vite 在背后偷偷转发给了后台。
vite.config.js 配置:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8080', // 你的后台地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') // 可选:去掉 /api 前缀
}
}
}
})
配置完后,前端发请求改为:
// 之前(跨域,会报错)
axios.get('http://localhost:8080/api/users')
// 之后(不跨域,Vite 代理转发)
axios.get('/api/users')
转发过程:
浏览器 → 发请求到 /api/users → Vite 开发服务器收到 → Vite 偷偷转发到 http://localhost:8080/api/users → 后台返回数据给 Vite → Vite 返回给浏览器 → 浏览器:嗯,同源的,放行!
注意: 代理只在开发阶段有效。部署到生产环境后,Vite 代理就不工作了。
方案二:后台配置 CORS(让后台告诉浏览器"我允许跨域")
生产环境部署后,前端的代码直接跑在浏览器里,没有 Vite 帮你转发了。这时候需要后台在返回数据时,在响应头里加上允许跨域的标记。
8.3 后端要配置吗?
答案是:要,但分阶段。
| 阶段 | 谁来解 | 怎么解 |
|---|---|---|
| 本地开发 | 前端开发自己搞定 | 配 Vite 代理,不需要后台改任何东西 |
| 部署上线 | 后台必须配置 CORS | 后端在响应头里加 Access-Control-Allow-Origin |
为什么部署上线后前端代理不管用了?
因为 npm run dev 的 Vite 开发服务器只在你本地跑。项目部署后,前端代码被打包成纯 HTML/JS/CSS,部署在 Nginx 或 CDN 上,没有 Vite 了,代理自然就不存在了。
后台常用的 CORS 配置:
以常见的后台框架为例,给后端同事参考(你是前端的话,把这段发给他就行):
Java Spring Boot:
// 在 Controller 上加注解(单个接口)
@CrossOrigin(origins = "http://你的前端域名.com")
@GetMapping("/api/users")
public List<User> getUsers() { ... }
// 或者全局配置(推荐)
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 允许哪些接口跨域
.allowedOrigins("http://localhost:5173") // 允许哪个前端地址
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true);
}
};
}
}
Node.js Express:
const cors = require('cors')
app.use(cors({
origin: 'http://localhost:5173', // 允许哪个前端地址
credentials: true
}))
Python Flask:
from flask_cors import CORS CORS(app, origins=['http://localhost:5173'])
Go Gin:
import "github.com/gin-contrib/cors"
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:5173"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
}))
8.4 跨域问题排查口诀
遇到跨域报错,按这个顺序检查:
1. 我是在本地开发吗? → 是:配 Vite 代理(方案一),不需要找后台 → 否:跳到第 2 步 2. 项目已经部署上线了 / 打成了生产包? → 必须找后台配 CORS(方案二) 3. 后台配了 CORS 还不行? → 检查后台 allowOrigins 里写的前端域名和你实际访问的是否一致 → 检查是否漏了 http:// 前缀 → 检查是否同时用了 withCredentials: true(带 cookie 时需要)
九、新手最常踩的 5 个坑
1. ref 的值要用 .value
const count = ref(0) count.value++ // ✅ 正确,JS 里要加 .value count++ // ❌ 错误,不会生效
但是 <template> 里不用加 .value,Vue 会自动解包:
<!-- 模板里直接用 count 就行 -->
<p>{{ count }}</p>
2. 忘记 v-model 和 ref 的区别
-
ref:定义一个响应式数据 -
v-model:把数据和输入框双向绑定(输入框变了,数据也变;数据变了,输入框也变)
<el-input v-model="username" /> <!-- 相当于:输入框的值 和 username 这个 ref 绑定在一起了 -->
3. Element Plus 组件没有样式
检查一下 main.js 里有没有导入 CSS:
import 'element-plus/dist/index.css' // 这行必须有!
4. npm install 之后报各种奇怪的错
最常见的原因是 Node 版本不兼容。建议 Node 版本 ≥ 16。 如果还不行,删掉 node_modules 文件夹和 package-lock.json,重新 npm install。
5. 开发时接口 404(路径写错了)
// ❌ 如果配了 Vite 代理 /api → localhost:8080
axios.get('http://localhost:8080/api/users') // 发到了 8080,但浏览器跨域拦截了
// ✅ 正确写法:走代理
axios.get('/api/users') // Vite 自动转发到 localhost:8080
十、总结
你从零开始,按这条路走完了一遍:
装 Node.js → 用 Vite 创建项目 → npm install 装依赖 → npm run dev 跑起来 ↓ 理解 .vue 文件的三部分(template / script / style) ↓ 掌握 onMounted 生命周期 → 页面加载时发请求拿数据 ↓ 用 Axios 发 GET / POST 请求 → 封装请求工具 ↓ 引入 Element Plus → 直接用现成的按钮、表格、弹窗 ↓ 拼出一个完整页面
接下来你该做什么:
-
把上面第七节的完整代码复制到你的项目里,跑起来看看效果
-
去 Element Plus 官网,把表格、表单、弹窗的示例都复制一遍试试
-
找个免费 API(比如 jsonplaceholder)自己写几个增删改查的页面
-
遇到问题就去搜,前端社区资料非常丰富
前端入门没有那么难,关键是先把东西跑起来,再去理解原理。代码敲起来才是最快的路。
更多推荐



所有评论(0)