作者 | 小谦
责编 | 郭芮
聊天宝、多闪、马桶MT围剿微信,谁会更有胜算?
2019年1月15日,这注定是一个载入互联网发展历史的重要时刻。仅仅一天时间内,三家企业接连召开社交产品的发布会,并且各自都能自圆其说地讲出自己的优势,这让不少吃瓜群众看着都替微信捏了一把汗。
挑战微信甚至说挑战腾讯社交地位的权威,这个“公益运动”从来就没有停下来过。不管是米聊、易信、钉钉、陌陌、微博,还是足记、探探、Soul、音遇、唱吧,大家都希望可以改变中国互联网社交领域的市场格局。
可时至今日,除了钉钉从企业社交领域让微信感受到巨大压力以外,其他的挑战者几乎都是以失败告终。
作为备受关注的三款社交产品,笔者认为聊天宝、马桶MT、快闪目前的状态都还不足以挑战微信,尤其是在产品上还缺乏足够的新意。不过,在互联网行业,通过小步快跑迭代而成功的产品其实也不少,聊天宝、马桶MT、快闪会不会就是这样的产品呢?
今天,笔者就分别来谈下自己对于这三款产品的一些看法,和大家一起聊聊谁未来或许更有可能夺走微信的一些市场机会。
罗永浩的聊天宝:很难改变昙花一现的命运
当老师,做演讲,做网红,甚至做手机,罗永浩都是专业的。不错,从2012年5月到今天,接近7年的时间,跨界的罗永浩一直在研究手机,整个中国又有多少人可以比罗永浩真正更懂、更了解手机?
在近期热议的互联网创业纪录片《燃点》中,罗永浩跨界做手机的案例对创业者来说反而是教训。跨界6-7年才真正对这个新的领域有了足够成功的理解,这并不是一般人熬得住的。
正如笔者此前的文章所言:生不逢时的锤子手机纵然依旧还有实力拿出深受铁粉支持的产品,但最终想要大成的可能性依旧还是很渺茫了。
对于罗永浩来说,跨界做手机已经如此之难,跨界做社交又是另外一码事了。这可能会是一件比做手机更难的事情,难道罗永浩还准备跨界学习6-7年学习做社交?显然,锤子科技当下的生存危机,不会给罗永浩太多时间,这让聊天宝先天上就存在发展障碍。
根据官方的相关资料显示,在聊天宝中,用户可以通过聊天、阅读、购物、玩游戏等方式“领钱”。这种靠金钱来推广用户的手段,就是属于一种名为用户补贴的用户增长模式。不管是ofo、摩拜也好,滴滴、趣头条也罢,你会发现用户补贴这种模式对用户增长确实有非常大的帮助。但是,为什么ofo、摩拜最终没能向滴滴、趣头条他们一样在各自领域成为市场黑马?
因为,补贴模式能低成本获客不假,但如果只是传统的、纯粹的补贴模式,并不能真正对既有对产品和产业形成挑战和服务创新。
一方面,早在趣头条之前,由南方报业传媒集团鼎力支持的并读新闻就以“看新闻赚钱”的概念在赚钱,但最终却没能在市场上产生太多水花。这是一个纯补贴模式失败的案例。
趣头条的成功,补贴模式只是一个基本条件,关键还是在于其以积分+用户激励建立了强运营的体系,通过积分的延展性在用户增长和用户持续运营方面建立了壁垒。这是一种用户补贴模式的创新,如果聊天宝只是纯粹的补贴,那这不过是一种花钱买用户的途径,这和并读、派派这类不温不火的产品有何区别呢?
另外一方面,聊天宝将聊天、阅读、购物、玩游戏等互动形式都可以变成让用户赚钱的机会。且不说这四个类目是否都适用于用户激励的模式,哪个业务都没有非常稳健的内容根基、市场先机的情况下,别说这四个业务被聊天宝做成正向的现金流,能做成功一个都并不容易。
如今大部分用户的社交关系都在微信上,如果缺乏正向的现金流或者可以持续地让用户稳定赚钱,没等到聊天宝火热,不少用户或许就很难被非常细微的利益吸引,反而重新回到微信了。
同时,在微信已经将聊天宝封杀的情况下,聊天宝用户的最大来源或许就是以罗永浩个人为中心通过营销造势带来的用户。罗永浩上一个超级网红大IP,真正愿意支持他的或许只是那些锤子手机铁粉,但哪些经济基础不错的锤子手机用户会被聊天宝的细微受益所吸引?
因此,不管是产品还是用户增长模式和渠道上,聊天宝都不太具备一个迅速崛起的条件。尤其是做子弹短信此前积累的日活规模还非常少的情况下,聊天宝如果是按照现在这种机制和产品发展,或许最终难逃昙花一现的结局。
马桶MT:小众社交或为其区块链产品打铺垫
有人说,微信朋友圈看不到的内容,在王欣的马桶MT上或许就可以看到。这是个好事,但可能也局限着马桶MT的未来。
出狱一年多,除了获得3000万美元天使轮融资以外,王欣真心的异常低调。作为王欣出狱后首次高调推出的产品,依托于王欣对于产品、人性的理解和把控能力,马桶MT成功的概率确实比聊天宝要高一些。
但从定位这些来看,马桶MT真正要成为主流还真不容易。
根据马桶MT公开的资料显示,马桶MT是一个人脉暗网,是朋友圈的影子,所有微信上看不到的听不到的,甚至是被删除的内容都可能出现在这里。在马桶MT里,你可以发起打探话题,向你的好友匿名打探秘密;也可以随心所欲匿名吐槽八卦,还可以收发红包。
从QQ群聊开始到无秘、乌鸦、几度等匿名社交前辈,匿名的形式成为用户的免责金牌,卸下表达包袱的同时却也放大了他们的欲望、压低了原则底线。
以国外的情况举例,不管是阅后即焚还是匿名聊天,都和法律所不允许的内容息息相关,尤其以色情内容闻名。
虽然王欣公开表示:马桶MT考虑到了敏感政治/色情的风险隐患,他们会进行人工智能的过滤和人工干预。但如果马桶MT不能提供其他社交平台所不能聊的内容,那大家又会被什么内容所长期吸引呢?
对马桶MT来说,如何在必然面临监管的情况下,做好用户需求和平台底线的平衡,非常关键。
同时,纵然上述这些问题不存在,匿名社交或许终归只能是一个小众社交需求,马桶MT要向大众社交领域拓展,天然就会存在问题。所以马桶MT要挑战微信的可能性非常小,但成为一款现象级的区块链产品,这确实是非常有可能的。
此前王欣刚刚出狱不久,就有媒体报道称王欣非常看好区块链,并将区块链视为帮助他拿回以前所失去的一个好技术、好工具。匿名社交,其实本身就是适合区块链的一类产品,尤其是贴上了“暗网”这样的标签后,马桶MT更是一款既用于传统互联网又适用于区块链的产品。
在目前区块链市场和政策都不是特别明朗的情况下,或许这种情况影响了王欣此前在区块链的计划。通过马桶MT积累一批匿名社交应用后,这可能就是王欣未来在区块链实际应用中的一个优势,帮助他为他去做区块链的产品积累用户数据。
根据媒体此前的报道,区块链社交 App BCM Messenger 的技术人员表示,区块链社交的优势在于去中心化,其加密算法则可帮助信息进行有效加密,从而保证信息本身的私密,并且「服务器遍布各地,一个普通用户都可作为应用的服务器,从严格意义上来讲,是没有办法把这个社交平台关掉的」。
因此,如果有一天匿名社交被区块链这类技术真正带火,那么马桶MT的威力后续真心不容小觑。但挑战微信,马桶MT或许还没这个实力。
推多闪的头条:或许是最有可能挑战微信的一个新锐力量
与马桶MT和聊天宝相比,拥有上亿日活用户的字节跳动,显然也是微信和腾讯最大的威胁。
作为一款主打年轻人视频社交的产品,多闪主要分为三个模块:消息列表、随拍、世界。消息列表中是好友的对话列表,可以发送文字、表情、点赞以及红包等,图片和视频是主打功能。
由此看来,多闪的产品功能并没有太多的亮点。不过,在目前抖音主要只能满足用户看视频和评论需求的情况下,不少抖音用户也都在纷纷地将其粉丝导入到微信中才能进行进一步的沟通。
直接可以通过抖音账号登陆的多闪,不仅可以将抖音用户目前尚未满足的社交沟通需求填充,同时也还可以将抖音账号体系应用到更多的产品中,从而更多地留住用户在今日头条的大生态中,构建起更加稳健的流量体系。
从QQ和微信此前的发展经历来看,大量的游戏和其他的APP产品,他们不仅是通过QQ和微信获得了大量的用户,同时也通过账号体系授权让QQ和微信的整体日活和黏度都有所提升。
面对头条的威胁,腾讯此前曾一连推出十多款短视频应用,深谙算法推荐的字节跳动,也悄然在学习腾讯的做法。在短视频领域,今日头条推出抖音、火山小视频、西瓜视频。如今在社交这个领域,除开多闪以外,飞聊这样传说中的社交产品或许也不久了。
这样一来,纯粹从多闪的功能来看,可能多闪不会是对微信对腾讯威胁最大的一个头条系社交产品。但以多闪所积累的数据和反馈来看,最终在不断优化问题的头条,依托于其本身所拥有的渠道优势,确实有可能成功挑战微信。
因此,整体来说,对比多闪、聊天宝、马桶MT的发展情况,虽然这三大产品可能都难以撼动微信的地位。但从未来前景和延展性来看,头条对于腾讯社交的威胁才是最大的。撼动微信的也许不会是多闪,却可能会是头条推出的其他社交产品,这是很有可能的。
作者:小谦,互联网观察员,CSDN 特约作者,多家科技媒体专栏作者,运营有个人微信号“小谦笔记”。
声明:本文为 CSDN 「畅言」栏目独家文章。作者独立观点,不代表 CSDN 立场。
「畅言」是 CSDN 公众号专门开设的评论类栏目,针对当前业界发生的大事以及行业痛点,面向所有互联网从业人士,专注于「百家争鸣,各抒己见」。我们相信观点愈辩愈明,摆此擂台,等你来战!只要你的逻辑表达清楚、专业,数据引用准确、可靠,角度独特、话题前沿深入,欢迎投稿,一起畅所欲言!
热 文 推 荐
☞ 刚刚!程序员集体荣获2个冠军,这份2018 IT报告还说这些!
☞ 云头条 | 华为云发布全新Slogan;AWS推出DocumentDB;FRB信号刷屏
Grin带火的MinbleWimble技术,到底是个什么鬼?
print_r('点个好看吧!');
var_dump('点个好看吧!');
NSLog(@"点个好看吧!");
System.out.println("点个好看吧!");
console.log("点个好看吧!");
print("点个好看吧!");
printf("点个好看吧!\n");
cout << "点个好看吧!" << endl;
Console.WriteLine("点个好看吧!");
fmt.Println("点个好看吧!");
Response.Write("点个好看吧!");
alert("点个好看吧!")
echo "点个好看吧!"
点击“阅读原文”,打开 CSDN App 阅读更贴心!



