组件化之路:Vue 3 基础入门

当前端页面越来越复杂时,只依靠原生 JavaScript 手动获取 DOM、修改内容、绑定事件,会让代码逐渐变得零散而难维护。尤其是当页面中存在大量列表、表单、弹窗、状态切换和组件复用时,我们就需要一个更高效的前端框架来管理界面。

Vue 就是在这种背景下诞生的。它通过声明式渲染、响应式数据和组件化开发,让我们能以更清晰的方式构建用户界面。

如果说前面的 HTML、CSS、JavaScript、Fetch / Ajax 让你具备了开发网页和请求数据的基础能力,那么 Vue 会进一步帮你把这些能力组织成真正的应用结构。


1. 什么是 Vue

Vue 是一个用于构建用户界面的 JavaScript 框架。

它最核心的价值有三点:

  1. 声明式渲染:你描述“页面应该长什么样”,Vue 负责帮你更新 DOM。
  2. 响应式数据:数据变化后,页面会自动更新。
  3. 组件化开发:可以把复杂页面拆成一个个可复用的小模块。

例如,在原生 JavaScript 中,你可能需要这样改文字:

const title = document.getElementById("title");
title.innerText = "欢迎来到 Vue";

而在 Vue 中,你更多是在描述:

<h1>{{ title }}</h1>

只要 title 这个数据变化,页面就会自动更新。

这就是 Vue 最重要的思维方式变化:

不再频繁手动操作 DOM,而是优先操作数据,让框架负责视图更新。


2. Vue 和原生 JavaScript 操作 DOM 的区别

2.1 原生 JavaScript 的特点

原生 JavaScript 并不是不能做页面开发,而是随着页面复杂度提升,会越来越容易出现这些问题:

  • DOM 查询代码变多
  • 事件绑定分散
  • 状态管理混乱
  • 多处逻辑相互影响
  • 可复用性差

2.2 Vue 的优势

Vue 把“数据”和“页面展示”建立了直接关系:

  • 数据驱动页面
  • 模板负责结构
  • 指令负责逻辑表达
  • 组件负责拆分与复用

这意味着:

  1. 你更关注“状态是什么”
  2. 不必反复手动改每一个 DOM 节点
  3. 页面结构更适合维护和扩展

3. 创建第一个 Vue 应用

在真实项目中,Vue 通常会通过工程化方式创建,例如使用 Vite。不过为了先理解基础结构,我们先看最核心的代码形式。

3.1 一个最小示例

import { createApp } from "vue";

// 定义根组件
const App = {
  template: `<h1>Hello Vue</h1>`
};

// 创建应用并挂载到页面容器
createApp(App).mount("#app");

这里有三个关键信息:

  • createApp():创建一个 Vue 应用实例
  • App:当前应用的根组件
  • .mount("#app"):把 Vue 应用挂载到页面中的某个 DOM 容器上

例如页面中通常会有一个容器:

<div id="app"></div>

Vue 会接管这个容器,并把组件渲染进去。

3.2 为什么真实项目常用 Vite

因为 Vue 项目往往会使用:

  • 单文件组件 .vue
  • 模块化导入导出
  • 本地开发服务器
  • 自动热更新

所以真正开发时,通常会用 Vite + Vue 3 作为项目基础。

3.3 这些代码到底放在哪里

很多初学者第一次看到下面这种代码时,会立刻困惑:

import { createApp } from "vue";

因为在前面学习原生 HTML、CSS、JavaScript 时,我们通常是在浏览器里直接写:

<script>
  // 直接写代码
</script>

但 Vue 工程项目通常不是这样运行的。更常见的结构是:

my-vue-app/
  index.html
  package.json
  src/
    main.js
    App.vue

其中:

  • index.html:页面入口,里面通常有一个 <div id="app"></div>
  • src/main.js:JavaScript 入口文件,通常在这里写 createApp(App).mount("#app")
  • src/App.vue:根组件,也就是整个应用最外层的 Vue 组件

也就是说,前面看到的 createApp() 代码,通常并不是随便写在浏览器控制台里,而是写在 src/main.js 中。

3.4 如何用 Vite 创建一个 Vue 3 项目

如果你已经安装了 Node.js,就可以通过命令行快速创建一个 Vue 项目。

最常见的流程如下:

