Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hook下如何书写发布订阅 #96

Open
bosens-China opened this issue Apr 18, 2023 · 1 comment
Open

hook下如何书写发布订阅 #96

bosens-China opened this issue Apr 18, 2023 · 1 comment
Labels
框架相关 目前Vue和React为主

Comments

@bosens-China
Copy link
Owner

这篇文章的思路来源为 ahooks,因为 react 已经有相对应的实现了,所以这里主要介绍 vue 下如何实现一个 hook 的发布订阅。

假设有一个需求,当价格发生改变的时候刷新列表,在 vue 中很自然而然想到调用 watch 观察,但是这样的调用还是有点繁琐。例如下面一段伪代码

import { ref, watch } from 'vue';

const isChange = ref(false);
const refreshList = () => {
  // 省略
  isChange.value = false;
};

const onClick = () => {
  isChange.value = true;
};

watch(isChange, (val) => {
  if (!val) {
    return;
  }
  refreshList();
});

但是仔细观察一下,其实我们只是想让变化的时候通知一下,然后调用 refreshList 即可。

且我们也不希望每次都复制这样一段代码在其他组件中重复使用,基于上面的场景我们很容易想到可以写一个发布订阅的模块来实现我们需求。

EventEmitter

class EventEmitter {
  subscriptions = new Set();

  emit = (val) => {
    for (const subscription of this.subscriptions) {
      subscription(val);
    }
  };

  useSubscription = (callback) => {
    this.subscriptions.add(subscription);
  };
}

上面的发布订阅模块很简单,useSubscription 来订阅,emit 来进行通知。下面是使用形式

const event = new EventEmitter();
const refreshList = () => {};
const fn = (val) => {
  if (val !== 'refresh') {
    return;
  }
  refreshList();
};
event.useSubscription('refresh', fn);

const onClick = () => {
  event.emit('refresh');
};

onUnmounted(() => {
  event.subscriptions.delete(fn);
});

使用方式稍微简化了一下,不过也多出了 onUnmounted,这也谈不上方便,因为会有额外的心智负担,上面之所以单独写一个 fn 就是因为卸载的时候需要卸载对应的函数才行。

基于简化和不想重复管理的需求,我们再重新写一版,这里我们采用 hook 的形式来书写

useEventEmitter

class EventEmitter {
  subscriptions = new Set();

  emit = (val) => {
    for (const subscription of this.subscriptions) {
      subscription(val);
    }
  };

  useSubscription = (callback) => {
    this.subscriptions.add(callback);
    onUnmounted(() => {
      this.subscriptions.delete(callback);
    });
  };
}
const useEventEmitter = () => {
  const event = new EventEmitter();

  return event;
};

上面的逻辑改变了一下,我们在 useSubscription 中调用 onUnmounted 来完成一个自动的卸载。

之后使用方法

const event = useEventEmitter();

const refreshList = () => {};

event.useSubscription((val) => {
  if (val !== 'refresh') {
    return;
  }
  refreshList();
});

const onClick = () => {
  event.emit('refresh');
};

这里就达到我们期待的一个效果了,不过还是有需要改进的地方:

在使用发布订阅的场景基本上组件之间的层级会嵌套很多,例如上面每次调用 useEventEmitter 生成一个新的 event 所以通过单例简化为全局唯一的 event 即可,或者也可以借助 provideinject 解决这一问题

改进之后

class EventEmitter {
  subscriptions = new Set();

  emit = (val) => {
    for (const subscription of this.subscriptions) {
      subscription(val);
    }
  };

  useSubscription = (callback) => {
    this.subscriptions.add(callback);
    onUnmounted(() => {
      this.subscriptions.delete(callback);
    });
  };
}
const event = new EventEmitter();
const useEventEmitter = () => {
  return event;
};
@bosens-China bosens-China added the 框架相关 目前Vue和React为主 label Apr 18, 2023
@xueyou2000
Copy link

你是否在搜索 mitt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
框架相关 目前Vue和React为主
Projects
None yet
Development

No branches or pull requests

2 participants