微前端框架 之 qiankun

2023-10-26

文章目录

参考文档

一、介绍

qiankun 是基于 single-spa 做了二次封装的微前端框架,通过解决了 single-spa 的一些弊端和不足,来帮助大家实现更简单、无痛的构建一个生产可用的微前端架构系统。

1.1 qiankun的优点/特点

1.HTML Entry

qiankun 通过 HTML Entry 的方式来解决 JS Entry 带来的问题,让你接入微应用像使用iframe 一样简单。

2.样式隔离

qiankun 实现了两种样式隔离

严格的样式隔离模式,为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响

实验性的方式,通过动态改写 css 选择器来实现,可以理解为 css scoped 的方式

3.运行时沙箱

qiankun 的运行时沙箱分为 JS 沙箱样式沙箱

JS 沙箱为每个微应用生成单独的 window proxy 对象,配合 HTML Entry 提供的 JS 脚本执行器 (execScripts) 来实现 JS 隔离;

样式沙箱 通过重写 DOM 操作方法,来劫持动态样式和 JS 脚本的添加,让样式和脚本添加到正确的地方,即主应用的插入到主应用模版内,微应用的插入到微应用模版,并且为劫持的动态样式做了 scoped css 的处理,为劫持的脚本做了 JS 隔离的处理,更加具体的内容可继续往下阅读或者直接阅读 qiankun 2.x 运行时沙箱 源码分析

4.资源预加载

qiankun 实现预加载的思路有两种,一种是当主应用执行 start 方法启动 qiankun 以后立即去预加载微应用的静态资源,另一种是在第一个微应用挂载以后预加载其它微应用的静态资源,这个是利用 single-spa 提供的 single-spa:first-mount 事件来实现的

5.应用间通信

qiankun 通过发布订阅模式来实现应用间通信,状态由框架来统一维护,每个应用在初始化时由框架生成一套通信方法,应用通过这些方法来更改全局状态和注册回调函数,全局状态发生改变时触发各个应用注册的回调函数执行,将新旧状态传递到所有应用

二、源码解读

2.1 框架目录结构

github 克隆项目以后,执行一下命令:

  • 安装 qiankun 框架所需的包
    yarn install
    
  • 安装示例项目的包
    yarn examples:install
    

以上命令执行结束以后:
在这里插入图片描述

2.2 有料的 package.json

  • npm-run-all
    一个 CLI 工具,用于并行或顺序执行多个 npm 脚本
  • father-build
    基于 rollup 的库构建工具,father 更加强大
  • 多项目的目录组织以及 scripts 部分的编写
  • main 和 module 字段
    标识组件库的入口,当两者同时存在时,module 字段的优先级高于 main

2.3 示例项目中的主应用

这里需要更改一下示例项目中主应用的 webpack 配置

{
  ...
  devServer: {
    // 从 package.json 中可以看出,启动示例项目时,主应用执行了两条命令,其实就是启动了两个主应用,但是却只配置了一个端口,浏览器打开 localhost:7099 和你预想的有一些出入,这时显示的是 loadMicroApp(手动加载微应用) 方式的主应用,基于路由配置的主应用没起来,因为端口被占用了
    // port: '7099'
		// 这样配置,手动加载微应用的主应用在 7099 端口,基于路由配置的主应用在 7088 端口
    port: process.env.MODE === 'multiple' ? '7099' : '7088'
  }
  ...
}

2.4 启动示例项目

yarn examples:start

命令执行结束以后,访问 localhost:7099localhost:7088 两个地址,可以看到如下内容:
在这里插入图片描述
到这一步,就证明项目正式跑起来了,所有准备工作就绪

三、示例项目

官方为我们准备了两种主应用的实现方式,五种微应用的接入示例,覆盖面可以说是比较广了,足以满足大家的普遍需要了

3.1 主应用

主应用在 examples/main 目录下,提供了两种实现方式,基于路由配置的 registerMicroApps 和 手动加载微应用的 loadMicroApp。主应用很简单,就是一个从 0 通过 webpack 配置的一个同时支持 react 和 vue 的项目,至于为什么同时支持 react 和 vue,继续往下看

3.1.1 webpack.config.js