npm create vite@latest my-vue-app -- --template vue
cd my-vue-app
npm install
npm run dev

这几步分别表示:

  1. 创建一个基于 Vite 的 Vue 项目
  2. 进入项目目录
  3. 安装依赖包
  4. 启动本地开发服务器

启动成功后,终端通常会给你一个本地地址,例如:

http://localhost:5173/

浏览器访问这个地址,就能看到项目跑起来了。

💡 如果你对 Node.jsnpmVite 这些词还不熟悉: 它们并不是 Vue 本身的语法,而是现代前端工程化开发的基础工具。后续最好单独补上这部分知识,否则很容易出现“会写 Vue,但不会启动项目”的情况。

3.5 第一次看懂项目目录

一个最基础的 Vue 3 + Vite 项目里,经常会看到这些文件:

  • package.json:记录项目依赖和可执行脚本
  • index.html:浏览器入口页面
  • src/main.js:应用启动入口
  • src/App.vue:根组件
  • src/components/:放可复用组件
  • src/assets/:放图片、样式等静态资源

你可以先建立这样一个最简心智模型:

  1. 浏览器先打开 index.html
  2. index.html 再加载 src/main.js
  3. main.js 创建 Vue 应用并挂载 App.vue
  4. App.vue 再继续组合其他组件

3.6 为什么 .vue 文件能运行

.vue 文件叫作 单文件组件(Single File Component)

例如:

<script setup>
const title = "Hello Vue";
</script>

<template>
  <h1>{{ title }}</h1>
</template>

<style>
h1 {
  color: #42b883;
}
</style>

浏览器本身其实并不直接认识 .vue 文件。

之所以它能运行,是因为 Vite 和 Vue 的相关插件会在开发和构建阶段把它转换成浏览器能理解的 JavaScript、HTML 和 CSS。

这也是为什么:

  • .vue 文件不能直接双击运行
  • import 在工程项目里能用
  • Vue 项目通常需要 npm run dev

它们背后依赖的都是工程化工具链。


4. 模板语法:让数据显示到页面上

Vue 的模板语法本质上是在 HTML 里嵌入和数据相关的表达方式。

4.1 插值表达式

<h1>{{ title }}</h1>
<p>{{ message }}</p>

这里的 {{ }} 表示把 JavaScript 数据插入页面。

4.2 属性绑定

如果要给 HTML 属性绑定动态值,使用 v-bind,简写为 :

<img :src="imageUrl" :alt="imageDesc">
<a :href="link">查看详情</a>

动态 Class 绑定(高频交互): 我们可以通过对象语法动态切换 class,这在做 Tab 切换、高亮选中时非常常用:

<!-- 当 isActive 为 true 时,加上 'active' 这个类名 -->
<div :class="{ active: isActive }">内容</div>

4.3 事件绑定

给元素绑定事件,使用 v-on,简写为 @

<button @click="handleClick">点我</button>

5. 常用指令:Vue 模板的基础能力

指令是 Vue 提供的一套特殊语法,用于在模板中表达逻辑。

5.1 v-ifv-show:条件渲染

<p v-if="isLogin">欢迎回来</p>
<p v-else>请先登录</p>

isLogintrue 时显示第一段,否则显示第二段。

💡 面试必考:v-if vs v-show

  • v-if 是“真正的”条件渲染,如果不满足条件,DOM 元素会被直接移除/销毁。
  • v-show 只是简单地切换 CSS 的 display: none
  • 选择建议:如果需要非常频繁地切换显示/隐藏,用 v-show(性能更好);如果在运行时条件很少改变,用 v-if

5.2 v-for:列表渲染

<ul>
  <li v-for="user in users" :key="user.id">
    {{ user.name }}
  </li>
</ul>

这里表示遍历 users 数组,把每一项渲染成列表。

💡 key 很重要: 它用于帮助 Vue 更准确地识别每一项,提高更新效率,也能减少意外的渲染问题。

5.3 v-model:双向绑定

<input v-model="username" placeholder="请输入用户名">
<p>当前输入:{{ username }}</p>

输入框的内容会同步到 username,而 username 的变化也会自动体现在页面中。

这在表单开发里非常常见。


6. 组合式 API:Vue 3 的主流写法

Vue 3 最推荐的开发方式是 组合式 API(Composition API)

它的核心思想是:

