页面中模块通讯简单实现

一、背景

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

五、总结

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

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

相关推荐
你的电影很有趣8 小时前
lesson70:jQuery Ajax完全指南:从基础到4.0新特性及现代替代方案引言:jQuery Ajax的时代价值与演进
javascript·ajax·jquery
2503_928411569 小时前
9.26 数据可视化
前端·javascript·信息可视化·html5
我叫唧唧波9 小时前
【打包工具】webpack基础
前端·webpack
知识分享小能手11 小时前
React学习教程,从入门到精通,React 单元测试:语法知识点及使用方法详解(30)
前端·javascript·vue.js·学习·react.js·单元测试·前端框架
PineappleCoder14 小时前
搞定用户登录体验:双 Token 认证(Vue+Koa2)从 0 到 1 实现无感刷新
前端·vue.js·koa
Min;15 小时前
cesium-kit:让 Cesium 开发像写 UI 组件一样简单
javascript·vscode·计算机视觉·3d·几何学·贴图
EveryPossible15 小时前
展示内容框
前端·javascript·css
伊织code15 小时前
WebGoat - 刻意设计的不安全Web应用程序
前端·安全·webgoat
子兮曰15 小时前
Vue3 生命周期与组件通信深度解析
前端·javascript·vue.js
拉不动的猪15 小时前
回顾关于筛选时的隐式返回和显示返回
前端·javascript·面试