就是一个普通的 webpack 配置,配置了一个开发服务器 devServer、两个 loader (babel-loader、css loader)、一个插件 HtmlWebpackPlugin (告诉 webpack html 模版文件是哪个)

通过 webpack 配置文件的 entry 字段得知入口文件分别为 index.jsmultiple.js

3.1.2 基于路由配置

通用将微应用关联到一些 url 规则的方式,实现当浏览器 url 发生变化时,自动加载相应的微应用的功能

3.1.2.1 index.js

// qiankun api 引入
import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from '../../es';
// 全局样式
import './index.less';

// 专门针对 angular 微应用引入的一个库
import 'zone.js';

/**
 * 主应用可以使用任何技术栈,这里提供了 react 和 vue 两种,可以随意切换
 * 最终都导出了一个 render 函数,负责渲染主应用
 */
// import render from './render/ReactRender';
import render from './render/VueRender';

// 初始化主应用,其实就是渲染主应用
render({ loading: true });

// 定义 loader 函数,切换微应用时由 qiankun 框架负责调用显示一个 loading 状态
const loader = loading => render({ loading });

// 注册微应用
registerMicroApps(
  // 微应用配置列表
  [
    {
      // 应用名称
      name: 'react16',
      // 应用的入口地址
      entry: '//localhost:7100',
      // 应用的挂载点,这个挂载点在上面渲染函数中的模版里面提供的
      container: '#subapp-viewport',
      // 微应用切换时调用的方法,显示一个 loading 状态
      loader,
      // 当路由前缀为 /react16 时激活当前应用
      activeRule: '/react16',
    },
    {
      name: 'react15',
      entry: '//localhost:7102',
      container: '#subapp-viewport',
      loader,
      activeRule: '/react15',
    },
    {
      name: 'vue',
      entry: '//localhost:7101',
      container: '#subapp-viewport',
      loader,
      activeRule: '/vue',
    },
    {
      name: 'angular9',
      entry: '//localhost:7103',
      container: '#subapp-viewport',
      loader,
      activeRule: '/angular9',
    },
    {
      name: 'purehtml',
      entry: '//localhost:7104',
      container: '#subapp-viewport',
      loader,
      activeRule: '/purehtml',
    },
  ],
  // 全局生命周期钩子,切换微应用时框架负责调用
  {
    beforeLoad: [
      app => {
        // 这个打印日志的方法可以学习一下,第三个参数会替换掉第一个参数中的 %c%s,并且第三个参数的颜色由第二个参数决定
        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
      },
    ],
    beforeMount: [
      app => {
        console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
      },
    ],
    afterUnmount: [
      app => {
        console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
      },
    ],
  },
);

// 定义全局状态,并返回两个通信方法
const { onGlobalStateChange, setGlobalState } = initGlobalState({
  user: 'qiankun',
});

// 监听全局状态的更改,当状态发生改变时执行回调函数
onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev));

// 设置新的全局状态,只能设置一级属性,微应用只能修改已存在的一级属性
setGlobalState({
  ignore: 'master',
  user: {
    name: 'master',
  },
});

// 设置默认进入的子应用,当主应用启动以后默认进入指定微应用
setDefaultMountApp('/react16');

// 启动应用
start();

// 当第一个微应用挂载以后,执行回调函数,在这里可以做一些特殊的事情,比如开启一监控或者买点脚本
runAfterFirstMounted(() => {
  console.log('[MainApp] first app mounted');
});

3.1.2.2 VueRender.js

/**
 * 导出一个由 vue 实现的渲染函数,渲染了一个模版,模版里面包含一个 loading 状态节点和微应用容器节点
 */
import Vue from 'vue/dist/vue.esm';

// 返回一个 vue 实例
function vueRender({ loading }) {
  return new Vue({
    template: `
      <div id="subapp-container">
        <h4 v-if="loading" class="subapp-loading">Loading...</h4>
        <div id="subapp-viewport"></div>
      </div>
    `,
    el: '#subapp-container',
    data() {
      return {
        loading,
      };
    },
  });
}

// vue 实例
let app = null;