按“逻辑功能”组织代码,而不是按 datamethodscomputed 这些固定区域拆开。

在 Vue 3 中,最常见的组合式 API 会放在 setup<script setup> 中编写。

6.1 ref:定义基础响应式数据

import { ref } from "vue";

// ref 适合包裹基础类型数据
const count = ref(0);

function add() {
  // 在 JS 中修改 ref 要通过 .value
  count.value++;
}

模板中使用时:

<button @click="add">点击次数:{{ count }}</button>

这里有两个关键点:

  • ref(0) 创建了一个响应式数据
  • 在 JavaScript 中访问它需要 .value

6.2 reactive:定义对象类型数据

import { reactive } from "vue";

// reactive 适合处理对象结构的数据
const user = reactive({
  name: "Tom",
  age: 20
});

它适合处理对象结构的数据。

例如:

<p>{{ user.name }} - {{ user.age }}</p>

6.3 refreactive 怎么选

入门阶段可以这样理解:

  • 基础类型,如数字、字符串、布尔值,优先用 ref
  • 对象、数组等结构化数据,可以使用 reactive

不过在很多真实项目中,开发者也会统一更多地使用 ref,因为它的行为更稳定、使用边界更清晰。

6.4 响应式丢失陷阱(新手高频 Bug)

使用 reactive 时,千万不要直接对它进行解构赋值,否则会丢失响应式特性:

const user = reactive({ name: "Tom", age: 20 });

// ❌ 错误做法:这里的 name 变成了普通字符串,修改它页面不会更新!
/*
let { name } = user; 
const changeName = () => {
  name = "Jerry" 
  console.log(name) // 控制台打印 Jerry,但页面不会更新
}
*/

// ✅ 正确做法:始终通过 user.name 访问,或使用 toRefs 转换
const changeName = () => {
  // 直接修改响应式对象的属性,触发更新
  user.name = "Jerry"
}

7. 计算属性与监听

7.1 computed:根据已有数据推导新值

import { ref, computed } from "vue";

const firstName = ref("张");
const lastName = ref("三");

// 根据已有状态派生出新结果
const fullName = computed(() => {
  return firstName.value + lastName.value;
});

模板中直接使用:

<p>{{ fullName }}</p>

适合场景:

  • 拼接姓名
  • 统计购物车总价
  • 根据状态生成展示文案

7.2 watch:监听数据变化

import { ref, watch } from "vue";

const keyword = ref("");

// 监听 keyword 变化,执行副作用逻辑
watch(keyword, (newValue, oldValue) => {
  console.log("新值:", newValue);
  console.log("旧值:", oldValue);
});

适合场景:

  • 监听输入变化后发请求
  • 监听筛选条件变化后重新加载列表
  • 监听某个状态变化后执行副作用逻辑

7.3 进阶:深度监听 (Deep Watch)

在 Vue 3 中,watch 的行为会根据你监听的数据源类型而有所不同:

  1. 直接监听 reactive 对象:Vue 会自动开启深度监听。无论你修改了对象里多深的属性,都会触发回调。
  2. 监听 ref 中保存的对象,或监听返回对象的 getter 函数:默认是浅层监听。如果不加 deep: true,只有对象本身被替换时才会触发。

如果你想监听一个 ref 内部对象的深层变化,或者通过 getter 函数监听 reactive 对象内部的某个嵌套对象,就需要手动开启 { deep: true }

import { ref, reactive, watch } from "vue";

// 场景 1:直接监听 reactive 对象(自动深度监听)
const user = reactive({ profile: { age: 20 } });
watch(user, () => {
  console.log("user 内部数据变了!"); // user.profile.age = 21 会触发
});

// 场景 2:监听 ref 对象(需要手动开启深度监听)
const settings = ref({ theme: 'dark', layout: { sidebar: true } });
watch(
  settings,
  () => {
    console.log("settings 内部数据变了!");
  },
  { deep: true } // 必须加这个,否则 settings.value.layout.sidebar = false 不会触发
);

// 场景 3:通过 getter 监听 reactive 对象的某个嵌套属性(需要手动开启深度监听)
watch(
  () => user.profile,
  () => {
    console.log("profile 内部数据变了!");
  },
  { deep: true } // 必须加这个,否则 user.profile.age = 22 不会触发
);

