普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月12日首页

Vue中级冒险:3-4周成为组件沟通大师 🚀

作者 JS_GGbond
2025年12月12日 09:56

欢迎使用我的小程序👇👇👇👇

small.png


欢迎回到你的Vue学习之旅!如果你已经跨过了基础门槛,那么接下来的3-4周将带你进入一个全新的世界——在这里,组件不再孤立,数据流动如交响乐般和谐,代码组织变得优雅而强大。

📅 第一周:与“时间魔法师”——生命周期成为好友

想象一下,每个Vue组件都像一个小生命,有自己的出生、成长和告别时刻。生命周期钩子就是这些关键时刻的提醒铃铛🔔。

// 以前你可能只认识created和mounted
// 现在来认识整个生命周期家族吧!
export default {
  beforeCreate() { console.log('我即将诞生!') },
  created() { console.log('我出生了!可以访问数据啦') },
  beforeMount() { console.log('准备挂载到DOM树上...') },
  mounted() { console.log('成功安家!现在可以操作DOM了') },
  beforeUpdate() { console.log('数据变了,我要准备更新啦') },
  updated() { console.log('更新完成!界面焕然一新') },
  beforeUnmount() { console.log('我即将离开这个世界...') },
  unmounted() { console.log('再见!清理工作完成') }
}

本周小挑战:写一个组件,在它生命的每个阶段都在控制台留下足迹👣,观察数据变化和DOM更新时的触发顺序。

📅 第二周:掌握组件间的“悄悄话”艺术

组件不会读心术,但它们有6种方式可以交流!让我们把它们想象成住在不同房间的室友:

1. Props:妈妈喊你吃饭式(父→子)

// 爸爸组件大喊:
<ChildComponent :dinner="'红烧肉'" />

// 孩子组件乖乖接收:
props: ['dinner']

2. $emit:孩子有事报告式(子→父)

// 孩子在房间里喊:
this.$emit('hungry', '想吃零食')

// 爸爸在外面监听:
<ChildComponent @hungry="handleHungry" />

3. Refs:直接敲门式

// 获取组件实例,直接调用方法
<ChildComponent ref="child" />
this.$refs.child.doSomething()

4. Event Bus:小区广播式(任意组件间)

// 创建一个中央事件总线
// 组件A:广播消息
eventBus.emit('news', '今天小区停水')

// 组件B:收听广播
eventBus.on('news', (msg) => {
  console.log(msg) // 今天小区停水
})

5. Provide/Inject:家族秘密传承式(跨层级)

// 爷爷辈组件:
provide() {
  return { familySecret: '传家宝的位置' }
}

// 孙子辈组件(跳过爸爸直接获取):
inject: ['familySecret']

6. Vuex/Pinia:社区公告栏式(全局状态)

// 任何组件都可以:
store.commit('setMessage', '社区通知:明天停电')

// 任何组件也都能看到:
store.state.message

本周实践:创建一个“家庭聊天室”应用,使用至少4种通信方式让祖孙三代的组件互相传递消息!

📅 第三、四周:解锁组合式API的“积木魔法”

还记得小时候搭积木的乐趣吗?组合式API让你重新体验这种快乐!

选项式API vs 组合式API

// 以前(选项式) - 像整理抽屉
export default {
  data() { return { count: 0 } },
  methods: { increment() { this.count++ } },
  computed: { doubleCount() { return this.count * 2 } }
}

// 现在(组合式) - 像搭积木
import { ref, computed } from 'vue'

export default {
  setup() {
    // 逻辑1:计数器
    const count = ref(0)
    const increment = () => { count.value++ }
    const doubleCount = computed(() => count.value * 2)
    
    // 逻辑2:用户信息
    const user = ref(null)
    const fetchUser = async () => { /* ... */ }
    
    // 像搭积木一样组合功能
    return { count, increment, doubleCount, user, fetchUser }
  }
}

超能力:自定义组合函数

