三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

2026年Vue3零基础实战:从环境搭建到商城项目开发全攻略

2026年Vue3零基础实战:从环境搭建到商城项目开发全攻略

暑假想学前端,但不知道从哪开始?看到满屏的“Vue3教程”却分不清哪个适合自己?很多教程要么上来就讲复杂概念,要么只给代码不给思路,学完感觉还是不会做项目。

这篇文章要解决的就是这个问题。我们不谈“前端已死”的焦虑,也不画“学完月入过万”的大饼,只聚焦一件事:如何用2026年最新的技术栈,从零开始,系统、高效地掌握Vue3,并最终能独立完成一个完整的项目。这不是一个简单的知识点罗列,而是一份结合了最新实践、避坑指南和项目驱动的学习路径。

你会发现,Vue3的核心优势不在于语法糖,而在于其组合式API带来的逻辑复用能力基于Vite的极速开发体验。对于初学者,最大的障碍往往不是Vue3本身,而是围绕它的一整套现代前端工具链(Node.js, npm, Vite, VS Code)。本文将带你一站式解决所有环境问题,从安装配置到写出第一个组件,再到理解响应式原理,最后动手实现一个包含商品展示、购物车、用户交互的简易商城项目。全程提供可运行的源码、清晰的课件思路和必要的软件指引,目标是让你“看完就能动手,动手就能跑通”。

1. 为什么2026年了,你还需要从Vue3开始学前端?

很多初学者会纠结:前端框架更新这么快,Vue3还值得学吗?直接学更新的不行吗?这是一个典型的认知误区。选择学习一项技术,关键不是看它是不是“最新”,而是看它的生态稳定性、市场需求和长期维护状态

Vue3自2020年正式发布以来,经过数年迭代,其核心API(Composition API)已非常稳定,并且成为了Vue团队的推荐写法。更重要的是,整个Vue生态(Vue Router 4, Pinia, Vite)都已全面适配Vue3,形成了成熟、高效的技术闭环。在2026年的招聘市场,Vue3及相关生态技术是前端岗位的普遍要求,而非加分项。学习Vue3,你学到的是一套经过大规模生产验证的、完整的现代前端解决方案。

对于零基础者,Vue3的入门曲线相比React更为平缓。其基于HTML的模板语法更符合直觉,细致的官方文档和丰富的社区资源能极大降低学习成本。本教程将避开“Hello World”式的浅尝辄止,直接切入开发者日常工作的核心场景:组件开发、状态管理、路由控制和工程化构建,确保你学到的每一个知识点都能立刻应用到项目实践中。

2. 学前准备:搭建你的现代化前端开发环境

在写第一行Vue代码前,一个顺畅的开发环境至关重要。很多教程会假设你已经装好了所有东西,但这里我们会一步步带你走通,并解释每个工具的作用。

2.1 核心工具安装与配置

  1. Node.js 与 npmNode.js是JavaScript的运行环境,npm(Node Package Manager)是随Node.js安装的包管理工具,用于安装和管理项目依赖(如Vue框架本身)。

    • 作用:没有Node.js,你的Vue项目无法运行和构建。
    • 安装:访问Node.js官网,下载并安装LTS(长期支持版)。安装完成后,打开终端(Windows用CMD或PowerShell,Mac用Terminal),输入以下命令验证:
      node -v npm -v
      成功显示版本号即表示安装正确。
  2. 代码编辑器:VS CodeVS Code是当前前端开发的首选编辑器,轻量、免费且插件生态丰富。

    • 安装:从官网下载安装。
    • 必备插件:安装后,在扩展商店搜索并安装以下插件,能极大提升Vue开发效率:
      • Volar:Vue官方的语言支持插件,提供语法高亮、智能提示、类型检查等(务必禁用旧版的Vetur插件)。
      • Vue VSCode Snippets:提供Vue代码片段,快速生成模板。
      • ESLint:代码规范检查工具。
      • Prettier:代码自动格式化工具。
  3. 版本控制:GitGit用于管理代码版本,是团队协作和代码备份的必备工具。

    • 安装:从Git官网下载安装。安装后可在终端使用git --version验证。
    • 基础配置(首次使用需要):
      git config --global user.name "你的名字" git config --global user.email "你的邮箱"