⚠️ 性能警告: 深度监听会递归遍历对象的所有属性,如果对象非常大,开启 deep: true 可能会带来性能开销。因此,如果只需要监听对象中的某一个特定基础属性,更推荐的做法是直接监听那个属性的 getter 函数(不需要 deep):

// 只监听 user.profile.age 的变化,性能最好
watch(
  () => user.profile.age,
  (newAge) => {
    console.log("年龄变了:", newAge);
  }
);

💡 区别要记住: computed 更像“根据旧数据算新数据”; watch 更像“当数据变化时执行额外动作”。


8. 生命周期:组件什么时候开始工作

Vue 组件从创建到销毁,会经历一系列阶段,这些阶段就叫生命周期。

在组合式 API 中,最常用的是 onMounted

8.1 onMounted(挂载完毕)

import { onMounted } from "vue";

onMounted(() => {
  // 组件首次挂载完成后执行
  console.log("组件已经挂载到页面上");
});

它常用于:

  • 页面加载完成后请求接口
  • 初始化图表
  • 获取某个 DOM 节点

如果你后续要在 Vue 中配合 axios 请求接口,最常见的写法就是在 onMounted() 中发起请求。

8.2 onUnmounted(卸载完毕)

import { onUnmounted } from "vue";

onUnmounted(() => {
  // 组件销毁前后常用来做清理工作
  console.log("组件已经被销毁");
});

