iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >vue3的动态组件是如何工作的
  • 847
分享到

vue3的动态组件是如何工作的

2024-04-02 19:04:59 847人浏览 独家记忆
摘要

目录一、组件注册 1.1 全局注册 1.2 注册全局组件的过程 1.3 应用挂载的过程 二、动态组件 2.1 绑定字符串类型 2.2 绑定对象类型 三、阿宝哥有话说 3.1 除了 c

在这篇文章中,阿宝哥将介绍 Vue 3 中的内置组件 —— component,该组件的作用是渲染一个 “元组件” 为动态组件。如果你对动态组件还不了解的话也没关系,文中阿宝哥会通过具体的示例,来介绍动态组件的应用。由于动态组件内部与组件注册之间有一定的联系,所以为了让大家能够更好地了解动态组件的内部原理,阿宝哥会先介绍组件注册的相关知识。

一、组件注册

1.1 全局注册

在 Vue 3.0 中,通过使用 app 对象的 component 方法,可以很容易地注册或检索全局组件。component 方法支持两个参数:

  • name:组件名称;
  • component:组件定义对象。

接下来,我们来看一个简单的示例:


<div id="app">
 <component-a></component-a>
 <component-b></component-b>
 <component-c></component-c>
</div>
<script>
 const { createApp } = Vue
 const app = createApp({}); // ①
 app.component('component-a', { // ②
  template: "<p>我是组件A</p>"
 });
 app.component('component-b', {
  template: "<p>我是组件B</p>"
 });
 app.component('component-c', {
  template: "<p>我是组件C</p>"
 });
 app.mount('#app') // ③
</script>

在以上代码中,我们通过 app.component 方法注册了 3 个组件,这些组件都是全局注册的 。也就是说它们在注册之后可以用在任何新创建的组件实例的模板中。该示例的代码比较简单,主要包含 3 个步骤:创建 App 对象、注册全局组件和应用挂载。其中创建 App 对象的细节,阿宝哥会在后续的文章中单独介绍,下面我们将重点分析其他 2 个步骤,首先我们先来分析注册全局组件的过程。

1.2 注册全局组件的过程

在以上示例中,我们使用 app 对象的 component 方法来注册全局组件:


app.component('component-a', {
 template: "<p>我是组件A</p>"
});

当然,除了注册全局组件之外,我们也可以注册局部组件,因为组件中也接受一个 components 的选项:


const app = Vue.createApp({
 components: {
 'component-a': ComponentA,
 'component-b': ComponentB
 }
})

需要注意的是,局部注册的组件在其子组件中是不可用的。接下来,我们来继续介绍注册全局组件的过程。对于前面的示例来说,我们使用的 app.component 方法被定义在 runtime-core/src/apiCreateApp.ts 文件中:


export function createAppAPI<HostElement>(
 render: RootRenderFunction,
 hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
 return function createApp(rootComponent, rootProps = null) {
 const context = createAppContext()
 const installedPlugins = new Set()
 let isMounted = false

 const app: App = (context.app = {
  // 省略部分代码
  _context: context,

  // 注册或检索全局组件
  component(name: string, component?: Component): any {
  if (__DEV__) {
   validateComponentName(name, context.config)
  }
  if (!component) { // 获取name对应的组件
   return context.components[name]
  }
  if (__DEV__ && context.components[name]) { // 重复注册提示
   warn(`Component "${name}" has already been reGIStered in target app.`)
  }
  context.components[name] = component // 注册全局组件
  return app
  },
 })

 return app
 }
}

当所有的组件都注册成功之后,它们会被保存到 context 对象的 components 属性中,具体如下图所示:

而 createAppContext 函数被定义在 runtime-core/src/apiCreateApp.ts 文件中:


// packages/runtime-core/src/apiCreateApp.ts
export function createAppContext(): AppContext {
 return {
 app: null as any,
 config: { // 应用的配置对象
  isNativeTag: NO,
  perfORMance: false,
  globalProperties: {},
  optionMergeStrategies: {},
  isCustomElement: NO,
  errorHandler: undefined,
  warnHandler: undefined
 },
 mixins: [], // 保存应用内的混入
 components: {}, // 保存全局组件的信息
 directives: {}, // 保存全局指令的信息
 provides: Object.create(null)
 }
}

分析完 app.component 方法之后,是不是觉得组件注册的过程还是挺简单的。那么对于已注册的组件,何时会被使用呢?要回答这个问题,我们就需要分析另一个步骤 —— 应用挂载。