2.2 创建你的第一个Vue项目

过去,我们使用Vue CLI创建项目。现在,更推荐使用Vite,它提供了闪电般的冷启动和热更新速度。

在终端中,进入你打算存放项目的目录(例如Desktop),执行以下命令:

# 使用 npm 创建项目 npm create vue@latest

执行后,命令行会交互式地询问你项目配置:

  • Project name: 输入你的项目名,如my-vue-app
  • Add TypeScript?:对于初学者,可以先选No,后期再学。
  • Add JSX Support?:选No
  • Add Vue Router for Single Page Application development?Yes。这是做多页面应用必需的。
  • Add Pinia for state management?Yes。这是Vue官方推荐的状态管理库,比Vuex更简单。
  • Add ESLint for code quality?:选Yes。有助于保持代码规范。
  • Add Prettier for code formatting?:选Yes

配置完成后,进入项目目录并安装依赖:

cd my-vue-app npm install

安装完成后,运行开发服务器:

npm run dev

终端会输出一个本地地址(通常是http://localhost:5173),用浏览器打开它。看到Vue的欢迎页面,恭喜你,环境搭建成功!

3. Vue3核心概念精讲:告别“懂但不会用”

很多教程讲概念是孤立的。我们将概念置于“为什么需要它”和“怎么用它”的场景中。

3.1 响应式数据:refreactive的本质区别

Vue3的核心是响应式系统。数据变了,视图自动更新。实现响应式主要靠refreactive

  • ref:用于定义基本类型(字符串、数字、布尔值)或任何类型的响应式数据。它在内部通过.value属性访问和修改值。

    <script setup> import { ref } from 'vue' // 定义响应式数据 const count = ref(0) const message = ref('Hello Vue3') function increaseCount() { // 修改时需要通过 .value count.value++ } </script> <template> <button @click="increaseCount">点击了 {{ count }} 次</button> <p>{{ message }}</p> </template>

    为什么用.valueref接收一个内部值,返回一个响应式的、可变的ref对象,该对象只有一个.value属性指向内部值。在模板中,Vue会自动“解包”,所以模板里直接用{{ count }},但在<script setup>里修改时必须用.value

  • reactive:用于定义对象或数组的响应式数据。它返回一个对象的响应式代理。

    <script setup> import { reactive } from 'vue' const userInfo = reactive({ name: '张三', age: 25, hobbies: ['篮球', '编程'] }) function updateAge() { // 直接修改属性即可,无需 .value userInfo.age++ userInfo.hobbies.push('音乐') } </script> <template> <p>姓名:{{ userInfo.name }}</p> <p>年龄:{{ userInfo.age }}</p> <button @click="updateAge">增加年龄</button> </template>

如何选择?

  • 定义基本类型数据,用ref
  • 定义一组逻辑相关的数据(如表单对象),用reactive
  • 一个实用技巧:在组合式函数(Composables)中,通常返回ref,因为ref在解构时能保持响应性(需配合toRefs)。

3.2 组合式API (<script setup>):逻辑复用的革命

这是Vue3对比Vue2最大的进步。在Vue2的Options API中,代码被分散到data,methods,computed,mounted等选项中,当组件复杂时,维护同一功能的代码需要上下翻找。

组合式API允许你将与同一个逻辑功能相关的代码(数据、方法、生命周期等)组织在一起。

示例:一个获取鼠标位置的功能传统方式下,这个功能会分散在各个生命周期和data里。现在我们可以把它抽离成一个可复用的“组合式函数”:

  1. 创建composables/useMouse.js:

    // composables/useMouse.js import { ref, onMounted, onUnmounted } from 'vue' export function useMouse() { // 用ref定义响应式数据 const x = ref(0) const y = ref(0) // 定义更新鼠标位置的函数 function update(event) { x.value = event.pageX y.value = event.pageY } // 在组件挂载时添加事件监听 onMounted(() => window.addEventListener('mousemove', update)) // 在组件卸载时移除事件监听,防止内存泄漏 onUnmounted(() => window.removeEventListener('mousemove', update)) // 返回响应式数据,供组件使用 return { x, y } }
  2. 在任何组件中使用它:

    <!-- MouseTracker.vue --> <script setup> import { useMouse } from './composables/useMouse' const { x, y } = useMouse() </script> <template> <p>鼠标位置:{{ x }}, {{ y }}</p> </template>

这样一来,鼠标跟踪的逻辑被完美封装和复用,组件本身变得非常简洁。这才是Vue3组合式API威力的体现。

3.3 计算属性 (computed) 与侦听器 (watch)

  • computed:用于依赖其他响应式数据的计算属性。它会缓存计算结果,只有依赖项变化时才重新计算。

    <script setup> import { ref, computed } from 'vue' const price = ref(10) const quantity = ref(2) // 总价依赖于单价和数量 const total = computed(() => price.value * quantity.value) // 当 price 或 quantity 变化时,total 会自动更新 </script>
  • watch:用于观察响应式数据的变化并执行副作用(如发起请求、操作DOM)。

    <script setup> import { ref, watch } from 'vue' const searchQuery = ref('') // 监听 searchQuery 的变化,延迟执行搜索 watch(searchQuery, (newValue, oldValue) => { if (newValue.trim() === '') return // 模拟搜索API调用 console.log(`正在搜索: ${newValue}`) // 实际项目中这里可能是 fetch 或 axios 请求 }, { immediate: false }) // immediate: false 表示初始时不执行 </script> <template> <input v-model="searchQuery" placeholder="搜索..." /> </template>

4. 项目实战:构建一个简易商品商城

理论必须结合实践。我们将构建一个具备核心功能的简易商城,涵盖以下知识点:

  • 组件化开发 (.vue文件)
  • 组件间通信 (Props / Emits)
  • 状态管理 (Pinia)
  • 路由导航 (Vue Router)
  • 样式与布局

4.1 项目结构与状态管理 (Pinia) 设置

首先,在项目根目录下,你会看到src/stores文件夹,里面有一个counter.js示例。我们创建一个管理商品和购物车的Store。

创建src/stores/product.js:

// src/stores/product.js import { ref, computed } from 'vue' import { defineStore } from 'pinia' export const useProductStore = defineStore('product', () => { // 状态:商品列表 const products = ref([ { id: 1, name: 'Vue3实战指南', price: 69, inventory: 10 }, { id: 2, name: 'TypeScript入门', price: 49, inventory: 15 }, { id: 3, name: '前端工程化', price: 89, inventory: 5 }, ]) // 状态:购物车,数组项为 { productId, quantity } const cartItems = ref([]) // 计算属性:购物车总价 const cartTotalPrice = computed(() => { return cartItems.value.reduce((total, item) => { const product = products.value.find(p => p.id === item.productId) return total + (product ? product.price * item.quantity : 0) }, 0) }) // 计算属性:购物车商品总数 const cartTotalCount = computed(() => { return cartItems.value.reduce((sum, item) => sum + item.quantity, 0) }) // Action:添加商品到购物车 function addToCart(productId) { const existingItem = cartItems.value.find(item => item.productId === productId) if (existingItem) { existingItem.quantity++ } else { cartItems.value.push({ productId, quantity: 1 }) } // 对应商品库存减1 const product = products.value.find(p => p.id === productId) if (product && product.inventory > 0) { product.inventory-- } } // Action:从购物车移除商品 function removeFromCart(productId) { const index = cartItems.value.findIndex(item => item.productId === productId) if (index > -1) { const item = cartItems.value[index] // 恢复库存 const product = products.value.find(p => p.id === productId) if (product) { product.inventory += item.quantity } cartItems.value.splice(index, 1) } } // 暴露状态和函数给组件使用 return { products, cartItems, cartTotalPrice, cartTotalCount, addToCart, removeFromCart } })

4.2 商品列表组件

创建src/components/ProductList.vue:

<!-- src/components/ProductList.vue --> <script setup> import { useProductStore } from '@/stores/product' const productStore = useProductStore() </script> <template> <div class="product-list"> <h2>商品列表</h2> <div class="products"> <div v-for="product in productStore.products" :key="product.id" class="product-card"> <h3>{{ product.name }}</h3> <p>价格:¥{{ product.price }}</p> <p>库存:{{ product.inventory }}</p> <button @click="productStore.addToCart(product.id)" :disabled="product.inventory === 0" class="add-btn" > {{ product.inventory > 0 ? '加入购物车' : '已售罄' }} </button> </div> </div> </div> </template> <style scoped> .product-list { padding: 20px; } .products { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; margin-top: 20px; } .product-card { border: 1px solid #ddd; border-radius: 8px; padding: 15px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .add-btn { background-color: #42b883; /* Vue主题色 */ color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 10px; } .add-btn:disabled { background-color: #ccc; cursor: not-allowed; } </style>

4.3 购物车组件

创建src/components/ShoppingCart.vue:

<!-- src/components/ShoppingCart.vue --> <script setup> import { useProductStore } from '@/stores/product' import { computed } from 'vue' const productStore = useProductStore() // 计算购物车详情,将id映射为商品对象 const cartDetails = computed(() => { return productStore.cartItems.map(item => { const product = productStore.products.find(p => p.id === item.productId) return { ...item, product } }).filter(item => item.product) // 过滤掉可能已不存在的商品 }) </script> <template> <div class="shopping-cart"> <h2>购物车 ({{ productStore.cartTotalCount }})</h2> <div v-if="cartDetails.length === 0" class="empty-cart"> 购物车空空如也~ </div> <ul v-else class="cart-items"> <li v-for="item in cartDetails" :key="item.productId" class="cart-item"> <span class="item-name">{{ item.product.name }}</span> <span class="item-quantity">x{{ item.quantity }}</span> <span class="item-price">¥{{ item.product.price * item.quantity }}</span> <button @click="productStore.removeFromCart(item.productId)" class="remove-btn">移除</button> </li> </ul> <div class="cart-footer" v-if="cartDetails.length > 0"> <div class="total">总计:<strong>¥{{ productStore.cartTotalPrice }}</strong></div> <button class="checkout-btn">去结算</button> </div> </div> </template> <style scoped> .shopping-cart { padding: 20px; border-left: 1px solid #eee; } .empty-cart { text-align: center; color: #999; padding: 40px 0; } .cart-items { list-style: none; padding: 0; } .cart-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; } .remove-btn { background-color: #ff6b6b; color: white; border: none; padding: 4px 8px; border-radius: 3px; cursor: pointer; font-size: 0.8em; } .cart-footer { margin-top: 20px; padding-top: 20px; border-top: 2px solid #42b883; text-align: right; } .total { margin-bottom: 10px; font-size: 1.2em; } .checkout-btn { background-color: #35495e; /* Vue深色 */ color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 1em; } </style>

4.4 整合页面与路由

修改主页面src/views/HomeView.vue,引入我们的组件:

<!-- src/views/HomeView.vue --> <script setup> import ProductList from '@/components/ProductList.vue' import ShoppingCart from '@/components/ShoppingCart.vue' </script> <template> <div class="home"> <header class="app-header"> <h1>Vue3 简易商城</h1> <p>一个使用Vue3 + Pinia构建的实战项目</p> </header> <main class="main-content"> <ProductList /> <ShoppingCart /> </main> </div> </template> <style scoped> .home { min-height: 100vh; background-color: #f8f9fa; } .app-header { background: linear-gradient(135deg, #42b883, #35495e); color: white; padding: 2rem; text-align: center; } .main-content { display: flex; max-width: 1200px; margin: 0 auto; padding: 20px; } .main-content > * { flex: 1; } /* 响应式布局:小屏幕下垂直排列 */ @media (max-width: 768px) { .main-content { flex-direction: column; } } </style>

现在,运行npm run dev,打开浏览器,你将看到一个具备商品展示、加入购物车、移除商品、计算总价等核心功能的简易商城。所有状态都通过Pinia集中管理,组件间通信清晰简洁。

5. 核心进阶:组件通信与生命周期

掌握了基础项目后,需要深入理解组件如何协作。

5.1 组件通信:Props与Emits

  • Props (父传子):父组件通过属性向子组件传递数据。

    <!-- ParentComponent.vue --> <template> <ChildComponent :title="parentTitle" :count="parentCount" /> </template> <script setup> import { ref } from 'vue' import ChildComponent from './ChildComponent.vue' const parentTitle = ref('来自父组件的标题') const parentCount = ref(0) </script>
    <!-- ChildComponent.vue --> <template> <div> <h3>{{ title }}</h3> <p>计数:{{ count }}</p> </div> </template> <script setup> // 定义Props,指定类型和默认值 defineProps({ title: { type: String, required: true }, count: { type: Number, default: 0 } }) </script>
  • Emits (子传父):子组件通过触发事件向父组件传递信息。

    <!-- ChildComponent.vue --> <template> <button @click="handleClick">通知父组件</button> </template> <script setup> // 定义 emits const emit = defineEmits(['customEvent']) function handleClick() { // 触发事件,并传递数据 emit('customEvent', { message: '子组件被点击了', time: new Date() }) } </script>
    <!-- ParentComponent.vue --> <template> <ChildComponent @custom-event="handleCustomEvent" /> </template> <script setup> import ChildComponent from './ChildComponent.vue' function handleCustomEvent(payload) { console.log('收到子组件事件:', payload.message, payload.time) } </script>

    注意:Vue推荐使用kebab-case(短横线分隔)的事件名,如@custom-event

5.2 生命周期钩子:在正确的时间做正确的事

生命周期钩子让你在组件创建、更新、销毁的关键时刻执行代码。Vue3的组合式API中,它们是以函数形式导入的。

<script setup> import { onMounted, onUpdated, onUnmounted, ref } from 'vue' const count = ref(0) // 组件挂载完成后执行(常用于数据请求、DOM操作、事件监听) onMounted(() => { console.log('组件已挂载到DOM') // 例如:从API获取初始数据 // fetchData() }) // 组件因响应式状态变更而更新其DOM树后执行(谨慎使用,避免无限循环) onUpdated(() => { console.log('组件已更新,当前count:', count.value) }) // 组件实例被卸载之前执行(用于清理工作,如清除定时器、取消事件监听) onUnmounted(() => { console.log('组件即将被销毁') // clearInterval(timerId) }) function increment() { count.value++ } </script> <template> <button @click="increment">点击 {{ count }}</button> </template>

最重要的钩子是onMountedonUnmounted,分别用于初始化和清理,是避免内存泄漏的关键。

6. 工程化与构建:让项目更专业

一个真实项目离不开工程化工具的支持。

6.1 环境变量与模式

Vite使用.env文件管理环境变量。在项目根目录创建:

  • .env:所有环境的默认变量。
  • .env.development:开发环境变量(npm run dev时加载)。
  • .env.production:生产环境变量(npm run build时加载)。

文件内容示例(.env.development):

VITE_API_BASE_URL=http://localhost:3000/api VITE_APP_TITLE=My Vue3 App (Dev)

在代码中通过import.meta.env.VITE_API_BASE_URL访问。变量名必须以VITE_开头,这是Vite的规定。

6.2 打包与部署

开发完成后,需要构建生产版本:

npm run build

该命令会在项目根目录生成一个dist文件夹,里面是优化、压缩后的静态文件(HTML, JS, CSS)。你可以将这个文件夹部署到任何静态文件服务器,如Nginx、Apache,或云服务(Vercel, Netlify, GitHub Pages)。

部署到GitHub Pages的简易步骤:

  1. vite.config.js中设置正确的base路径(如果你的仓库名不是根目录)。
    // vite.config.js export default defineConfig({ base: process.env.NODE_ENV === 'production' ? '/你的仓库名/' : '/', // ... 其他配置 })
  2. 本地构建:npm run build
  3. dist文件夹的内容推送到GitHub仓库的gh-pages分支,或使用gh-pages等自动化工具。

7. 常见问题与排查思路 (Q&A)

在实际学习和开发中,你一定会遇到各种问题。这里列出高频问题及解决方案。

问题现象可能原因排查方式解决方案
项目运行npm run dev失败,提示端口被占用5173端口已被其他程序(如另一个Vite项目)使用查看终端错误信息,确认端口冲突1. 终止占用端口的进程。2. 在vite.config.js中配置其他端口:server: { port: 3000 }
修改代码后,浏览器没有自动刷新(热更新失效)1. 编辑器保存设置问题。2. 文件系统监视限制(如WSL2、某些虚拟机)。3. 浏览器缓存。1. 检查VS Code是否自动保存。2. 查看终端是否有编译错误。1. 确保VS Code开启自动保存。2. 在vite.config.js中配置server: { watch: { usePolling: true } }(适用于WSL2/Docker)。3. 尝试禁用浏览器缓存或使用无痕模式。
组件引入后,模板中无法使用,提示“xxx is not defined”1. 组件未正确导入。2. 在<script setup>外使用了组合式API。1. 检查import语句路径是否正确。2. 检查组件名在模板中是否拼写错误(大小写)。1. 使用相对路径./或别名@/导入。2. 确保所有Vue3组合式API(ref, reactive等)都在<script setup>标签内使用。
Pinia store中的状态更新了,但组件视图没更新1. 直接修改了store状态的某个属性,破坏了响应性。2. 在异步回调中修改状态,Vue可能无法追踪。1. 检查是否对数组用了索引赋值或对对象新增了属性。2. 检查修改是否发生在setTimeout/Promise等异步中。1. 对于数组/对象,尽量使用返回新对象的方法或使用store.$patch。2. 确保在异步操作中修改状态后,数据确实是响应式代理。
打包后页面空白,控制台报资源加载错误1. 资源路径错误(特别是使用了路由History模式)。2. 公共路径(base)配置错误。1. 检查浏览器控制台Network标签页,看哪个文件404。2. 检查vite.config.js中的base配置。1. 如果部署到非根目录,确保base配置正确。2. 如果使用Vue Router的History模式,需要在服务器配置Fallback到index.html
ESLint报一堆红色波浪线,代码格式混乱1. ESLint和Prettier规则冲突。2. 未安装或未正确配置相关插件。1. 查看VS Code输出面板的ESLint日志。2. 检查项目根目录是否有.eslintrc.cjs.prettierrc文件。1. 运行npm run lint查看具体错误。2. 安装VS Code的ESLint和Prettier插件,并确保工作区设置中启用“保存时格式化”。

8. 最佳实践与学习建议

掌握了基础和实践后,如何写出更健壮、更易维护的代码?以下是一些关键建议。

  1. 组件设计原则

    • 单一职责:一个组件只做一件事。如果一个组件变得过于复杂,就拆分它。
    • 拥抱组合:多用组合(Composition),少用继承(Inheritance)。Vue3的组合式API就是为此而生。
    • Props向下,Events向上:数据流清晰,便于理解和调试。
  2. 状态管理选择

    • 简单的父子组件通信,用Props/Emits
    • 跨层级组件共享状态,用Provide/Inject
    • 复杂的、全局的、需要持久化的状态(如用户信息、主题、购物车),用Pinia
  3. 样式管理

    • 组件内样式使用<style scoped>,避免全局污染。
    • 对于全局主题、变量,在src/assets下创建全局CSS文件。
    • 考虑使用CSS预处理器(如Sass/Scss),在创建项目时可选装,或后期通过npm install sass安装。
  4. 性能优化意识

    • 列表渲染始终使用:key,且key最好是唯一ID,而非索引。
    • 使用v-show替代v-if来频繁切换显示/隐藏(v-show只是CSS切换,v-if是DOM的销毁和重建)。
    • 对于复杂计算,使用computed缓存结果。
    • 使用watchEffectwatchflush: 'post'选项来在DOM更新后执行副作用。
  5. 持续学习路径

    • 夯实基础:彻底理解响应式原理(ref,reactive,effect)、生命周期。
    • 掌握生态:深入学习Vue Router(路由守卫、懒加载)、Pinia(状态持久化、插件)。
    • TypeScript:这是趋势。尝试在下一个项目中使用Vue3 + TypeScript,它能提供更好的代码提示和类型安全。
    • 底层原理:阅读Vue3源码导读、虚拟DOM、Diff算法,这能让你在面试和解决复杂问题时游刃有余。
    • 工程化:学习Vite原理、打包优化、CI/CD、单元测试(Vitest + Vue Test Utils)。

学习前端,尤其是Vue3,最好的方法就是“动手”。不要满足于看懂教程,一定要把代码敲一遍,然后尝试修改它、扩展它。比如,给我们的简易商城加上商品详情页、用户登录、订单管理等功能。遇到问题,善用官方文档、搜索引擎和社区(如GitHub Issues、Stack Overflow)。这个暑假,从完成这个项目开始,你的前端逆袭之路就已经踏出了最坚实的一步。

← 返回列表