目录

Vue笔记

目录

Vue笔记

01 Vue概述

Vue 是一个 渐进式 JavaScript 框架 ,用于构建用户界面(UI)。它以简单、灵活和高性能著称,尤其适合初学者和需要快速开发的项目。


一、Vue 是什么?

渐进式框架 :可以从简单的功能开始(如渲染静态页面),逐步添加复杂功能(如路由、状态管理),无需一开始就掌握全部知识。

专注于视图层 :Vue 的核心库专注于 UI 渲染,但通过官方工具和插件(如 Vue Router、Pinia)支持构建完整的前端应用。

兼容性强 :支持直接嵌入现有项目,或作为独立应用开发。


二、Vue 的核心特性

  1. 响应式数据绑定

    数据变化时,视图自动更新。例如,修改 data 中的值,页面会实时反映变化。

    <div id="app">{{ message }}</div>
    <script>
      const app = Vue.createApp({
        data() {
          return { message: "Hello Vue!" };
        },
      }).mount("#app");
    </script>
  2. 组件化开发

    将 UI 拆分为独立、可复用的组件,每个组件包含自己的 HTML、CSS 和 JavaScript。

    <template>
      <button @click="count++">点击次数:{{ count }}</button>
    </template>
    <script>
    export default {
      data() {
        return { count: 0 };
      },
    };
    </script>
  3. 指令系统

    Vue 提供简洁的指令(Directives)来操作 DOM:

    v-bind :绑定属性(如 v-bind:href="url"

    v-model :表单输入双向绑定

    v-for :循环渲染列表

    v-if / v-show :条件渲染

  4. 虚拟 DOM

    Vue 通过虚拟 DOM 优化性能,仅更新变化的 DOM 部分,减少直接操作真实 DOM 的开销。


三、为什么选择 Vue?

  1. 易学易用

    语法简洁,中文文档完善,学习曲线平缓。

  2. 灵活性

    支持传统 HTML 开发,也支持结合现代工具链(如 Vite、Webpack)。

  3. 强大生态

    • 官方库:Vue Router(路由)、Pinia(状态管理)

    • 工具链:Vite(快速构建)、Vue Devtools(调试工具)

    • 社区活跃:丰富的第三方组件库(如 Element Plus、Vant)。


四、快速上手 Vue 3

  1. 通过 CDN 引入

    <script src="https://unpkg.com/vue@3"></script>
  2. 使用脚手架创建项目 (推荐正式开发)

    安装 Node.js 后,运行:

    npm create vue@latest
    # 或
    yarn create vue

    按提示选择需要的功能(路由、状态管理等)。

  3. 单文件组件(.vue 文件)

    一个组件包含 <template> , <script> , <style> 三部分:

    <template>
      <h1>{{ title }}</h1>
    </template>
    <script>
    export default {
      data() {
        return { title: "Vue 3 入门" };
      },
    };
    </script>
    <style scoped>
    h1 { color: blue; }
    </style>

五、学习资源

  1. 官方文档

    • 教程、示例、API 参考齐全,适合系统学习。

  2. 推荐工具和库

    • Vite:极速构建工具

    • Pinia:新一代状态管理

    • VueUse:常用工具函数集合


02 基础语法

以下是 Vue 的基本语法总结,涵盖 模板语法指令组件核心功能


一、模板语法

1. 插值
<!-- 文本插值 -->
<p>{{ message }}</p>

<!-- 原始 HTML(慎用,避免 XSS 攻击) -->
<p v-html="rawHtml"></p>

<!-- 动态属性 -->
<a :href="url">链接</a>
2. 指令 (Directives)

v-bind :动态绑定属性(可简写为 :

<img :src="imageUrl" :alt="description">

v-model :表单双向绑定

<input v-model="inputText" type="text">
<select v-model="selectedOption">
  <option value="A">选项A</option>
</select>

v-if / v-else-if / v-else :条件渲染

<div v-if="score >= 90">优秀</div>
<div v-else-if="score >= 60">及格</div>
<div v-else>不及格</div>

v-show :切换显示(通过 CSS display 控制)

<div v-show="isVisible">显示/隐藏内容</div>

v-for :列表渲染(必须加 :key

<ul>
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }} - {{ item.name }}
  </li>
</ul>

二、事件处理

<!-- 内联事件 -->
<button @click="counter += 1">点击+1</button>

<!-- 方法调用 -->
<button @click="handleClick">点击事件</button>

<!-- 事件修饰符 -->
<form @submit.prevent="onSubmit">阻止默认行为</form>
<a @click.stop="doSomething">阻止事件冒泡</a>
export default {
  methods: {
    handleClick(event) {
      console.log('点击事件', event.target);
    },
    onSubmit() {
      console.log('表单提交');
    }
  }
}

三、计算属性和侦听器

1. 计算属性 (computed)
export default {
  data() {
    return { firstName: '张', lastName: '三' }
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName;
    }
  }
}
2. 侦听器 (watch)
export default {
  data() {
    return { count: 0 }
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`计数从 ${oldVal} 变为 ${newVal}`);
    }
  }
}

四、组件基础

1. 定义组件
<!-- ChildComponent.vue -->
<template>
  <div>
    <h3>{{ title }}</h3>
    <button @click="sendMessage">传递消息</button>
  </div>
</template>

<script>
export default {
  props: ['title'],
  emits: ['message'],
  methods: {
    sendMessage() {
      this.$emit('message', '来自子组件的消息');
    }
  }
}
</script>
2. 使用组件
<template>
  <div>
    <ChildComponent 
      title="子组件标题" 
      @message="handleMessage"
    />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  methods: {
    handleMessage(msg) {
      console.log(msg); // 输出:来自子组件的消息
    }
  }
}
</script>

五、生命周期钩子

钩子函数触发时机
beforeCreate实例初始化前
created实例创建完成(可访问数据)
beforeMount挂载到 DOM 前
mounted挂载完成(可操作 DOM)
beforeUpdate数据更新前
updated数据更新后
beforeUnmount实例销毁前
unmounted实例销毁后

六、常见注意事项

  1. data 必须为函数

    组件中的 data 必须返回一个对象,避免数据共享:

    data() {
      return { count: 0 }
    }
  2. v-for 必须加 key

    :key 帮助 Vue 高效更新虚拟 DOM:

    <div v-for="item in list" :key="item.id">{{ item.text }}</div>
  3. 避免直接修改 props

    子组件应通过事件 ( $emit ) 通知父组件修改数据。


03 事件绑定

在 Vue 中,事件绑定是前端交互的核心功能之一。以下是 事件绑定 的完整指南,涵盖基础语法、修饰符、参数传递等核心内容:


一、基础事件绑定

1. 使用 v-on@ 简写
<!-- 点击事件 -->
<button v-on:click="handleClick">点击我</button>
<button @click="handleClick">简写形式</button>
2. 直接编写简单逻辑
<button @click="count += 1">计数:{{ count }}</button>

二、方法调用

1. 调用组件方法
<button @click="sayHello">打招呼</button>
export default {
  methods: {
    sayHello() {
      alert('Hello Vue!')
    }
  }
}
2. 传递参数
<button @click="saySomething('你好')">说中文</button>
<button @click="saySomething('Bonjour')">说法语</button>
export default {
  methods: {
    saySomething(message) {
      alert(message)
    }
  }
}

三、事件修饰符

Vue 提供事件修饰符来处理 DOM 事件的细节。

1. 常用修饰符
修饰符作用
.stop阻止事件冒泡
.prevent阻止默认行为
.capture使用事件捕获模式
.self仅当事件在元素自身触发时生效
.once事件只触发一次
.passive提升滚动性能
2. 使用示例
<!-- 阻止表单默认提交 -->
<form @submit.prevent="onSubmit">
  <button type="submit">提交</button>
</form>

<!-- 阻止点击事件冒泡 -->
<div @click="parentClick">
  <button @click.stop="childClick">子按钮</button>
</div>

<!-- 按键修饰符 -->
<input @keyup.enter="submitForm" placeholder="按回车提交">

四、事件对象

1. 获取原生事件对象
<button @click="handleEvent">获取事件对象</button>
export default {
  methods: {
    handleEvent(event) {
      console.log(event.target) // 获取触发元素
    }
  }
}
2. 同时传递参数和事件对象
<button @click="handleArgs('参数', $event)">传递参数和事件</button>
export default {
  methods: {
    handleArgs(arg, event) {
      console.log(arg, event.clientX)
    }
  }
}

五、动态事件名

通过变量动态绑定事件类型:

<button @[eventName]="handleDynamicEvent">动态事件</button>
export default {
  data() {
    return {
      eventName: 'click' // 可动态改为 'mouseover' 等
    }
  },
  methods: {
    handleDynamicEvent() {
      console.log('动态事件触发')
    }
  }
}

