页面中模块通讯简单实现

一、背景

我们公司的官网因为种种原因,是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>

五、总结

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

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

相关推荐
国家不保护废物1 分钟前
TailwindCSS:原子化CSS的革命,让React开发爽到飞起!🚀
前端·css·react.js
程序视点25 分钟前
如何高效率使用 Cursor ?
前端·后端·cursor
前端领航者26 分钟前
重学Vue3《 v-for的key属性:性能差异与最佳实践》
前端·javascript
归于尽26 分钟前
跨域问题从青铜到王者:JSONP、CORS原理详解与实战(前端必会)
前端·浏览器
Andy_GF37 分钟前
纯血鸿蒙HarmonyOS Next 远程测试包分发
前端·ios·harmonyos
嗑药狂写9W行代码1 小时前
cesium修改源码支持4490坐标系
前端
小山不高1 小时前
react实现leaferjs编辑器之形状裁剪功能点
前端
20261 小时前
13.2 ssr基本原理,构建步骤
前端·vue.js
cpp加油站1 小时前
打脸来的太快了,又发现一个Trae的宝藏功能--内置浏览器可以指定机型来显示前端界面
前端·ai编程·trae
Web极客码1 小时前
如何为WordPress启用LiteSpeed缓存
前端·缓存