// 创建一个可复用的“鼠标跟踪器”积木
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)
  
  const update = (event) => {
    x.value = event.pageX
    y.value = event.pageY
  }
  
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
  
  return { x, y }
}

// 在任何组件中轻松使用:
const { x, y } = useMouse()
// 看!鼠标坐标自动跟踪!

响应式进阶:reactive vs ref

// ref - 给单个值加响应式外衣
const count = ref(0) // 访问时:count.value

// reactive - 给对象加响应式外衣
const state = reactive({ 
  name: 'Vue', 
  version: 3 
}) // 访问时:state.name

// 小贴士:简单值用ref,复杂对象用reactive

终极挑战:用组合式API重构你之前的一个项目,把相关逻辑抽成自定义组合函数,体验“代码乐高”的快乐!

🎉 庆祝时刻:你已经成为Vue中级开发者!

经过这3-4周的冒险,你已经掌握了:

  • 生命周期管理:像时间旅行者一样掌控组件的一生
  • 6种组件通信:让组件间的对话流畅自然
  • 组合式API:用乐高式思维构建可维护的代码

现在你的Vue技能树已经枝繁叶茂🌳!这些技能不仅在面试中闪闪发光,更能让你在实际项目中游刃有余。

下一步冒险预告:高级路由管理、性能优化、服务端渲染... 但先给自己放个小假,用新技能做个有趣的小项目吧!


分享你的学习成果或遇到的问题,在评论区一起交流成长!你的3周挑战故事是什么? 💬

#Vue #前端开发 #编程学习 #JavaScript #组合式API

Vue3 组件入门:像搭乐高一样玩转前端!

作者 JS_GGbond
2025年12月12日 09:55

欢迎使用我的小程序👇👇👇👇

small.png


你好呀!如果你刚开始学习 Vue3 组件开发,那你来对地方了!想象一下,组件就像是前端世界的乐高积木——小巧、独立、可重复使用,还能组合成酷炫的东西。让我们花 1-2 周时间,轻松掌握组件开发的三大基石!

🎯 第一周:认识你的“乐高积木”

组件基本结构:Vue 的“基因代码”

每个 Vue 组件都像一个独立的小程序,有自己的模板、逻辑和样式:

<template>
  <!-- 这里是组件的“外貌” -->
  <div class="my-component">
    <h1>{{ title }}</h1>
    <button @click="handleClick">点我!</button>
  </div>
</template>

<script setup>
// 这里是组件的“大脑”
import { ref } from 'vue'

const title = ref('你好,我是组件!')

const handleClick = () => {
  console.log('按钮被点击啦!')
}
</script>

<style scoped>
/* 这里是组件的“穿搭” */
.my-component {
  border: 2px solid #42b983;
  padding: 20px;
  border-radius: 10px;
}
</style>

💡 小贴士<script setup> 是 Vue3 的语法糖,让代码更简洁!scoped 样式确保穿搭只影响自己,不会“撞衫”。

🔄 第二周:让积木“活”起来

Props:组件的“个性定制”

就像给乐高人仔换装一样,Props 让组件可以接收外部数据:

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <h2>{{ name }}</h2>
    <p>年龄:{{ age }}</p>
    <p v-if="isVip">⭐ VIP会员</p>
  </div>
</template>

<script setup>
// 定义组件可以接收哪些“定制参数”
const props = defineProps({
  name: {
    type: String,
    required: true  // 这个必须传!
  },
  age: {
    type: Number,
    default: 18     // 不传的话默认18岁
  },
  isVip: Boolean    // 简写形式
})
</script>

使用这个组件时:

<template>
  <UserCard name="小明" :age="20" :is-vip="true" />
  <UserCard name="小红" /> <!-- 小红自动18岁,不是VIP -->
</template>

🎭 有趣比喻:Props 就像点奶茶时的选项——甜度、冰度、加料,同一个奶茶组件,能调出千变万化的味道!

Events:组件的“悄悄话机制”

