范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

封装几个Vue3中很有用的组合式API

  useRequest背景
  使用hook来封装一组数据的操作是很容易的,例如下面的 useBook import {ref, onMounted} from "vue"  function fetchBookList() {     return new Promise((resolve) => {         setTimeout(() => {             resolve([1, 2, 3])         }, 1000)     }) }  export function useBook() {     const list = ref([])     const loading = ref(false)     const getList = async () => {         loading.value = true         const data = await fetchBookList({page: 1})         loading.value = false         list.value = data     }      onMounted(() => {         getList()     })      return {         list,         loading,         getList     } }
  其中封装了获取资源、处理加载状态等逻辑,看起来貌似能满足我们的需求了
  缺点在于对应另外一个资源而言,我们貌似还需要写类似的模板代码,因此可以将这一堆代码进行抽象,封装成 useApi 方法实现function useApi(api) {     const loading = ref(false)     const result = ref(null)     const error = ref(null)      const fetchResource = (params) => {         loading.value = true         return api(params).then(data => {             // 按照约定,api返回的结果直接复制给result             result.value = data         }).catch(e => {             error.value = e         }).finally(() => {             loading.value = false         })     }     return {         loading,         error,         result,         fetchResource     } }
  然后修改上面的 useBook 方法function useBook2() {     const {loading, error, result, fetchResource,} = useApi(fetchBookList)      onMounted(() => {         fetchResource({page: 1})     })      return {         loading,         error,         list: result     } }
  注意这是一个非常通用的方法,假设现在需求封装其他的请求,处理起来也是非常方便的,不需要再一遍遍地处理loading和error等标志量 function fetchUserList() {     return new Promise((resolve) => {         setTimeout(() => {             const payload = {                 code: 200,                 data: [11, 22, 33],                 msg: "success"             }             resolve(payload)         }, 1000)     }) }  function useUser() {     const {loading, error, result, fetchResource,} = useApi((params) => {         // 封装请求返回值         return fetchUserList(params).then(res => {             console.log(res)             if (res.code === 200) {                 return res.data             }             return []         })     })     // ... } 思考
  处理网络请求是前端工作中十分常见的问题,处理上面列举到的加载、错误处理等,还可以包含去抖、节流、轮询等各种情况,还有离开页面时取消未完成的请求等,都是可以在 useRequest 中进一步封装的useEventBus
  EventBus在多个组件之间进行事件通知的场景下还是比较有用的,通过监听事件和触发事件,可以在订阅者和发布者之间解耦,实现一个常规的eventBus也比较简单 class EventBus {     constructor() {         this.eventMap = new Map()     }      on(key, cb) {         let handlers = this.eventMap.get(key)         if (!handlers) {             handlers = []         }         handlers.push(cb)         this.eventMap.set(key, handlers)     }      off(key, cb) {         const handlers = this.eventMap.get(key)         if (!handlers) return         if (cb) {             const idx = handlers.indexOf(cb)             idx > -1 && handlers.splice(idx, 1)             this.eventMap.set(key, handlers)         } else {             this.eventMap.delete(key)         }     }      once(key, cb) {         const handlers = [(payload) => {             cb(payload)             this.off(key)         }]         this.eventMap.set(key, handlers)     }      emit(key, payload) {         const handlers = this.eventMap.get(key)         if (!Array.isArray(handlers)) return         handlers.forEach(handler => {             handler(payload)         })     } }
  我们在组件初始化时监听事件,在交互时触发事件,这些是很容易理解的;但很容易被遗忘的是,我们还需要在组件卸载时取消事件注册,释放相关的资源。
  因此可以封装一个 useEventBus 接口,统一处理这些逻辑实现
  既然要在组件卸载时取消注册的相关事件,简单的实现思路是:只要在注册时( on 和once )收集相关的事件和处理函数,然后在onUnmounted 的时候取消(off )收集到的这些事件即可
  因此我们可以劫持事件注册的方法,同时额外创建一个 eventMap 用于收集使用当前接口注册的事件// 事件总线,全局单例 const bus = new EventBus()  export default function useEventBus() {     let instance = {         eventMap: new Map(),         // 复用eventBus事件收集相关逻辑         on: bus.on,         once: bus.once,         // 清空eventMap         clear() {             this.eventMap.forEach((list, key) => {                 list.forEach(cb => {                     bus.off(key, cb)                 })             })             eventMap.clear()         }     }     let eventMap = new Map()     // 劫持两个监听方法,收集当前组件对应的事件     const on = (key, cb) => {         instance.on(key, cb)         bus.on(key, cb)     }     const once = (key, cb) => {         instance.once(key, cb)         bus.once(key, cb)     }      // 组件卸载时取消相关的事件     onUnmounted(() => {         instance.clear()     })     return {         on,         once,         off: bus.off.bind(bus),         emit: bus.emit.bind(bus)     } }
  这样,当组价卸载时也会通过 instance.clear 移除该组件注册的相关事件,比起手动在每个组件onUnmounted 时手动取消要方便很多。思考
  这个思路可以运用在很多需要在组件卸载时执行清理操作的逻辑,比如: DOM事件注册 addEventListener 和removeEventListener 计时器 setTimeout 和clearTimeout 网络请求 request 和abort
  从这个封装也可以看见组合API一个非常明显的优势:尽可能地抽象公共逻辑,而无需关注每个组件具体的细节 useModel
  参考: hox源码 [2] 背景
  当掌握了Hook(或者Composition API)之后,感觉万物皆可hook,总是想把数据和操作这堆数据的方法封装在一起,比如下面的计数器 function useCounter() {     const count = ref(0)     const decrement = () => {         count.value--     }     const increment = () => {         count.value++     }     return {         count,         decrement,         increment     } }
  这个 useCounter 暴露了获取当前数值count、增加数值decrement和减少数值increment等数据和方法,然后就可以在各个组件中愉快地实现计数器了
  在某些场景下我们希望多个组件可以共享同一个计数器,而不是每个组件自己独立的计数器。
  一种情况是使用诸如vuex等全局状态管理工具,然后修改 useCounter 的实现import {createStore} from "vuex"  const store = createStore({     state: {         count: 0     },     mutations: {         setCount(state, payload) {             state.count = payload         }     } })
  然后重新实现 useCounter export function useCounter2() {     const count = computed(() => {         return store.state.count     })     const decrement = () => {         store.commit("setCount", count.value + 1)     }     const increment = () => {         store.commit("setCount", count.value + 1)     }     return {         count,         decrement,         increment     } }
  很显然,现在的 useCounter2 仅仅只是store的state 与mutations 的封装,直接在组件中使用store也可以达到相同的效果,封装就变得意义不大;此外,如果单单只是为了这个功能就为项目增加了vuex依赖,显得十分笨重。
  基于这些问题,我们可以使用一个 useModel 来实现复用某个钩子状态的需求实现
  整个思路也比较简单,使用一个Map来保存某个hook的状态 const map = new WeakMap() export default function useModel(hook) {     if (!map.get(hook)) {         let ans = hook()         map.set(hook, ans)     }     return map.get(hook) }
  然后包装一下 useCounter export function useCounter3() {     return useModel(useCounter) }  // 在多个组件调用 const {count, decrement, increment} = useCounter3() // ... const {count, decrement, increment} = useCounter3()
  这样,在每次调用 useCounter3 时,都返回的是同一个状态,也就实现了多个组件之间的hook状态共享。思考
  userModel 提供了一种除vuex 和provide()/inject() 之外共享数据状态的思路,并且可以很灵活的管理数据与操作数据的方案,而无需将所有state放在一起或者模块下面。
  缺点在于,当不使用 useModel 包装时,useCounter 就是一个普通的hook,后期维护而言,我们很难判断某个状态到底是全局共享的数据还是局部的数据。
  因此在使用 useModel 处理hook的共享状态时,还要要慎重考虑一下到底合不合适。useReducer
  redux的思想可以简单概括为 store维护全局的state数据状态, 各个组件可以按需使用state中的数据,并监听state的变化 reducer 接收action并返回新的state,组件可以通过dispatch 传递action触发reducerstate更新后,通知相关依赖更新数据
  我们甚至可以将redux的使用hook化,类似于 function reducer(state, action){     // 根据action进行处理     // 返回新的state } const initialState = {} const {state, dispatch} = useReducer(reducer, initialState); 实现
  借助于Vue的数据响应系统,我们甚至不需要实现任何发布和订阅逻辑 import {ref} from "vue"  export default function useReducer(reducer, initialState = {}) {     const state = ref(initialState)      // 约定action格式为 {type:string, payload: any}     const dispatch = (action) => {         state.value = reducer(state.value, action)     }     return {         state,         dispatch     } }
  然后实现一个 useRedux 负责传递reducer 和action import useReducer from "./index"  function reducer(state, action) {     switch (action.type) {         case "reset":             return initialState;         case "increment":             return {count: state.count + 1};         case "decrement":             return {count: state.count - 1};     } }  function useStore() {     return useReducer(reducer, initialState); }
  我们希望是维护一个全局的store,因此可以使用上面的 useModel export function useRedux() {     return useModel(useStore); }
  然后就可以在组件中使用了 
  看起来跟我们上面 useModel 的例子并没有什么区别,主要是暴露了通用的dispatch 方法,在reducer处维护状态变化的逻辑,而不是在每个useCounter中自己维护修改数据的逻辑思考
  当然这个redux是非常简陋的,包括中间件、 combineReducers 、connect 等方法均为实现,但也为我们展示了一个最基本的redux数据流转过程。useDebounce与useThrottle背景
  前端很多业务场景下都需要处理节流或去抖的场景,节流函数和去抖函数本身没有减少事件的触发次数,而是控制事件处理函数的执行来减少实际逻辑处理过程,从而提高浏览器性能。
  一个去抖的场景是:在搜索框中根据用户输入的文本搜索关联的内容并下拉展示,由于input是一个触发频率很高的事件,一般需要等到用户停止输出文本一段时间后才开始请求接口查询数据。
  先来实现最原始的业务逻辑 import {ref, watch} from "vue"  function debounce(cb, delay = 100) {     let timer     return function () {         clearTimeout(timer)         let args = arguments,             context = this         timer = setTimeout(() => {             cb.apply(context, args)         }, delay)     } } export function useAssociateSearch() {     const keyword = ref("")      const search = () => {         console.log("search...", keyword.value)         // mock 请求接口获取数据     }      // watch(keyword, search) // 原始逻辑,每次变化都请求     watch(keyword, debounce(search, 1000)) // 去抖,停止操作1秒后再请求      return {         keyword     } }
  然后在视图中引入 
  与 useApi 同理,我们可以将这个debounce的逻辑抽象出来,,封装成一个通用的useDebounce 实现useDebounce
  貌似不需要我们再额外编写任何代码,直接将 debounce 方法重命名为useDebounce 即可,为了凑字数,我们还是改装一下,同时增加cancel方法export function useDebounce(cb, delay = 100) {     const timer = ref(null)      let handler = function () {         clearTimeout(timer.value)         let args = arguments,             context = this         timer.value = setTimeout(() => {             cb.apply(context, args)         }, delay)     }      const cancel = () => {         clearTimeout(timer)         timer.value = null     }      return {         handler,         cancel     } } 实现useThrottle
  节流与去抖的封装方式基本相同,只要知道 throttle 的实现就可以了。export function useThrottle(cb, duration = 100) {     let start = +new Date()     return function () {         let args = arguments         let context = this         let now = +new Date()         if (now - start >= duration) {             cb.apply(context, args)             start = now         }     } } 思考
  从去抖/节流的形式可以看出,某些hook与我们之前的工具函数并没有十分明显的边界。是将所有代码统一hook化,还是保留原来引入工具函数的风格,这是一个需要思考和实践的问题

交管12123怎么找回密码?想登录交管12123APP却发现密码忘记了怎么办?怎么办?怎么办?别着急,下面手把手教您找回密码!1hr拨打12123热线重置交管12123APP的密码忘记了,可以直接拨打1212电信千兆宽带为什么下载不到20Mbs?网线的原因比较大,可以看看网线上的丝印标志代号你的wifi不支持千兆,换一个就好了,还有网线换成六类线就可以了。千兆光纤是指运营商给到你家网络入口的网速,测试网速的时候您应该在入口4月1日起实施!滴滴T3美团等6家网约车平台联合发布管理公约目前,网约车行业几乎没有门槛,只要你想干就能干,没证,无法注册?被淘汰,被封号,被清退?没关系,还有很多平台向你敞开大门。但是,注意了,4月1日,网约车管理公约出台,今后网约车行业Perl基础引用1引用基础在Perl中,默认的数据结构都是平面线性的,数组,散列都是一维的。那如果我们需要使用多维或者多层次的数据结构时,我们就需要使用到引用。在Perl中,存在2种类型的引用符号引用,硬引网上下载歌曲开始收钱,但腾讯音乐网易云音乐并不开心视频网站在会员付费方面有着明确的分水岭。2015年,爱奇艺上线了网络热播剧盗墓笔记,不过需要会员才能观看,瞬时涌入的流量导致爱奇艺的服务器都爆了。此后,爱奇艺开始押宝会员付费,腾讯全国机动车保有量突破4亿辆本报北京4月7日讯记者董凡超据公安部统计,截至2022年3月底,全国机动车保有量达4。02亿辆,其中汽车3。07亿辆机动车驾驶人4。87亿人,其中汽车驾驶人4。5亿人。截至3月底,golang常用库常用库fmt占位符说明v按原本的值来输出v类似v,但输出结构体是会加上字段名称v在v的基础上额外输出类型T输出类型s输出字符串或者byted表示十进制printPrintln一次输一周教你学会基本的C语言程序设计今天教给大家基本的C语言程序设计,本文用到的是cfree5。0编辑软件,电脑可以到官网下载下载地址(CFree首页优秀的CC编程开发工具(CCIDE))C语言代码要运行成功必须包括Python机器学习(五十三)SciPy特殊函数scipy。special模块中包含了一些常用的杂项函数,例如经常使用的立方根函数指数函数相对误差指数函数对数和指数函数兰伯特函数排列组合函数函数示例解立方根fromscipy。s鸿蒙系统升级会全线打通吗?今天收到系统升级提醒,打开居然是我的华为手环,难道刚升级的230版本已经开始打通各设备的互通互联?各设备都可以一键打通。这是为接下来的鸿蒙系统3。0做测试吗?这次手环的升级主要是增FileReader1FileReader是用于读取字符流。要读取原始字节流,请考虑使用FileInputStream。2FileReader构造方法1publicFileReader(Stringf
数据中台V3。6上线,提升业务数据化整体效率8月6日,云徙科技中台产品线宣布更新,正式发布云徙数据中台V3。6版本。该版本重点更新了数据工具线内的数据研发平台标签工厂和营销智能3个部分。其中1。数据研发平台1)源数据管理对业数字创新年会重磅嘉宾阿里副总裁肖利华深度讲解全速重构当前,数字化转型浪潮爆发。作为数字商业的新基建数字中台,已是各行各业落地数字化转型战略的共同选择。9月25日,今年中台赛道的第一大会2020云徙数字创新年会强势来袭!大会汇聚地产汽会员营销二次传播太难?全域会员帮你打破次员壁如今私域流量大火,忠诚且庞大的会员群体作为私域流量最好的转化渠道都是各大厂商用心呵护的心头肉。随着各种场景需求雨后丛生,厂商对于会员营销的相关需求十分强烈。9月2日,双中台领导者云云徙技术平台V3。6版本更新,后端测试效率再次提升8月6日,云徙科技宣布产品更新,正式发布技术平台V3。6版本。在技术平台V3。6版本中,数字化研发服务平台(iDP)测试管理中新增了自动化接口测试相关模块,重点包括1。支持接口自动经验不足工具弥补,营销画布预演助力市场营销提效对于商家来说,市场营销的好坏,直接决定着最终的销售业绩。在过去很长一段时间,市场营销的质量都是由市场营销人员来决定的。但是,人的经验和认知毕竟有限。一个经验再丰富的营销人,免不了也数字创新年会重磅嘉宾毕马威合伙人毛健拆解新零售未来发展2020年是中国新基建的元年。在国内国际双循环格局下,新基建作为引领经济转型升级的重要动力和新一代中国版信息高速公路的重要引擎,对国家经济和科技产业的重要性和长期价值不言而喻。数字全渠道交易3。6。0版本更新,库存问题无压力全渠道交易3。6。0版本在近日完成更新。本次更新实现了订单店铺仓库的更高效精准协同。3。6。0更新功能订单管理直接对接B2C商城,完成自营渠道订单下单发货全动作。库存管理实现了逻辑数字凝聚转型力,云上龙光已启程对绝大部分行业来说,2020走得很艰难。地产行业也不例外,数字云的新地产时代加速到来,在标杆房企的数字化构想里,数字化转型其中一个重要的场景就是所有核心业务流程都能实现在线管理,大乐歌绘画学习升降桌,电动升降小桌板,办公学习都轻松桌子呢相信屏幕前的所有小伙伴的家里都有,不管是书桌办工桌还是餐桌,每个家里多多少少都会有那么一张到几张桌子。虽然每个人的家里都有那么两台桌子,但不是每个人家里都有那么一台多功能的好大咖来了科特勒中国区总裁曹虎将担任2021数创会演讲嘉宾随着数字化转型进入深水区,数字化转型已不再是一场单纯的技术追逐战,而是要利用技术手段重构业务,提升消费者体验。消费者越来越注重产品和服务的即时可得性,谁能够低成本快速地提供超预期的荣耀30S上手玩颜值拍照性能5G,两千多块都能有!3月30日荣耀30S发布,这意味着荣耀在2020年首款5G新成员手机正式到来。荣耀30S首发了麒麟820处理器,还支持6400万像素全焦段AI四摄,这款手机的实际体验究竟如何?我们