六、高级技巧

1. 多个修饰符链式调用
<a @click.stop.prevent="doSomething">同时阻止冒泡和默认行为</a>
2. 系统修饰键
<!-- Ctrl + 点击 -->
<div @click.ctrl="handleCtrlClick">Ctrl + 点击</div>

<!-- Alt + Enter -->
<input @keyup.alt.enter="clearInput">
3. 鼠标按键修饰符
<button @click.left="leftClick">左键</button>
<button @click.right="rightClick">右键</button>

04 双向数据绑定

一、什么是双向数据绑定?

核心概念 :数据变化自动更新视图,视图变化同步更新数据。

单向 vs 双向

• 单向绑定: { { data }}v-bind (数据 → 视图)

• 双向绑定: v-model (数据 ↔ 视图)

单向绑定 :像公告栏(数据变 → 页面变,但页面不能改数据)

<div>{{ news }}</div> <!-- 只能显示,不能修改 -->

双向绑定 :像可编辑的共享文档(双方都能改,实时同步)

<input v-model="docContent"> <!-- 既能显示,又能修改 -->

典型场景 :表单输入(如文本框、复选框、下拉框)


二、v-model 基础用法

1. 文本输入
<input type="text" v-model="message">
<p>输入内容:{{ message }}</p>
2. 多行文本
<textarea v-model="content"></textarea>
3. 复选框
<!-- 单个复选框(布尔值) -->
<input type="checkbox" v-model="isAgree"> 同意协议

<!-- 多个复选框(数组) -->
<input type="checkbox" value="vue" v-model="skills"> Vue
<input type="checkbox" value="react" v-model="skills"> React
4. 单选按钮
<input type="radio" value="male" v-model="gender"><input type="radio" value="female" v-model="gender">
5. 下拉框
<select v-model="selectedCity">
  <option value="beijing">北京</option>
  <option value="shanghai">上海</option>
</select>

三、v-model 原理

v-model 本质是语法糖,等价于:

<input 
  :value="message" 
  @input="message = $event.target.value"
>

四、修饰符

1. .lazy - 转为 change 事件更新
<input v-model.lazy="message"> <!-- 在 input 失焦时更新 -->
2. .number - 输入转为数字类型
<input v-model.number="age" type="number">
3. .trim - 自动去除首尾空格
<input v-model.trim="username">

五、高级用法

1. 绑定对象属性
<input v-model="formData.username">
<input v-model="formData.password">
data() {
  return {
    formData: {
      username: '',
      password: ''
    }
  }
}
2. 结合计算属性
export default {
  data() {
    return { rawInput: '' }
  },
  computed: {
    formattedInput: {
      get() {
        return this.rawInput.toUpperCase()
      },
      set(value) {
        this.rawInput = value.toLowerCase()
      }
    }
  }
}
<input v-model="formattedInput">

05 组件

1. 组件是什么?

类比 :组件就像是一个 自定义的 HTML 标签 ,比如你可以创建一个 <my-button> 标签,用来表示一个特定样式的按钮。

组成 :每个组件包含:

HTML 结构 (模板):决定组件长什么样。

JavaScript 逻辑 (脚本):控制组件的行为(比如点击按钮后做什么)。

CSS 样式 (样式):定义组件的外观(支持隔离作用域,避免样式冲突)。


2. 为什么要用组件?

复用性 :比如你写了一个“登录按钮”组件,可以在多个页面重复使用。

维护性 :把页面拆分成多个组件,修改时只需改对应的组件,不会影响其他部分。

协作性 :多人开发时,每个人负责不同的组件,最后拼装起来。


3. 创建一个最简单的组件

步骤 1:定义组件

在 Vue 中,组件通常写在 .vue 文件中(单文件组件),比如 MyButton.vue

<template>
  <!-- HTML 结构 -->
  <button class="my-btn" @click="handleClick">
    {{ buttonText }}
  </button>
</template>

<script>
export default {
  // 数据:控制按钮显示的文字
  data() {
    return {
      buttonText: "点我!"
    };
  },
  // 方法:点击按钮后的逻辑
  methods: {
    handleClick() {
      alert("按钮被点击了!");
    }
  }
};
</script>

<style scoped>  /* scoped 表示样式只在当前组件生效 */
.my-btn {
  background: blue;
  color: white;
  padding: 10px 20px;
}
</style>
步骤 2:在父组件中使用它

假设父组件是 App.vue ,你需要先导入 MyButton ,再像普通标签一样使用:

<template>
  <div>
    <h1>我的第一个组件</h1>
    <!-- 使用自定义的按钮组件 -->
    <MyButton />
    <MyButton />  <!-- 可以重复使用 -->
  </div>
</template>

<script>
import MyButton from './MyButton.vue';  // 导入组件

export default {
  components: { MyButton }  // 注册组件
};
</script>

4. 组件的关键特性

(1) 组件通信:父传子数据(Props)

父组件 通过 props 向子组件传递数据:

<!-- 父组件 App.vue -->
<template>
  <MyButton :text="parentText" />  <!-- 传递 text 属性 -->
</template>
<script>
export default {
  data() {
    return { parentText: "父组件传来的文字" };
  }
};
</script>

<!-- 子组件 MyButton.vue -->
<template>
  <button>{{ text }}</button>  <!-- 使用 props -->
</template>
<script>
export default {
  props: ['text']  // 声明接收的 props
};
</script>
(2) 组件通信:子传父事件($emit)

子组件 通过 $emit 触发事件, 父组件 通过 @事件名 监听:

<!-- 子组件 MyButton.vue -->
<button @click="$emit('btn-click', '传递的数据')">按钮</button>

<!-- 父组件 App.vue -->
<template>
  <MyButton @btn-click="handleClick" />
</template>
<script>
export default {
  methods: {
    handleClick(data) {
      console.log("子组件传来的数据:", data);
    }
  }
};
</script>
(3) 插槽(Slot)

• 让父组件可以向子组件插入内容:

<!-- 子组件 MyCard.vue -->
<div class="card">
  <slot>默认内容(如果父组件不传内容,显示这里)</slot>
</div>

<!-- 父组件 App.vue -->
<MyCard>
  <h2>这是插入的内容</h2>
  <p>会替换子组件的 slot 标签</p>
</MyCard>

5. 组件的注册方式

全局注册 :在任何地方都能使用(适合通用组件,如按钮、输入框):

// main.js
import MyButton from './MyButton.vue';
Vue.component('MyButton', MyButton);  // 全局注册

局部注册 :只在当前组件内使用(适合特定页面组件):

<!-- App.vue -->
<script>
import MyButton from './MyButton.vue';

export default {
  components: { MyButton }  // 局部注册
};
</script>

6. 常见问题

组件命名 :推荐用大写字母开头(如 MyComponent ),使用时可用 <my-component>

数据隔离 :每个组件的数据( data )是独立的,互不影响。

样式冲突 :用 <style scoped> 避免组件样式污染全局。


总结

组件是 Vue 的核心 ,像积木一样拼装页面。

关键三步 :创建组件 → 注册组件 → 使用组件。

通信方式 :父传子用 props ,子传父用 $emit

06 父子组件通信

在 Vue 中, 父子组件通信 是最常见的场景,比如父组件传递数据给子组件(如参数),或者子组件通知父组件发生了某些事件(如按钮点击)。


一、父传子:通过 props

1. 核心机制

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

子组件 需要声明接收的 props ,类似接收快递包裹。

2. 代码示例
<!-- 父组件 Parent.vue -->
<template>
  <div>
    <!-- 传递静态值或动态绑定 -->
    <ChildComponent :message="parentMessage" :count="100" />
  </div>
</template>

<script>
import ChildComponent from './Child.vue';

export default {
  components: { ChildComponent },
  data() {
    return {
      parentMessage: "Hello from Parent!"
    };
  }
};
</script>
<!-- 子组件 Child.vue -->
<template>
  <div>
    <!-- 使用 props -->
    <p>父组件消息:{{ message }}</p>
    <p>父组件给的数字:{{ count }}</p>
  </div>
</template>

<script>
export default {
  // 声明接收的 props(推荐用对象形式指定类型和默认值)
  props: {
    message: {
      type: String,    // 类型校验
      required: true   // 必传
    },
    count: {
      type: Number,
      default: 0       // 默认值
    }
  }
};
</script>
3. 注意事项

单向数据流 :子组件不能直接修改 props (否则会警告),若需要修改,应在子组件中用 datacomputed 接收。

动态传递 :用 v-bind (或简写 : )传递动态数据,如 :count="parentCount"


二、子传父:通过 $emit

1. 核心机制