组件不能总是被动接收,有时也需要主动“说话”:

<!-- Counter.vue -->
<template>
  <div>
    <p>计数:{{ count }}</p>
    <button @click="increment">+1</button>
    <button @click="reset">归零</button>
  </div>
</template>

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

const emit = defineEmits(['count-change', 'reset-done'])

const count = ref(0)

const increment = () => {
  count.value++
  // 对外“喊话”:计数变化啦!
  emit('count-change', count.value)
}

const reset = () => {
  count.value = 0
  // 喊另一句话:重置完成啦!
  emit('reset-done')
}
</script>

父组件接收“悄悄话”:

<template>
  <Counter 
    @count-change="onCountChange"
    @reset-done="showAlert('已归零!')"
  />
</template>

<script setup>
const onCountChange = (newCount) => {
  console.log(`计数器变成${newCount}了!`)
}

const showAlert = (msg) => {
  alert(msg)
}
</script>

🔊 生动解释:Events 就像组件之间的“对讲机”。子组件按下按钮,父组件就能听到:“嘿!我这里发生事情了!”

插槽:组件的“留白艺术”

有时候,我们想在组件里留一块空地,让使用它的人自由发挥:

<!-- FancyBox.vue -->
<template>
  <div class="fancy-box">
    <div class="header">
      <slot name="header">默认标题</slot>
    </div>
    
    <div class="content">
      <!-- 匿名插槽:不写name的那个 -->
      <slot>默认内容</slot>
    </div>
    
    <div class="footer">
      <slot name="footer"></slot>
      <!-- 如果没提供footer,这里什么都不显示 -->
    </div>
  </div>
</template>

尽情发挥创意:

<template>
  <FancyBox>
    <!-- #header 是 v-slot:header 的简写 -->
    <template #header>
      <h1>🎉 我的个性化标题!</h1>
    </template>
    
    <!-- 这里是匿名插槽的内容 -->
    <p>这是放在主区域的内容...</p>
    <img src="/my-image.jpg" alt="我的图片">
    
    <template #footer>
      <button>确定</button>
      <button>取消</button>
    </template>
  </FancyBox>
</template>

🎨 精妙比喻:插槽就像相框——相框组件提供结构和样式(边框、材质),但你可以在里面放任何照片!

🚀 两周学习路线图

第一周:打好地基

  • 第1-2天:创建你的第一个组件,理解“单文件组件”概念
  • 第3-4天:玩转 Props,尝试各种类型验证
  • 第5-7天:组件通信初体验,父子组件互相“对话”

第二周:进阶组合

  • 第8-10天:掌握具名插槽和作用域插槽
  • 第11-12天:构建一个小项目(如用户卡片集)
  • 第13-14天:重构重复代码为可复用组件

💪 动手挑战!

试着创建一个 MessageBubble 组件:

  1. 通过 type prop 控制样式(成功、警告、错误)
  2. 点击气泡时发射 close 事件
  3. 使用插槽让内容可以包含任何 HTML
  4. 添加一个 icon 插槽,允许自定义图标

🌟 总结

Vue3 组件开发其实就像玩乐高:

  • 基本结构 = 积木的基础形状
  • Props = 给积木涂上不同颜色
  • Events = 积木之间的连接卡扣
  • 插槽 = 预留的特殊接口

记住,最好的学习方式就是动手去做!从今天起,试着把页面上的每个部分都想象成可复用的组件。两周后,你会惊讶地发现,自己已经能用“乐高思维”构建整个应用了!

有什么问题或有趣的组件创意吗?欢迎在评论区分享!一起在 Vue3 的世界里搭出炫酷的作品吧!✨


📅 学习进度提醒:标记你的日历,两周后回来看看自己构建了多少个酷炫组件!

昨天 — 2025年12月11日首页

module federation,monorepo分不清楚?

作者 ggbond
2025年12月11日 17:57

