Vue -组件
1、组件的作用:组件(Component)是Vue.js最强大的功能之一。. 组件可以扩展HTML元素,封装可以重复使用的代码。. 在较高层次上,组件就是自定义元素,Vue.js的编辑器为它添加特殊功能。2、组件的分类:在vue中组件可以分为全局组件和局部组件。全局组件:所谓的全局组件就是不仅仅可以在一个vue实例中使用,在其他vue实例中也可以使用 的组件。局部组件:所谓的局部组件就是只能在某个
目录
1、组件的概念及作用:
组件(Component)是Vue.js最强大的功能之一。. 组件可以扩展HTML元素,封装可以重复使用的代码。. 在较高层次上,组件就是自定义元素,Vue.js的编辑器为它添加特殊功能。
一般的前端页面由头部、中间区域、底部构成,中间区域又可以分为各种的功能模块,例如轮播图、商品展示区等等。vue组件的作用就是将不同的功能区域独立的封装起来,可以封装的部分有结构、样式、逻辑代码。这样封装的好处就是当页面中的某个功能出现问题的时候我们只需要找到对应的组件进行维护处理就行了,而不再需要考虑整个页面,这样既提高了功能的复用性也提高了功能的可维护性。
2、组件基础
1、组件的基本使用
<div id="app">
<p>p标签内容</p>
<cpc></cpc>
</div>
在app元素中(或者叫vue实例)里面存在一个叫<cpc></cpc>的自定义标签,这个标签是我们自定义的标签,实际上在HTML中是不存在的。该标签其实就是组件的标签名,我们只需要在特定位置书写该标签就能使用到该标签所对应的组件。
2、 组件本质
组件在本质上是个可复用的vue实例。它们可以和Vue根组件一样有相同的选项,例如:data、methods、以及生命周期钩子等,只有el选项是vue根实例特有的,因为el代表的是挂载元素, 也就是说根元素是需要挂载到页面上某个元素身上的。而组件是需要被其他组件或者是根实例使用的因此不需要挂载到页面中的某个元素身上。
3、组件的命名规则
组件的命名规则有两种方式:
kebab-case(驼峰命名):"my-component"
PascalCase(帕斯卡命名):"MyComponent"
【注意】无论采用上面哪一种的命名方式,在使用组件时都只能使用kebab-case(驼峰命名)来使用组件。
Vue.component("my-component",{/*选项对象*/})
Vue.component("MyComponent",{/*选项对象*/})
4、template选项
template,模板的意思。用于设置组件的结构,最终通过标签被引入根实例或者是其他组件中。
【注意】在template中只能设置一个根元素,也就是说只能有一个div标签,其他标签都需要写在div标签中。此外,在template中也可以设置插值表达式(也就是mustache表达式)
Vue.component("my-component",{
template:`
<div>
<h2>my-component组件:{{1+2*4}}</h2>
</div>
`
})
5、data选项
data选项用于存储组件的数据,和根实例不同,组件的data选项必须是一个函数,数据设置在返回值对象中。
data选项为什么是一个函数(或者叫方法)?是因为这种实现方式可以确保每个组件实例都可以维护一份被返回对象的拷贝,不会互相影响。因为在实际项目中组件可能会被多次引用,每次引用都会调用一次组件结构,这时候为了确保每个被引用组件的数据是相互独立的而不是公用的,就需要使用作用域对其进行隔离,而data是个函数,函数内部是有作用域的,当我们在函数内部声明了数据以后利用该函数进行多次调用那么内部相当于形成了多个独立的作用域,每个作用域中的数据都是独立的。
Vue.component("my-component",{
template:`
<div>
<h2>{{title}}</h2>
<p>{{content}}</p>
</div>
`,
data(){
return {
title:'组件中的数据',
content:'组件中的内容'
}
}
})
3、组件的分类:
在vue中组件可以分为全局组件和局部组件。
全局组件:所谓的全局组件就是不仅仅可以在一个vue实例中使用,在其他vue实例或者组件中也可以使用的组件。
局部组件:所谓的局部组件就是只能在某个vue实例中使用的组件,在其他的vue实例中不能使用的组件。
4、全局组件
在组件基础中演示的例子就是注册全局组件的步骤,因此这里不再赘述。
5、局部组件
局部组件在注册后只能在当前实例或者是组件中使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件</title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
<my-component-a></my-component-a>
<my-component-b></my-component-b>
</div>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊!"
},
components:{
"my-component-a":{
template:'<h2>{{title}}</h2>',
data(){
return{
title:'这是组件a的标题'
}
}
},
"my-component-b":{
template: '<h2>{{title}}</h2>',
data(){
return{
title: '这是组件b的标题'
}
}
}
}
})
</script>
</body>
</html>
代码运行结果:
6、组件抽取
所谓的抽取组件实际上是单独设置选项对象,这样是为了简化代码,方便阅读和维护。 可以将组件抽取到外面,在需要注册时直接将组件的对象注册到其他组件或者实例中即可。
组件抽取有两种方式:
1、不使用标签的选项抽取方式(全部抽取);这种方式可以将组件的左右选项,例如template、data、methods全部抽取出来。
2、使用<template><template/>抽取,这种抽取方式仅仅只能抽取template选项的内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件</title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
<my-component-a></my-component-a>
<my-component-b></my-component-b>
</div>
<!--使用template标签抽取-->
<template id="temp">
<div>
<h2>{{title}}</h2>
</div>
</template>
<script>
/*组件B模板抽取*/
let MyConmponentA = {
template:'<h2>{{title}}</h2>',
data(){
return{
title:'这是组件A的标题'
}
}
}
const app = new Vue({
el:"#app",
data:{
message:"你好啊!"
},
//注册子组件
components:{
//A子组件使用的是全部抽取
"my-component-a":MyConmponentA,
//B组件使用的是只抽取template选项
"my-component-b":{
template: `#temp`,
data(){
return{
title: '这是组件B的标题'
}
}
}
}
})
</script>
</body>
</html>
7、组件通讯
什么是组件通讯,为什么要组件通讯?
上面说到组件和组件之间是完全独立的功能,在局部注册组件中我们是将组件注册到了vue根组件中,那么子组件是如何获取根足件中的数据的呢?以及子组件与子组件之间是如何相互获取数据的呢?我们将子组件获取根组件、子组件与子组件之间数据获取的过程叫组件通讯。
1、父组件向子组件传递数据
props接收父组件传递来的值。
【注意】props与data不要存在同名属性。
props命名规则:假设props中的变量是驼峰命名,即props['myTitle'] 那么在使用子组件的时候需要这么写<my-component v-bind:my-title="myTitle"></my-component>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件</title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
<my-component-b v-bind:value1="item.message" v-bind:value2="item.language"></my-component-b>
</div>
<!--使用template标签抽取-->
<template id="temp">
<div>
<h2>{{value1}}</h2>
<p>{{value2}}</p>
</div>
</template>
<script>
const app = new Vue({
el:"#app",
data:{
item:{
message:"你好啊!",
language:'vue'
}
},
//注册子组件
components:{
"my-component-b":{
props:['value1','value2'],
template: `#temp`,
data(){
return{
title: '这是组件B的标题'
}
}
}
}
})
</script>
</body>
</html>
components:{
"my-component-b":{
props:{
value1:{
type:String,
default:'value1'
},
value2:{
type:String,
default: 'value2'
}
},
template: `#temp`,
data(){
return{
title: '这是组件B的标题'
}
}
}
}
代码运行结果:
首先需要明白父组件向子组件传递数据是怎么传递的,对于初学vue的小伙伴来说数据传递这部分算是比较难理解的地方了,最初开始学的时候我也是一头雾水,哈哈!好,我们暂时不考虑组件通讯这个问题。假设我们的组件模板是这样的:
<template id="temp"> <div> <h2>我是标题</h2> <p>我是内容</p> </div> </template>h2标签和p标签中的内容都是固定死的,此时我们在vue的实例中使用组件my-component-b
<div id="app"> <my-component-b></my-component-b> </div>这样,在浏览器中我们就能看见输出的两行字:这两行字是我们在模板中定义好的。但是在实际开发中往往是父组件向服务器发送请求获取数据,然后将数据传递给子组件的。也就是说子组件中的模板中的数据是和服务端挂钩的,用jio指头想想都是不能写死的。所以子组件模板中的数据都是动态的。现在我们还是从使用组件开始:
<div id="app"> <my-component-b v-bind:value1="item.message" v-bind:value2="item.language"></my-component-b> </div>上面这段代码是将vue组件中的数据分别动态绑定到了value1和value2上去了,需要一下,value1和value2这两个变量的作用范围是vue实例,现在到props出场的时候了,props的作用就是获取vue实例中的value1和value2这两个变量,获取后为vue子组件所用:
components:{ "my-component-b":{ props:['value1','value2'], template: `#temp`, data(){ return{ title: '这是组件B的标题' } } } }上面这段代码就是获取了value1和value2两个变量,然后组件既然获取了这两个数据,那么组件中的模板也就可以使用这两个值了:
<template id="temp"> <div> <h2>{{value1}}</h2> <p>{{value2}}</p> </div> </template>这样就能在浏览器获取到父组件的值了。
2、子组件向父组件传数据
1、使用$emit('事件名称'),向父组件传值。
上面说到父组件向子组件传值,子组件处理完这些数据后可能会返回给父组件一些数据,此时就不能使用
props来处理了而是通过自定义事件来实现。因为子组件什么时候处理完数据是不确定的,因此需要捕获子组件触发的事件得知子组件的状态。简单说就是当子组件处理数据完成之后触发事件,父组件通过$emit('事件名称')捕获子组件发出的事件进行相应的处理即可。
以购物车为例,购物车中包含着全部的商品,每个商品都代表着一个子组件,也就是说需要将父组件中的商品数据传递到子组件中,购物车中有N个商品那么就有N个子组件。改变每个子组件的数量后最终在购物车内部统计总数量。下面是效果图:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件</title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
<h2>购物车</h2>
<!--子组件实例,获取vue组件的数据,v-bind用于接受vue组件传递来的name-->
<my-component v-for="fruit in fruits" v-bind:fname="fruit.name" :key="fruit.id" @btnclick="total++"></my-component>
<p>商品的总数量是:{{total}}</p>
</div>
<!--子组件需要接受父组件的值-->
<template id="temp">
<div>
<h2>商品的名称是:{{fname}},数量是:{{count}}
<button @click="btnclick">+1</button>
</h2>
</div>
</template>
<script>
const app = new Vue({
el:"#app",
data:{
fruits:[
{
id:1,
name:'香蕉',
},
{
id:2,
name:'苹果',
},
{
id:3,
name:'橘子',
},
],
total:0
},
//注册子组件
components:{
"my-component":{
props:[
'fname'
],
template: `#temp`,
data(){
return{
count:0
}
},
methods:{
/*触发事件时数量+1*/
btnclick(){
/*接受到事件后将事件发射*/
this.$emit('btnclick')
this.count++
}
}
}
}
})
</script>
</body>
</html>
<my-component v-for="fruit in fruits" v-bind:name="fruit.name" :key="fruit.id" @btnclick="total++"></my-component>v-for="fruit in fruits":遍历父组件中的fruits对象。
v-bind:fname="fruit.name":将fruit对象的name属性绑定到fname变量上。目的是为了使用props将该变量传递到子组件上。当子组件获取到fname变量的时候,在子组件模板中就可以使用啦!
:key="fruit.id":相当于是每个组件唯一的标识。
<template id="temp"> <div> <h2>商品的名称是:{{fname}},数量是:{{count}} <button @click="btnclick">+1</button> </h2> </div> </template>商品的名称是:{{fname}}:fname是获取的父组件中的fname变量。
数量是:{{count}}:count是子组件的值。
<button @click="btnclick">+1</button>:点击按钮触发事件btnclick
methods:{ /*触发事件时数量+1*/ btnclick(){ /*接受到事件后将事件发射*/ this.$emit('btnclick') this.count++ } }this.$emit('btnclick'):子组件在触发btnclick事件后将该事件发射出去。
<my-component v-for="fruit in fruits" v-bind:fname="fruit.name" :key="fruit.id" @btnclick="total++"></my-component>@btnclick="total++":父组件获捕获到btnclick事件,将其作用域内的total值+1。
<p>商品的总数量是:{{total}}</p>{{total}}:获取父组件vue的值。
2、使用$emit('事件名称',值)向父组件传值。
这种方式就是在原来的基础上可以传递参数了。需要注意的是在父组件中接受该值的时候要使用$event来接受,也就是说子组件向父组件传递的数据实际上是个事件对象。
3、非父子组件之间的数据传递
非父子组件之间的数据传递其实指的就是兄弟组件之间或者是完全无关的两个组件之间的数据的传递(例如A组件和B组件是兄弟组件,而A组件和B组件的自组件关系就比较远了,因此这两个组件就是完全无关系的组件)。
1、兄弟组件传值
兄弟组件之间传值可以将数据传递到父组件进行中转。假设存在兄弟组件A、B,如果子组件A向子组件B传递数据首先A组件要向父组件传递数据,父组件接受到数据之后将数据传递给子组件B。
子组件向父组件传值:$emit
父组件向子组件传值:props
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件</title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
<!--4.父组件捕获子组件A发射的事件,并获取子组件A的值,然后将获取的值传递给父组件的value-->
<com-a @btna="value=$event"></com-a>
<!--5.这里的value是从父组件获取的,是组件A传递到父组件的-->
<p>父组件:{{value}}</p>
<!--6.父组件将值value值传递给另一个变量value上-->
<com-b :value="value"></com-b>
</div>
<!--A租几间-->
<template id="tempA">
<div>
<!--1.获取子组件A中的内容-->
<p>组件A:{{value}}</p>
<!--2.btna事件触发-->
<button @click="btna(value)">发送</button>
</div>
</template>
<!--B组件-->
<template id="tempB">
<div>
<!--8.子组件B模板获取子组件B中的value-->
<p>组件B:{{value}}</p>
</div>
</template>
<script>
const v = new Vue({
el:"#app",
data:{
//value用来进行数据中转
value:''
},
methods:{
fu(){
this.value=$event;
}
},
//注册子组件
components:{
"com-a":{
template:`#tempA`,
data(){
return{
value: "hahah"
}
},
methods:{
/*3.将触发事件发射出去*/
btna(value){
this.$emit("btna",value)
}
}
},
"com-b":{
/*7.子组件B获取value值供子组件B的模板使用*/
props:["value"],
template: `#tempB`
}
}
})
</script>
</body>
</html>
2、完全无关的组件之间的传值(EventBus)
上面的兄弟组件使用了父组件中转传值,首先A组件使用$emit向父组件传值,父组件接受到值后使用props向B组件传值,但是如果层级不只有一层的话该怎么办呢?很显然使用父组件中转传值的话就比较麻烦了,此时就需要用到EventBus.
何为EventBus?
1、EventBus是一个独立的事件中心,用于管理不同组件之间的传值操作。简单来说,EventBus更像是个邮递员,主要负责将组件A的数据传递给组件B,而他本身是不存储数据的。只是负责中转处理。
2、EventBus是通过一个新的vue实例来管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递。因为在实际开发中vue实例会书写很多功能代码,其复杂度和体积可能非常巨大,如果使用该vue实例进行数据传递的话可能消耗的性能会非常大,因此需要一个新的vue实例来进行传值操作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件</title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
<coma></coma>
<comb></comb>
</div>
<script>
/*事件管理中心bus*/
const bus = new Vue();
/*全局组件A*/
Vue.component('coma',{
template:`
<div>
<p>苹果,数量是:{{ counta }}</p>
<button @click="btnclick">+1</button>
</div>>
`,
data(){
return{
counta:0
}
},
methods:{
btnclick(){
bus.$emit('btnclick',1);
this.counta++
}
}
})
/*全局组件B*/
Vue.component('comb',{
template: `
<p>数量是:{{ countb }}</p>
`,
data(){
return{
countb:0
}
},
created(){
/*给bus注册事件并接受数据,$on('事件名称',事件处理程序)*/
bus.$on('btnclick',(counta)=>{
/*实例创建完毕可以使用data等功能了*/
this.countb+=counta
})
}
})
/*根组件*/
const v = new Vue({
el:"#app",
data:{
message:"你好啊!"
}
})
</script>
</body>
</html>
8、插槽
1、何为插槽?
插槽,英文名是slot.很容易让人联想到电脑上,手机上的形形色色的插槽,这些插槽有的链接着音响有的链接着鼠标、有的链接着键盘…… 组件的插槽能使我们封装的组件更有扩展性可以让使用者决定组件内部到底需要展示哪些东西。
2、slot的基本使用
我们可以对比网购商城的页面来深化一下对组件插槽的理解,网购商城的每个页面基本上都会有一个导航栏或者说是搜索框,但是这些搜索框在每个页面中的显示的效果是不一样的,因次我们说这些页面都有一个共性,那就是都包含有个搜索框组件,但是每个搜索框的显示样式又是不同的,说白了就是每个搜索框的结构是一样的但是内容不一样。所以我们说这个组件使用了插槽,在不同页面中插槽引入了不同的样式展示给用户。在开发中我们将共性封装成组件,不同的地方定义成插槽。
小案例:在第一次使用组件的时候使用button,第二次使用组件的时候不想显示button按钮而是显示span标签。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>slot</title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
{{message}}
<!--相当于是在cpc中将标签写入到插槽-->
<cpc><button>按钮</button></cpc>
<cpc><span>span</span></cpc>
<cpc></cpc>
<cpc></cpc>
</div>
<template id="cpc">
<dov>
<h1>我是组件</h1>
<p>我是组件哈啊哈</p>
<!--定义个插槽,就相当于是电脑上的预留的插槽-->
<slot></slot>
</dov>
</template>
<script>
const v = new Vue({
el:"#app",
data:{
message:"你好啊!"
},
//注册子组件
components:{
"cpc":{
template:`#cpc`
}
}
})
</script>
</body>
</html>
代码运行结果:
如果在插槽中设置了默认标签的话,<cpc></cpc>是会显示<slot></slot>标签中的内容的。要是<cpc></cpc>标签中有自己的内容的话那么就会覆盖掉原来的<slot></slot>标签中的内容。
<div id="app">
{{message}}
<!--相当于是在cpc中将标签写入到插槽-->
<cpc></cpc>
<cpc><span>不显示按钮,显示自己的内容!</span></cpc>
<cpc></cpc>
<cpc><span>不显示按钮,显示自己的内容!</span></cpc>
</div>
<template id="cpc">
<dov>
<h1>我是组件</h1>
<p>我是组件哈啊哈</p>
<!--定义个插槽,就相当于是电脑上的预留的插槽-->
<slot><button>按钮</button></slot>
</dov>
</template>
运行结果:
3、具名slot的使用
所谓的具名插槽就是为每个插槽取个具体的名字。
如果说组件中存在三个插槽,<cpc></cpc>标签中又有自己的内容那么,<cpc></cpc>标签中的内容会将所有的<slot></slot>标签中的内容替换。这时候要想自定义替换插槽中的内容的话就需要给插槽取名字。这时候<cpc></cpc>标签中自己的内容只会替换掉没有名字的插槽。
以上面购物商城的搜索框为例,具名插槽就是为了方面在展示不同页面的时候,导航栏的左边替换成什么,中间部分替换成什么,右边部分替换成什么。
<div id="app">
{{message}}
<cpc><span slot="left">替换左边的</span></cpc>
</div>
<template id="temp">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
代码运行结果:
4、编译作用域
何为【编译作用域】?
简单理解就是变量的作用域!通过代码简单说明一下。
官方给出的准则:父组件模板中的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
{{message}}
<cpc v-show="isShow"></cpc>
</div>
<template id="temp">
<div>
<h1>我是组件</h1>
<p>我是组件</p>
</div>
</template>
<script>
const v = new Vue({
el:"#app",
data:{
message:"你好啊!",
isShow:true
},
//注册局部组件
components:{
"cpc":{
template:`#temp`,
data(){
return{
isShow:false
}
}
}
}
})
</script>
</body>
</html>
代码运行结果:
上面的代码中vue实例和其子组件各有一个布尔类型的变量isShow ,在<cpc v-show="isSow"></cpc>使用了该变量isShow,但是这个变量究竟是使用的vue实例中的变量呢?还是使用的vue子组件中的编变量呢?答案是:vue实例中的变量。
那为啥是vue组件中的变量呢?
其实不光是这一个变量,只要是在vue实例中出现的变量,在使用时都会首先取vue实例中寻找,也就是说此时的<cpc><cpc/>就和普通的div没有区别。姑且将他看成时div。这样就在情理之中了。如果是在模板中使用isShow变量时,会优先从子组件中寻找该变量。代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script src="../js/vue.js"></script>
<div id="app">
{{message}}
<!--去vue实例中寻找变量-->
<cpc v-show="isShow"></cpc>
</div>
<template id="temp">
<div>
<h1>我是组件</h1>
<p>我是组件</p>
<!--去子组件中寻找该变量-->
<button v-show="isShow">按钮</button>
</div>
</template>
<script>
const v = new Vue({
el:"#app",
data:{
message:"你好啊!",
isShow:true
},
//注册局部组件
components:{
"cpc":{
template:`#temp`,
data(){
return{
isShow:false
}
}
}
}
})
</script>
</body>
</html>
代码运行结果:发现button未展示。
5、作用域插槽(*较难理解)
何为作用域插槽?
作用域插槽就是父组件替换插槽中的标签,但是内容是由子组件来提供的。
小案例:将第二组数据展示的形式由原来的列表变为元素之前以”-"链接。
因为我们想让第二个<cpc></cpc>展示的数据格式发生变化所以我们很容易想到让子组件模板里面的数据放到插槽中,当用户使用模板时默认展示插槽中的数据,用户在模板标签中自定义内容时就会展示用户自定义的内容。下面是代码演示:
<div id="app">
第一组数据
<cpc></cpc>
第二组数据
<cpc>
<span>java-</span>
<span>vue-</span>
<span>go-</span>
<span>swift-</span>
<span>c#-</span>
<span>c++-</span>
<span>javascript-</span>
</cpc>
第三组数据
<cpc></cpc>
</div>
<template id="temp">
<!--以列表的形式展示子组件的数据-->
<div>
<slot>
<ul>
<li v-for="language in languages">{{language}}</li>
</ul>
</slot>
</div>
</template>
代码运行结果:
看吧,第二组数据的值确实是变化了。一二组数据使用的还是插槽中的数据。但是为什么不直接使用v-for遍历子组件中的数据?一个个写span标签岂不是太麻烦?还真不能用v-for遍历子组件中的数据,因为我们是在vue模板中使用的插槽,要想在vue模板中使用变量那么该变量一定得是vue实例中存在的变量。就此案例而言vue实例中不存在languages数组,因此我们只能一个一个地写span完成需求。当然在实际开发中我们想到的肯定是如何将子组件的数据传递到父组件中。下面演示子组件传递数据到父组件(未完待续……)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)