子组件 通过 $emit 触发自定义事件 ,类似打电话通知父组件。

父组件 通过 v-on (或简写 @监听事件 并处理。

2. 代码示例
<!-- 子组件 Child.vue -->
<template>
  <button @click="handleClick">点我通知父组件</button>
</template>

<script>
export default {
  methods: {
    handleClick() {
      // 触发自定义事件,并传递数据
      this.$emit('child-click', { data: "子组件的数据" });
    }
  }
};
</script>
<!-- 父组件 Parent.vue -->
<template>
  <div>
    <!-- 监听子组件触发的事件 -->
    <ChildComponent @child-click="handleChildClick" />
  </div>
</template>

<script>
import ChildComponent from './Child.vue';

export default {
  components: { ChildComponent },
  methods: {
    // 接收子组件传递的数据
    handleChildClick(payload) {
      console.log("收到子组件的消息:", payload.data); // 输出:收到子组件的消息:子组件的数据
    }
  }
};
</script>
3. 注意事项

事件命名 :推荐使用 kebab-case (如 child-click ),因为 HTML 不区分大小写。

参数传递$emit 的第二个参数可以是任意类型数据(如对象、数组)。


三、简化写法: v-model.sync

1. v-model (双向绑定简化版)

• 适用于表单类组件,如自定义输入框。

本质 :是 :value + @input 的语法糖。

<!-- 父组件 -->
<ChildComponent v-model="parentValue" />

<!-- 等价于 -->
<ChildComponent :value="parentValue" @input="parentValue = $event" />
<!-- 子组件 -->
<template>
  <input :value="value" @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
  props: ['value']  // 必须接收名为 value 的 prop
};
</script>
2. .sync 修饰符

• 用于简化父子组件间的“双向绑定”(Vue 2.3+ 支持)。

本质 :是 :propName + @update:propName 的语法糖。

<!-- 父组件 -->
<ChildComponent :title.sync="parentTitle" />

<!-- 等价于 -->
<ChildComponent :title="parentTitle" @update:title="parentTitle = $event" />
<!-- 子组件 -->
<button @click="$emit('update:title', '新标题')">修改标题</button>

四、父子通信示意图

父组件 → 子组件:通过 props(单向)
子组件 → 父组件:通过 $emit 事件(触发父组件方法)

五、常见问题

1. 为什么子组件不能直接修改 props?

• 为了遵循 单向数据流 ,保证数据来源清晰可追踪。若需要修改,应该:

子组件内部data 接收 props 的初始值:

javascript data() { return { localData: this.parentData }; }

• 或通过 $emit 让父组件修改原始数据。

2. 如何传递复杂数据(如对象、数组)?

• 直接传递即可,Vue 会自动检测对象内部变化(需注意引用类型的特性)。


总结

父传子 :用 props ,子组件声明接收。

子传父 :用 $emit ,父组件监听事件。

简化双向绑定v-model 用于表单, .sync 用于其他场景。

• 保持数据流的清晰性,是 Vue 组件化开发的关键!

07 axios

Axios 是一个基于 Promise 的 HTTP 客户端库,专门用于浏览器和 Node.js 中发送 HTTP 请求。它简单易用,支持异步请求、拦截请求和响应、自动转换 JSON 数据等功能,是前端开发中与后端 API 交互的常用工具。


一、Axios 的核心优势

  1. 简单语法 :链式调用,类似 fetch 但更简洁。
  2. 自动转换数据 :自动将响应数据解析为 JSON,无需手动处理。
  3. 拦截器 :可以在请求发送前或响应返回后统一处理逻辑(如添加 Token、处理错误)。
  4. 取消请求 :支持取消未完成的请求(比如用户频繁点击时)。

二、安装 Axios

1. 通过 npm 安装(推荐):
npm install axios
2. 通过 CDN 引入:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

三、基础用法示例

1. 发送 GET 请求
// 语法:axios.get(url, [配置参数])
axios.get('https://api.example.com/data')
  .then(response => {
    console.log(response.data); // 响应数据
  })
  .catch(error => {
    console.error('请求失败:', error);
  });
2. 发送 POST 请求
// 语法:axios.post(url, 请求体数据, [配置参数])
axios.post('https://api.example.com/users', {
  name: 'John',
  age: 30
})
  .then(response => {
    console.log('创建成功:', response.data);
  })
  .catch(error => {
    console.error('创建失败:', error);
  });
3. 发送其他请求(PUT、DELETE)
// PUT 请求(更新数据)
axios.put('https://api.example.com/users/1', {
  name: 'Alice'
});

// DELETE 请求(删除数据)
axios.delete('https://api.example.com/users/1');

四、配置参数详解

Axios 支持全局配置和请求级别的配置:

1. 全局默认配置
axios.defaults.baseURL = 'https://api.example.com'; // 统一设置根路径
axios.defaults.headers.common['Authorization'] = 'Bearer token123'; // 统一添加请求头
2. 单个请求配置
axios.get('/data', {
  params: { id: 1 },          // URL 参数(自动转为 ?id=1)
  headers: { 'X-Custom-Header': 'value' }, // 自定义请求头
  timeout: 5000               // 超时时间(毫秒)
});

五、错误处理

Axios 的错误分为两种:

  1. 请求成功,但 HTTP 状态码非 2xx (如 404、500)。
  2. 请求本身失败 (如网络错误、跨域问题)。
axios.get('/data')
  .then(response => {
    // 处理成功响应
  })
  .catch(error => {
    if (error.response) {
      // 请求已发送,服务器返回了错误状态码(如 404)
      console.log('状态码:', error.response.status);
      console.log('错误数据:', error.response.data);
    } else if (error.request) {
      // 请求已发送,但未收到响应(如网络断开)
      console.log('未收到响应:', error.request);
    } else {
      // 其他错误(如配置错误)
      console.log('错误信息:', error.message);
    }
  });

六、拦截器(高级用法)

拦截器可以在请求或响应被处理前统一处理逻辑:

1. 请求拦截器
axios.interceptors.request.use(config => {
  // 在发送请求前添加 Token
  config.headers.Authorization = 'Bearer token123';
  return config;
}, error => {
  return Promise.reject(error);
});
2. 响应拦截器
axios.interceptors.response.use(response => {
  // 对响应数据统一处理(如提取 data 字段)
  return response.data;
}, error => {
  // 统一处理错误
  alert('请求失败,请重试!');
  return Promise.reject(error);
});

七、在 Vue 项目中使用 Axios

1. 在组件中直接使用
<script>
import axios from 'axios';

export default {
  data() {
    return {
      users: []
    };
  },
  created() {
    axios.get('https://api.example.com/users')
      .then(response => {
        this.users = response.data;
      })
      .catch(error => {
        console.error(error);
      });
  }
};
</script>
2. 全局挂载(推荐)

main.js 中全局引入:

import axios from 'axios';

// 全局配置
axios.defaults.baseURL = 'https://api.example.com';

// 挂载到 Vue 原型(Vue2 用法)
Vue.prototype.$axios = axios;

// 在组件中直接使用
this.$axios.get('/users').then(...);

八、常见问题

1. 跨域问题(CORS)

• 跨域是浏览器的安全限制,需后端配置 Access-Control-Allow-Origin

• 开发环境可通过代理解决(如 Vue CLI 的 proxy 配置)。

2. 处理文件上传
const formData = new FormData();
formData.append('file', file); // file 是 input[type=file] 的文件对象

axios.post('/upload', formData, {
  headers: { 'Content-Type': 'multipart/form-data' }
});

总结

Axios 是前端与后端交互的利器,掌握它的基本用法和配置,能让你高效处理 HTTP 请求。初学时重点关注:

  1. GET/POST 请求的发送
  2. 错误处理 (避免程序崩溃)。
  3. 拦截器的简单使用 (如添加 Token)。

08 axios和ajax的区别


Axios 和 Ajax 的核心区别

Axios 和 Ajax 都用于前端与服务器的异步数据交互,但它们在实现方式、功能和使用体验上有显著差异:


1. 定义与背景

Ajax (Asynchronous JavaScript and XML)

概念 :一种技术统称,基于 XMLHttpRequest 对象实现异步请求,无需刷新页面即可更新数据。

诞生时间 :2005 年(历史悠久,兼容性广泛)。

Axios

概念 :一个基于 Promise 的 第三方 HTTP 客户端库 ,封装了 XMLHttpRequest ,提供更简洁的 API。

诞生时间 :2014 年后(现代开发常用工具)。


2. 核心实现方式

特性AjaxAxios
底层依赖原生 XMLHttpRequest 对象封装了 XMLHttpRequest
语法风格回调函数(Callback Hell 风险)Promise/Async Await(链式调用,代码更清晰)
代码复杂度代码冗长,需手动处理更多细节开箱即用,API 简洁
JSON 数据处理需手动解析 JSON.parse()自动转换 JSON 数据