一代版本一代神,现代的前端已经不是会用一个react就能混过去的了,虽然正式工作上还是打螺丝,调包侠+切图仔,但是有些时候,新知识不可不学。 有两个概念近些年很火,一个是module federation一个是monorepo,光看名字可能觉得有点像,但是其实是两个东西。

模块联邦module federation

这是webpack在v5被投入生产,并作为v5的核心特性之一。它的出现解决了一些问题,或者说它适用于以下场景:

  1. 微前端架构:实现独立部署的子应用动态集成(如电商平台的首页、商品页拆分)。
  2. 大型应用拆分:逐步重构单体应用,降低维护成本。
  3. 跨团队代码共享:避免重复发布 npm 包,直接运行时复用模块。

基本上可以说他是微前端的方式。当然市面上肯定大部分工具也会跟上webpack,比如vite就通过rollup钩子实现了(vite-plugin-federation),又比如@module-federation/rollup插件,next-mf插件,Rspack(基于webpack)。 接下来看下他的主要配置

主应用webpack.config.js:

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  ...
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        app1: 'remote@http://localhost:3001/app1Entry.js', // 子应用地址
      },
      shared: { 
        react: { singleton: true }, 'react-dom': { singleton: true }                 },
    }),
  ],
};

这里看到了一个shared配置singleton,指明了哪些模块应该作为单例共享,也就是单例模式,true的话父子应用共用一个实例,避免重复加载,但是当插件需要完全隔离的依赖如react环境时,可以设置成false;

remotes字段指定了远程微应用的名称和其远程入口文件URL。 当主应用需要用到子应用的玩意时,如下:

// 动态加载子应用的Button组件 
const RemoteButton = React.lazy(() => import('app1/Button'));

子应用webpack配置

const { ModuleFederationPlugin } = require('webpack').container; module.exports = { 
    entry: './src/moduleOutput.js', // 必须通过moduleOutput.js间接引入 
    plugins: [ 
        new ModuleFederationPlugin({ 
            name: 'app1', // 子应用名称(全局唯一) 
            filename: 'app1Entry.js', // 入口文件名(默认),模块清单名
            exposes: { 
                './Button': './src/Button.js', // 暴露组件路径 
            }, 
            shared: { 
                react: { singleton: true }, 'react-dom': { singleton: true } 
            },             
        }), 
    ], 
};
// moduleOutput.js
import('./index'); // 延迟加载业务代码

注意,这里我们看到entry不是我们平时项目脚手架自带的index.js/ts,而是通过其他文件moduleOutput.js,这个文件的存在是为了正确执行模块联邦的动态加载机制和代码执行顺序,而主要导致这样的原因是:

  1. 主应用加载子应用时,会先下载app1Entry.js模块清单文件,然后在按需加载子模块,比如exposes中的Button,如果子应用直接以index.js作为entry,可能会在子应用的子模块模块被主应用加载时,子应用的依赖(如react)未准备就绪,毕竟子应用也是配置了按需加载,这就会导致运行错误
  2. app1Entry.js这文件的作用就是延迟执行,通过动态导入(import())将子应用的业务代码(如 index.js)的加载推迟到 所有共享依赖(如 React)已就绪后。 当然,如果父子应用没有共享的模块,那么这个文件也就没必要了,另外shared的依赖中,有一个requiredVersion字段,可以让父子协商是否共享模块。

monorepo

