分类: 他山之玉

  • Pinia 和EvantBus

    在 Vue3 里,Pinia 并不是为了“父传子”这种简单场景设计的。它的核心职责是跨层级、跨组件地共享状态,把所有需要共享的数据都放在一个中央“仓库”里,让任何组件都能访问和修改,从而避免在多层组件中“prop drilling”(逐层传递)的繁琐。

    用代码理解 Pinia:将数据放进“中央仓库”

    Pinia的核心机制就是把数据放进Store(仓库),然后各个组件就像顾客一样,直接从仓库里取用自己需要的数据,或者把数据存进去。

    第一步:安装并创建一个 Store

    首先,你需要有一个中央仓库来存放全局数据。这通常在 store 目录下完成。

    // store/user.js
    import { defineStore } from 'pinia'
    
    export const useUserStore = defineStore('user', {
      // 存放全局数据:用户信息
      state: () => ({
        name: '张三',
        avatar: '',
        age: 30
      }),
      // 存放计算属性:比如根据年龄判断是否成年
      getters: {
        isAdult: (state) => state.age >= 18
      },
      // 存放修改数据的方法
      actions: {
        // 一个同步的更新方法
        updateName(newName) {
          this.name = newName
        },
        // 也可以有异步操作,比如从API获取用户数据
        async fetchUser() {
          // const res = await api.getUser()
          // this.name = res.data.name
        }
      }
    })

    第二步:在组件中使用这个 Store

    任何组件都能通过 useUserStore() 这个“钩子”来访问仓库里的数据和方法。

    <!-- 组件A.vue -->
    <template>
      <div>
        <!-- 直接使用 state 中的数据 -->
        <p>用户名:{{ userStore.name }}</p>
        <!-- 使用 getters -->
        <p>是否成年:{{ userStore.isAdult ? '是' : '否' }}</p>
        <!-- 可以绑定到输入框,进行双向修改 -->
        <input v-model="userStore.name" />
      </div>
    </template>
    
    <script setup>
    import { useUserStore } from '@/store/user'
    
    // 获取 Store 实例
    const userStore = useUserStore()
    </script>
    <!-- 组件B.vue -->
    <template>
      <div>
        <!-- 这里的 name 会和组件A中的 name 保持同步 -->
        <p>另一个组件看到的用户名:{{ userStore.name }}</p>
        <!-- 调用 action 来修改数据 -->
        <button @click="userStore.updateName('李四')">修改姓名</button>
      </div>
    </template>
    
    <script setup>
    import { useUserStore } from '@/store/user'
    
    const userStore = useUserStore()
    </script>

    在这个例子中,组件A和组件B通过Pinia里的 useUserStore 共享了同一份 name 数据,任意一个组件修改它,另一个组件都会立刻看到变化,完美实现跨组件通信。

    ⚔️ 组件间通信方式对比

    在 Vue 中,到底该用 Pinia 还是其他方式,可以遵循以下原则:

    通信场景推荐方案适用场景
    简单父子组件props / emit数据仅由父组件传给子组件,或子组件触发父组件事件。例如一个按钮组件触发父页面的提交事件。
    爷孙/深层组件provide / inject一个组件为它所有的后代组件提供一个数据源,避免数据在中间层组件被层层传递。
    全局/复杂状态Pinia任何组件之间需要共享和变更同一个数据,或者你的应用规模较大、组件层级很深时。例如前面提到的用户登录信息、整个购物车的数据等。

    ✨ 顺带一提:关于“值传递”的澄清

    我们最常用的父子组件通信方式,Pinia并不是用来“传递值”,而是用来创建一份在全局可访问的、响应式的“共享值”。它维护的是单一数据源,相比传统的 props/emit,能有效降低大型项目的维护成本。

    Pinia 和 EventBus 在定位上完全不同,可以说 Pinia 是处理复杂应用状态的“全局仓库”,而 EventBus 更像一个“临时传话器”。

    我将它们的区别总结为以下几点:

    📜 核心定位:状态管理 vs. 事件通信

    • Pinia (状态管理):它是一个全局的“仓库”,用于存储和管理应用的状态。组件可以从仓库里读取或修改数据,这些数据是响应式的(即数据变了,用到它的组件会自动更新)。
    • EventBus (事件通信):它像一个“广播系统”。一个组件可以“广播”一个事件,其他组件可以“收听”这个事件并做出反应。它侧重的是行为触发,而不是数据存储。

    🧬 核心机制与数据流向

    • Pinia (状态驱动):所有的状态都集中在Store中,数据流向是单向、清晰的。组件通过 store.name = '新名字' 等简单方式来修改状态,并且Vue的DevTools可以精确追踪每一次变化。
    • EventBus (事件驱动):遵循经典的发布-订阅(Pub/Sub) 模式。数据流向是隐藏且分散的,你很难直观地看出一个事件的触发会对哪些组件产生影响。

    📦 数据持久性与响应式

    • Pinia (持久且响应式):数据会一直保存在Store中,并且是响应式的。这意味着任何组件对Store里数据的修改,都会即时且自动地反映到其他所有引用了该数据的组件上。
    • EventBus (临时且非响应式):事件本身是“一次性”的,它负责传递数据,但不负责保存状态。数据传递后,如果没有被接收方存储,就无法再追踪。

    🛠️ 调试、类型与内存管理

    • Pinia (支持更完善)
      • 调试性:借助Vue DevTools,可以清晰地看到状态的变化历史,追踪性能更出色。
      • 类型安全:对TypeScript提供一流的支持,在编辑器中就能获得强大的类型提示,能有效减少拼写错误。
      • 内存管理:由Pinia自身管理监听和状态,开发者无需手动干预。
    • EventBus (支持较弱)
      • 调试性:由于事件的触发和监听分散在各处,当项目规模变大时,代码逻辑会难以追踪。
      • 类型安全:事件名是字符串,容易拼写错误,且事件参数没有类型约束,容易出错。
      • 内存管理:开发者必须在组件销毁时手动移除事件监听,否则极易导致内存泄漏

    🌳 可扩展性与架构

    • Pinia (可扩展性更强):它是为大型、复杂应用设计的,状态高度集中,逻辑清晰,易于团队协作和后期维护。
    • EventBus (可扩展性较弱):简单场景下很便利,但会随着应用增长,很容易演变成难以维护的“事件蜘蛛网”,让项目陷入混乱。

    📊 总结对比

    我把核心区别整理成了一张表,方便你对比选择:

    特性Pinia (全局状态管理)EventBus (事件总线)
    核心思想集中式状态管理发布-订阅事件通信
    数据存储✅ 持久化存储❌ 临时传递,不存储
    响应式✅ 是❌ 否
    数据流清晰、单向、可追踪隐蔽、分散、难追踪
    调试支持✅ Vue DevTools 强力支持❌ 困难,依赖 console.log
    TypeScript✅ 原生支持,类型安全❌ 事件名和载荷易出错
    内存风险⚠️ 高,易内存泄漏
    适用场景中大型应用、复杂状态共享小型项目、临时通信
    Vue 3 官方推荐✅ 是❌ 否 (Vue 3移除了内置方法)

    💡 使用场景与建议

    • 推荐使用 Pinia:当你的应用需要在多个不相关的组件间共享和修改状态时(例如用户登录信息、购物车数据、应用主题设置等)。这是现代 Vue 3 应用的标准和首选方案。
    • 考虑使用 mitt (现代 EventBus 实现):在 Vue 3 中,社区推荐使用 mitt 这样的小巧库来实现事件总线功能,而不是自己实现。它适用于非常轻量、临时的跨组件通信,比如两个非父子组件之间的一次简单通知,且你明确不想引入 Pinia。
    • 什么情况下可以放心使用 EventBus 或 mitt? 通常是原型开发小型或工具类项目,或在将 Vue 2 项目迁移到 Vue 3 的过渡期,作为临时方案。

    ⚠️ 使用 EventBus 的关键提醒

    如果在特定场景下你仍需使用 EventBus,务必严格遵守:

    1. 必须在 onUnmounted (Vue 3) 或 beforeUnmount 钩子中,使用 off 方法清理掉你添加的事件监听器,否则会导致严重的内存泄漏和意想不到的bug。
    2. 建议将所有的事件名称定义为常量,统一管理,避免拼写错误和命名冲突。

    在你的学习项目里,可以先用 EventBus 理解其“广播与接收”的概念,这能帮助你更深刻地体会到 Pinia 在大型项目中的价值和优势。

    你是在学习过程中对这两个概念有疑惑,还是正在实际开发中面临选型呢?大概是什么类型的项目?我可以帮你分析一下到底用哪个更合适。

    右上角那个“信息提示数字”(比如未读消息数量),最适合用 Pinia 来解决

    原因很简单:这个数字是一个全局状态,可能被多个组件访问和修改:

    • 头部组件:显示这个数字
    • 消息列表组件:当用户点击消息或收到新消息时,需要修改这个数字
    • 其他页面:也可能需要知道这个数字(如首页的小红点)

    用 Pinia 可以轻松实现数据共享和响应式更新。


    用 Pinia 实现通知数字(示例)

    1. 创建 Store

    // store/notification.js
    import { defineStore } from 'pinia'
    
    export const useNotificationStore = defineStore('notification', {
      state: () => ({
        unreadCount: 0   // 未读数量
      }),
      actions: {
        increment() {
          this.unreadCount++
        },
        decrement() {
          if (this.unreadCount > 0) this.unreadCount--
        },
        setCount(count) {
          this.unreadCount = count
        },
        async fetchUnreadCount() {
          // 调用 API 获取服务器上的未读数量
          // const res = await api.getUnreadCount()
          // this.unreadCount = res.data.count
        }
      }
    })

    2. 在头部组件中显示数字

    <!-- Header.vue -->
    <template>
      <div class="header">
        <i class="message-icon"></i>
        <span class="badge" v-if="unreadCount > 0">{{ unreadCount }}</span>
      </div>
    </template>
    
    <script setup>
    import { useNotificationStore } from '@/store/notification'
    import { storeToRefs } from 'pinia'
    
    const notificationStore = useNotificationStore()
    const { unreadCount } = storeToRefs(notificationStore)
    </script>

    3. 在消息列表中修改数字

    <!-- MessageList.vue -->
    <template>
      <div>
        <div v-for="msg in messages" @click="markAsRead(msg.id)">
          {{ msg.content }}
        </div>
      </div>
    </template>
    
    <script setup>
    import { useNotificationStore } from '@/store/notification'
    
    const notificationStore = useNotificationStore()
    
    function markAsRead(id) {
      // 标记已读逻辑...
      notificationStore.decrement()   // 减少未读数量
    }
    </script>

    为什么不用 EventBus?

    EventBus 也能实现,但有几个弊端:

    • 状态不持久:数字修改后,页面刷新就没了。Pinia 可以和 localStorage 或后端同步。
    • 难以追踪:谁修改了数字,在代码里很难一眼看出。
    • 容易内存泄漏:需要在组件销毁时手动解绑事件。

    用 EventBus 实现小红点通常是“收到一个通知事件,然后修改”,但那个“修改”最终还是要落在一个地方存储。Pinia 就是那个“地方”。


    额外建议

    如果你的应用不大,而且只有头部和消息页两处用到这个数字,也可以用 provide/inject 配合 ref 来实现。但一旦系统稍复杂,Pinia 是更专业、更省心的选择。

    你已经在看 Pinia 和 EventBus 的区别,说明你正在学习 Vue3 的状态管理。直接用 Pinia 来练手这个“右上角数字”,会是一个很好的实践。

  • Web应用防火墙

    Web应用防火墙(Web Application Firewall,简称WAF)对网站或者App的业务流量进行恶意特征识别及防护,在对流量进行清洗和过滤后,将正常、安全的流量返回给服务器,避免网站服务器被恶意入侵导致性能异常等问题,从而保障网站的业务安全和数据安全。

    功能介绍

       
    功能类别 功能说明
    业务配置 支持对网站的HTTP、HTTPS流量进行安全防护。
    Web应用安全防护 常见Web应用攻击防护 防御OWASP常见威胁:SQL注入、XSS跨站、WebShell上传、后门攻击、命令注入、非法HTTP协议请求、常见Web服务器漏洞攻击、CSRF、核心文件非授权访问、路径穿越、网站被扫描等。网站隐身:不对攻击者暴露站点地址,避免其绕过Web应用防火墙直接攻击。0day补丁及时更新:及时更新漏洞补丁,防护网站安全。友好的观察模式:针对网站新上线的业务开启观察模式,对于匹配中防护规则的疑似攻击只告警不阻断,方便统计业务误报状况。
    深度精确防护 支持全解析多种常见HTTP协议数据格式:任意头部字段、Form表单、Multipart、JSON、XML。支持解码常见编码类型:URL编码、JavaScript Unicode编码、HEX编码、HTML实体编码、Java序列化编码、PHP序列化编码、Base64编码、UTF-7编码、UTF-8编码、混合嵌套编码。支持预处理机制:空格压缩、注释删减、特殊字符处理,向上层多种检测引擎提供更为精细、准确的数据源。支持复杂格式数据环境下的检测能力;支持合理的检测逻辑复杂度,避免过多检测数据导致的误报,降低误报率;支持多种形式数据编码的自适应解码,避免利用各种编码形式的绕过。
    CC恶意攻击防护 控制单一源IP的访问频率,基于重定向跳转验证、人机识别等。针对海量慢速请求攻击,根据统计响应码及URL请求分布、异常Referer及User-Agent特征识别,结合网站精准防护规则综合防护。充分利用阿里云大数据安全优势,建立威胁情报与可信访问分析模型,快速识别恶意流量。
    精准访问控制 提供友好的配置控制台界面,支持IP、URL、Referer、User-Agent等HTTP常见字段的条件组合,配置强大的精准访问控制策略;支持盗链防护、网站后台保护等防护场景。与Web常见攻击防护、CC防护等安全模块结合,搭建多层综合保护机制;依据需求,轻松识别可信与恶意流量。
    虚拟补丁 在Web应用漏洞补丁发布和修复之前,通过调整Web防护策略实现快速防护。
    攻击事件管理 支持对攻击事件、攻击流量、攻击规模的集中管理统计。
    灵活性、可靠性 支持负载均衡:以集群方式提供服务,多台服务器负载均衡,支持多种负载均衡策略。支持平滑扩容:可根据实际流量情况,缩减或增加集群服务器的数量,实现服务能力弹性扩容。无单点问题:单台服务器宕机或者维修,均不影响正常服务。

    更多产品信息,请参见Web应用防火墙产品页面

    产品优势

       
    产品优势 优势说明
    10年以上网络安全经验 建立在阿里巴巴集团10年以上的网络安全经验上,提供与淘宝、天猫、支付宝等成功应用案例同样的安全体验。由专业的安全团队为您提供服务。抵御已知的OWASP漏洞并不断修复披露漏洞。
    防御CC攻击和爬虫攻击 帮助您抵御和减缓CC攻击。帮助您防御网络爬虫,避免网络资源消耗。检测和阻挡恶意请求,帮助您减少带宽消耗,防止数据库、SMS、API资源亏空,减少响应延时,避免宕机等。针对多样业务场景支持自定义防护规则。
    集成大数据能力 每天约抵御数亿次网络攻击。拥有丰富的IP数据库。拥有广泛的应用案例,对各类常见网络攻击的模式、方法和签名有大量研究。大数据分析不断整合先进的技术。
    简易性、可靠性 5分钟内部署和激活。无需安装任何软硬件或调整路由配置。通过防护集群作用,避免单点故障和冗余。防护流量处理性能高。

    应用场景

    适用于阿里云以及阿里云外所有用户,主要用于金融、电商、O2O、互联网+、游戏、政府、保险等行业各类网站的Web应用安全防护。

    说明

    仅支持通过域名或实例方式接入WAF,不支持使用IP直接接入。

    如何使用WAF

    如何使用WAF

    更多信息,请参见快速使用WAF 3.0

    应用防护RASP和Web应用防火墙的关系

    应用防护RASP(Runtime Application Self-Protection)是一种运行在应用程序内部的安全保护机制,它能够在应用运行时检测攻击并进行自我保护。更多详情,请参见接入应用防护

    RASP和Web应用防火墙并不是相互取代的技术,而是在不同业务和安全防护场景下各有所长。例如,RASP更适合应对未知漏洞(0day漏洞)利用和加密流量等场景,而网络访问控制、区域封禁、CC攻击、爬虫攻击等威胁防护则需要WAF的有效补充。因此,对于应用防护来说,您需要根据业务环境和要求接入RASP以及Web应用防火墙,协同构建应用内生与边界双重防护能力,通过设置多层重叠的安全防护系统来构建多道防线,从而降低应用被入侵、数据泄露和服务不可用等风险。

    合规资质

    WAF已通过ISO 9001、ISO 20000、ISO 22301、ISO 27001、ISO 27017、ISO 27018、ISO 27701、ISO 29151、BS 10012、CSA STAR、等保三级、SOC 1/2/3、C5、HK金融、OSPAR、PCI DSS等多项国际权威认证。

    WAF作为标准的阿里云云产品,在云平台层面具备与阿里云同等水平的安全合规资质。详细内容,请参见阿里云信任中心