3. 功能特性对比

Ajax 的局限性

• 需要自行处理跨浏览器兼容性(如旧版 IE)。

• 需手动设置请求头、转换数据格式。

• 错误处理分散,易产生回调地狱。

Axios 的优势

拦截器 :统一处理请求和响应(如添加 Token、日志监控)。

取消请求 :通过 CancelToken 中止未完成的请求。

并发处理 :支持 axios.all() 同时发送多个请求。

浏览器兼容 :自动适配新旧浏览器(包括 IE11)。


4. 代码示例

原生 Ajax 实现 GET 请求
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    const data = JSON.parse(xhr.responseText); // 手动解析 JSON
    console.log(data);
  } else if (xhr.status !== 200) {
    console.error('请求失败:', xhr.status);
  }
};
xhr.send();
Axios 实现 GET 请求
axios.get('https://api.example.com/data')
  .then(response => {
    console.log(response.data); // 自动解析 JSON
  })
  .catch(error => {
    console.error('请求失败:', error);
  });

5. 错误处理差异

Ajax :需在回调中检查 statusreadyState ,错误处理分散。

Axios :通过 .catch() 集中捕获错误,且能区分请求错误和响应错误:

axios.get('/data')
  .catch(error => {
    if (error.response) {
      console.log('服务器返回错误状态码:', error.response.status);
    } else if (error.request) {
      console.log('请求已发送但无响应:', error.request);
    } else {
      console.log('配置错误:', error.message);
    }
  });

09 计算属性

一、计算属性是什么?

定义 :计算属性是 基于响应式依赖动态计算的值 ,它会根据依赖的数据自动更新结果。

核心特点

缓存机制 :依赖的数据未变化时,直接返回缓存值,避免重复计算。

声明式逻辑 :将复杂逻辑封装为属性,模板中可像普通数据一样使用。

适用场景

• 需要根据已有数据动态计算的场景(如:购物车总价、数据过滤)。

• 模板中需要简化复杂表达式时。


二、计算属性 vs 方法

对比维度计算属性(Computed)方法(Methods)
触发时机依赖数据变化时重新计算每次调用时重新执行
缓存有缓存(依赖不变时复用结果)无缓存(每次调用重新计算)
模板中使用方式作为属性使用(无需括号)作为方法调用(需括号)
性能适合频繁计算的复杂逻辑(高效)适合不频繁或需要参数的场景

三、Vue.js 中的计算属性示例

1. 基础用法
export default {
  data() {
    return {
      price: 100,
      quantity: 5
    };
  },
  computed: {
    // 计算总价(依赖 price 和 quantity)
    total() {
      return this.price * this.quantity;
    }
  }
};

在模板中直接使用:

<p>总价:{{ total }}</p>  <!-- 输出:500 -->
2. 复杂逻辑封装
computed: {
  // 过滤出价格大于 50 的商品
  filteredProducts() {
    return this.products.filter(product => product.price > 50);
  }
}

四、计算属性的缓存机制

缓存生效条件 :只有当依赖的响应式数据(如 pricequantity )变化时,才会重新计算。

手动跳过缓存 :若需每次都重新计算,应改用 方法(Methods)


五、计算属性的 Setter(高级用法)

计算属性默认只有 getter ,但也可手动定义 setter

computed: {
  fullName: {
    get() {
      return this.firstName + ' ' + this.lastName;
    },
    set(newValue) {
      // 当修改 fullName 时,拆分并更新 firstName 和 lastName
      const names = newValue.split(' ');
      this.firstName = names[0];
      this.lastName = names[1] || '';
    }
  }
}

使用示例:

this.fullName = 'John Doe';  // 自动触发 setter

六、代码实战练习

// 示例:根据用户输入过滤列表
data() {
  return {
    searchText: '',
    products: [
      { name: 'Apple', price: 10 },
      { name: 'Banana', price: 5 },
      { name: 'Orange', price: 8 }
    ]
  };
},
computed: {
  filteredProducts() {
    return this.products.filter(product => 
      product.name.toLowerCase().includes(this.searchText.toLowerCase())
    );
  }
}

模板中使用:

<input v-model="searchText" placeholder="搜索商品">
<ul>
  <li v-for="product in filteredProducts" :key="product.name">
    {{ product.name }} - ¥{{ product.price }}
  </li>
</ul>

10 监听器


侦听器(Watcher) 是前端框架(如 Vue.js)中用于监听数据变化并执行特定逻辑的功能,尤其适合处理异步操作或复杂数据变更场景。


一、侦听器的核心概念

作用 :监听某个数据的变化,并在变化时触发回调函数。

适用场景

• 数据变化后需执行异步操作(如 API 请求)。

• 数据变化后需执行复杂逻辑(如多个步骤处理)。

• 需要观察数据变化的具体过程(如旧值和新值对比)。


二、Vue.js 中的侦听器示例

1. 基础用法
export default {
  data() {
    return {
      count: 0,
      userInput: ''
    };
  },
  watch: {
    // 监听 count 的变化
    count(newVal, oldVal) {
      console.log(`count 从 ${oldVal} 变为 ${newVal}`);
    },
    // 监听用户输入的变化(用于搜索联想)
    userInput(newVal) {
      this.fetchSearchResults(newVal); // 调用异步方法
    }
  },
  methods: {
    fetchSearchResults(query) {
      // 模拟 API 请求
      setTimeout(() => {
        console.log('搜索:', query);
      }, 300);
    }
  }
};

三、侦听器的配置选项

通过对象语法,可配置 handler 函数及监听策略:

watch: {
  dataToWatch: {
    handler(newVal, oldVal) { /* 逻辑 */ },
    deep: true,      // 深度监听(对象内部变化)
    immediate: true  // 立即触发(初始化时执行一次)
  }
}
1. 深度监听(deep)

用于监听对象或数组内部属性的变化:

data() {
  return {
    user: { name: 'Alice', age: 25 }
  };
},
watch: {
  user: {
    handler(newVal) {
      console.log('用户信息变化:', newVal);
    },
    deep: true // 监听 user 对象所有属性的变化
  }
}
2. 立即执行(immediate)

初始化时立即触发一次侦听逻辑:

watch: {
  count: {
    handler(newVal) {
      console.log('初始值:', newVal); // 初始化时打印 count 的初始值
    },
    immediate: true
  }
}

四、经典使用场景

1. 表单输入实时校验
watch: {
  email(newVal) {
    if (!this.validateEmail(newVal)) {
      this.error = '邮箱格式错误';
    } else {
      this.error = '';
    }
  }
}
2. 路由参数变化重新加载数据
watch: {
  '$route.params.id'(newId) {
    this.fetchData(newId); // 路由变化时重新请求数据
  }
}
3. 监听对象属性变化(需深度监听)
watch: {
  'formData.username': {
    handler(newVal) {
      console.log('用户名变化:', newVal);
    }
  }
}

11 计算属性和侦听器的区别


计算属性(Computed Properties)与侦听器(Watchers)的区别

对比维度计算属性(Computed)侦听器(Watchers)
核心目的动态计算并返回新值 ,用于模板展示或依赖其他数据的场景。响应数据变化 ,执行副作用(如异步操作、复杂逻辑)。
缓存机制有缓存 :依赖数据未变化时直接返回缓存结果,避免重复计算。无缓存 :每次数据变化均触发回调。
语法定义computed 选项中定义,返回计算结果。watch 选项中定义,指定监听的数据及回调函数。
依赖追踪自动追踪依赖 :根据函数内使用的响应式数据自动更新。手动指定依赖 :需明确监听特定数据或路径。
返回值必须返回一个值,供模板或逻辑使用。无需返回值,侧重执行操作。
适用场景1. 模板中需要动态计算的表达式(如拼接字符串、过滤列表)。 2. 依赖多个数据的同步计算(如购物车总价)。1. 数据变化后需执行异步任务(如API请求)。 2. 需要旧值和新值对比的场景(如记录变化历史)。 3. 监听非响应式数据或复杂对象内部变化(需 deep: true )。
性能优化高效 :依赖不变时直接使用缓存,适合频繁计算的场景。需谨慎使用 :频繁变化的数据可能引发多次回调,需防抖或节流。

核心区别解析

  1. 设计目的不同

    计算属性 :核心是 派生新数据 。例如,将 firstNamelastName 组合成 fullName

    侦听器 :核心是 观察数据变化后的动作 。例如,用户输入搜索关键词时自动发起API请求。

  2. 响应方式不同

    计算属性 :自动追踪函数内部依赖的响应式数据,依赖变化时重新计算。

    侦听器 :需手动指定监听的目标数据,且支持深度监听( deep: true )或立即触发( immediate: true )。

  3. 缓存机制的影响

    计算属性 :多次访问同一计算属性时,只要依赖未变化,直接返回缓存值,避免重复计算。

    侦听器 :每次数据变化均触发回调,适合需要实时响应的场景(如输入框校验)。


何时使用计算属性?

场景1 :需要基于现有数据动态生成新值。

computed: {
  totalPrice() {
    return this.items.reduce((sum, item) => sum + item.price, 0);
  }
}

场景2 :模板中有复杂表达式需简化。

<!-- 模板中直接使用计算属性 -->
<p>{{ fullName }}</p>

何时使用侦听器?

场景1 :数据变化后需执行异步操作。

watch: {
  searchText(newVal) {
    this.debouncedFetchResults(newVal); // 防抖后请求
  }
}

场景2 :需要深度监听对象或数组内部变化。

watch: {
  user: {
    handler(newVal) {
      console.log('用户信息变化:', newVal);
    },
    deep: true // 监听 user 对象所有属性
  }
}

总结

计算属性 :用于 同步计算派生数据 ,优先使用,提升代码可读性和性能。

侦听器 :用于 数据变化后的副作用操作 (如异步、DOM操作),需谨慎避免滥用。

12 插槽

插槽是什么?

想象你有一个 手机壳 ,壳子上预留了摄像头和按钮的孔位。

插槽 就像这些孔位——壳子(子组件)定义好结构,你(父组件)可以按需要插入不同的摄像头模组或按钮(自定义内容)。


为什么需要插槽?

插槽 就是解决这个问题的: 壳子固定,内容随便换


插槽的三种用法

1. 默认插槽(万能孔)

子组件 :预留一个“万能孔”,不指定内容时显示默认值。

<!-- 子组件:Card.vue -->
<div class="card">
  <slot>默认提示文字</slot> <!-- 这里是插槽位置 -->
</div>

父组件 :往孔里塞任意内容(替换默认文字)。

<Card>
  <img src="cat.jpg"> <!-- 插入图片 -->
</Card>

2. 具名插槽(多个孔,对号入座)

子组件 :定义多个带名字的孔(比如头部、内容、底部)。

<!-- 子组件:Layout.vue -->
<div class="layout">
  <slot name="header"></slot> <!-- 头部孔 -->
  <slot name="content"></slot> <!-- 内容孔 -->
  <slot name="footer"></slot> <!-- 底部孔 -->
</div>

父组件 :把内容精准插入对应孔。

<Layout>
  <template #header><h1>我是标题</h1></template>
  <template #content><p>我是正文...</p></template>
  <template #footer><button>提交</button></template>
</Layout>

3. 作用域插槽(带数据的孔)

子组件 :往孔里传递数据,父组件决定怎么用。

<!-- 子组件:DataList.vue -->
<ul>
  <li v-for="item in items">
    <slot :item="item">默认 {{ item.name }}</slot>
  </li>
</ul>

父组件 :拿到数据,自定义显示方式。

<DataList :items="products">
  <template #default="{ item }"> <!-- 解构数据 -->
    {{ item.name }} - ¥{{ item.price }}
  </template>
</DataList>

插槽 vs Props

Props :传递数据(比如数字、字符串),子组件自己决定怎么显示。

插槽 :传递内容(比如HTML、组件),父组件完全控制显示方式。


总结:什么时候用插槽?

就像 玩具的电池槽 :玩具(子组件)提供槽位和电力,你(父组件)决定用 5 号电池还是充电电池!

13 自定义事件内容分发

自定义事件的内容分发是一种在前端框架(如 Vue.js、React 等)中实现组件间通信的常见模式,尤其在父子组件或跨组件数据传递时非常有用。其核心思想是 子组件通过触发自定义事件,将数据或行为传递给父组件或其他监听者 ,从而实现解耦和复用。


核心概念

  1. 自定义事件

    子组件可以定义并触发自己的事件(例如 updatesubmit ),父组件监听这些事件并执行对应的逻辑。

  2. 内容分发

    通常指通过插槽(Slot)将父组件的内容传递到子组件的特定位置,但结合自定义事件后,可以实现更灵活的数据交互。


步骤 1: 子组件定义并触发事件

子组件通过 $emit 方法触发自定义事件,并向父组件传递数据。

<!-- 子组件 ChildComponent.vue -->
<template>
  <div>
    <!-- 触发自定义事件 -->
    <button @click="notifyParent">向父组件发送数据</button>
  </div>
</template>

<script>
export default {
  methods: {
    notifyParent() {
      // 触发名为 'custom-event' 的事件,并传递数据
      this.$emit('custom-event', { data: '来自子组件的数据' });
    }
  }
}
</script>

步骤 2: 父组件监听并处理事件

父组件通过 v-on (或 @ 简写)监听子组件触发的自定义事件,并绑定处理函数。

<!-- 父组件 ParentComponent.vue -->
<template>
  <div>
    <!-- 监听子组件的自定义事件 -->
    <child-component @custom-event="handleCustomEvent" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  methods: {
    // 处理子组件传递的数据
    handleCustomEvent(payload) {
      console.log('接收到子组件数据:', payload.data); // 输出:来自子组件的数据
    }
  }
}
</script>

步骤 3: 结合插槽实现内容分发

通过插槽(Slot)将父组件的内容分发到子组件,并利用作用域插槽传递子组件的数据。

子组件(定义插槽并传递数据)
<!-- 子组件 SlotComponent.vue -->
<template>
  <div>
    <!-- 定义作用域插槽,暴露数据给父组件 -->
    <slot :item="item" :onClick="handleClick"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return { item: { id: 1, name: '示例数据' } };
  },
  methods: {
    handleClick() {
      // 触发自定义事件
      this.$emit('item-clicked', this.item);
    }
  }
}
</script>
父组件(使用插槽并绑定事件)
<!-- 父组件 ParentComponent.vue -->
<template>
  <slot-component>
    <!-- 接收子组件传递的作用域数据和方法 -->
    <template v-slot:default="slotProps">
      <button @click="slotProps.onClick">
        {{ slotProps.item.name }}
      </button>
    </template>
  </slot-component>
</template>

<script>
import SlotComponent from './SlotComponent.vue';

export default {
  components: { SlotComponent },
  methods: {
    // 监听子组件触发的自定义事件
    handleItemClicked(item) {
      console.log('点击的项:', item.name); // 输出:示例数据
    }
  }
}
</script>

步骤 4: 跨组件通信(非父子)

对于非父子组件,可通过 全局事件总线 或状态管理工具(如 Vuex)实现通信。

事件总线示例
// 创建事件总线(Event Bus)
// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();

// 组件 A(触发事件)
EventBus.$emit('global-event', { data: '跨组件数据' });

// 组件 B(监听事件)
EventBus.$on('global-event', (payload) => {
  console.log(payload.data); // 输出:跨组件数据
});

关键步骤总结

  1. 子组件触发事件

    使用 this.$emit('事件名', 数据) 发送数据。

  2. 父组件监听事件

    通过 @事件名="处理方法" 接收并处理数据。

  3. 插槽分发内容

    结合作用域插槽( v-slot )传递子组件数据和方法。

  4. 跨组件通信

    使用全局事件总线或状态管理工具。


注意事项

  1. 事件命名规范

    使用 kebab-case (如 update-data ),避免与原生事件(如 click )冲突。

  2. 单向数据流

    子组件不要直接修改父组件传递的 props ,应通过事件通知父组件修改。

  3. 解耦设计

    子组件只负责触发事件,具体逻辑由父组件或其他监听者处理。


14 vue-cli

Vue CLI 是 Vue.js 的 ,用于快速搭建 Vue 项目、管理项目配置和集成开发工具链。


一、Vue CLI 的核心功能

  1. 标准化项目模板

    提供预配置的 Webpack、Babel、ESLint 等工具链,开箱即用。

  2. 图形化项目管理

    支持通过 vue ui 命令启动可视化界面管理项目。

  3. 插件系统

    可扩展功能(如添加 TypeScript、Vuex、Router 等)。

  4. 环境变量管理

    支持开发、测试、生产环境的变量配置。


二、安装与使用步骤

1. 安装 Vue CLI
# 全局安装(需要 Node.js >= 8.9)
npm install -g @vue/cli
# 或使用 yarn
yarn global add @vue/cli

# 验证安装
vue --version

2. 创建新项目
# 交互式创建项目
vue create my-project

# 选择预设(默认或手动配置)
? Please pick a preset:
  Default ([Vue 2] babel, eslint)
  Default (Vue 3) ([Vue 3] babel, eslint)
  Manually select features  # 手动选择特性(推荐)