1.3 应用挂载的过程

为了更加直观地了解应用挂载的过程,阿宝哥利用 Chrome 开发工具的 Performance 标签栏,记录了应用挂载的主要过程:

在上图中我们发现了一个与组件相关的函数 resolveComponent。很明显,该函数用于解析组件,且该函数在 render 方法中会被调用。在源码中,我们找到了该函数的定义:


// packages/runtime-core/src/helpers/resolveAssets.ts
const COMPONENTS = 'components'

export function resolveComponent(name: string): ConcreteComponent | string {
 return resolveAsset(COMPONENTS, name) || name
}

由以上代码可知,在 resolveComponent 函数内部,会继续调用 resolveAsset 函数来执行具体的解析操作。在分析 resolveAsset 函数的具体实现之前,我们在 resolveComponent 函数内部加个断点,来一睹 render 方法的 “芳容”:

在上图中,我们看到了解析组件的操作,比如 _resolveComponent("component-a")。前面我们已经知道在 resolveComponent 函数内部会继续调用 resolveAsset 函数,该函数的具体实现如下:


// packages/runtime-core/src/helpers/resolveAssets.ts
function resolveAsset(
 type: typeof COMPONENTS | typeof DIRECTIVES,
 name: string,
 warnMissing = true
) {
 const instance = currentRenderingInstance || currentInstance
 if (instance) {
 const Component = instance.type
 // 省略大部分处理逻辑
 const res =
  // 局部注册
  // check instance[type] first for components with mixin or extends.
  resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
  // 全局注册
  resolve(instance.appContext[type], name)
 return res
 } else if (__DEV__) {
 warn(
  `resolve${capitalize(type.slice(0, -1))} ` +
  `can only be used in render() or setup().`
 )
 }
}

因为注册组件时,使用的是全局注册的方式,所以解析的过程会执行 resolve(instance.appContext[type], name) 该语句,其中 resolve 方法的定义如下:


// packages/runtime-core/src/helpers/resolveAssets.ts
function resolve(registry: Record<string, any> | undefined, name: string) {
 return (
 registry &&
 (registry[name] ||
  registry[camelize(name)] ||
  registry[capitalize(camelize(name))])
 )
}

分析完以上的处理流程,我们在解析全局注册的组件时,会通过 resolve 函数从应用的上下文对象中获取已注册的组件对象。


(function anonymous() {
 const _Vue = Vue

 return function render(_ctx, _cache) {
  with (_ctx) {
   const {resolveComponent: _resolveComponent, createVnode: _createVNode, 
   Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock} = _Vue

   const _component_component_a = _resolveComponent("component-a")
   const _component_component_b = _resolveComponent("component-b")
   const _component_component_c = _resolveComponent("component-c")

   return (_openBlock(),
   _createBlock(_Fragment, null, [
    _createVNode(_component_component_a), 
    _createVNode(_component_component_b), 
    _createVNode(_component_component_c)], 64))
  }
 }
})

在获取到组件之后,会通过 _createVNode 函数创建 VNode 节点。然而,关于 VNode 是如何被渲染成真实的 DOM 元素这个过程,阿宝哥就不继续往下介绍了,后续会写专门的文章来单独介绍这块的内容,接下来我们将介绍动态组件的相关内容。

二、动态组件

在 Vue 3 中为我们提供了一个 component 内置组件,该组件可以渲染一个 “元组件” 为动态组件。根据 is 的值,来决定哪个组件被渲染。如果 is 的值是一个字符串,它既可以是 html 标签名称也可以是组件名称。对应的使用示例如下:


<!-- 动态组件由 vm 实例的 `componentId` property 控制 -->
<component :is="componentId"></component>

<!-- 也能够渲染注册过的组件或 prop 传入的组件-->
<component :is="$options.components.child"></component>

<!-- 可以通过字符串引用组件 -->
<component :is="condition ? 'FooComponent' : 'BarComponent'"></component>

<!-- 可以用来渲染原生 HTML 元素 -->
<component :is="href ? 'a' : 'span'"></component>

2.1 绑定字符串类型

介绍完 component 内置组件,我们来举个简单的示例:


<div id="app">
 <button
  v-for="tab in tabs"
  :key="tab"
  @click="currentTab = 'tab-' + tab.toLowerCase()">
  {{ tab }}
 </button>
 <component :is="currentTab"></component>