由于目前维护的项目比较老,所以想记录下在 Vue 2 项目中使用 VueUse (@vueuse/core
) 这个强大的组合式函数库, 写下这篇文章。
VueUse 主要是为 Vue 3 的 Composition API 设计的,但幸运的是,Vue 官方提供了一个插件 @vue/composition-api
,它将 Composition API 的核心功能引入到了 Vue 2.x 中。这使得我们可以在 Vue 2 项目里享受到 VueUse 带来的便利。
核心步骤:
- 安装依赖: 安装
@vue/composition-api
和@vueuse/core
。 - 注册插件: 在你的 Vue 应用入口文件 (
main.js
或main.ts
) 中注册@vue/composition-api
插件。 - 使用 Composition API: 在 Vue 组件中使用
setup()
函数来组织逻辑。 - 引入和使用 VueUse 函数: 在
setup()
函数中引入并调用所需的 VueUse 函数。
下面我们将逐步展开,并提供详尽的代码示例。
1. 安装依赖
bash 代码解读复制代码# 使用 npm
npm install @vue/composition-api @vueuse/core --save
# 或者使用 yarn
yarn add @vue/composition-api @vueuse/core
2. 注册插件 (main.js
)
你需要在使用 Vue 的任何其他插件或实例化 Vue 应用之前,先 use
这个 Composition API 插件。
javascript 代码解读复制代码// src/main.js
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api'; // 引入插件
import App from './App.vue';
// !!必须在使用其他插件或 new Vue() 之前调用 Vue.use() !!
Vue.use(VueCompositionAPI); // 注册插件
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');
3. 基本使用模式 (在 Vue 组件中)
在 Vue 2 组件中,你需要使用 @vue/composition-api
提供的 defineComponent
(推荐,尤其是在使用 TypeScript 时) 或直接在选项对象中添加 setup()
方法。setup
函数在 beforeCreate
和 created
生命周期钩子之前执行。它接收 props
和 context
(包含 attrs
, slots
, emit
) 作为参数。
从 setup
函数返回的对象会暴露给组件的模板和选项式 API (如 methods
, computed
, data
等)。
vue代码解读复制代码
import { defineComponent, ref } from '@vue/composition-api'; export default defineComponent({ // setup 函数是 Composition API 的入口 setup() { // 使用 ref 创建一个响应式引用 const count = ref(0); // 定义一个方法 const increment = () => { count.value++; // 修改 ref 的值需要通过 .value }; // 必须返回需要在模板或其他选项中使用的变量和方法 return { count, increment, }; }, // 你仍然可以像以前一样使用选项式 API, // 并且可以访问 setup 返回的内容 (通过 this) mounted() { console.log('组件挂载,计数器初始值:', this.count); // 可以访问 count }, methods: { logFromOptionsAPI() { console.log('从选项式 API 访问计数器:', this.count); // 也可以访问 this.increment(); // 也可以调用 setup 中返回的方法 } } });计数器: {{ count }}
4. 常用 VueUse 函数在 Vue 2 中的使用示例
现在,我们将展示一些常用的 VueUse 函数如何在集成了 @vue/composition-api
的 Vue 2 项目中使用。
示例 1: useMouse
- 跟踪鼠标位置
useMouse
可以轻松获取鼠标在页面或特定元素内的实时坐标。
vue代码解读复制代码
import { defineComponent, ref, reactive } from '@vue/composition-api'; import { useMouse, useMouseInElement } from '@vueuse/core'; export default defineComponent({ name: 'UseMouseExample', setup() { // 1. 跟踪整个页面的鼠标位置 // useMouse 返回一个包含 x, y, sourceType 的响应式对象 const pageMouse = reactive(useMouse()); // 使用 reactive 包裹,方便模板直接使用 // 2. 跟踪特定元素内的鼠标位置 const targetArea = ref(null); // 创建一个 ref 来引用 DOM 元素 // useMouseInElement 需要一个目标元素的 ref // 它返回更详细的信息,包括相对元素的位置 (x, y), 元素边界,是否在元素内等 const elementMouseState = reactive(useMouseInElement(targetArea)); // 为了简洁,我们只返回关心的部分 const elementMouse = reactive({ x: elementMouseState.x, y: elementMouseState.y, isOutside: elementMouseState.isOutside, elementX: elementMouseState.elementX, // 元素左上角相对于视口的 X elementY: elementMouseState.elementY, // 元素左上角相对于视口的 Y elementPositionX: elementMouseState.elementPositionX, // 元素左边缘相对于文档的 X elementPositionY: elementMouseState.elementPositionY, // 元素上边缘相对于文档的 Y elementHeight: elementMouseState.elementHeight, // 元素高度 elementWidth: elementMouseState.elementWidth // 元素宽度 }); // 3. 仅在按下鼠标左键时跟踪 (高级用法) const leftClickMouse = reactive(useMouse({ touch: false, type: 'page', mouseButton: 'left' })); // 必须返回所有需要在模板中使用的数据和 ref return { pageMouse, targetArea, // 需要把 ref 返回给模板,以便关联 DOM 元素 elementMouse, leftClickMouse, }; }, }); .mouse-tracker-container { padding: 20px; border: 1px solid #ccc; margin-bottom: 20px; } .target-area { width: 300px; height: 200px; background-color: lightblue; padding: 10px; margin-top: 10px; position: relative; /* useMouseInElement 经常需要这个 */ border: 1px solid blue; } pre { background-color: #f5f5f5; padding: 8px; border-radius: 4px; font-size: 0.9em; overflow-x: auto; } hr { margin: 20px 0; }useMouse 示例
跟踪整个页面的鼠标位置:
{{ JSON.stringify(pageMouse, null, 2) }}X: {{ pageMouse.x }}, Y: {{ pageMouse.y }}
Source Type: {{ pageMouse.sourceType }}
跟踪下方蓝色区域内的鼠标位置 (相对该区域):
将鼠标移到这里
相对位置: {{ JSON.stringify(elementMouse, null, 2) }}Rel X: {{ elementMouse.x }}, Rel Y: {{ elementMouse.y }}
只在按下左键时跟踪:
{{ JSON.stringify(leftClickMouse, null, 2) }}按下左键移动试试看...
示例 2: useLocalStorage
- 与 LocalStorage 同步的 Ref
useLocalStorage
创建一个 ref,它的值会自动与浏览器的 LocalStorage 同步。
vue代码解读复制代码
import { defineComponent } from '@vue/composition-api'; import { useLocalStorage } from '@vueuse/core'; export default defineComponent({ name: 'UseLocalStorageExample', setup() { // 1. 基本用法:同步一个字符串 // 第一个参数是 LocalStorage 的键名 // 第二个参数是初始值(如果 localStorage 中没有对应项) const syncedData = useLocalStorage('my-vue2-key', '默认值'); const resetData = () => { syncedData.value = ''; // 设置为空字符串来清空 // 或者 syncedData.value = null; 如果你希望 localStorage 中存储 'null' // 或者 syncedData.value = undefined; 会从 localStorage 移除该项 }; // 2. 使用对象和自定义序列化器 (默认使用 JSON) // 如果初始值是一个对象或数组,useLocalStorage 会自动使用 JSON.stringify/parse const userInfo = useLocalStorage('user-info-vue2', { name: '匿名用户', age: 18, preferences: { theme: 'light' } }); const clearUserInfo = () => { // 设置为 null 或 undefined 都会移除 localStorage 中的项 userInfo.value = null; // 如果想置为初始空对象结构,可以: // userInfo.value = { name: '', age: 0, preferences: {} }; }; // 返回需要在模板中使用的数据 return { syncedData, resetData, userInfo, clearUserInfo }; }, mounted() { // 可以在这里观察 localStorage 的变化 window.addEventListener('storage', (event) => { if (event.key === 'my-vue2-key' || event.key === 'user-info-vue2') { console.log(`Storage changed for key "${event.key}":`, event.newValue); // 注意:useLocalStorage 已经处理了响应式更新,这里只是演示监听原生事件 } }); } }); .local-storage-container { padding: 20px; border: 1px solid #ccc; margin-bottom: 20px; } label { margin-right: 5px; font-weight: bold; } input[type="text"], input[type="number"] { margin-right: 10px; padding: 5px; border: 1px solid #ddd; } button { margin-left: 10px; padding: 5px 10px; cursor: pointer; } pre { background-color: #f5f5f5; padding: 8px; border-radius: 4px; margin-top: 10px; white-space: pre-wrap; word-wrap: break-word; } hr { margin: 20px 0; } small { display: block; margin-top: 5px; color: #666; }useLocalStorage 示例
这个输入框的值会实时同步到 LocalStorage (键: 'my-vue2-key')。
关闭浏览器标签页或刷新页面后,值仍然存在。
当前 LocalStorage ('my-vue2-key') 中的值:
{{ syncedData || '(空)' }}
使用对象和序列化器:
用户信息 (存储为 JSON 字符串):
{{ JSON.stringify(userInfo, null, 2) }}刷新页面,用户信息也会保留。
示例 3: useNetwork
- 检测网络状态
useNetwork
提供关于用户网络连接的信息,如是否在线、连接类型等。
vue代码解读复制代码
import { defineComponent, reactive, computed } from '@vue/composition-api'; import { useNetwork } from '@vueuse/core'; export default defineComponent({ name: 'UseNetworkExample', setup() { // useNetwork 返回一个包含网络状态信息的响应式对象 const networkState = reactive(useNetwork()); // 为了模板可读性,可以创建一些计算属性或直接解构 (如果用 reactive 包裹了) const isOnline = computed(() => networkState.isOnline); const offlineAt = computed(() => networkState.offlineAt); const downlinkMax = computed(() => networkState.downlinkMax); const effectiveType = computed(() => networkState.effectiveType); const rtt = computed(() => networkState.rtt); const saveData = computed(() => networkState.saveData); const networkType = computed(() => networkState.type); // `type` 是原生属性名 const isSupported = computed(() => networkState.isSupported); // VueUse 通常会自动更新,但如果需要手动触发,可以这样做 (虽然很少需要) const forceUpdateInfo = () => { // useNetwork 内部没有直接暴露 update 方法,它是事件驱动的 // 这个按钮主要是为了演示目的,实际中网络变化会自动触发更新 console.log('手动检查并不能直接触发 useNetwork 更新,它是基于浏览器事件的。'); // 如果你想强制重新评估,可以改变依赖项,但这不适用于 useNetwork }; return { networkState, // 也可以直接返回整个对象 isOnline, offlineAt, downlinkMax, effectiveType, rtt, saveData, networkType, isSupported, forceUpdateInfo, }; }, }); .network-status-container { padding: 20px; border: 1px solid #ccc; margin-bottom: 20px; } .online { color: green; font-weight: bold; } .offline { color: red; font-weight: bold; } pre { background-color: #f5f5f5; padding: 8px; border-radius: 4px; margin-top: 10px; font-size: 0.9em; overflow-x: auto; } small { display: block; margin-top: 5px; color: #666; } button { margin-top: 10px; padding: 5px 10px; }useNetwork 示例
实时检测你的网络连接状态。
当前状态: {{ isOnline ? '在线 (Online)' : '离线 (Offline)' }}上次离线时间: {{ offlineAt ? new Date(offlineAt).toLocaleString() : 'N/A' }}
网络类型 (估计): {{ networkType }}
有效连接类型 (估计): {{ effectiveType }}
下行最大比特率 (Mbps, 估计): {{ downlinkMax === Infinity ? '未知' : downlinkMax }}
往返时间 (RTT, ms, 估计): {{ rtt }}
数据保护模式是否开启: {{ saveData ? '是' : '否' }}
提示: 尝试在浏览器开发者工具中切换在线/离线状态,观察变化。
你的浏览器不支持 Network Information API。
原始数据: {{ JSON.stringify(networkState, null, 2) }}
示例 4: useClipboard
- 与剪贴板交互
useClipboard
提供了读取和写入系统剪贴板的功能。注意:剪贴板 API 通常需要用户交互(如点击按钮)才能触发,并且可能需要 HTTPS 环境或特定的浏览器权限。
vue代码解读复制代码
import { defineComponent, ref, watch } from '@vue/composition-api'; import { useClipboard } from '@vueuse/core'; export default defineComponent({ name: 'UseClipboardExample', setup() { const sourceText = ref('你好,VueUse in Vue 2!'); const clipboardText = ref(''); // 用于存储读取到的内容 const copyError = ref(null); const readError = ref(null); // 1. 基本复制功能 // useClipboard 返回响应式的 text, copied, isSupported, 和 copy 方法 const { text, copy, copied, isSupported, error } = useClipboard({ source: sourceText }); // 监视复制操作的错误 watch(error, (newError) => { copyError.value = newError; if (newError) console.error('复制时出错:', newError); }); const copyText = async () => { copyError.value = null; // 重置错误状态 // copy(sourceText.value) // 也可以不通过 source 选项,直接调用 copy 方法传递值 await copy(); // 调用 copy 方法,它会复制 source (即 sourceText) 的当前值 // copied ref 会自动更新 }; // 2. 读取剪贴板 (这是一个异步操作,可能需要用户权限) // navigator.clipboard.readText() 返回 Promise const readFromClipboard = async () => { readError.value = null; // 重置错误状态 try { // 注意:useClipboard 本身不直接提供 read 方法,需要使用原生的 API if (navigator.clipboard && navigator.clipboard.readText) { clipboardText.value = await navigator.clipboard.readText(); console.log('剪贴板内容已读取:', clipboardText.value); } else { throw new Error('浏览器不支持读取剪贴板或缺少权限。'); } } catch (err) { readError.value = err; clipboardText.value = ''; // 清空显示 console.error('读取剪贴板失败:', err); } }; // 3. 遗留模式示例 (当 navigator.clipboard 不可用时,useClipboard 会尝试 document.execCommand) const legacyText = ref('这是用于旧版复制的文本'); // 可以为不同的复制操作创建不同的 useClipboard 实例 const { copy: copyLegacy, copied: legacyCopied } = useClipboard({ source: legacyText, legacy: true }); return { sourceText, copyText, copied, // 显示复制状态 isSupported, copyError, clipboardText, readFromClipboard, readError, legacyText, copyLegacy, legacyCopied, }; }, }); .clipboard-container { padding: 20px; border: 1px solid #ccc; margin-bottom: 20px; } input[type="text"] { margin-right: 10px; padding: 5px; min-width: 200px; } button { padding: 5px 10px; cursor: pointer; margin-right: 10px; } button:disabled { cursor: not-allowed; opacity: 0.6; } pre { background-color: #f5f5f5; padding: 8px; border-radius: 4px; margin-top: 10px; white-space: pre-wrap; word-wrap: break-word; } small { display: block; margin-bottom: 10px; color: #666; } .error-text { color: red; font-size: 0.9em; margin-left: 10px; } hr { margin: 20px 0; } code { background-color: #eee; padding: 2px 4px; border-radius: 3px; }useClipboard 示例
注意:剪贴板操作可能需要用户授权或在安全上下文 (HTTPS) 中运行。
写入剪贴板
{{ copied ? '已复制!' : '复制' }} 复制失败: {{ copyError.message }}读取剪贴板 (需要权限)
读取到的内容:
{{ clipboardText || '(尚未读取或权限不足)' }}读取失败: {{ readError.message }}
遗留模式 (不支持 Navigator API 时备用)
文本内容:
{{ legacyCopied ? '已复制 (Legacy)!' : '复制 (Legacy)' }}{{ legacyText }}
你的浏览器不支持 Clipboard API。
示例 5: useDebounceFn
和 useThrottleFn
- 函数防抖与节流
这两个函数用于控制函数的执行频率,常用于性能优化,如处理输入事件、窗口大小调整等。
useDebounceFn
: 在事件触发后等待指定时间,若期间没有再次触发,则执行函数。useThrottleFn
: 在指定时间间隔内最多执行一次函数。
vue代码解读复制代码
import { defineComponent, ref } from '@vue/composition-api'; import { useDebounceFn, useThrottleFn } from '@vueuse/core'; export default defineComponent({ name: 'UseDebounceThrottleExample', setup() { // --- 防抖 --- const debouncedInput = ref(''); const lastDebouncedValue = ref(''); const debouncedCallCount = ref(0); // 创建防抖函数 const debouncedLog = useDebounceFn((value) => { console.log('防抖触发:', value); lastDebouncedValue.value = value; debouncedCallCount.value++; }, 500); // 500ms 延迟 const handleDebouncedInput = (event) => { // 每次输入都调用防抖函数,但它内部会处理延迟执行 debouncedLog(event.target.value); }; // --- 节流 --- const throttledCallCount = ref(0); const lastThrottledTime = ref(null); // 创建节流函数 const throttledHandler = useThrottleFn(() => { console.log('节流触发!'); throttledCallCount.value++; lastThrottledTime.value = new Date().toLocaleTimeString(); }, 1000); // 1000ms 间隔 const throttledClickHandler = () => { // 每次点击都调用节流函数,但它内部会限制执行频率 throttledHandler(); }; // --- 节流 (带尾随调用) --- const throttledTrailingCallCount = ref(0); const lastThrottledTrailingTime = ref(null); // 创建节流函数 (trailing: true) const throttledTrailingHandler = useThrottleFn(() => { console.log('节流 (带尾随) 触发!'); throttledTrailingCallCount.value++; lastThrottledTrailingTime.value = new Date().toLocaleTimeString(); }, 1000, true); // 第三个参数 true 开启尾随调用 const throttledTrailingClickHandler = () => { throttledTrailingHandler(); }; return { debouncedInput, handleDebouncedInput, lastDebouncedValue, debouncedCallCount, throttledClickHandler, throttledCallCount, lastThrottledTime, throttledTrailingClickHandler, throttledTrailingCallCount, lastThrottledTrailingTime }; }, }); .debounce-throttle-container { padding: 20px; border: 1px solid #ccc; margin-bottom: 20px; } input[type="text"] { margin-right: 10px; padding: 5px; width: 250px; } button { padding: 8px 15px; cursor: pointer; } pre { background-color: #f5f5f5; padding: 8px; border-radius: 4px; margin-top: 5px; display: inline-block; /* 让 pre 不占满整行 */ min-height: 1.5em; /* 给点高度防止空的时候塌陷 */ vertical-align: middle; /* 与旁边的文字对齐 */ } hr { margin: 25px 0; } p { margin-bottom: 10px; }useDebounceFn & useThrottleFn 示例
防抖 (Debounce) - 500ms
输入时,只有停止输入 500ms 后才会触发日志记录。
最后触发的输入值 (防抖后):
{{ lastDebouncedValue }}触发次数: {{ debouncedCallCount }}
节流 (Throttle) - 1000ms
快速点击按钮,函数最多每 1000ms 执行一次。
节流函数触发次数: {{ throttledCallCount }}
最后触发时间: {{ lastThrottledTime || 'N/A' }}
节流 (Throttle) - 带有尾随调用 (trailing: true)
快速点击按钮,函数最多每 1000ms 执行一次,并且在最后一次触发后如果处于冷却期,还会再执行一次。
节流 (尾随) 函数触发次数: {{ throttledTrailingCallCount }}
最后触发时间: {{ lastThrottledTrailingTime || 'N/A' }}
示例 6: useDateFormat
和 useNow
- 时间格式化与实时时间
useNow
: 提供一个响应式的当前时间 ref,可以配置更新间隔。useDateFormat
: 格式化日期或时间戳。
vue代码解读复制代码
import { defineComponent, ref, computed } from '@vue/composition-api'; import { useNow, useDateFormat } from '@vueuse/core'; export default defineComponent({ name: 'UseTimeExample', setup() { // --- useNow --- // 1. 默认更新 (每秒) const now = useNow(); // 结合 useDateFormat 格式化 useNow 返回的响应式时间 const formattedNow = useDateFormat(now, 'YYYY-MM-DD HH:mm:ss'); // 2. 自定义更新间隔 (每 5000ms) const slowNow = useNow({ interval: 5000 }); const formattedSlowNow = useDateFormat(slowNow, 'HH:mm:ss'); // 3. 使用 requestAnimationFrame 更新 (高频) const fastNow = useNow({ interval: 'requestAnimationFrame' }); const formattedFastNow = useDateFormat(fastNow, 'HH:mm:ss.SSS'); // 显示毫秒 // --- useDateFormat --- const fixedDate = new Date(2024, 0, 1, 10, 30, 0); // 2024年1月1日 10:30:00 (注意月份从0开始) const timestamp = 1678886400000; // 一个时间戳 (例如: 2023-03-15T13:20:00Z) const reactiveDate = ref(new Date()); // 一个响应式的 Date 对象 // 格式化固定日期 const format1 = useDateFormat(fixedDate, 'YYYY-MM-DD HH:mm:ss'); const format2 = useDateFormat(fixedDate, 'dddd, MMMM D, YYYY'); // 星期几, 月份全称 日, 年份 const format3 = useDateFormat(fixedDate, 'short'); // 预设格式 // 自定义格式,使用方括号 [] 来转义字符 const formatCustom = useDateFormat(fixedDate, '[Year:] YYYY [Month:] MM'); // 格式化时间戳 const formatTimestamp = useDateFormat(timestamp, 'YYYY/MM/DD'); // 格式化响应式日期 (当 reactiveDate.value 变化时,格式化结果也会自动更新) const formatReactiveDate = useDateFormat(reactiveDate, 'DD-MMM-YYYY HH:mm'); const changeReactiveDate = () => { reactiveDate.value = new Date(Date.now() + 24 * 60 * 60 * 1000); // 设置为明天的当前时间 }; return { now, formattedNow, slowNow, formattedSlowNow, fastNow, formattedFastNow, fixedDate, // 仅用于显示原始日期 timestamp, // 仅用于显示原始时间戳 format1, format2, format3, formatCustom, formatTimestamp, reactiveDate, // 原始响应式日期 ref formatReactiveDate, changeReactiveDate, }; }, }); .time-container { padding: 20px; border: 1px solid #ccc; margin-bottom: 20px; } p { margin-bottom: 8px; } hr { margin: 20px 0; } button { padding: 5px 10px; margin-top: 5px; }useNow & useDateFormat 示例
实时时间 (useNow)
useNow 默认每秒更新一次。
当前时间 (原始): {{ now }}
当前时间 (格式化): {{ formattedNow }}
不同更新频率的 useNow
每 5 秒更新一次: {{ slowNow }} (格式化: {{ formattedSlowNow }})
高频更新 (每帧动画): {{ fastNow }} (格式化: {{ formattedFastNow }})
使用 useDateFormat 格式化固定日期
固定日期: {{ fixedDate }}
格式 'YYYY-MM-DD HH:mm:ss': {{ format1 }}
格式 'dddd, MMMM D, YYYY': {{ format2 }}
格式 'short': {{ format3 }}
格式 '自定义 [Year:] YYYY [Month:] MM': {{ formatCustom }}
格式化时间戳 {{ timestamp }}: {{ formatTimestamp }}
格式化响应式日期 {{ reactiveDate }}: {{ formatReactiveDate }}
示例 7: useFullscreen
- 控制元素全屏
useFullscreen
允许你轻松地进入或退出一个元素(或整个页面)的全屏模式。
vue代码解读复制代码
import { defineComponent, ref } from '@vue/composition-api'; import { useFullscreen } from '@vueuse/core'; export default defineComponent({ name: 'UseFullscreenExample', setup() { // 1. 控制整个页面 (document.documentElement) // 不传参数时,默认目标是 document.documentElement const { isSupported: isPageFsSupported, // 重命名以区分 isFullscreen: isPageFullscreen, enter: enterPageFullscreen, exit: exitPageFullscreen, toggle: togglePageFullscreen, // ...还有 error 事件等,这里省略 } = useFullscreen(); // 如果需要捕获错误,可以添加 { onError: (e) => console.error(e) } // 2. 控制特定元素 const elementTarget = ref(null); // Ref 引用目标 DOM 元素 const { isSupported: isElementFsSupported, // 一般与页面支持情况相同 isFullscreen: isElementFullscreen, enter: enterElementFullscreen, exit: exitElementFullscreen, toggle: toggleElementFullscreen, } = useFullscreen(elementTarget); // 将 ref 传入 useFullscreen // 通常 isSupported 对两者来说是相同的,所以我们可以用一个统一的 const isFullscreenSupported = isPageFsSupported; // 或 isElementFsSupported // 你也可以直接返回整个 useFullscreen 返回的对象,方便调试 // const pageFsState = useFullscreen(); // const elementFsState = useFullscreen(elementTarget); return { isFullscreenSupported, isPageFullscreen, togglePageFullscreen, elementTarget, // 必须返回 ref 以便模板关联 isElementFullscreen, toggleElementFullscreen, // pageFsState, // (调试用) // elementFsState, // (调试用) }; }, }); .fullscreen-container { padding: 20px; border: 1px solid #ccc; margin-bottom: 20px; } .fullscreen-element { width: 300px; height: 150px; padding: 20px; background-color: lightcoral; color: white; margin-top: 10px; border: 2px solid darkred; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; } /* 当元素全屏时的特殊样式(可选) */ .fullscreen-element:fullscreen { background-color: coral; font-size: 1.5em; } /* 针对 Webkit 浏览器 (如 Chrome, Safari) */ .fullscreen-element:-webkit-full-screen { background-color: coral; font-size: 1.5em; } /* 针对 Firefox */ .fullscreen-element:-moz-full-screen { background-color: coral; font-size: 1.5em; } /* 针对 Edge 和 IE */ .fullscreen-element:-ms-fullscreen { background-color: coral; font-size: 1.5em; } button { padding: 8px 15px; cursor: pointer; margin-top: 10px; } hr { margin: 20px 0; } small { display: block; margin-bottom: 10px; color: #666; } pre { background-color: #f5f5f5; padding: 8px; border-radius: 4px; margin-top: 10px; font-size: 0.9em; overflow-x: auto; }useFullscreen 示例
全屏 API 的可用性和行为可能因浏览器而异。
控制整个页面全屏
{{ isPageFullscreen ? '退出页面全屏' : '进入页面全屏' }}当前页面是否全屏: {{ isPageFullscreen }}
控制特定元素全屏
点击下方按钮,让这个蓝色区域全屏显示。
这是一个可以全屏的目标元素。
{{ isElementFullscreen ? '退出元素全屏' : '进入元素全屏' }}该元素是否全屏: {{ isElementFullscreen }}
你的浏览器不支持 Fullscreen API。
5. 在App.vue
中使用这些组件
vue代码解读复制代码
import UseMouseExample from './components/UseMouseExample.vue'; import UseLocalStorageExample from './components/UseLocalStorageExample.vue'; import UseNetworkExample from './components/UseNetworkExample.vue'; import UseClipboardExample from './components/UseClipboardExample.vue'; import UseDebounceThrottleExample from './components/UseDebounceThrottleExample.vue'; import UseTimeExample from './components/UseTimeExample.vue'; import UseFullscreenExample from './components/UseFullscreenExample.vue'; export default { name: 'App', components: { UseMouseExample, UseLocalStorageExample, UseNetworkExample, UseClipboardExample, UseDebounceThrottleExample, UseTimeExample, UseFullscreenExample, } } #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; margin: 20px; } h1, h3 { margin-bottom: 15px; }Vue 2 + @vue/composition-api + VueUse 示例
注意事项与总结
@vue/composition-api
是关键: 没有这个插件,Vue 2 无法理解setup()
和其他 Composition API 功能,也就无法使用 VueUse。务必正确安装和注册。setup()
函数: 所有的 VueUse 函数调用都应该在setup()
函数内部进行。- 响应式: VueUse 函数通常返回响应式的数据(Refs 或 Reactive Objects)。你需要将它们从
setup
return 出去,才能在模板中使用。修改 Ref 时记得使用.value
。 - DOM Refs: 某些 VueUse 函数(如
useMouseInElement
,useFullscreen
)需要引用 DOM 元素。你需要使用@vue/composition-api
提供的ref(null)
创建一个 ref,在模板中通过ref="elementRefName"
绑定到元素上,并将这个 ref 从setup
返回。 - 生命周期: Composition API 有自己的生命周期钩子(如
onMounted
,onUnmounted
等),它们也由@vue/composition-api
提供。VueUse 内部通常会使用这些钩子来设置和清理事件监听器等。 - 兼容性: 虽然
@vue/composition-api
尽力模拟 Vue 3 的行为,但可能存在微小的差异或边缘情况。大多数@vueuse/core
函数在 Vue 2 中都能良好工作,因为它们主要依赖 Composition API 的核心功能。如果遇到问题,查阅 VueUse 和@vue/composition-api
的文档或 GitHub Issues 可能会有帮助。 - 按需引入: VueUse 支持摇树优化 (Tree Shaking),所以只引入你实际使用的函数,可以有效减小最终打包体积。
评论记录:
回复评论: