页面中模块通讯简单实现

一、背景

我们公司的官网因为种种原因,是phpcms做的,模板生成的SEO静态页

某天产品:我希望在官网添加一套登录逻辑,我看这套登录逻辑已经在X项目实现了,你直接复制粘贴就好了,很简单吧!

我:@%#......¥

二、思考一番

已知条件:一个静态页面、一个已有的登录功能

需求:把这个登录功能加入到静态页面中

我们先把这个需求拆解成:

  1. 实现一个通信机制,让页面和登录模块能够交流,进行数据的同步
  2. 通信机制写成通用的形式,保不准后面还要插上其他功能也需要用到通讯

三、通讯系统的实现

因为是在同一个页面的实现,不需要考虑跨域的问题,所以考虑使用SDK的方式实现,这样就可以挂载到window上,直接使用了

1. 实现事件系统

我们先完成通信中不可缺少的事件模块,包含简单的 onoffemit

js 复制代码
export const SDK = {
    
    globalNameSpace: 'sdk',
    
    listeners: {},
    
    on(event, callback, namespace = this.globalNamespace) {
        const eventKey = `${namespace}:${event}`;
        if (!this.listeners[eventKey]) {
          this.listeners[eventKey] = [];
        }
        this.listeners[eventKey].push(callback);
    },
    
    off(event, callback, namespace = this.globalNamespace) {
        const eventKey = `${namespace}:${event}`;
        if (this.listeners[eventKey]) {
          const index = this.listeners[eventKey].indexOf(callback);
          if (index > -1) {
            this.listeners[eventKey].splice(index, 1);
          }
        }
    },
    
    emit(event, data, namespace = this.globalNamespace) {
        const eventKey = `${namespace}:${event}`;
        if (this.listeners[eventKey]) {
          this.listeners[eventKey].forEach(callback => {
            try {
              callback(data);
            } catch (error) {
              console.error(error);
            }
          });
        }
    }
}
2. 实现模块系统

通讯系统应该是一个管理的地方,不涉及具体的业务实现。所以登录相关的业务逻辑应该是一个可插拔的模块去实现,那么我们可以在SDK中添加一个模块系统去管理这些插拔的业务模块

js 复制代码
export const SDK = {
    
    // 省略已有的代码
    
    modules: {},
    
    registerModule(moduleName, module) {
        this.modules[moduleName] = module;
        this.emit('moduleRegistered', { name: moduleName, module });
    },
    
    unregisterModule(moduleName) {
        if (this.modules[moduleName]) {
          if (typeof this.modules[moduleName].destroy === 'function') {
            this.modules[moduleName].destroy();
          }

          delete this.modules[moduleName];
          this.emit('moduleUnregistered', { name: moduleName });
        }
    },
    
    getModule(moduleName) {
        return this.modules[moduleName] || null;
    },
    
    waitForModules(moduleNames) {
        const names = Array.isArray(moduleNames) ? moduleNames : [moduleNames];

        return new Promise((resolve) => {
          const checkModules = () => {
            const allLoaded = names.every(name => this.getModule(name));

            if (allLoaded) {
              resolve(names.map(name => this.getModule(name)));
            } else {
              setTimeout(checkModules, 100);
            }
          };

          checkModules();
        });
    },
}

if (typeof window !== 'undefined') {
    window.SDK = SDK
    window.dispatchEvent(new CustomEvent('SDKMounted'))
}

export default SDK
3. 实现登录模块

登录模块更多的是实现业务逻辑,包含注册自身到SDK中、监听登录数据、登录、退出登录等等

js 复制代码
import { SDK } from './sdk.js';

const sdkEmit = (event, data) => {
    SDK.emit(event, data)
}

export const loginModule = {
    initialized: false

    init () {
        if (this.initialized) return;
        this.initialized = true;
        this.setupWatchers();
    },
    
    setupWatchers () {
        // 监听 token 变化
        watch(() => store.token, (newToken, oldToken) => {
          if (newToken !== oldToken) {
            sdkEmit('loginStateChange', { token: newToken });
          }
        });

        // 监听 userInfo 变化
        watch(() => store.userInfo, (newUserInfo) => {
          sdkEmit('userInfoChange', { userInfo: Object.assign({}, newUserInfo) });
        }, { deep: true });
    },
    
    getToken () {},
    
    getUserInfo () {},
    
    openLogin (data) {
        store.openLogin(data)
    },
    
    logout() {
        store.logout()
    }
}

export default const initLoginModule () {
    SDK.registerModule('login', LoginModule)
    LoginModule.init()
}

四、静态页面接入SDK

在静态页面中,我们需要完成以下几个实现

  1. 完成和SDK的连接
  2. 获取登录模块
  3. 监听登录数据的改变

下面我们一一实现

html 复制代码
<script>

    const init = (SDK, [loginModule]) => {
        // 主动获取数据
        // 获取token
        loginModule.getToken().then((token) => {
          console.log('getToken', token)
        })
        // 获取用户信息
        loginModule.getUserInfo().then((userInfo) => {
          console.log('getUserInfo', userInfo)
        })
        
        // 数据变更监听
        // 登录状态变更
        SDK.on('loginStateChange', (res) => {
          console.log('loginStateChange', res)
        })
        // 用户信息变更
        SDK.on('userInfoChange', (res) => {
          console.log('userInfoChange', res)
        })
    }

    const obLoginModule = () => {
        window.SDK.waitForModules(['login]).then(modules => {
            init(window.SDK, modules)
        })
    }
    
    if (window.SDK) {
       obLoginModule() 
    } else {
        window.addEventListener('SDKMounted', () => {
            obLoginModule() 
        })
    }
    
</script>

五、总结

对于常见的页面模块通信处理,可以参考上面的思路

因为有模块系统在,后续产品再插入其他功能,新增一个模块的业务代码即可,不需要更改已有的通信机制,还是很方便的

相关推荐
M ? A8 分钟前
VuReact 编译器核心重构:统一管理组件元数据收集
前端·javascript·vue.js·react.js·重构·开源
山海AI手册9 分钟前
030、AI应用前端展示:Streamlit快速构建交互式Web应用
前端·人工智能
专注VB编程开发20年10 分钟前
C#异步状态机,内部的信号机制TaskCompletionSource
前端
csdn_aspnet13 分钟前
在无状态 ASP.NET Core 8 Web API 中实现 CSRF 令牌,无需 Views/MVC!
前端·csrf·.net core
ByteCraze32 分钟前
手写高性能虚拟列表(详解!!!)
javascript·学习
M ? A38 分钟前
Vue转React最佳工具对比:Vuera、Veaury与VuReact
前端·javascript·vue.js·经验分享·react.js
We་ct44 分钟前
JS手撕:函数进阶 & 设计模式解析
开发语言·前端·javascript·设计模式·面试·前端框架
悟空瞎说1 小时前
前端老鸟实战:纯 CSS 实现小红书「真・瀑布流」,零 JS、自动错落、生产可用
前端
yuki_uix1 小时前
当 reduce 遇到二维数据:从"聚合直觉"到"复合 Map"的思维跃迁
前端·javascript·面试
我叫黑大帅1 小时前
Vue3中的computed 与 watch 的区别
前端·javascript·面试