</div>
<script>
 const { createApp } = Vue
 const tabs = ['Home', 'My']
 const app = createApp({
  data() {
  return {
   tabs,
   currentTab: 'tab-' + tabs[0].toLowerCase()
  }
  },
 });
 app.component('tab-home', {
  template: `<div style="border: 1px solid;">Home component</div>`
 })
 app.component('tab-my', {
  template: `<div style="border: 1px solid;">My component</div>`
 })
 app.mount('#app')
</script>

在以上代码中,我们通过 app.component 方法全局注册了 tab-home 和 tab-my 2 个组件。此外,在模板中,我们使用了 component 内置组件,该组件的 is 属性绑定了 data 对象的 currentTab 属性,该属性的类型是字符串。当用户点击 Tab 按钮时,会动态更新 currentTab 的值,从而实现动态切换组件的功能。以上示例成功运行后的结果如下图所示:

看到这里你会不会觉得 component 内置组件挺神奇的,感兴趣的小伙伴继续跟阿宝哥一起,来揭开它背后的秘密。下面我们利用 Vue 3 Template Explorer 在线工具,看一下 <component :is="currentTab"></component> 模板编译的结果:


const _Vue = Vue

return function render(_ctx, _cache, $props, $setup, $data, $options) {
 with (_ctx) {
 const { resolveDynamicComponent: _resolveDynamicComponent, openBlock: _openBlock, 
  createBlock: _createBlock } = _Vue
 return (_openBlock(), _createBlock(_resolveDynamicComponent(currentTab)))
 }
}

通过观察生成的渲染函数,我们发现了一个 resolveDynamicComponent 的函数,根据该函数的名称,我们可以知道它用于解析动态组件,它被定义在 runtime-core/src/helpers/resolveAssets.ts 文件中,具体实现如下所示:


// packages/runtime-core/src/helpers/resolveAssets.ts
export function resolveDynamicComponent(component: unknown): VNodeTypes {
 if (isString(component)) {
 return resolveAsset(COMPONENTS, component, false) || component
 } else {
 // invalid types will fallthrough to createVNode and raise warning
 return (component || NULL_DYNAMIC_COMPONENT) as any
 }
}

在 resolveDynamicComponent 函数内部,若 component 参数是字符串类型,则会调用前面介绍的 resolveAsset 方法来解析组件:


// packages/runtime-core/src/helpers/resolveAssets.ts
function resolveAsset(
 type: typeof COMPONENTS | typeof DIRECTIVES,
 name: string,
 warnMissing = true
) {
 const instance = currentRenderingInstance || currentInstance
 if (instance) {
 const Component = instance.type
 // 省略大部分处理逻辑
 const res =
  // 局部注册
  // check instance[type] first for components with mixin or extends.
  resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
  // 全局注册
  resolve(instance.appContext[type], name)
 return res
 }
}

对于前面的示例来说,组件是全局注册的,所以解析过程中会从 app.context 上下文对象的 components 属性中获取对应的组件。当 currentTab 发生变化时,resolveAsset 函数就会返回不同的组件,从而实现动态组件的功能。此外,如果 resolveAsset 函数获取不到对应的组件,则会返回当前 component 参数的值。比如 resolveDynamicComponent('div') 将返回 'div' 字符串。


// packages/runtime-core/src/helpers/resolveAssets.ts
export const NULL_DYNAMIC_COMPONENT = Symbol()

export function resolveDynamicComponent(component: unknown): VNodeTypes {
 if (isString(component)) {
 return resolveAsset(COMPONENTS, component, false) || component
 } else {
 return (component || NULL_DYNAMIC_COMPONENT) as any
 }
}

细心的小伙伴可能也注意到了,在 resolveDynamicComponent 函数内部,如果 component 参数非字符串类型,则会返回 component || NULL_DYNAMIC_COMPONENT 这行语句的执行结果,其中 NULL_DYNAMIC_COMPONENT 的值是一个 Symbol 对象。

2.2 绑定对象类型

了解完上述的内容之后,我们来重新实现一下前面动态 Tab 的功能:


<div id="app">
 <button
  v-for="tab in tabs"
  :key="tab"
  @click="currentTab = tab">
  {{ tab.name }}
 </button>
 <component :is="currentTab.component"></component>
</div>
<script>
 const { createApp } = Vue
 const tabs = [
  {
  name: 'Home',
  component: {
   template: `<div style="border: 1px solid;">Home component</div>`
  }
  },
  {
  name: 'My',
  component: {
   template: `<div style="border: 1px solid;">My component</div>`
  }
 }]
 const app = createApp({
  data() {
  return {
   tabs,
   currentTab: tabs[0]
  }
  },
 });
 app.mount('#app')