3. 手动配置选项(常见选择)

Babel :ES6+ 语法转换

TypeScript :支持 TS

Router :Vue 路由

Vuex :状态管理

CSS Pre-processors :Sass/Less

Linter/Formatter :代码规范检查

Unit Testing :单元测试


4. 启动开发服务器
cd my-project
npm run serve
# 访问 http://localhost:8080

三、项目目录结构

my-project/
├── node_modules/     # 依赖包
├── public/           # 静态资源(直接复制到输出目录)
│   ├── index.html    # 主 HTML 文件
│   └── favicon.ico
├── src/              # 源码目录
│   ├── assets/       # 图片、字体等资源
│   ├── components/   # 组件
│   ├── views/        # 页面级组件
│   ├── router/       # 路由配置
│   ├── store/        # Vuex 状态管理
│   ├── App.vue       # 根组件
│   └── main.js       # 入口文件
├── .eslintrc.js      # ESLint 配置
├── babel.config.js   # Babel 配置
└── package.json      # 项目依赖和脚本

四、常用命令

命令作用
npm run serve启动开发服务器
npm run build打包生产环境代码(输出到 /dist
npm run lint运行 ESLint 代码检查
npm run test:unit运行单元测试
vue add plugin-name添加插件(如 vue add router

五、Vue CLI 插件系统

1. 添加插件
# 添加 Vue Router
vue add router

# 添加 Vuex
vue add vuex

# 添加 UI 库(如 Element UI)
vue add element-plus

六、环境变量配置

  1. 创建环境文件

    .env.development :开发环境变量

    .env.production :生产环境变量

  2. 定义变量(以 VUE_APP_ 开头)

    # .env.development
    VUE_APP_API_URL = http://localhost:3000/api
  3. 代码中使用

    // 通过 process.env 访问
    axios.get(process.env.VUE_APP_API_URL);

七、图形化管理界面(Vue UI)

# 启动可视化界面
vue ui

功能 :创建项目、安装依赖、运行任务、配置插件等。

优势 :适合不熟悉命令行的开发者。


八、Vue CLI vs Vite

特性Vue CLIVite
构建工具WebpackRollup(开发时用 ESBuild)
启动速度较慢极快(基于原生 ESM)
适用场景复杂项目、兼容性要求高现代浏览器、快速开发
配置复杂度

注意 :Vue 3 官方推荐新项目使用 Vite,但 Vue CLI 仍广泛用于企业级项目。


九、升级 Vue CLI

# 更新全局 CLI
npm update -g @vue/cli

# 更新项目本地依赖
npm update @vue/cli-service

十、总结

适用场景 :需要标准化配置、企业级项目、兼容旧浏览器。

优势 :生态成熟、插件丰富、配置可控。

替代方案 :Vite(适合追求速度的现代项目)。

通过 Vue CLI,开发者可以专注于业务逻辑,而无需手动配置复杂的构建工具链。

15 Webpack

学习 Webpack 是现代前端开发的重要技能,它是一款强大的 模块打包工具 ,能将前端资源(JS、CSS、图片等)转换为优化的静态文件。

一、Webpack 核心概念

  1. 入口(Entry)

    定义打包的起点文件(默认为 ./src/index.js )。

  2. 出口(Output)

    指定打包后的文件输出位置(如 ./dist )。

  3. 加载器(Loader)

    处理非 JS 文件(如 CSS、图片、字体等),将其转换为模块。

  4. 插件(Plugin)

    扩展功能(如代码压缩、HTML 生成、环境变量注入)。

  5. 模式(Mode)

    区分开发和生产环境( developmentproduction )。


二、快速上手

1. 初始化项目
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
2. 基础目录结构
webpack-demo/
├── src/
│   ├── index.js      # 入口文件
│   └── utils.js      # 工具模块
├── public/           # 静态资源
│   └── index.html    # HTML 模板
├── webpack.config.js # Webpack 配置文件
└── package.json
3. 基础配置
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development', // 开发模式
  entry: './src/index.js', // 入口文件
  output: {
    filename: 'bundle.js', // 输出文件名
    path: path.resolve(__dirname, 'dist'), // 输出路径
  },
  plugins: [
    // 自动生成 HTML 并注入打包后的 JS
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ]
};
4. 运行打包
npx webpack --config webpack.config.js
# 或添加到 package.json 脚本
"scripts": {
  "build": "webpack"
}

三、核心功能详解

1. 处理 CSS 和 SCSS

安装 Loader:

npm install style-loader css-loader sass-loader sass --save-dev

配置:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /css$/i,
        use: ['style-loader', 'css-loader'], // 从右向左执行
      },
      {
        test: /scss$/i,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      }
    ]
  }
};

2. 处理图片和字体

安装 Loader:

npm install file-loader url-loader --save-dev

配置:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /(png|jpg|gif|svg)$/i,
        type: 'asset/resource', // Webpack 5+ 推荐方式
        // 或使用 url-loader(处理小文件转 Base64)
        // use: {
        //   loader: 'url-loader',
        //   options: { limit: 8192 }
        // }
      },
      {
        test: /(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      }
    ]
  }
};

3. 使用 Babel 转译 JS

安装依赖:

npm install babel-loader @babel/core @babel/preset-env --save-dev

配置:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};

4. 开发环境优化
// webpack.config.js
module.exports = {
  mode: 'development',
  devtool: 'inline-source-map', // 生成 Source Map
  devServer: {
    static: './dist', // 开发服务器根目录
    hot: true, // 热更新
    port: 3000,
  },
};

启动开发服务器:

npm install webpack-dev-server --save-dev
# package.json 添加脚本
"scripts": {
  "start": "webpack serve --open"
}

四、常用插件

  1. HtmlWebpackPlugin

    自动生成 HTML 并注入打包后的 JS/CSS。

  2. MiniCssExtractPlugin

    将 CSS 提取为独立文件(生产环境优化)。

  3. CleanWebpackPlugin

    每次打包前清空输出目录。

  4. DefinePlugin

    定义全局常量(如 process.env.NODE_ENV )。

安装及配置示例:

// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
    new webpack.DefinePlugin({
      'process.env.API_URL': JSON.stringify('https://api.example.com'),
    }),
  ],
};

五、生产环境优化

  1. 代码压缩

    • JS 压缩: TerserWebpackPlugin (Webpack 5+ 默认启用)。

    • CSS 压缩: CssMinimizerWebpackPlugin

  2. 代码分割(Code Splitting)

    // webpack.config.js
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all', // 分割公共模块
        },
      },
    };
  3. 缓存优化

    使用 [contenthash] 生成文件名:

    output: {
      filename: '[name].[contenthash].js',
    }

六、调试与常见问题

  1. 查看打包分析

    使用 webpack-bundle-analyzer

    npm install webpack-bundle-analyzer --save-dev
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
    plugins: [new BundleAnalyzerPlugin()];
  2. 路径问题

    使用 path.resolve(__dirname, '路径') 处理绝对路径。

  3. Loader 顺序错误

    Loader 执行顺序为 从后向前 ,例如 ['style-loader', 'css-loader'] 会先执行 css-loader


七、学习资源推荐

  1. 官方文档

  2. 实战教程


八、总结

核心目标 :将分散的前端资源打包为优化的静态文件。

核心能力 :Loader 处理非 JS 文件,Plugin 扩展功能。

进阶方向 :性能优化(Tree Shaking、懒加载)、自定义插件开发。

16 vue-router

Vue Router 是 Vue.js 的 官方路由管理库 ,用于构建 单页面应用(SPA) ,实现页面跳转、动态路由、嵌套路由等功能。


一、核心概念

  1. 路由(Route)

    定义 URL 路径与组件的映射关系。

  2. 路由器(Router)

    管理所有路由规则和导航行为。

  3. 路由视图(RouterView)

    用于渲染匹配的组件。

  4. 导航守卫(Navigation Guards)

    控制路由跳转前后的逻辑(如权限校验)。


二、快速入门

1. 安装
# Vue 2 项目
npm install vue-router@3

# Vue 3 项目
npm install vue-router@4
2. 基础配置(Vue 3 示例)
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';

// 1. 定义路由组件
const Home = () => import('./views/Home.vue');
const About = () => import('./views/About.vue');

// 2. 定义路由规则
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
];

// 3. 创建路由实例
const router = createRouter({
  history: createWebHistory(), // 使用 HTML5 历史模式
  routes
});

export default router;
3. 挂载到 Vue 应用
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(router); // 启用路由
app.mount('#app');
4. 在组件中使用
<!-- App.vue -->
<template>
  <div>
    <!-- 导航链接 -->
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>

    <!-- 路由出口 -->
    <router-view></router-view>
  </div>
</template>

三、核心功能详解