这其实不是具体工具,而是一种思想:强关联性,同一业务线的项目,可以将项目放在同一个版本管理工具中(比如git),这么做的好处有很多,比如

  1. 代码共享与复用,一些公共的ts定义,和api接口层,组件能直接引用,并且所有项目共用顶层node_modules,减少重复依赖安装(通过workspaces功能)
  2. 统一工程化配置,比如eslint,pritter,jest和webpack等构建工具等,这会让维护成本降低。
  3. 统一版本管理,通过changesets等工具自动化版本号和changeLog管理
  4. 版本提交的完整性,当修改底层库时,可同时更新依赖他的所有应用,这保证了提交的完整性
  5. 依赖关系可视化,可用preune等命令工具生成关系图,便于框架优化
  6. 统一CI配置,所有项目共用一套CI/CD流程 当然也不是所有业务线都要这么做,这适用于部分场景:
  7. 微前端架构
  8. 全栈项目(对我来说当然是js的全栈)
  9. 多应用平台,比如pc,mobile共用业务逻辑
  10. 大型团队协作,减少代码碎片化
  11. 替代npm的频繁更替 常用来实现monorepo的工具有pnpm,lerna,turborepo,我一般使用pnpm

总结

这么一盘,好像两者也不是毫无联系,这都和微前端扯到了关系,但是两者场景并不是非常一致,且手段不同。最共同的点是,他们都是要学的东西。

昨天以前首页

模块化开发:当CommonJS遇见ES Module

作者 JS_GGbond
2025年12月10日 09:15

欢迎使用我的小程序👇👇👇👇

small.png


一、为什么我们需要模块化?

想象一下,你正在建造一座乐高城堡。如果所有的积木都混在一个大袋子里,每次要找特定零件都得翻遍整个袋子,这会是多么低效!早期的JavaScript开发就是这样——所有代码都写在一个或几个文件中,导致:

  • 变量污染全局作用域
  • 难以维护和调试
  • 代码复用困难
  • 依赖关系混乱

模块化就像把乐高积木按颜色、形状分类放在不同的盒子里,让搭建变得更有序、更高效。

二、CommonJS:Node.js的“老将”

基本概念

CommonJS是Node.js默认的模块系统,它采用同步加载的方式,非常适合服务器端环境。

核心语法

// 导出模块 - math.js
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

// 方式1:导出单个对象
module.exports = { add, multiply };

// 方式2:逐个添加属性
exports.add = add;
exports.multiply = multiply;

// 导入模块 - app.js
const math = require('./math.js');
console.log(math.add(2, 3)); // 5

特点解析

同步加载:像在图书馆借书,必须等上一本书拿到手,才能借下一本

// 模块会立即执行
const fs = require('fs'); // 阻塞执行,直到fs模块完全加载
const data = fs.readFileSync('file.txt'); // 继续阻塞

动态导入:你可以在条件语句中导入模块

if (userNeedsAdvancedFeature) {
    const advancedModule = require('./advanced');
    // 使用模块
}

适用场景

  • Node.js后端开发
  • 构建工具(如Webpack、Browserify转换后可在浏览器使用)

三、ES Module:现代JavaScript的“新星”

基本概念

ES Module是ECMAScript 2015(ES6)引入的官方模块标准,采用异步加载,是现代前端开发的首选。

核心语法

// 导出模块 - math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;

// 默认导出(每个模块只能有一个)
const defaultFunction = () => console.log('默认导出');
export default defaultFunction;

// 导入模块 - app.js
import { add, multiply } from './math.js';
import myDefault from './math.js'; // 导入默认导出

// 重命名导入
import { add as sum } from './math.js';

// 整体导入
import * as math from './math.js';

特点解析

静态分析:像餐厅点餐,先看完整菜单再决定点什么

// 导入声明必须在顶层,不能在条件语句中
import { featureA } from './moduleA'; // ✓ 正确

if (condition) {
    import { featureB } from './moduleB'; // ✗ 错误!语法不允许
}

// 但可以使用动态导入函数
if (condition) {
    import('./moduleB').then(module => {
        // 使用module.featureB
    });
}

异步加载:多个模块可以并行加载

// 模块1和模块2可以同时加载
import { utils } from './utils.js';
import { api } from './api.js';

// 动态导入返回Promise
const loadModule = async () => {
    const module = await import('./dynamic-module.js');
    module.doSomething();
};

实时绑定:导入的是值的引用,而不是副本

// counter.js
export let count = 0;
export const increment = () => { count++; };

// app.js
import { count, increment } from './counter.js';