</script>

在以上示例中,component 内置组件的 is 属性绑定了 currentTab 对象的 component 属性,该属性的值是一个对象。当用户点击 Tab 按钮时,会动态更新 currentTab 的值,导致 currentTab.component 的值也发生变化,从而实现动态切换组件的功能。需要注意的是,每次切换的时候,都会重新创建动态组件。但在某些场景下,你会希望保持这些组件的状态,以避免反复重渲染导致的性能问题。

对于这个问题,我们可以使用 Vue 3 的另一个内置组件 —— keep-alive,将动态组件包裹起来。比如:


<keep-alive>
 <component :is="currentTab"></component>
</keep-alive> 

keep-alive 内置组件的主要作用是用于保留组件状态或避免重新渲染,使用它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。关于 keep-alive 组件的内部工作原理,阿宝哥后面会写专门的文章来分析它,对它感兴趣的小伙伴记得关注 Vue 3.0 进阶 系列哟。

三、阿宝哥有话说

3.1 除了 component 内置组件外,还有哪些内置组件?

在 Vue 3 中除了本文介绍的 component 和 keep-alive 内置组件之外,还提供了 transition、transition-group 、slot 和 teleport 内置组件。

3.2 注册全局组件与局部组件有什么区别?

注册全局组件


const { createApp, h } = Vue
const app = createApp({});
app.component('component-a', {
 template: "<p>我是组件A</p>"
});

使用 app.component 方法注册的全局的组件,被保存到 app 应用对象的上下文对象中。而通过组件对象 components 属性注册的局部组件是保存在组件实例中。

注册局部组件


const { createApp, h } = Vue
const app = createApp({});
const componentA = () => h('div', '我是组件A');
app.component('component-b', {
 components: {
 'component-a': componentA
 },
 template: `<div>
 我是组件B,内部使用了组件A
 <component-a></component-a> 
 </div>`
})

解析全局注册和局部注册的组件


// packages/runtime-core/src/helpers/resolveAssets.ts
function resolveAsset(
 type: typeof COMPONENTS | typeof DIRECTIVES,
 name: string,
 warnMissing = true
) {
 const instance = currentRenderingInstance || currentInstance
 if (instance) {
 const Component = instance.type
 // 省略大部分处理逻辑
 const res =
  // 局部注册
  // check instance[type] first for components with mixin or extends.
  resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
  // 全局注册
  resolve(instance.appContext[type], name)
 return res
 }
}

3.3 动态组件能否绑定其他属性?

component 内置组件除了支持 is 绑定之外,也支持其他属性绑定和事件绑定:


<component :is="currentTab.component" :name="name" @click="sayHi"></component>

这里阿宝哥使用 Vue 3 Template Explorer 这个在线工具,来编译上述的模板:


const _Vue = Vue
return function render(_ctx, _cache, $props, $setup, $data, $options) {
 with (_ctx) {
 const { resolveDynamicComponent: _resolveDynamicComponent, 
  openBlock: _openBlock, createBlock: _createBlock } = _Vue

 return (_openBlock(), _createBlock(_resolveDynamicComponent(currentTab.component), {
  name: name,
  onClick: sayHi
 }, null, 8 , ["name", "onClick"]))
 }
}

观察以上的渲染函数可知,除了 is 绑定会被转换为 _resolveDynamicComponent 函数调用之外,其他的属性绑定都会被正常解析为 props 对象。

以上就是vue3的动态组件是如何工作的的详细内容,更多关于vue3动态组件的资料请关注编程网其它相关文章!

--结束END--

本文标题: vue3的动态组件是如何工作的

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

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

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

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