1. 动态路由匹配

通过 : 定义动态参数,在组件中通过 $route.params 获取。

// 路由配置
{ path: '/user/:id', component: User }

// 组件内访问参数
this.$route.params.id; // Vue 2
import { useRoute } from 'vue-router';
const route = useRoute(); // Vue 3
console.log(route.params.id);

2. 嵌套路由

使用 children 定义子路由,父组件需包含 <router-view>

// 路由配置
{
  path: '/dashboard',
  component: Dashboard,
  children: [
    { path: 'profile', component: Profile }, // /dashboard/profile
    { path: 'settings', component: Settings }
  ]
}

3. 编程式导航

通过 router.pushrouter.replace 跳转。

// 在组件中(Vue 3 组合式 API)
import { useRouter } from 'vue-router';
const router = useRouter();

// 跳转到指定路径
router.push('/about');
router.push({ path: '/user/1' });

// 替换当前页面(无历史记录)
router.replace('/login');

4. 导航守卫

控制路由跳转逻辑(如登录验证)。

// 全局前置守卫
router.beforeEach((to, from, next) => {
  const isAuthenticated = checkLogin(); // 检查是否登录
  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login'); // 跳转到登录页
  } else {
    next(); // 允许通过
  }
});

// 路由独享守卫
{
  path: '/admin',
  component: Admin,
  beforeEnter: (to, from, next) => { /* 逻辑 */ }
}

// 组件内守卫
export default {
  beforeRouteEnter(to, from, next) { /* 进入前 */ },
  beforeRouteUpdate(to, from, next) { /* 路由更新 */ },
  beforeRouteLeave(to, from, next) { /* 离开前 */ }
}

5. 路由元信息(Meta Fields)

通过 meta 字段存储额外信息(如页面标题、权限)。

{
  path: '/profile',
  component: Profile,
  meta: { requiresAuth: true, title: '用户中心' }
}

6. 懒加载路由

按需加载组件,优化性能。

// Vue 3 动态导入
const User = () => import('./views/User.vue');

// Webpack 魔法注释(指定 chunk 名称)
const User = () => import(/* webpackChunkName: "user" */ './views/User.vue');

四、高级功能

1. 滚动行为

控制页面切换后的滚动位置。

const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition; // 恢复历史滚动位置
    } else {
      return { top: 0 }; // 滚动到顶部
    }
  }
});

2. 路由模式

Hash 模式 :URL 带 # ,兼容性好。

createWebHashHistory()

History 模式 :需服务器配置支持。

createWebHistory()

五、常见问题

1. 404 页面处理
{ path: '/:pathMatch(.*)*', component: NotFound }
2. 路由重复跳转报错

在路由跳转时捕获错误:

router.push('/path').catch(err => {});

六、注意事项

  1. 路由命名

    建议为路由定义 name 属性,便于编程式导航。

    { path: '/user', name: 'user', component: User }
    router.push({ name: 'user' });
  2. 路由顺序

    动态路由应放在静态路由之后。

  3. 性能优化

    使用懒加载拆分代码块。


七、总结

Vue Router 是构建 Vue 单页面应用的核心工具,掌握其核心功能(动态路由、守卫、懒加载)和最佳实践,能高效实现复杂路由逻辑。结合 Vuex 状态管理,可构建企业级前端应用。

推荐学习

17 elemenUI

一、环境准备

1. 创建 Vue 项目并安装依赖
# 创建 Vue 项目(Vue 3)
npm create vue@latest
# 进入项目目录
cd your-project

# 安装 Element Plus(适用于 Vue 3)
npm install element-plus --save

# 安装 Vue Router
npm install vue-router@4
2. 完整引入 Element Plus
// 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')

二、路由配置(Vue Router)

1. 定义路由文件
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// 导入页面组件
const Home = () => import('@/views/Home.vue')
const User = () => import('@/views/User.vue')
const NotFound = () => import('@/views/404.vue')

const routes = [
  { path: '/', redirect: '/home' },
  { 
    path: '/home',
    name: 'Home',
    component: Home,
    meta: { title: '首页', icon: 'House' } // 用于菜单渲染
  },
  { 
    path: '/user/:id',
    name: 'User',
    component: User,
    meta: { requiresAuth: true } // 需要登录
  },
  { path: '/:pathMatch(.*)*', component: NotFound } // 404 页面
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

三、结合 Element UI 实现布局

1. 使用 Element 的布局组件
<!-- src/App.vue -->
<template>
  <el-container>
    <!-- 侧边栏 -->
    <el-aside width="200px">
      <el-menu
        router
        :default-active="$route.path"
        @select="handleMenuSelect"
      >
        <el-menu-item index="/home">
          <el-icon><House /></el-icon>
          <span>首页</span>
        </el-menu-item>
        <el-menu-item index="/user/1">
          <el-icon><User /></el-icon>
          <span>用户中心</span>
        </el-menu-item>
      </el-menu>
    </el-aside>

    <!-- 主内容区 -->
    <el-main>
      <router-view v-slot="{ Component }">
        <transition name="fade" mode="out-in">
          <component :is="Component" />
        </transition>
      </router-view>
    </el-main>
  </el-container>
</template>

<script setup>
import { House, User } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'

const router = useRouter()

const handleMenuSelect = (index) => {
  router.push(index)
}
</script>

<style>
.el-aside {
  background-color: #304156;
}

.el-menu {
  border-right: none;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

四、常见场景实现

1. 表单提交后跳转(Element 表单 + 路由)
<!-- src/views/Login.vue -->
<template>
  <el-form :model="form" @submit.prevent="handleSubmit">
    <el-form-item label="用户名">
      <el-input v-model="form.username" />
    </el-form-item>
    <el-form-item label="密码">
      <el-input v-model="form.password" type="password" />
    </el-form-item>
    <el-button type="primary" native-type="submit">登录</el-button>
  </el-form>
</template>

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'

const router = useRouter()
const form = ref({ username: '', password: '' })

const handleSubmit = async () => {
  // 模拟登录成功
  if (form.value.username && form.value.password) {
    ElMessage.success('登录成功!')
    router.push('/dashboard')
  } else {
    ElMessage.error('请填写完整信息')
  }
}
</script>

2. 面包屑导航(Element Breadcrumb)
<template>
  <el-breadcrumb separator="/">
    <el-breadcrumb-item 
      v-for="(route, index) in matchedRoutes" 
      :key="index"
      :to="{ path: route.path }"
    >
      {{ route.meta.title }}
    </el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

const matchedRoutes = computed(() => {
  return route.matched.filter(r => r.meta?.title)
})
</script>

五、权限控制实现

1. 全局路由守卫 + Element 弹窗
// router/index.js
router.beforeEach((to, from, next) => {
  const isAuthenticated = localStorage.getItem('token')
  
  if (to.meta.requiresAuth && !isAuthenticated) {
    ElMessageBox.confirm('请先登录', '提示', {
      confirmButtonText: '去登录',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(() => {
      next('/login')
    }).catch(() => {
      next(false) // 取消导航
    })
  } else {
    next()
  }
})

六、优化技巧

  1. 图标自动导入

    使用 unplugin-icons 自动导入 Element 图标:

    // vite.config.js
    import Icons from 'unplugin-icons/vite'
    
    export default defineConfig({
      plugins: [
        Icons({ compiler: 'vue3' })
      ]
    })
  2. 按需加载组件

    使用 unplugin-vue-components 自动导入 Element 组件:

    // vite.config.js
    import Components from 'unplugin-vue-components/vite'
    import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
    
    export default defineConfig({
      plugins: [
        Components({
          resolvers: [ElementPlusResolver()]
        })
      ]
    })

七、项目结构建议

src/
├── assets/
├── components/
├── router/
├── store/          # Vuex/Pinia 状态管理
├── styles/         # 全局样式
├── utils/          # 工具函数
├── views/
│   ├── Home.vue
│   ├── User.vue
│   └── Login.vue
└── App.vue

八、常见问题

  1. 菜单高亮问题

    确保 el-menudefault-active 绑定当前路由路径:

    :default-active="$route.path"
  2. 动态路由刷新白屏

    使用 router.onReady() 或在 App.vue 中添加 key

    <router-view :key="$route.fullPath" />

通过 Vue Router 管理页面导航,结合 快速搭建美观界面,可高效开发后台管理系统、数据看板等中后台项目。推荐结合 Pinia 进行状态管理,使用 Axios 处理接口请求,构建完整的前端解决方案。

18 参数传递和重定向

在 Vue 应用中, 参数传递重定向 是路由管理的核心功能。

一、参数传递的 4 种方式

1. 动态路由参数(Params)

适合传递 必填参数 (如用户ID),通过 : 定义动态路径。

// 路由配置
{
  path: '/user/:id',
  name: 'User',
  component: User
}

// 跳转传递
router.push({ name: 'User', params: { id: 123 } });
// 或
<router-link :to="{ name: 'User', params: { id: 123 }}">用户</router-link>

// 组件内获取
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.params.id); // 123

注意

params 必须与路由定义的参数名一致

• 使用 name 跳转更可靠(直接 path 跳转可能导致 params 失效)


2. 查询参数(Query)

适合传递 可选参数 (如搜索关键词),参数显示在 URL 中。

// 跳转传递
router.push({ path: '/search', query: { keyword: 'vue' } });
// 或
<router-link :to="{ path: '/search', query: { keyword: 'vue' }}">搜索</router-link>

// 组件内获取
console.log(route.query.keyword); // 'vue'

URL 示例/search?keyword=vue


3. Props 传参

解耦组件与路由,使组件不直接依赖 $route

// 路由配置
{
  path: '/user/:id',
  component: User,
  props: true // 将 params 自动转为 props
  // 或使用函数模式
  // props: (route) => ({ id: route.params.id, query: route.query })
}

// 组件定义
export default {
  props: ['id'], // 接收 props
  setup(props) {
    console.log(props.id);
  }
}

4. 状态管理(Vuex/Pinia)

适合 复杂数据跨组件共享 的场景。

// 存储数据
import { useStore } from '@/store';
const store = useStore();
store.setUserInfo({ name: 'John' });

// 跳转
router.push('/profile');

// 目标组件获取
const userInfo = store.userInfo;

二、重定向的 3 种实现方式

1. 全局重定向(路由配置)

在路由规则中使用 redirect 字段:

// 简单重定向
{ path: '/home', redirect: '/' }

// 命名路由重定向
{ path: '/old', redirect: { name: 'Home' } }

// 动态重定向(函数模式)
{ 
  path: '/redirect/:path*',
  redirect: (to) => {
    return { path: '/' + to.params.path };
  }
}

2. 导航守卫重定向

在全局/路由独享守卫中控制跳转:

// 全局前置守卫
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isLoggedIn()) {
    next({ path: '/login', query: { redirect: to.fullPath } });
  } else {
    next();
  }
});