// 渲染函数
export default function render({ loading }) {
  // 单例,如果 vue 实例不存在则实例化主应用,存在则说明主应用已经渲染,需要更新主营应用的 loading 状态
  if (!app) {
    app = vueRender({ loading });
  } else {
    app.loading = loading;
  }
}

3.1.2.3 ReactRender.js

/**
 * 同 vue 实现的渲染函数,这里通过 react 实现了一个一样的渲染函数
 */
import React from 'react';
import ReactDOM from 'react-dom';

// 渲染主应用
function Render(props) {
  const { loading } = props;

  return (
    <>
      {loading && <h4 className="subapp-loading">Loading...</h4>}
      <div id="subapp-viewport" />
    </>
  );
}

// 将主应用渲染到指定节点下
export default function render({ loading }) {
  const container = document.getElementById('subapp-container');
  ReactDOM.render(<Render loading={loading} />, container);
}

3.1.3 手动加载微应用

3.1.3.1 multiple.js

/**
 * 调用 loadMicroApp 方法注册了两个微应用
 */
import { loadMicroApp } from '../../es';

const app1 = loadMicroApp(
  // 应用配置,名称、入口地址、容器节点
  { name: 'react15', entry: '//localhost:7102', container: '#react15' },
  // 可以添加一些其它的配置,比如:沙箱、样式隔离等
  {
    sandbox: {
      // strictStyleIsolation: true,
    },
  },
);

const app2 = loadMicroApp(
  { name: 'vue', entry: '//localhost:7101', container: '#vue' },
  {
    sandbox: {
      // strictStyleIsolation: true,
    },
  },
);

3.2 vue

vue 微应用在 examples/vue 目录下,就是一个通过 vue-cli 创建的 vue demo 应用,然后对 vue.config.jsmain.js 做了一些更改

3.2.1 vue.config.js

一个普通的 webpack 配置,需要注意的地方就三点