下载Word文档
猜你喜欢
  • vue3的动态组件是如何工作的
    目录一、组件注册 1.1 全局注册 1.2 注册全局组件的过程 1.3 应用挂载的过程 二、动态组件 2.1 绑定字符串类型 2.2 绑定对象类型 三、阿宝哥有话说 3.1 除了 c...
    99+
    2022-11-11
  • vue3动态组件如何使用
    这篇“vue3动态组件如何使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue3动态组件如何使用”文章吧。问题:为什么v...
    99+
    2023-07-05
  • Vue3 Teleport是如何工作的
    这篇文章主要讲解了“Vue3 Teleport是如何工作的”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Vue3 Teleport是如何工作的”吧!Tele...
    99+
    2022-10-19
  • Vue3动态组件如何进行异常处理
    这篇“Vue3动态组件如何进行异常处理”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue3动态组件如何进行异常处理”文章吧...
    99+
    2023-07-04
  • VUE3+vite项目中动态引入组件与异步组件的详细实例
    目录一、全量注册,随用随取1. 把项目中所有vue文件注册成异步组件。2. 获取组件3. 参考如下二、使用@rollup/plugin-dynamic-import-vars插件 1...
    99+
    2022-11-13
  • vue3如何使用defineAsyncComponent与component标签实现动态渲染组件
    本篇内容介绍了“vue3如何使用defineAsyncComponent与component标签实现动态渲染组件”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅...
    99+
    2023-07-06
  • Vue3之元素和组件的动画如何切换
    本篇内容介绍了“Vue3之元素和组件的动画如何切换”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!实例解析元素间的动画切换元素之间的动画切换指...
    99+
    2023-07-06
  • 动态代理ip的工作原理是什么
    这篇文章主要讲解了“动态代理ip的工作原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“动态代理ip的工作原理是什么”吧!所以它要求对HTTP进行请求和响应模拟。基于以下图像直观地展示...
    99+
    2023-06-25
  • Vue中动态组件和异步组件的原理是什么
    今天就跟大家聊聊有关Vue中动态组件和异步组件的原理是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。前言在vue官方资料中,我们可以可以很学会如...
    99+
    2022-10-19
  • Vue3组件库的环境如何配置
    这篇文章主要讲解了“Vue3组件库的环境如何配置”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Vue3组件库的环境如何配置”吧!因为我们是使用 Vite+Ts 开发的是 Vue3 组件库,所...
    99+
    2023-07-06
  • vue如何给组件动态绑定不同的事件
    目录vue给组件动态绑定不同的事件场景vue组件绑定事件无效下面提供两者方法,可以任选其一vue给组件动态绑定不同的事件 场景 根据用户配置的事件动态绑定。也就是用户可以动态选择配置...
    99+
    2022-11-13
    vue组件 动态绑定事件 vue动态绑定
  • 如何变更vue3中组件的非兼容
    这篇文章主要介绍“如何变更vue3中组件的非兼容”,在日常操作中,相信很多人在如何变更vue3中组件的非兼容问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何变更vue3中组件的非兼容”的疑惑有所帮助!接下来...
    99+
    2023-06-06
  • Vue3实现一个可左右滑动操作组件的示例代码
    目录代码实现如何使用为了实现左右滑动能够切换页面,便有了做成组件的想法。 代码实现 监听touchstart,记录开始位置。 监听touchmove,记录移动的位置,计算移动的方向...
    99+
    2022-11-21
    Vue左右滑动组件 Vue左右滑动 Vue 滑动组件
  • vue自定义动态组件的方法是什么
    本篇内容主要讲解“vue自定义动态组件的方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“vue自定义动态组件的方法是什么”吧! Vue.extend 思路就是拿到组件...
    99+
    2023-07-04
  • Vue中的动态组件是什么及怎么用
    这篇“Vue中的动态组件是什么及怎么用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue中的动态组件是什么及怎么用”文章吧...
    99+
    2023-07-04
  • Angular中组件样式的工作原理是什么
    这篇文章给大家分享的是有关Angular中组件样式的工作原理是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。 在开发 Angular 组件的过程中...
    99+
    2022-10-19
  • vue3子组件如何修改父组件传过来的props数据
    目录前言1. 修改父组件普通数据2. 修改父组件复杂数据(对象)最后前言 最近新项目用vue3搭建的,准备开始使用vue3的语法,从这篇开始记录下vue3遇到的一些问题和一些语法的使...
    99+
    2022-11-13
    vue子组件可以修改父组件传的prop vue父子组件传值propsm vue修改props的值
  • 基于组件的机制的SimpleFramework工作原理是什么
    本篇文章给大家分享的是有关基于组件的机制的SimpleFramework工作原理是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。生命周期:了解SimpleFramework...
    99+
    2023-06-17
  • vue3中使用ant-design-vue的layout组件实现动态导航栏和面包屑功能
    目录0 前言1 准备工作1.1 安装ant-design-vue1.2 安装图标组件包2 选择组件3 路由文件4 Vue导航页面5 最终效果0 前言   &nbs...
    99+
    2023-01-29
    vue3使用ant-design-vue实现动态导航栏 vue3使用ant-design-vue
  • vue3 hook如何重构DataV的全屏容器组件
    今天小编给大家分享一下vue3 hook如何重构DataV的全屏容器组件的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下...
    99+
    2023-07-06
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作