console.log(count); // 0
increment();
console.log(count); // 1(值被更新了!)

四、CommonJS vs ES Module:直接对比

特性 CommonJS ES Module
语法 require() / module.exports import / export
加载时机 运行时加载 编译时静态解析
加载方式 同步 异步
环境 主要为Node.js 浏览器和Node.js
值类型 值的拷贝 值的引用(实时绑定)
条件导入 支持 静态导入不支持,动态导入支持
循环依赖 支持但可能有问题 支持且更可靠
严格模式 默认非严格模式 默认严格模式

循环依赖示例对比

CommonJS的问题

// a.js
exports.loaded = false;
const b = require('./b.js');
console.log('在a中,b是', b);
exports.loaded = true;

// b.js
exports.loaded = false;
const a = require('./a.js'); // 此时a还没有完全加载
console.log('在b中,a是', a); // a.loaded为false
exports.loaded = true;

ES Module的处理

// a.js
import { bLoaded } from './b.js';
export let aLoaded = false;
aLoaded = true;

// b.js
import { aLoaded } from './a.js';
export let bLoaded = false;
bLoaded = true;
// 可以正常工作,但需要注意初始化顺序

五、在现代项目中如何使用?

Node.js中的使用

Node.js从v13.2.0开始稳定支持ES Module:

  1. 使用.mjs扩展名

    // module.mjs
    export const hello = () => "Hello ES Module!";
    
    // app.mjs
    import { hello } from './module.mjs';
    
  2. package.json中设置"type": "module"

    {
      "name": "my-app",
      "type": "module",
      "scripts": {
        "start": "node app.js"
      }
    }
    

浏览器中的使用

<!-- 直接使用ES Module -->
<script type="module">
  import { createApp } from './app.js';
  createApp();
</script>

<!-- 支持相对和绝对路径 -->
<script type="module" src="./modules/main.js"></script>

互操作性

在ES Module中导入CommonJS模块:

// 可以导入CommonJS模块
import commonJSModule from './commonjs-module.cjs';
import { namedExport } from './commonjs-module.cjs'; // 如果CommonJS模块有提供

// 在package.json中指定不同扩展名的处理方式

六、实践建议

  1. 新项目:优先使用ES Module,它是未来的标准
  2. Node.js项目
    • 新项目:使用ES Module(设置"type": "module"
    • 现有项目:逐步迁移或维持CommonJS
  3. 浏览器项目:使用ES Module,配合构建工具(如Webpack、Vite)
  4. 库/包开发:考虑双模式支持
    // package.json
    {
      "name": "my-library",
      "main": "./dist/commonjs/index.js",
      "module": "./dist/esm/index.js",
      "exports": {
        "require": "./dist/commonjs/index.js",
        "import": "./dist/esm/index.js"
      }
    }
    

七、常见问题解答

Q:我应该学习哪一个? A:两者都要了解!ES Module是未来,但很多现有项目使用CommonJS。

Q:可以在同一个文件中混用吗? A:尽量避免,但在Node.js中可以通过动态导入互相调用。

Q:哪个性能更好? A:ES Module的静态特性允许更好的优化和摇树(tree-shaking)。

总结

模块化让JavaScript从"玩具语言"成长为"工程语言"。CommonJS像是一位经验丰富的老兵,在Node.js生态中建立了坚实基础;ES Module则像是一位充满活力的新星,代表着JavaScript的未来方向。

记住一个简单的选择策略:

  • 前端开发 → ES Module
  • Node.js新项目 → ES Module
  • Node.js旧项目维护 → CommonJS
  • 通用库开发 → 考虑双模式支持

无论选择哪种方式,模块化的核心思想都是一致的:关注点分离、高内聚低耦合、代码复用。掌握了这些概念,你就掌握了现代JavaScript开发的钥匙。

模块化不是目的,而是手段。真正的目标是编写可维护、可扩展、高质量的代码。

❌
❌