{
  ...
  // publicPath 没在这里设置,是通过 webpack 提供的全局变量 __webpack_public_path__ 来即时设置的,webpackjs.com/guides/public-path/
  devServer: {
    ...
    // 设置跨域,因为主应用需要通过 fetch 去获取微应用引入的静态资源的,所以必须要求这些静态资源支持跨域
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  output: {
    // 把子应用打包成 umd 库格式
    library: `${name}-[name]`,	// 库名称,唯一
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${name}`,
  }
  ...
}

3.2.2 main.js

// 动态设置 __webpack_public_path__
import './public-path';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
// 路由配置
import routes from './router';
import store from './store';

Vue.config.productionTip = false;

Vue.use(ElementUI);

let router = null;
let instance = null;

// 应用渲染函数
function render(props = {}) {
  const { container } = props;
  // 实例化 router,根据应用运行环境设置路由前缀
  router = new VueRouter({
    // 作为微应用运行,则设置 /vue 为前缀,否则设置 /
    base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/',
    mode: 'history',
    routes,
  });

  // 实例化 vue 实例
  instance = new Vue({
    router,
    store,
    render: h => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 支持应用独立运行
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

/**
 * 从 props 中获取通信方法,监听全局状态的更改和设置全局状态,只能操作一级属性
 * @param {*} props 
 */
function storeTest(props) {
  props.onGlobalStateChange &&
    props.onGlobalStateChange(
      (value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
      true,
    );
  props.setGlobalState &&
    props.setGlobalState({
      ignore: props.name,
      user: {
        name: props.name,
      },
    });
}

/**
 * 导出的三个生命周期函数
 */
// 初始化
export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}

// 挂载微应用
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  storeTest(props);
  render(props);
}

// 卸载、销毁微应用
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}

3.2.3 public-path.js

/**
 * 在入口文件中使用 ES6 模块导入,则在导入后对 __webpack_public_path__ 进行赋值。
 * 在这种情况下,必须将公共路径(public path)赋值移至专属模块,然后将其在最前面导入
 */

// qiankun 设置的全局变量,表示应用作为微应用在运行
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

3.3 jQuery

这是一个使用了 jQuery 的项目,在 examples/purehtml 目录下,展示了如何接入使用 jQuery 开发的应用

3.3.1 package.json

为了达到演示效果,使用 http-server 在起了一个本地服务器,并且支持跨域

{
  ...
  "scripts": {
    "start": "cross-env PORT=7104 http-server . --cors",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  ...
}

3.3.2 entry.js

// 渲染函数
const render = $ => {
  $('#purehtml-container').html('Hello, render with jQuery');
  return Promise.resolve();
};

// 在全局对象上导出三个生命周期函数
(global => {
  global['purehtml'] = {
    bootstrap: () => {
      console.log('purehtml bootstrap');
      return Promise.resolve();
    },
    mount: () => {
      console.log('purehtml mount');
      // 调用渲染函数
      return render($);
    },
    unmount: () => {
      console.log('purehtml unmount');
      return Promise.resolve();
    },
  };
})(window);

3.3.3 index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Purehtml Example</title>
  <script src="//cdn.bootcss.com/jquery/3.4.1/jquery.min.js">
  </script>
</head>
<body>
  <div style="display: flex; justify-content: center; align-items: center; height: 200px;">
    Purehtml Example
  </div>
  <div id="purehtml-container" style="text-align:center"></div>
  <!-- 引入 entry.js,相当于 vue 项目的 publicPath 配置 -->
  <script src="//localhost:7104/entry.js" entry></script>
</body>
</html>

3.4 angular 9、react 15、react 16

这三个实例项目就不一一分析了,和 vue 项目类似,都是配置打包工具将微应用打包成一个 umd 格式,然后配置应用入口文件 和 路由前缀

3.5 UmiJS

UmiJS有自己独立支持qiankun的插件@umijs/plugin-qiankun

  1. yarn add @umijs/plugin-qiankun -D
  2. 配置 qiankun 开启。

3.5.1 主应用配置

第一步:注册子应用:

子应用注册有两种方式,二选一即可

a. 插件构建期配置子应用

export default {
  qiankun: {
    master: {
      // 注册子应用信息
      apps: [
        {
          name: 'app1', // 唯一 id
          entry: '//localhost:7001', // html entry
        },
        {
          name: 'app2', // 唯一 id
          entry: '//localhost:7002', // html entry
        },
      ],
    },
  },
};

b. 运行时动态配置子应用(src/app.ts 里开启)

// 从接口中获取子应用配置,export 出的 qiankun 变量是一个 promise
export const qiankun = fetch('/config').then(({ apps }) => ({
  // 注册子应用信息
  apps,
  // 完整生命周期钩子请看 https://qiankun.umijs.org/zh/api/#registermicroapps-apps-lifecycles
  lifeCycles: {
    afterMount: (props) => {
      console.log(props);
    },
  },
  // 支持更多的其他配置,详细看这里 https://qiankun.umijs.org/zh/api/#start-opts
}));

完整的主应用配置项看这里 masterOptions 配置列表

第二步:装载子应用

子应用的装载有两种方式,二选一即可

a. 使用路由绑定的方式(建议使用这种方式来引入自带路由的子应用。)

/app1/project 和 /app2 这两个路径时分别加载微应用 app1 和 app2,只需要增加这样一些配置即可:
export default {
	routes: [
    {
      path: '/',
      component: '../layouts/index.js',
      routes: [
        {
          path: '/app1',
          component: './app1/index.js',
          routes: [
            {
              path: '/app1/user',
              component: './app1/user/index.js',
            },
+            // 配置微应用 app1 关联的路由
+            {
+              path: '/app1/project',
+              microApp: 'app1',
+            },
          ],
        },
+       // 配置 app2 关联的路由
+       {
+         path: '/app2',
+         microApp: 'app2'
+       },
        {
          path: '/',
          component: './index.js',
        },
      ],
    },
  ],
}

微应用路由也可以配置在运行时,通过 src/app.ts 添加:

export const qiankun = fetch('/config').then(({ apps }) => {
  return {
    apps,
    routes: [
      {
        path: '/app1',
        microApp: 'app1',
      },
    ],
  };
});

运行时注册的路由会自动关联到你配置的根路由下面:

export default {
  routes: [
    {
      path: '/',
      component: '../layouts/index.js',
      routes: [
+       {
+         path: '/app1',
+         microApp: 'app1',
+       },
        {
          path: '/test',
          component: './test.js',
        },
      ],
    },
  ]
}

b. 使用 <MicroApp /> 组件的方式(建议使用这种方式来引入不带路由的子应用。)

我们可以直接使用 React 标签的方式加载我们已注册过的子应用:
import { MicroApp } from 'umi';

export function MyPage() {

  return (
    <div>
      <div>
+        <MicroApp name="app1" />
      </div>
    </div>
  )
}

可以通过配置 autoSetLoading 的方式,开启微应用的 loading 动画。

import { MicroApp } from 'umi';

export function MyPage() {
  return (
    <div>
      <div>
        <MicroApp name="app1" autoSetLoading />
      </div>
    </div>
  );
}

3.5.2 子应用配置

第一步:插件注册(config.js)

export default {
  qiankun: {
    slave: {},
  },
};

第二步:配置运行时生命周期钩子(可选)

插件会自动为你创建好 qiankun 子应用需要的生命周期钩子,但是如果你想在生命周期期间加一些自定义逻辑,可以在子应用的 src/app.ts 里导出 qiankun 对象,并实现每一个生命周期钩子,其中钩子函数的入参 props 由主应用自动注入。

export const qiankun = {
  // 应用加载之前
  async bootstrap(props) {
    console.log('app1 bootstrap', props);
  },
  // 应用 render 之前触发
  async mount(props) {
    console.log('app1 mount', props);
  },
  // 应用卸载之后触发
  async unmount(props) {
    console.log('app1 unmount', props);
  },
};

环境变量配置

建议您提前在子应用中指定应用启动的具体端口号,如通过.env指定

PORT=8081

3.5.3 父子应用通讯

3.5.3.1 配合 useModel 使用(推荐)

需确保已安装 @umijs/plugin-model@umijs/preset-react

主应用使用下面任一方式透传数据:

  1. 如果你用的 MicroApp 组件模式消费微应用,那么数据传递的方式就跟普通的 react 组件通信是一样的,直接通过 props 传递即可:

    function MyPage() {
      const [name, setName] = useState(null);
      return (
        <MicroApp name={name} onNameChange={(newName) => setName(newName)} />
      );
    }
    
  2. 如果你用的 路由绑定式 消费微应用,那么你需要在 src/app.ts 里导出一个 useQiankunStateForSlave 函数,函数的返回值将作为 props 传递给微应用,如:

    // src/app.ts
    export function useQiankunStateForSlave() {
      const [masterState, setMasterState] = useState({});
    
      return {
        masterState,
        setMasterState,
      };
    }
    

微应用中会自动生成一个全局 model,可以在任意组件中获取主应用透传的 props 的值。

import { useModel } from 'umi';

function MyPage() {
  const masterProps = useModel('@@qiankunStateFromMaster');
  return <div>{JSON.stringify(masterProps)}</div>;
}

或者可以通过高阶组件 connectMaster 来获取主应用透传的 props

import { connectMaster } from 'umi';

function MyPage(props) {
  return <div>{JSON.stringify(props)}</div>;
}

export default connectMaster(MyPage);

<MicroApp /> 的方式一同使用时,会额外向子应用传递一个 setLoading 的属性,在子应用中合适的时机执行 masterProps.setLoading(false),可以标记微模块的整体 loading 为完成状态。

3.5.3.2 基于 props 传递

主应用中配置 apps 时以 props 将数据传递下去(参考主应用运行时配置一节)

// src/app.js

export const qiankun = fetch('/config').then((config) => {
  return {
    apps: [
      {
        name: 'app1',
        entry: '//localhost:2222',
        props: {
          onClick: (event) => console.log(event),
          name: 'xx',
          age: 1,
        },
      },
    ],
  };
});

子应用在生命周期钩子中获取 props 消费数据(参考子应用运行时配置一节)

3.5.4 嵌套子应用

除了导航应用之外,App1 与 App2 均依赖浏览器 url,为了让 App1 嵌套 App2,两个应用同时存在,我们需要在运行时将 App2 的路由改为 memory 类型。

  1. 在 App1 中加入 master 配置
export default {
  qiankun: {
    master: {
      // 注册子应用信息
      apps: [
        {
          name: 'app2', // 唯一 id
          entry: '//localhost:7002', // html entry
        },
      ],
    },
  },
};
  1. 通过 <MicroAppWithMemoHistory /> 引入 App2
import { MicroAppWithMemoHistory } from 'umi';

export function MyPage() {

  return (
    <div>
      <div>
+        <MicroAppWithMemoHistory name="app2" url="/user" />
      </div>
    </div>
  )
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

微前端框架 之 qiankun 的相关文章

  • 是否可以加载本地版本的 JavaScript 文件而不是服务器版本?

    只是有一个简单的问题要抛出 看看是否有解决方案 假设我无法访问服务器 我加载一个网页 发现他们有一个从子文件夹加载的 Javascript 文件 比方说 scripts js some js 现在 我想在本地对此文件进行更改 并针对整个站点
  • svg路径指针事件-点击检测

    我正在编写一些 HTML 以便可以使用 HTML SVG 和 PATH 标签绘制贝塞尔曲线 我的曲线效果非常好 现在我想添加一项功能 如果用户将鼠标悬停在曲线上 我会更改颜色 但实际情况是 SVG 创建了一个包含路径的大框 并捕获所有点击
  • onbeforeunload 或单击浏览器后退按钮需要帮助

    如果用户单击浏览器的后退按钮 那么我希望出现提示并要求确认 如果用户单击 确定 那么它应该导航到xx html 如果用户单击 取消 则应阻止导航 我怎样才能做到这一点 注意 我已经尝试过onbeforeunload方法 但它适用于所有导航操
  • Django CBV表单提交返回的JSON显示为新页面

    我正在使用 Django 3 2 我正在创建一个简单的时事通讯订阅表格 表单提交将 JSON 返回到前端 然后应该使用前端来更新页面的部分内容 但是 当我发布表单时 JSON 字符串将在新页面上显示为文本 这是调用视图的路由 urlpatt
  • 缩放对象上的弹跳动画

    拥有对象比例 然后在返回到原始比例因子之前以该比例因子执行弹跳动画的最佳方法是什么 我意识到我可以做一些事情 比如将其缩放到 2 2 然后 1 8 然后 2 0 但我正在寻找一种方法 您只需在比例因子上执行弹跳动画 因为我的比例因子会改变
  • 在 Promise 中中止 ajax 请求

    我正在构建一个表单验证并学习承诺 我决定使用承诺模式实现异步验证函数 var validateAjax function value return new Promise function resolve reject ajax data
  • 如何将嵌套对象数组转换为 CSV?

    我有一个包含嵌套对象的数组 例如 name 1 children name 1 1 children 1 2 id 2 thing name 2 1 children 2 2 name 3 stuff name 3 1 children 3
  • SVG 沿圆弧添加文本

    我正在尝试绘制 SVG 径向饼图 如下所述 色卡 https stackoverflow com a 18210763 1395178 现在我尝试将文本与圆弧一起添加到每个切片 我试图展示Text 1具有与 M 和 A 值完全相同的 x y
  • 如何取消 ComponentWillUnmount 中的所有请求?

    根据docs https facebook github io react docs react component html componentwillunmount ComponentWillUnmount 能够取消请求 我有一个页面发
  • 从动态服务器中抓取 html 列表数据

    哈喽大家好 抱歉提出转储问题 这是我最后的手段 我发誓我尝试了无数其他 Stackoverflow 问题 不同的框架等 但这些似乎没有帮助 我有以下问题 一个网站显示一个数据列表 前面有大量的 div li span 等标签 它是一个很大的
  • 动态添加项目到放大弹出画廊

    有没有办法动态添加图库项目华丽的弹出窗口 http dimsemenov com plugins magnific popup 那已经开放了 或更新当前项目 找不到关于 in 的任何内容插件文档 http dimsemenov com pl
  • 但为什么浏览器 DOM 经过 10 年的努力仍然这么慢?

    Web 浏览器 DOM 自 90 年代末以来就已存在 但它仍然是性能 速度方面最大的限制之一 我们拥有来自 Google Mozilla Microsoft Opera W3C 和其他各种组织的一些世界上最聪明的人才 为我们所有人致力于 W
  • javascript:新日期,缺少年份

    我打电话给 new Date Jan 4 发现默认年份是2001年 a new Date Jan 4 Thu Jan 04 2001 00 00 00 GMT 0500 EST 有什么办法可以将默认年份设置为 2011 年吗 更新 我知道我
  • Javascript变量是一个对象数组,但无法访问元素

    我正在使用 Firebase 数据库和 Javascript 并且我有代码可以获取每个类别中的每个问题 我有一个名为 类别 的对象 其中包含名称 问题和问题计数 然后它将被推入类别列表 questionsPerCategory 在我刚刚做的
  • Div 上的倾斜边框

    我正在尝试倾斜一个 div 类似于 使用 css 倾斜 div 的顶部而不倾斜文本 https stackoverflow com questions 13591584 slant the top of a div using css wi
  • 如何使用FileSystem API的window.requestFileSystem?

    我用 JavaScript 编写了以下代码 JavaScript 代码 var fs null function initFS window requestFileSystem window requestFileSystem window
  • Origin 无权使用地理定位服务 - 即使通过 HTTPS

    我有一个通过 HTTPS 使用 HTML5 地理定位的网页 它在桌面浏览器上运行良好 然而 在 iOS Safari 上 我收到错误 Origin 无权使用地理定位服务 我已确保页面上的所有内容都通过 HTTPS 加载 每个图像 每个脚本和
  • 在不调用“then”的情况下解决 Promise

    我有这段代码 它是我为一个名为 Poolio 的 NPM 模块编写的小型 API 的一部分 对于那些支持错误优先回调和承诺的人来说 我遇到的问题似乎是一个常见问题 我们如何在支持两者的同时保持一致的 API 和 API 的一致返回值 例如
  • 如何在 jest 中测试调用和应用函数?

    这是我的callnapply js file const callAndApply caller object method nameArg ageArg tShirtSizeArg method call object nameArg a
  • axios在自调用函数内部只调用一次(Internet Explorer)

    我有一个函数每 2 5 秒调用自己一次来检查后台运行的任务 它调用 axiosget如果响应错误 则返回一个 url 如果响应成功 我将停止该函数 这在 Chrome 和 Mozilla 上完美运行 但由于某种原因 它在 IE 版本 11

随机推荐

  • 如何用计算机计算概率,概率计算器与阶乘方程

    我必须做一个代码 计算中奖彩票的概率 给定的数量可供选择的数量以及您必须选择多少 我必须在代码中使用阶乘方程 n k n k 代码本身工作正常 但公式不会编译 概率计算器与阶乘方程 This program calculates the p
  • 在局域网部署自己的Docker私有仓库

    本文参考自局域网部署Docker 从无到有创建自己的Docker私有仓库 内网用户由于无法链接互联网 所以无法像在线用户那样直接使用pull指令从Docker Hub上下载镜像 再查看了很多资料之后 发现可以使用文件操作在局域网上部署Doc
  • 手动搭建webase(3)——WeBASE管理平台

    1 依赖环境 Nginx安装 拉取代码 代码可以放在 data下面 执行命令 git clone https github com WeBankFinTech WeBASE Web git 若网络问题导致长时间无法下载 可尝试以下命令 gi
  • 从零开始搭建物联网平台(1):开篇

    前言 读大学的时候学的是物联网工程 大概是在大二的时候开始接触单片机 那时候特喜欢捣鼓那些东西 就觉得特别酷有极客范 还记得第一次做物联网相关的是一个远程控制的开关 第一次调通的时候真的很兴奋 啥也没干就挂在那用手机控制继电器听咔嗒咔哒的声
  • 在不同的维度中,态势感知存在着各自的规律和特点

    在不同的维度中 态势感知可以具有不同的规律和特点 以下是一些常见的维度和相关规律 时间维度 态势感知随时间的变化可以呈现出趋势和周期性 趋势是指态势感知在长期内的整体发展方向 可以是增长 下降或保持稳定 周期性是指态势感知在一定时间内重复出
  • CSS 学成网(二)

    学成网小圆点 小圆点模块 circle width 180px height 22px background color pink position absolute bottom 10px left 50 margin left 90px
  • 字节序网络序大小端问题

    基本概念 主机字节序 就是自己的主机内部 内存中数据的处理方式 可以分为两种 大端字节序 big endian 按照内存的增长方向 高位数据存储于低位内存中 小端字节序 little endian 按照内存的增长方向 高位数据存储于高位内存
  • Kafka复习计划 - Kafka基础知识以及集群参方案和参数

    Kafka复习计划 Kafka基础知识以及集群参方案和参数 前言 一 Kafka 相关术语 1 1 实现高可用的手段 1 2 Kafka的三层消息架构 1 3 Kafka如何持久化数据 1 4 常见问题 二 Kafka集群部署方案 2 1
  • 链码调试方法(在fabric-sample下)

    链码调试方法 在fabric sample下 启动网络 1 cd fabric samples chaincode docker devmode 1 docker compose f docker compose simple yaml u
  • YOLO v2(单尺度)

    文章目录 YOLO v2 单尺度 细粒度 fine grained 特征 锚框 Anchor 锚框 Anchor 和损失函数 Loss Anchor与YOLO YOLO v2 单尺度 能解决稠密物体的检测 但无法解决多尺度物体的检测 细粒度
  • 剑指 Offer 64. 求1+2+…+n----思路和心得分享

    对于这道题 我第一个反应就是高斯的算法 首位相加乘以一半的数量 奇数要加中间值 class Solution public int sumNums int n 求出中间值 int key n 2 设置结果 int sum 0 如果是奇数 加
  • 超全实用

    汽车领域 金融领域 由于篇幅有限 为了阅读体验本文只截取金融和汽车领域的数据存储期限汇总 其他领域如 通用 电商 工业 化妆品 医疗 游戏等行业要求在单独的汇总Excel中 可私信找我要 暗号 数据存储
  • Android 友盟集成华为,小米,魅族推送的基础实现与注意事项

    因为目前正处于实习阶段 所以公司安排了解一下华为 小米 魅族 友盟的推送 并将其集成在一个APP上 为此特地写一篇博客记录集成过程中需要注意的一些事项 如果想进行集成 建议还是先了解一下华为 小米 魅族它们自己厂商的推送服务及文档 毕竟集成
  • opencv不规则裁剪

    首先是一张卡通T袖十香 使用labelme标注 然后使用json里面标注的点坐标去裁剪 裁剪后的图片 下面附上代码 import cv2 import numpy as np opencv不规则裁剪 def ROI byMouse img
  • windows下查看端口的方法

    小编的同事昨天遇到了一个问题 在运行某个程序的时候 总提示说程序端口被占用 不能运行 这可就着急了 今天小编一系列的查看 解决了问题 今天就来教大家windows如何查看端口 从而知道端口被哪个程序占用了 然后在进行处理即可 下面我们一起来
  • VINS - Fusion GPS/VIO 融合 二、数据融合

    https zhuanlan zhihu com p 75492883 一 简介 源代码 VINS Fusion 数据集 KITTI 数据 程序入口 globalOptNode cpp 二 程序解读 2 1 主函数 int main int
  • 芯片的ATE测试简介

    ATE Automatic Test Equipment 即自动测试设备 它用于芯片大规模生产测试 保障稳健 质量 成本和进度 的供应 ATE测试基本的覆盖理念 主要是结构性测试 即Structure Test 再辅以一定的功能和性能测试
  • ch4 报错修正 & Sophus使用

    ch4 报错 修正 1 添加Eigen头文件 include directories usr include eigen3 2 include sophus so3 hpp include sophus se3 hpp 3 大量报错但都与S
  • CentOS 7安装OpenMPI

    文章目录 一 下载OpenMPI源码 二 解压缩OpenMPI源码 三 安装OpenMPI 四 配置环境变量 五 验证安装 参考资料 一 下载OpenMPI源码 wget https download open mpi org releas
  • 微前端框架 之 qiankun

    文章目录 一 介绍 1 1 qiankun的优点 特点 二 源码解读 2 1 框架目录结构 2 2 有料的 package json 2 3 示例项目中的主应用 2 4 启动示例项目 三 示例项目 3 1 主应用 3 1 1 webpack