iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >微前端框架qiankun隔离方法怎么使用
  • 173
分享到

微前端框架qiankun隔离方法怎么使用

2023-07-05 03:07:45 173人浏览 独家记忆
摘要

本文小编为大家详细介绍“微前端框架qiankun隔离方法怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“微前端框架qiankun隔离方法怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。沙箱隔离在基于

本文小编为大家详细介绍“微前端框架qiankun隔离方法怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“微前端框架qiankun隔离方法怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    沙箱隔离

    在基于single-spa开发的微前端应用中,子应用开发者需要特别注意的是:

    要谨慎修改和使用全局变量上的属性(如window、document等),以免造成依赖该属性的自身应用或其它子应用运行时出现错误;

    要谨慎控制CSS规则的生效范围,避免覆盖污染其它子应用的样式;

    但这样的低级人为保证机制是无法在大规模的团队开发过程中对应用的独立性起到完善保护的,而qiankun框架给我们提供的最便利和有用的功能就是其基于配置的自动化沙箱隔离机制了。有了框架层面的子应用隔离支持,用户无论是在编写js代码还是修改CSS样式时都不必再担心代码对于全局环境的污染问题了。沙箱机制一方面提升了微应用框架运行的稳定性和独立性,另一方面也降低了微前端开发者的心智负担,让其只需专注于自己的子应用代码开发之中。

    JS隔离

    在JS隔离方面,qiankun为开发者提供了三种不同模式的沙箱机制,分别适用于不同的场景之中。

    1. Snapshot沙箱

    该沙箱主要用于不支持Proxy对象的低版本浏览器之中,不能由用户手动指定该模式,qiankun会自动检测浏览器的支持情况并降级到Snapshot沙箱实现。由于这种实现方式在子应用运行过程中实际上修改了全局变量,因此不能用于多例模式之中(同时存在多个已挂载的子应用)。

    该沙箱实现方式非常简洁,下面我们给出其简化后的实现

    // 基于 diff 方式实现的沙箱,用于不支持 Proxy 的低版本浏览器export default class SnapshotSandbox implements SandBox {  private windowsnapshot!: Window;  private modifyPropsMap: Record<any, any> = {};  constructor() {}  active() {    // 记录当前快照    this.windowSnapshot = {} as Window;    iter(window, (prop) => {      this.windowSnapshot[prop] = window[prop];    });    // 恢复之前的变更    Object.keys(this.modifyPropsMap).forEach((p: any) => {      window[p] = this.modifyPropsMap[p];    });  }  inactive() {    this.modifyPropsMap = {};    iter(window, (prop) => {      if (window[prop] !== this.windowSnapshot[prop]) {        // 记录变更,恢复环境        this.modifyPropsMap[prop] = window[prop];        window[prop] = this.windowSnapshot[prop];      }    });  }}

    沙箱内部存在两个对象变量windowSnapshotmodifyPropsMap ,分别用来存储子应用挂载前原始window对象上的全部属性以及子应卸载时被其修改过的window对象上的相关属性。

    Snapshot沙箱会在子应用mount前将modifyPropsMap中存储的属性重新赋值给window以恢复该子应用之前执行时的全局变量上下文,并在子应用unmount后将windowSnapshot中存储的属性重新赋值给window以恢复该子应用运行前的全局变量上下文,从而使得两个不同子应用的window相互独立,达到JS隔离的目的。

    2. Legacy沙箱

    当用户手动配置sandbox.loose: true时该沙箱被启用。Legacy沙箱同样会对window造成污染,但是其性能比要比snapshot沙箱好,因为该沙箱不用遍历window对象。同样legacy沙箱也只适用于单例模式之中。

    下面一起来看一下其简化后的大致实现方式

    export default class LegacySandbox implements SandBox {    proxy: WindowProxy;    private addedPropsMapinSandbox = new Map<PropertyKey, any>();    private modifiedPropsOriginalValueMapInSandbox = new Map<PropertyKey, any>();    private currentUpdatedPropsValueMap = new Map<PropertyKey, any>();  constructor() {    const { addedPropsMapInSandbox, modifiedPropsOriginalValueMapInSandbox, currentUpdatedPropsValueMap } = this;    const rawWindow = window;    const fakeWindow = Object.create(null) as Window;    const setTrap = (p: PropertyKey, value: any, originalValue: any) => {      if (!rawWindow.hasOwnProperty(p)) {        // 当前 window 对象不存在该属性,将其记录在新增变量之中        addedPropsMapInSandbox.set(p, value);      } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {        // 如果当前 window 对象存在该属性,且 record map 中未记录过,则记录该属性初始值        modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);      }      // 无论何种修改都记录在currentUpdatedPropsValueMap中      currentUpdatedPropsValueMap.set(p, value);      // 必须重新设置 window 对象保证下次 get 时能拿到已更新的数据      (rawWindow as any)[p] = value;    };    const proxy = new Proxy(fakeWindow, {      set: (_: Window, p: PropertyKey, value: any): boolean => {        const originalValue = (rawWindow as any)[p];        return setTrap(p, value, originalValue, true);      },      get(_: Window, p: PropertyKey): any {        // avoid who using window.window or window.self to escape the sandbox environment to touch the really window or use window.top to check if an iframe context        if (p === 'top' || p === 'parent' || p === 'window' || p === 'self') {          return proxy;        }        const value = (rawWindow as any)[p];        return value;      },    });    this.proxy = proxy  }  active() {    // 激活时将子应用之前的所有改变重新赋予window,恢复其运行时上下文    this.currentUpdatedPropsValueMap.forEach((v, p) => this.setWindowProp(p, v));  }  inactive() {    // 卸载时将window上修改的值复原,新添加的值删除    this.modifiedPropsOriginalValueMapInSandbox.forEach((v, p) => this.setWindowProp(p, v));    this.addedPropsMapInSandbox.forEach((_, p) => this.setWindowProp(p, undefined, true));  }  private setWindowProp(prop: PropertyKey, value: any, toDelete?: boolean) {    if (value === undefined && toDelete) {      delete (this.globalContext as any)[prop];    } else {      (this.globalContext as any)[prop] = value;    }  }}

    Legacy沙箱为一个空对象fakewindow使用proxy代理拦截了其全部的set/get等操作,并在loader中用其替换了window。当用户试图修改window属性时,fakewindow上代理的set操作生效捕获了相关修改,其分别将新增的属性和修改前的值存入addedPropsMapInSandboxmodifiedPropsOriginalValueMapInSandbox这两个Map之中,此外还将所有修改记录在了currentUpdatedPropsValueMap之中,并改变了window对象。

    这样当子应用挂载前,legacy沙箱会将currentUpdatedPropsValueMap之中记录的子应用相关修改重新赋予window,恢复其运行时上下文。当子应用卸载后,legacy沙箱会遍历addedPropsMapInSandboxmodifiedPropsOriginalValueMapInSandbox这两个Map并将window上的相关值恢复到子应用运行之前的状态。最终达到了子应用间JS隔离的目的。

    3. Proxy沙箱

    Proxy沙箱是qiankun框架中默认使用的沙箱模式(也可以通过配置sandbox.loose: false来开启),只有该模式真正做到了对window的无污染隔离(子应用完全不能修改全局变量),因此可以被应用在单/多例模式之中。

    Proxy沙箱的原理也非常简单,它将window上的所有属性遍历拷贝生成一个新的fakeWindow对象,紧接着使用proxy代理这个fakeWindow,用户对window操作全部被拦截下来,只作用于在这个fakeWindow之上。

    // 便利window拷贝创建初始代理对象function createFakeWindow(globalContext: Window) {  const fakeWindow = {} as FakeWindow;  Object.getOwnPropertyNames(globalContext)    .forEach((p) => {      const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);      rawObjectDefineProperty(fakeWindow, p, Object.freeze(descriptor));    });  return { fakeWindow };}export default class ProxySandbox implements SandBox {  // 标志该沙箱是否被启用  sandboxRunning = true;  constructor() {    const { fakeWindow } = createFakeWindow(window);    const proxy = new Proxy(fakeWindow, {      set: (target: FakeWindow, p: PropertyKey, value: any): boolean => {        if(this.sandboxRunning){          // 修改代理对象的值          target[p] = value;          return true;         }      }      get: (target: FakeWindow, p: PropertyKey): any => {        // avoid who using window.window or window.self to escape the sandbox environment to touch the really window        if (p === 'window' || p === 'self' || p === 'globalThis') {          return proxy;        }        // 获取代理对象的值      const value = target[p];        return value;      },    })  }  active() {    if (!this.sandboxRunning) activeSandboxCount++;    this.sandboxRunning = true;  }  inactive() {    this.sandboxRunning = false;  }}

    CSS隔离

    对于CSS隔离的方式,在默认情况下由于切换子应用时,其相关的CSS内外连属性会被卸载掉,所以可以确保单实例场景子应用之间的样式隔离,但是这种方式无法确保主应用跟子应用、或者多实例场景的子应用样式隔离。不过,qiankun也提供了两种可配置生效的内置方式供使用者选择。

    1. ShadowDOM

    当用户配置sandbox.strictStyleIsolation: true时,ShadowDOM样式沙箱会被开启。在这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响。

    // 在子应用的DOM树最外层进行一次包裹function createElement(  appContent: string,  strictStyleIsolation: boolean,  scopedCSS: boolean,  appInstanceId: string,): htmlElement {  // 包裹节点  const containerElement = document.createElement('div');  containerElement.innerHTML = appContent;  // 子应用最外层节点  const appElement = containerElement.firstChild as HTMLElement;  // 当开启了ShadowDOM沙箱时  if (strictStyleIsolation) {    const { innerHTML } = appElement;    appElement.innerHTML = '';    let shadow: ShadowRoot;// 判断浏览器兼容的创建ShadowDOM的方式,并使用该方式创建ShadowDOM根节点    if (appElement.attachShadow) {      shadow = appElement.attachShadow({ mode: 'open' });    } else {      // createShadowRoot was proposed in initial spec, which has then been deprecated      shadow = (appElement as any).createShadowRoot();    }    // 将子应用内容挂在ShadowDOM根节点下    shadow.innerHTML = innerHTML;  }// 。。。。。。  return appElement;}

    这种方式虽然看起来清晰简单,还巧妙利用了浏览器对于ShadowDOM的CSS隔离特性,但是由于ShadowDOM的隔离比较严格,所以这并不是一种无脑使用的方案。例如:如果子应用内存在一个弹出时会挂在document根元素的弹窗,那么该弹窗的样式是否会受到ShadowDOM的影响而失效?所以开启该沙箱的用户需要明白自己在做什么,且可能需要对子应用内部代码做出一定的调整。

    2. Scoped CSS

    因为ShadowDOM存在着上述的一些问题,qiankun贴心的为用户提供了另一种更加无脑简便的样式隔离方式,那就是Scoped CSS。通过配置sandbox.experimentalStyleIsolation: true,Scoped样式沙箱会被开启。

    在这种模式下,qiankun会遍历子应用中所有的CSS选择器,通过对选择器前缀添加一个固定的带有该子应用标识的属性选择器的方式来限制其生效范围,从而避免子应用间、主应用与子应用的样式相互污染。

    export const QiankunCSSRewriteAttr = 'data-qiankun';// 在子应用的DOM树最外层进行一次包裹function createElement(  appContent: string,  strictStyleIsolation: boolean,  scopedCSS: boolean,  appInstanceId: string,): HTMLElement {  // 包裹节点  const containerElement = document.createElement('div');  containerElement.innerHTML = appContent;  // 子应用最外层节点  const appElement = containerElement.firstChild as HTMLElement;  // 。。。。。。  // 当开启了Scoped CSS沙箱时  if (scopedCSS) {    // 为外层节点添加qiankun自定义属性,其值设定为子应用id标识    const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr);    if (!attr) {      appElement.setAttribute(css.QiankunCSSRewriteAttr, appInstanceId);    }// 获取子应用中全部样式并进行处理    const stylenodes = appElement.querySelectorAll('style') || [];    forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => {      css.process(appElement!, stylesheetElement, appInstanceId);    });  }  return appElement;}

    qiankun首先对子应用最外层的包裹节点(一般为div节点)添加一个属性名为data-qiankun,值为appInstanceId的属性。接着遍历处理子应用中的所有样式。

    export const process = (  appWrapper: HTMLElement,  stylesheetElement: HTMLStyleElement | HTMLLinkElement,  appName: string,): void => {  // lazy singleton pattern  if (!processor) {    processor = new ScopedCSS();  }// !!!注意,对于link标签引入的外联样式不支持。qiankun在初期解析使用的import-html-entry在解析html模版时会将所有外联样式拉取并转换为style标签包裹的内联样式,所以这里不再处理link的外联样式。  if (stylesheetElement.tagName === 'LINK') {    console.warn('Feature: sandbox.experimentalStyleIsolation is not support for link element yet.');  }  const mountDOM = appWrapper;  if (!mountDOM) {    return;  }// 获取包裹元素标签  const tag = (mountDOM.tagName || '').toLowerCase();  if (tag && stylesheetElement.tagName === 'STYLE') {    // 生成属性选择器前缀,准备将其添加在选择器前(如div[data-qiankun=app1])    const prefix = `${tag}[${QiankunCSSRewriteAttr}="${appName}"]`;    processor.process(stylesheetElement, prefix);  }};// 。。。。。。process(styleNode: HTMLStyleElement, prefix: string = '') {  if (styleNode.textContent !== '') {    // 获取相关css规则rules    const textNode = document.createTextNode(styleNode.textContent || '');    this.swapNode.appendChild(textNode);    const sheet = this.swapNode.sheet as any; // type is missing    const rules = arrayify<CSSRule>(sheet?.cssRules ?? []);    // 重写这些CSS规则,将前缀添加进去    const css = this.rewrite(rules, prefix);    // 用重写后的CSS规则覆盖之前的规则    styleNode.textContent = css;    // 标志符,代表该节点已经处理过    (styleNode as any)[ScopedCSS.ModifiedTag] = true;    return;  }// 监听节点变化  const mutator = new MutationObserver((mutations) => {    for (let i = 0; i < mutations.length; i += 1) {      const mutation = mutations[i];      // 忽略已经处理过的节点      if (ScopedCSS.ModifiedTag in styleNode) {        return;      }      // 如果新增了未处理过的子节点(代表了用户新注入了一些属性),那么会再次重写所有的CSS规则以确保新增的CSS不会污染子应用外部      if (mutation.type === 'childList') {        const sheet = styleNode.sheet as any;        const rules = arrayify<CSSRule>(sheet?.cssRules ?? []);        const css = this.rewrite(rules, prefix);        styleNode.textContent = css;        (styleNode as any)[ScopedCSS.ModifiedTag] = true;      }    }});  // 注册监听  mutator.observe(styleNode, { childList: true });}// 具体CSS规则重写方式private rewrite(rules: CSSRule[], prefix: string = '') {  // 。。。。。。  // 这里省略其实现方式,整体实现思路简单但步骤很繁琐,主要就是对字符串的正则判断和替换修改。  // 1. 对于根选择器(html/body/:root等),直接将其替换为prefix  // 2. 对于其它选择器,将prefix放在最前面( selector1 selector2, selector3 =》 prefix selector1 selector2,prefix selector3)}

    可以看到,qiankun通过为子应用的外层包裹元素注入属性并将子应用全部样式的作用范围都限制在该包裹元素下(通过添加指定的属性选择器作为前缀)实现了scoped样式沙箱隔离。需要注意的是,如果用户在运行时对内联样式进行修改,qiankun是可以侦测到并帮助用户限制其作用范围,但如果用户在运行时引入了新的外联样式或者自行创建了新的内联标签,那么qiankun并不会做出反应,相关的CSS规则还是可能会污染全局样式。

    通信方式

    对于微前端来说,除了应用间的隔离外,应用间的通信也是非常重要的部分。这里,single-spa提供了从主应用向子应用传递customProps的方式实现了最基础的参数传递。但是真实的开发场景需要的信息传递是非常复杂的,静态的预设参数传递只能起到很小的作用,我们还需要一种更加强大的通信机制来帮助我们开发应用。

    这里,qiankun在框架内部预先设计实现了完善的发布订阅模式,降低了开发者的上手门槛。我们首先来看一下qiankun中的通信是如何进行的。

    // ------------------主应用------------------import { initGlobalState, MicroAppStateActions } from 'qiankun';// 初始化 stateconst actions: MicroAppStateActions = initGlobalState(state);// 在当前应用监听全局状态,有变更触发 callbackactions.onGlobalStateChange((state, prev) => {  // state: 变更后的状态; prev 变更前的状态  console.log(state, prev);});// 按一级属性设置全局状态,微应用中只能修改已存在的一级属性actions.setGlobalState(state);// 移除当前应用的状态监听,微应用 umount 时会默认调用actions.offGlobalStateChange();// ------------------子应用------------------// 从生命周期 mount 中获取通信方法,使用方式和 master 一致export function mount(props) {  props.onGlobalStateChange((state, prev) => {    // state: 变更后的状态; prev 变更前的状态    console.log(state, prev);  });  props.setGlobalState(state);}

    接下来,让我们一起来看一下它是如何实现的。

    import { cloneDeep } from 'lodash';import type { OnGlobalStateChangeCallback, MicroAppStateActions } from './interfaces';// 全局状态let globalState: Record<string, any> = {};// 缓存相关的订阅者const deps: Record<string, OnGlobalStateChangeCallback> = {};// 触发全局监听function emitGlobal(state: Record<string, any>, prevState: Record<string, any>) {  Object.keys(deps).forEach((id: string) => {    if (deps[id] instanceof Function) {      // 依次通知订阅者      deps[id](cloneDeep(state), cloneDeep(prevState));    }  });}// 初始化export function initGlobalState(state: Record<string, any> = {}) {  if (state === globalState) {    console.warn('[qiankun] state has not changed!');  } else {    const prevGlobalState = cloneDeep(globalState);    globalState = cloneDeep(state);    emitGlobal(globalState, prevGlobalState);  }  // 返回相关方法,形成闭包存储相关状态  return getMicroAppStateActions(`global-${+new Date()}`, true);}export function getMicroAppStateActions(id: string, isMaster?: boolean): MicroAppStateActions {  return {        onGlobalStateChange(callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) {      if (!(callback instanceof Function)) {        console.error('[qiankun] callback must be function!');        return;      }      if (deps[id]) {        console.warn(`[qiankun] '${id}' global listener already exists before this, new listener will overwrite it.`);      }      / 注册订阅      deps[id] = callback;      if (fireImmediately) {        const cloneState = cloneDeep(globalState);        callback(cloneState, cloneState);      }    },        setGlobalState(state: Record<string, any> = {}) {      if (state === globalState) {        console.warn('[qiankun] state has not changed!');        return false;      }      const changeKeys: string[] = [];      const prevGlobalState = cloneDeep(globalState);      globalState = cloneDeep(        Object.keys(state).reduce((_globalState, changeKey) => {          if (isMaster || _globalState.hasOwnProperty(changeKey)) {            changeKeys.push(changeKey);            return Object.assign(_globalState, { [changeKey]: state[changeKey] });          }          console.warn(`[qiankun] '${changeKey}' not declared when init state!`);          return _globalState;        }, globalState),      );      if (changeKeys.length === 0) {        console.warn('[qiankun] state has not changed!');        return false;      }      // 触发全局监听      emitGlobal(globalState, prevGlobalState);      return true;    },    // 注销该应用下的依赖    offGlobalStateChange() {      delete deps[id];      return true;    },  };}

    可以看到在initGlobalState函数的执行中完成了一个发布订阅模式的创建工作,并返回了相关的订阅/发布/注销方法。接着qiankun将这些返回的方法通过生命周期函数mount传递给子应用,这样子应用就能够拿到并使用全局状态了,从而应用间的通信就得以实现了。此外offGlobalStateChange会在子应用unmount时自动调用以解除该子应用的订阅,避免内存泄露。

    读到这里,这篇“微前端框架qiankun隔离方法怎么使用”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。

    --结束END--

    本文标题: 微前端框架qiankun隔离方法怎么使用

    本文链接: https://www.lsjlt.com/news/349154.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • 微前端框架qiankun隔离方法怎么使用
      本文小编为大家详细介绍“微前端框架qiankun隔离方法怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“微前端框架qiankun隔离方法怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。沙箱隔离在基于...
      99+
      2023-07-05
    • Bootstrap前端框架怎么使用
      今天小编给大家分享一下Bootstrap前端框架怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Bootstrap 简...
      99+
      2023-06-27
    • Bootstrap前端框架怎么用
      小编给大家分享一下Bootstrap前端框架怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1、Bootstrap 基本模板...
      99+
      2024-04-02
    • vue前端框架Mint UI怎么使用
      Mint UI 是一个基于 Vue.js 的移动端组件库,使用 Mint UI 可以快速构建移动端应用的界面。以下是在 Vue 项目...
      99+
      2023-08-09
      vue
    • Asp.net core前端框架Blazor怎么使用
      今天小编给大家分享一下Asp.net core前端框架Blazor怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一...
      99+
      2023-07-02
    • 微前端框架导入加载子应用的方法是什么
      本篇内容主要讲解“微前端框架导入加载子应用的方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“微前端框架导入加载子应用的方法是什么”吧!下面代码,我指定的...
      99+
      2024-04-02
    • 怎么用Bootstrap前端视图实现页面内容模块化的隔离
      这篇文章主要介绍“怎么用Bootstrap前端视图实现页面内容模块化的隔离”,在日常操作中,相信很多人在怎么用Bootstrap前端视图实现页面内容模块化的隔离问题上存在疑惑,小编查阅了各式资料,整理出简单...
      99+
      2024-04-02
    • Django框架怎么使用ajax的post方法
      这篇文章主要介绍Django框架怎么使用ajax的post方法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Django是一个开放源代码的Web应用框架,由Python写成。采用了MVC的软件设计模式,即模型M,视图...
      99+
      2023-06-08
    • Security框架中怎么使用CorsFilter解决前端跨域请求问题
      本篇内容主要讲解“Security框架中怎么使用CorsFilter解决前端跨域请求问题”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Security框架中怎么使用CorsFilter解决前端跨...
      99+
      2023-06-25
    • Python文本终端GUI框架怎么使用
      本篇内容主要讲解“Python文本终端GUI框架怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python文本终端GUI框架怎么使用”吧!Curses首先出场的是 Curses[1]。C...
      99+
      2023-07-06
    • vue项目使用node连接数据库的方法(前后端分离)
      目录写在前面编写顺序开始2.制作后端 node 服务器3.编写 node 连接数据库4.实现登录、修改密码和获取用户信息接口5.完成前后端交互结束学习关键语句:vue连接mysql数...
      99+
      2022-12-22
      vue使用node连接数据库 vue前后端分离项目
    • workflow框架使用的方法是什么
      Workflow框架的使用方法通常包括以下步骤: 确定需要进行流程管理的业务流程:首先需要明确要管理的业务流程,包括流程中的各个...
      99+
      2024-03-14
      workflow
    • Java微基准测试框架JMH怎么使用
      使用Java微基准测试框架JMH需要按照以下步骤进行:1. 创建一个Java项目,并将JMH依赖添加到项目的构建文件中(例如,Mav...
      99+
      2023-09-22
      Java JMH
    • cobit框架的使用方法是什么
      COBIT框架是一个全面的信息技术管理框架,旨在帮助组织建立和维护有效的信息技术治理和管理体系。以下是COBIT框架的使用方法: ...
      99+
      2024-04-02
    • nodejs怎么使用Express框架写后端接口
      本文小编为大家详细介绍“nodejs怎么使用Express框架写后端接口”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs怎么使用Express框架写后端接口”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧...
      99+
      2023-06-30
    • ASP.NET MVC框架的使用方法是什么
      ASP.NET MVC框架的使用方法如下:1. 创建ASP.NET MVC项目:打开Visual Studio,选择“创建新项目”,...
      99+
      2023-10-10
      ASP.NET MVC
    • java rpc框架的使用方法是什么
      Java中常用的RPC框架有Dubbo、gRPC、Thrift等,它们的使用方法大致相似,一般包括以下步骤: 定义接口:首先需要...
      99+
      2024-03-08
      java
    • WPF引用MVVM框架与使用方法是什么
      今天就跟大家聊聊有关WPF引用MVVM框架与使用方法是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1.NuGet引用MVVM框架包引入该框架包之后, 默认会在目录下创建View...
      99+
      2023-06-29
    • Express框架两个内置中间件方法怎么使用
      这篇文章主要介绍“Express框架两个内置中间件方法怎么使用”,在日常操作中,相信很多人在Express框架两个内置中间件方法怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Express框架两个内置...
      99+
      2023-07-05
    • python  OpenCV怎么使用背景分离方法
      理论背景分离(BS)是一种通过使用静态相机来生成前景掩码(包含属于场景中的移动对象像素的二进制图像)的常用技术顾名思义,BS计算前景掩码,在当前帧与背景模型之间执行减法运算,其中包含场景的静态部分,考虑到所观察场景的特征,可以将其视为背景的...
      99+
      2023-05-16
      Python OpenCV
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作