它常用于:

  • 清除定时器(clearInterval
  • 移除全局事件监听(window.removeEventListener
  • 断开 WebSocket 连接

⚠️ 易错坑点: 如果你在组件里设置了一个定时器,但在组件销毁时没有通过 onUnmounted 清除它,这个定时器会在后台一直运行,导致内存泄漏和不可预期的 Bug。

8.3 获取真实 DOM (Template Refs)

虽然 Vue 提倡数据驱动,不建议直接操作 DOM,但有时接入第三方库(如 ECharts 图表)或需要使输入框自动聚焦时,必须获取真实 DOM。

在 Vue 3 中,通过声明一个同名的 ref 变量即可:

<script setup>
import { ref, onMounted } from 'vue';

// 1. 声明一个名字和模板里 ref 属性一样的变量
const myInput = ref(null);

onMounted(() => {
  // 3. 在挂载完成后,才能拿到真实的 DOM 元素
  myInput.value.focus();
});
</script>

<template>
  <!-- 2. 绑定 ref 属性 -->
  <input ref="myInput" type="text" />
</template>

9. 组件通信:父子组件如何传数据

Vue 的强大之处,很大程度上来自组件化开发。但组件拆分后,数据就隔离了。父组件如何把数据传给子组件?子组件又如何告诉父组件自己发生了什么?

这就需要用到组件通信的两个核心概念:propsemits

9.1 父传子:使用 defineProps 接收数据

就像你给 HTML 标签传递 idclass 属性一样,你可以给自定义组件传递自定义属性。这在 Vue 中叫做 props

子组件(ArticleCard.vue):声明自己需要哪些数据。

<script setup>
// 使用 defineProps 定义接收的属性
const props = defineProps({
  title: String,
  author: String
});
</script>

<template>
  <div class="card">
    <h3>{{ title }}</h3>
    <p>作者:{{ author }}</p>
  </div>
</template>

父组件(App.vue):向子组件传递数据。

<template>
  <!-- 像写 HTML 属性一样传值 -->
  <ArticleCard title="Vue 入门教程" author="张三" />
  <!-- 如果传递的是动态变量,需要加 : (v-bind) -->
  <ArticleCard :title="dynamicTitle" :author="currentAuthor" />
</template>

9.2 子传父:使用 defineEmits 发送事件

子组件内部发生了一些操作(比如用户点击了删除按钮),需要通知父组件去更新列表,这时就需要 emit 抛出自定义事件。

子组件(TodoItem.vue):定义并触发事件。

<script setup>
// 定义当前组件会触发哪些自定义事件
const emit = defineEmits(["remove"]);

function handleRemove() {
  // 触发名为 'remove' 的事件,还可以带上参数:emit('remove', 123)
  emit("remove");
}
</script>

<template>
  <div>
    <span>买牛奶</span>
    <button @click="handleRemove">删除</button>
  </div>
</template>

父组件(App.vue):监听子组件发出的事件。

<script setup>
function deleteTodo() {
  console.log("父组件收到了删除通知,准备删除数据!");
}
</script>

<template>
  <!-- 使用 @ 监听子组件自定义的 remove 事件 -->
  <TodoItem @remove="deleteTodo" />
</template>

🔑 总结:这就是最基础的组件通信模型:数据向下流(Props),事件向上走(Emits)。理解了这个,你就掌握了编写复杂页面的基础。

9.3 父传结构:使用插槽 (<slot>)

除了传数据,很多时候我们需要向子组件传递 HTML 结构(例如自定义弹窗组件,内部的内容由父组件决定)。这就是插槽的作用。

子组件(Modal.vue):留下占位符。

<template>
  <div class="modal">
    <div class="modal-header">提示</div>
    <div class="modal-body">
      <!-- 默认插槽:父组件传进来的结构会替换这里 -->
      <slot>默认内容</slot>
    </div>
  </div>
</template>

父组件:传入具体结构。

<template>
  <Modal>
    <!-- 这里的结构会被塞进子组件的 <slot> 里 -->
    <p>确定要删除这条数据吗?</p>
    <button>确认</button>
  </Modal>
</template>

9.4 暴露子组件方法:defineExpose

<script setup> 中,组件默认是封闭的。如果父组件想通过 ref 直接调用子组件里的方法或访问数据,子组件必须使用 defineExpose 显式暴露出来。这在封装复杂组件(如表单校验、弹窗控制)时非常常用。

子组件 (Child.vue)

<script setup>
import { ref } from 'vue'

const isVisible = ref(false)

const open = () => {
  isVisible.value = true
}

// 只有暴露出去,父组件才能调用 open 方法
defineExpose({
  open
})
</script>

<template>
  <div v-if="isVisible">我是一个弹窗</div>
</template>

父组件

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

// 1. 创建一个 ref 变量,名字要和模板里的 ref 属性一致
const childRef = ref(null)

const handleOpen = () => {
  // 2. 通过 .value 访问子组件实例,并调用其暴露的方法
  childRef.value.open()
}
</script>

<template>
  <button @click="handleOpen">打开子组件弹窗</button>
  <!-- 绑定 ref -->
  <Child ref="childRef" />
</template>

9.5 双向绑定进阶:defineModel (Vue 3.4+)

在封装自定义输入框或表单组件时,我们希望像原生 <input v-model="val"> 一样使用自定义组件。在 Vue 3.4 之前,这需要结合 props: ['modelValue']emits: ['update:modelValue'] 来实现,比较繁琐。

Vue 3.4 引入了 defineModel 宏,让自定义组件的双向绑定变得极其简单:

子组件 (CustomInput.vue)

<script setup>
// 声明一个 model,Vue 会自动处理 modelValue prop 和 update:modelValue 事件
const model = defineModel()
</script>

<template>
  <!-- 直接绑定到原生 input 上 -->
  <input v-model="model" class="custom-input" />
</template>

父组件

<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'

const username = ref('')
</script>

<template>
  <!-- 完美支持 v-model -->
  <CustomInput v-model="username" />
  <p>当前输入:{{ username }}</p>
</template>

10. 进阶:DOM 更新机制与 nextTick

在 Vue 中,当你修改了响应式数据时,DOM 不会立即更新。Vue 会将这些更新操作放入一个队列中,等到下一个“Tick”(微任务阶段)才统一执行 DOM 更新。这种机制是为了避免频繁操作 DOM 带来的性能问题。

但这也带来了一个常见问题:修改数据后,立刻去获取 DOM 元素,拿到的还是旧的。

<script setup>
import { ref, nextTick } from 'vue'

const message = ref('Hello')
const textRef = ref(null)

const updateText = async () => {
  message.value = 'World'
  
  // 此时 DOM 还没更新,打印出来的还是 'Hello'
  console.log('直接获取:', textRef.value.innerText) 
  
  // 等待 DOM 更新完成
  await nextTick()
  
  // 此时 DOM 已经更新,打印出来的是 'World'
  console.log('nextTick 后获取:', textRef.value.innerText)
}
</script>

<template>
  <div ref="textRef">{{ message }}</div>
  <button @click="updateText">修改文本</button>
</template>

nextTick 在处理需要依赖最新 DOM 状态的场景(如:列表新增数据后自动滚动到底部、弹窗打开后立刻让输入框获取焦点)时是必不可少的利器。


11. 组合式 API 与选项式 API 的区别

很多初学者在学习 Vue 时,都会遇到两个术语:

  • 组合式 API(Composition API)
  • 选项式 API(Options API)

10.1 选项式 API 长什么样

例如:

export default {
  data() {
    return {
      count: 0
    };
  },
  methods: {
    add() {
      this.count++;
    }
  }
};

它会把数据、方法、计算属性分别写在不同区域。

10.2 为什么现在更推荐组合式 API

因为当组件逻辑变复杂时,组合式 API 有几个明显优势:

  • 相关逻辑可以写在一起
  • 更利于抽离复用逻辑
  • 更适合 TypeScript 和中大型项目
  • 更符合 Vue 3 的主流生态

10.3 那选项式 API 还要不要学

要知道它的存在,因为:

  • 老项目里很常见
  • 很多旧教程还在使用
  • 面试和工作中仍然可能遇到

但如果你现在是从零开始建立新知识体系,建议主线先学:

Vue 3 + Composition API


12. 一个完整的 Vue 3 组合式 API 小例子

下面是一个简化后的计数器示例:

<script setup>
import { ref, computed } from "vue";

const count = ref(0);

// 计算属性会跟随 count 自动更新
const doubleCount = computed(() => count.value * 2);

function add() {
  count.value++;
}
</script>

<template>
  <div>
    <p>当前计数{{ count }}</p>
    <p>双倍结果{{ doubleCount }}</p>
    <button @click="add">+1</button>
  </div>
</template>

这个例子同时体现了:

  • ref 定义响应式数据
  • computed 计算派生值
  • 事件绑定 @click
  • 模板自动响应更新

13. 用 Vue.js devtools 看懂组件状态

当你开始写 Vue 页面后,很快就会遇到一个问题:数据明明变了,页面为什么没有按预期更新?

这时候,Vue.js devtools 就很有价值了。它是 Vue 官方生态里最常用的调试工具,可以帮助你直接查看当前页面上的组件树,以及每个组件里的 props、响应式状态和事件变化。

你可以把它理解成:专门给 Vue 组件准备的“可视化调试面板”

12.1 它最适合用来做什么

在日常开发中,Vue.js devtools 最常见的作用有这些:

  • 查看当前页面渲染出了哪些组件
  • 检查父组件传给子组件的 props 是否正确
  • 观察 refreactivecomputed 等状态的实时变化
  • 排查“数据变了但页面没更新”到底卡在哪一层

比如你写了前面的计数器例子,就可以一边点击按钮,一边在 devtools 中看 countdoubleCount 的变化,这比只靠 console.log() 更直观。

12.2 新手应该重点看哪几个地方

通常先看这两个面板就够用了:

  1. Components:查看组件层级、组件 props 和本地状态
  2. Timeline:观察组件更新、事件触发等运行过程

如果你在做中大型项目,后面接入 Pinia 后,它也能帮助你观察全局状态变化。

12.3 为什么它比单纯打印日志更高效

console.log() 更适合看某一个瞬时值,而 Vue.js devtools 更适合看组件关系、状态流动和响应式更新过程

对于 Vue 初学者来说,越早学会用它,越容易真正理解:

  • 数据是从哪里传下去的
  • 组件是在哪一层更新的
  • 当前看到的页面结果,到底由哪一份状态驱动

14. Vue 基础和后续实战怎么衔接

当你掌握了模板语法、响应式数据、生命周期和组件基础后,就可以把前面学过的网络请求能力接到 Vue 项目里。

例如:

  1. onMounted 中发起请求
  2. ref 保存接口返回的数据
  3. v-for 渲染列表
  4. v-if 控制加载和错误状态
  5. 用组件拆分页面结构

这就是 Vue 项目最常见的基础开发流程。


15. 小结

Vue 的核心不是“记住多少 API”,而是建立这样一套思维方式:

  1. 用数据驱动页面
  2. 用响应式系统自动更新视图
  3. 用组件拆分复杂界面
  4. 用组合式 API 组织业务逻辑

掌握这些之后,你就已经完成了从“会写页面”到“会搭应用结构”的关键跨越。