// 登录成功后跳回原页面
const redirect = route.query.redirect || '/';
router.push(redirect);

3. 编程式重定向

在组件逻辑中强制跳转:

import { useRouter } from 'vue-router';
const router = useRouter();

// 替换当前历史记录
router.replace('/new-path');

// 带参数跳转
router.push({ 
  path: '/error',
  query: { code: 404, message: '页面不存在' }
});

三、特殊场景处理

1. 404 页面配置
{
  path: '/:pathMatch(.*)*', // 匹配所有未定义路径
  name: 'NotFound',
  component: NotFound
}

2. 路由参数类型转换

动态参数默认是字符串,可通过 props

  • 转换函数处理类型:
// 路由配置
{
  path: '/product/:id(\\d+)', // 限制为数字
  component: Product,
  props: (route) => ({ id: Number(route.params.id) })
}

3. 保留当前页面查询参数
// 跳转时保留现有查询参数
router.push({ query: { ...route.query, newParam: 'value' } });

四、最佳实践总结

场景推荐方式
必填参数(如ID)动态路由参数 ( params )
可选参数(如筛选条件)查询参数 ( query )
解耦组件与路由Props 传参
敏感数据(如Token)Vuex/Pinia 状态管理
登录跳转导航守卫重定向
页面不存在全局 404 路由

五、注意事项

  1. 避免直接修改 $route 对象 ,应使用路由方法跳转
  2. 动态路由参数变化时 ,需用 beforeRouteUpdate 守卫监听
  3. Hash vs History 模式 :生产环境需服务器配置支持 History 模式

19 404页面和路由钩子

在 Vue 应用中, 404 页面处理 和**路由钩子(导航守卫)**是实现复杂路由逻辑的关键技术。

一、404 页面处理

1. 配置通配符路由
// router/index.js
{
  // 匹配所有未定义路径(Vue Router 4+ 语法)
  path: '/:pathMatch(.*)*',
  name: 'NotFound',
  component: () => import('@/views/404.vue')
}
2. 404 页面组件示例
<!-- views/404.vue -->
<template>
  <div class="not-found">
    <h1>404</h1>
    <p>您访问的页面不存在</p>
    <el-button @click="router.push('/')">返回首页</el-button>
  </div>
</template>

<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
</script>
3. 注意事项

路由顺序 :必须放在路由配置的最后

路径匹配语法

:pathMatch(.*)* :匹配任意路径并保留嵌套结构

:pathMatch(.*) :匹配任意路径但不保留嵌套

服务器配置 :若使用 History 模式,需配置服务器 fallback 到 index.html


二、路由钩子(导航守卫)

路由钩子用于在路由跳转前后执行特定逻辑,分为三类:

1. 全局守卫
// router/index.js
router.beforeEach((to, from, next) => {
  // 逻辑 1:修改页面标题
  document.title = to.meta.title || '默认标题';

  // 逻辑 2:登录验证
  if (to.meta.requiresAuth && !isLoggedIn()) {
    next({ 
      path: '/login',
      query: { redirect: to.fullPath } // 记录原路径
    });
  } else {
    next(); // 放行
  }
});

router.afterEach((to, from) => {
  // 跳转完成后执行(无 next 参数)
  logPageView(to.fullPath); // 示例:统计页面访问
});
2. 路由独享守卫
{
  path: '/admin',
  component: Admin,
  beforeEnter: (to, from, next) => {
    if (!checkAdminPermission()) {
      next('/no-permission');
    } else {
      next();
    }
  }
}
3. 组件内守卫
// 在组件选项中定义
export default {
  beforeRouteEnter(to, from, next) {
    // 不能访问组件实例 `this`
    next(vm => {
      // 通过 vm 访问组件实例
      vm.loadData();
    });
  },

  beforeRouteUpdate(to, from, next) {
    // 路由变化但组件被复用时触发
    this.userId = to.params.id;
    next();
  },

  beforeRouteLeave(to, from, next) {
    // 离开前确认未保存更改
    if (this.hasUnsavedChanges) {
      const answer = confirm('有未保存的更改,确定离开吗?');
      answer ? next() : next(false);
    } else {
      next();
    }
  }
}

三、进阶使用场景

1. 异步导航守卫
router.beforeEach(async (to, from, next) => {
  // 等待异步检查
  const hasPermission = await checkPermission(to.meta.role);
  hasPermission ? next() : next('/403');
});
2. 动态添加路由
// 添加管理后台路由(需权限)
if (user.isAdmin) {
  router.addRoute({
    path: '/admin',
    component: Admin,
    meta: { requiresAuth: true }
  });
}
3. 导航取消处理
router.push('/dashboard').catch(error => {
  if (error.name === 'NavigationDuplicated') {
    // 处理重复导航错误
  }
});

四、执行顺序与流程图

1. 完整导航解析流程
1. 导航被触发
2. 调用离开组件的 `beforeRouteLeave`
3. 调用全局 `beforeEach`
4. 调用复用组件的 `beforeRouteUpdate`
5. 调用路由配置的 `beforeEnter`
6. 解析异步路由组件
7. 调用进入组件的 `beforeRouteEnter`
8. 调用全局 `beforeResolve`
9. 导航被确认
10. 调用全局 `afterEach`
11. 触发 DOM 更新
12. 执行 `beforeRouteEnter` 中的回调函数
2. 钩子执行顺序示例
// 假设从 /a 跳转到 /b
beforeRouteLeave (组件A)      // 离开当前组件

beforeEach (全局)             // 全局前置钩子

beforeEnter (路由B的独享守卫)  // 目标路由配置的守卫

beforeRouteEnter (组件B)      // 进入目标组件前

afterEach (全局)              // 全局后置钩子

五、最佳实践

1. 404 页面使用场景

• 未匹配的路由

• 服务器 404 状态码返回(需 SSR 支持)

• 权限不足时的兜底展示

2. 导航守卫使用场景
钩子类型典型场景
beforeEach全局权限验证、页面标题设置
beforeEnter特定路由的访问控制(如VIP页面)
beforeRouteLeave阻止用户意外离开表单页
afterEach页面访问统计、滚动位置复位
3. 注意事项

避免阻塞逻辑 :导航守卫中不要执行长时间同步操作

next() 必须调用 :否则导航会挂起

组件守卫与组合式 API :Vue 3 中可通过 onBeforeRouteUpdate 等组合式函数使用


通过合理使用 404 处理和路由钩子,可以实现:

• 更友好的错误提示

• 精细化的权限控制

• 导航过程的全链路监控

• 用户体验优化(如未保存提示)