热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:GraphQL入门有这一篇就足够了

本文由编程笔记#小编为大家整理,主要介绍了GraphQL入门有这一篇就足够了相关的知识,希望对你有一定的参考价值。一、GraphQL是什么?关于GraphQL是什么
本文由编程笔记#小编为大家整理,主要介绍了GraphQL入门有这一篇就足够了相关的知识,希望对你有一定的参考价值。


一、GraphQL是什么?
关于GraphQL是什么,网上一搜一大堆。根据官网的解释就是一种用于 API 的查询语言。

一看到用于API的查询语言,我也是一脸懵逼的。博主你在开玩笑吧?你的翻译水平不过关?API还能查吗?API不是后端写好,前端调用的吗?

的确可以,这就是GraphQL强大的地方。
引用官方文档的一句话:

ask exactly what you want.

二、为什么要使用GraphQL?
在实际工作中往往会有这种情景出现:比如说我需要展示一个游戏名的列表,可接口却会把游戏的详细玩法,更新时间,创建者等各种各样的 (无用的) 信息都一同返回。

问了后端,原因大概如下:

原来是为了兼容PC端和移动端用同一套接口
或者在整个页面,这里需要显示游戏的标题,可是别的地方需要显示游戏玩法啊,避免多次请求我就全部返回咯
或者是因为有时候项目经理想要显示“标题+更新时间”,有时候想要点击标题展开游戏玩法等等需求,所以把游戏相关的信息都一同返回
简单说就是:

兼容多平台导致字段冗余
一个页面需要多次调用 API 聚合数据
需求经常改动导致接口很难为单一接口精简逻辑
有同学可能会说那也不一定要用GraphQL啊,比方说第一个问题,不同平台不同接口不就好了嘛


http://api.xxx.com/web/getGameInfo/:gameID
http://api.xxx.com/app/getGameInfo/:gameID
http://api.xxx.com/mobile/getGameInfo/:gameID

 

或者加个参数也行


http://api.xxx.com/getGameInfo/:gameID?platfrom=web


这样处理的确可以解决问题,但是无疑加大了后端的处理逻辑。你真的不怕后端程序员打你?

这个时候我们会想,接口能不能不写死,把静态变成动态?

回答是可以的,这就是GraphQL所做的!

三、GraphQL尝尝鲜——(GraphQL简单例子)
下面是用GraphQL.js和express-graphql搭建一个的普通GraphQL查询(query)的例子,包括讲解GraphQL的部分类型和参数,已经掌握了的同学可以跳过。

1. 先跑个hello world
新建一个graphql文件夹,然后在该目录下打开终端,执行npm init --y初始化一个packjson文件。
安装依赖包:npm install --save -D express express-graphql graphql
新建sehema.js文件,填上下面的代码


//schema.js
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
}
= require(‘graphql‘);
const queryObj
= new GraphQLObjectType({
name:
‘myFirstQuery‘,
description:
‘a hello world demo‘,
fields: {
hello: {
name:
‘a hello world query‘,
description:
‘a hello world demo‘,
type: GraphQLString,
resolve(parentValue, args, request) {
return ‘hello world !‘;
}
}
}
});
module.exports
= new GraphQLSchema({
query: queryObj
});


这里的意思是新建一个简单的查询,查询名字叫hello,会返回字段hello world !,其他的是定义名字和查询结果类型的意思。

同级目录下新建server.js文件,填上下面的代码


// server.js
const express = require(‘express‘);
const expressGraphql
= require(‘express-graphql‘);
const app
= express();
const schema
= require(‘./schema‘);
app.use(
‘/graphql‘, expressGraphql({
schema,
graphiql:
true
}));
app.get(
‘/‘, (req, res) => res.end(‘index‘));
app.listen(
8000, (err) => {
if(err) {throw new Error(err);}
console.log(
‘*** server started ***‘);
});


这部分代码是用express跑起来一个服务器,并通过express-graphql把graphql挂载到服务器上。

运行一下node server,并打开http://localhost:8000/

技术图片

 

 

 


如图,说明服务器已经跑起来了
打开http://localhost:8000/graphql,是类似下面这种界面说明已经graphql服务已经跑起来了!

在左侧输入 (graphql的查询语法这里不做说明)


{
hello
}

技术图片

 

 

 

点击头部的三角形的运行按钮,右侧就会显示你查询的结果了

技术图片


2. 不仅仅是hello world
先简单讲解一下代码:


const queryObj = new GraphQLObjectType({
name:
‘myFirstQuery‘,
description:
‘a hello world demo‘,
fields: {}
});

 

GraphQLObjectType是GraphQL.js定义的对象类型,包括name、description 和fields三个属性,其中name和description 是非必填的。fields是解析函数,在这里可以理解为查询方法


hello: {
name:
‘a hello world query‘,
description:
‘a hello world demo‘,
type: GraphQLString,
resolve(parentValue, args, request) {
return ‘hello world !‘;
}
}

 

对于每个fields,又有name,description,type,resolve参数,这里的type可以理解为hello方法返回的数据类型,resolve就是具体的处理方法。

说到这里有些同学可能还不满足,如果我想每次查询都想带上一个参数该怎么办,如果我想查询结果有多条数据又怎么处理?

下面修改schema.js文件,来一个加强版的查询(当然,你可以整理一下代码,我这样写是为了方便阅读)


const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLBoolean
}
= require(‘graphql‘);
const queryObj
= new GraphQLObjectType({
name:
‘myFirstQuery‘,
description:
‘a hello world demo‘,
fields: {
hello: {
name:
‘a hello world query‘,
description:
‘a hello world demo‘,
type: GraphQLString,
args: {
name: {
// 这里定义参数,包括参数类型和默认值
type: GraphQLString,
defaultValue:
‘Brian‘
}
},
resolve(parentValue, args, request) {
// 这里演示如何获取参数,以及处理
return ‘hello world ‘ + args.name + ‘!‘;
}
},
person: {
name:
‘personQuery‘,
description:
‘query a person‘,
type:
new GraphQLObjectType({ // 这里定义查询结果包含name,age,sex三个字段,并且都是不同的类型。
name: ‘person‘,
fields: {
name: {
type: GraphQLString
},
age: {
type: GraphQLInt
},
sex: {
type: GraphQLBoolean
}
}
}),
args: {
name: {
type: GraphQLString,
defaultValue:
‘Charming‘
}
},
resolve(parentValue, args, request) {
return {
name: args.name,
age: args.name.length,
sex: Math.random()
> 0.5
};
}
}
}
});
module.exports
= new GraphQLSchema({
query: queryObj
});

 

重启服务后,继续打开http://localhost:8000/graphql,在左侧输入


{
hello(name:
"charming"),
person(name:
"charming"){
name,
sex,
age
}
}

 

右侧就会显示出:

技术图片


你可以在左侧仅输入person方法的sex和age两个字段,这样就会只返回sex和age的信息。动手试一试吧!


{
person(name:
"charming"){
sex,
age
}
}

 

当然,结果的顺序也是按照你输入的顺序排序的。

定制化的数据,完全根据你查什么返回什么结果。这就是GraphQL被称作API查询语言的原因。

四、GraphQL实战
下面我将搭配koa实现一个GraphQL查询的例子,逐步从简单koa服务到mongodb的数据插入查询,再到GraphQL的使用,最终实现用GraphQL对数据库进行增删查改。

项目效果大概如下:

技术图片

 

 

 

有点意思吧?那就开始吧~
先把文件目录建构建好

1. 初始化项目

初始化项目,在根目录下运行npm init --y,
然后安装一些包:npm install koa koa-static koa-router koa-bodyparser --save -D
新建config、controllers、graphql、mongodb、public、router这几个文件夹。装逼的操作是在终端输入mkdir config controllers graphql mongodb public router回车,ok~
2. 跑一个koa服务器
新建一个server.js文件,写入以下代码


// server.js
import Koa from ‘koa‘
import Router from
‘koa-router‘
import bodyParser from
‘koa-bodyparser‘
const app
= new Koa()
const router
= new Router();
const port
= 4000
app.use(bodyParser());
router.get(
‘/hello‘, (ctx, next) => {
ctx.body
="hello world"
});
app.use(router.routes())
.use(router.allowedMethods());
app.listen(port);
console.log(
‘server listen port: ‘ + port)

 

执行node server跑起来服务器,发现报错了:

技术图片


这是正常的,这是因为现在的node版本并没有支持es6的模块引入方式。

百度一下就会有解决方案了,比较通用的做法是用babel-polyfill进行转译。

详细的可以看这一个参考操作:How To Enable ES6 Imports in Node.JS

具体操作是:新建一个start.js文件,写入:


// start.js
require(‘babel-register‘)({
presets: [
‘env‘ ]
})
require(
‘babel-polyfill‘)
require(
‘./server.js‘)


安装相关包:npm install --save -D babel-preset-env babel-polyfill babel-register

修改package.json文件,把"start": "start http://localhost:4000 && node start.js"这句代码加到下面这个位置:

技术图片


运行一下npm run start,打开http://localhost:4000/hello,结果如图:

说明koa服务器已经跑起来了。

那么前端页面呢?

(由于本文内容不是讲解前端,所以前端代码自行去github复制)

在public下新建index.html文件和js文件夹,代码直接查看我的项目public目录下的 index.html 和 index-s1.js 文件
修改server.js,引入koa-static模块。koa-static会把路由的根目录指向自己定义的路径(也就是本项目的public路径)


//server.js
import Koa from ‘koa‘
import Router from
‘koa-router‘
import KoaStatic from
‘koa-static‘
import bodyParser from
‘koa-bodyparser‘
const app
= new Koa()
const router
= new Router();
const port
= 4000
app.use(bodyParser());
router.get(
‘/hello‘, (ctx, next) => {
ctx.body
="hello world"
});
app.use(KoaStatic(__dirname
+ ‘/public‘));
app.use(router.routes())
.use(router.allowedMethods());
app.listen(port);
console.log(
‘server listen port: ‘ + port)


打开http://localhost:4000/,发现是类似下面的页面:

技术图片

 

 

 


这时候页面已经可以进行简单的交互,但是还没有和后端进行数据交互,所以是个静态页面。

3. 搭一个mongodb数据库,实现数据增删改查

注意: 请先自行下载好mongodb并启动mongodb。

a. 写好链接数据库的基本配置和表设定

在config文件夹下面建立一个index.js,这个文件主要是放一下链接数据库的配置代码。


// config/index.js
export default {
dbPath:
‘mongodb://localhost/todolist‘
}

 

在mongodb文件夹新建一个index.js和 schema文件夹, 在 schema文件夹文件夹下面新建list.js

在mongodb/index.js下写上链接数据库的代码,这里的代码作用是链接上数据库


// mongodb/index.js
import mongoose from ‘mongoose‘
import config from
‘../config‘
require(
‘./schema/list‘)
export const database
= () => {
mongoose.set(
‘debug‘, true)
mongoose.connect(config.dbPath)
mongoose.connection.on(
‘disconnected‘, () => {
mongoose.connect(config.dbPath)
})
mongoose.connection.on(
‘error‘, err => {
console.error(err)
})
mongoose.connection.on(
‘open‘, async () => {
console.log(
‘Connected to MongoDB ‘, config.dbPath)
})
}

 

在mongodb/schema/list.js定义表和字段:


//mongodb/schema/list.js
import mongoose from ‘mongoose‘
const Schema
= mongoose.Schema
const ObjectId
= Schema.Types.ObjectId
const ListSchema
= new Schema({
title: String,
desc: String,
date: String,
id: String,
checked: Boolean,
meta: {
createdAt: {
type: Date,
default: Date.now()
},
updatedAt: {
type: Date,
default: Date.now()
}
}
})
ListSchema.pre(
‘save‘, function (next) {// 每次保存之前都插入更新时间,创建时插入创建时间
if (this.isNew) {
this.meta.createdAt = this.meta.updatedAt = Date.now()
}
else {
this.meta.updatedAt = Date.now()
}
next()
})
mongoose.model(
‘List‘, ListSchema)

 

b. 实现数据库增删查改的控制器

建好表,也链接好数据库之后,我们就要写一些方法来操作数据库,这些方法都写在控制器(controllers)里面。

在controllers里面新建list.js,这个文件对应操作list数据的控制器,单独拿出来写是为了方便后续项目复杂化的模块化管理。


// controllers/list.js
import mongoose from ‘mongoose‘
const List
= mongoose.model(‘List‘)
// 获取所有数据
export const getAllList = async (ctx, next) => {
const Lists
= await List.find({}).sort({date:-1}) // 数据查询
if (Lists.length) {
ctx.body
= {
success:
true,
list: Lists
}
}
else {
ctx.body
= {
success:
false
}
}
}
// 新增
export const addOne= async (ctx, next) => {
// 获取请求的数据
const opts = ctx.request.body
const list
= new List(opts)
const saveList
= await list.save() // 保存数据
console.log(saveList)
if (saveList) {
ctx.body
= {
success:
true,
id: opts.id
}
}
else {
ctx.body
= {
success:
false,
id: opts.id
}
}
}
// 编辑
export const editOne= async (ctx, next) => {
const obj
= ctx.request.body
let hasError
= false
let error
= null
List.findOne({id: obj.id}, (err, doc)
=> {
if(err) {
hasError
= true
error
= err
}
else {
doc.title
= obj.title;
doc.desc
= obj.desc;
doc.date
= obj.date;
doc.save();
}
})
if (hasError) {
ctx.body
= {
success:
false,
id: obj.id
}
}
else {
ctx.body
= {
success:
true,
id: obj.id
}
}
}
// 更新完成状态
export const tickOne= async (ctx, next) => {
const obj
= ctx.request.body
let hasError
= false
let error
= null
List.findOne({id: obj.id}, (err, doc)
=> {
if(err) {
hasError
= true
error
= err
}
else {
doc.checked
= obj.checked;
doc.save();
}
})
if (hasError) {
ctx.body
= {
success:
false,
id: obj.id
}
}
else {
ctx.body
= {
success:
true,
id: obj.id
}
}
}
// 删除
export const delOne= async (ctx, next) => {
const obj
= ctx.request.body
let hasError
= false
let msg
= null
List.remove({id: obj.id}, (err, doc)
=> {
if(err) {
hasError
= true
msg
= err
}
else {
msg
= doc
}
})
if (hasError) {
ctx.body
= {
success:
false,
id: obj.id
}
}
else {
ctx.body
= {
success:
true,
id: obj.id
}
}
}

 

c. 实现路由,给前端提供API接口

数据模型和控制器都已经设计好了,下面就利用koa-router路由中间件,来实现请求的接口。

我们回到server.js,在上面添加一些代码。如下:


// server.js
import Koa from ‘koa‘
import Router from
‘koa-router‘
import KoaStatic from
‘koa-static‘
import bodyParser from
‘koa-bodyparser‘
import {database} from
‘./mongodb‘
import {addOne, getAllList, editOne, tickOne, delOne} from
‘./controllers/list‘
database()
// 链接数据库并且初始化数据模型

const app
= new Koa()
const router
= new Router();
const port
= 4000
app.use(bodyParser());
router.get(
‘/hello‘, (ctx, next) => {
ctx.body
= "hello world"
});
// 把对请求的处理交给处理器。
router.post(‘/addOne‘, addOne)
.post(
‘/editOne‘, editOne)
.post(
‘/tickOne‘, tickOne)
.post(
‘/delOne‘, delOne)
.get(
‘/getAllList‘, getAllList)
app.use(KoaStatic(__dirname
+ ‘/public‘));
app.use(router.routes())
.use(router.allowedMethods());
app.listen(port);
console.log(
‘server listen port: ‘ + port)

 

上面的代码,就是做了:

1. 引入mongodb设置、list控制器,
2. 链接数据库
3. 设置每一个设置每一个路由对应的我们定义的的控制器。

安装一下mongoose:npm install --save -D mongoose

运行一下npm run start,待我们的服务器启动之后,就可以对数据库进行操作了。我们可以通过postman来模拟请求,先插几条数据:

技术图片

 

 

查询全部数据:

技术图片

 

 

d. 前端对接接口

前端直接用ajax发起请求就好了,平时工作中都是用axios的,但是我懒得弄,所以直接用最简单的方法就好了。

引入了JQuery之后,改写public/js/index.js文件:略(项目里的public/index-s2.js的代码)

项目跑起来,发现已经基本上实现了前端发起请求对数据库进行操作了。
至此你已经成功打通了前端后台数据库,可以不要脸地称自己是一个小全栈了!

不过我们的目的还没有达到——用grapql实现对数据的操作!

4. 用grapql实现对数据的操作

GraphQL 的大部分讨论集中在数据获取(query),但是任何完整的数据平台也都需要一个改变服务端数据的方法。
REST 中,任何请求都可能最后导致一些服务端副作用,但是约定上建议不要使用 GET 请求来修改数据。GraphQL 也是类似 —— 技术上而言,任何查询都可以被实现为导致数据写入。然而,建一个约定来规范任何导致写入的操作都应该显式通过变更(mutation)来发送。

简单说就是,GraphQL用mutation来实现数据的修改,虽然mutation能做的query也能做,但还是要区分开这连个方法,就如同REST中约定用GET来请求数据,用其他方法来更新数据一样。

a. 实现查询
查询的话比较简单,只需要在接口响应时,获取数据库的数据,然后返回;


const objType = new GraphQLObjectType({
name:
‘meta‘,
fields: {
createdAt: {
type: GraphQLString
},
updatedAt: {
type: GraphQLString
}
}
})
let ListType
= new GraphQLObjectType({
name:
‘List‘,
fields: {
_id: {
type: GraphQLID
},
id: {
type: GraphQLString
},
title: {
type: GraphQLString
},
desc: {
type: GraphQLString
},
date: {
type: GraphQLString
},
checked: {
type: GraphQLBoolean
},
meta: {
type: objType
}
}
})
const listFields
= {
type:
new GraphQLList(ListType),
args: {},
resolve (root, params, options) {
return List.find({}).exec() // 数据库查询
}
}
let queryType
= new GraphQLObjectType({
name:
‘getAllList‘,
fields: {
lists: listFields,
}
})
export
default new GraphQLSchema({
query: queryType
})

 

把增删查改都讲完再更改代码~
b. 实现增删查改

一开始说了,其实mutation和query用法上没什么区别,这只是一种约定。
具体的mutation实现方式如下:


const outputType = new GraphQLObjectType({
name:
‘output‘,
fields: ()
=> ({
id: { type: GraphQLString},
success: { type: GraphQLBoolean },
})
});
const inputType
= new GraphQLInputObjectType({
name:
‘input‘,
fields: ()
=> ({
id: { type: GraphQLString },
desc: { type: GraphQLString },
title: { type: GraphQLString },
date: { type: GraphQLString },
checked: { type: GraphQLBoolean }
})
});
let MutationType
= new GraphQLObjectType({
name:
‘Mutations‘,
fields: ()
=> ({
delOne: {
type: outputType,
description:
‘del‘,
args: {
id: { type: GraphQLString }
},
resolve: (value, args)
=> {
console.log(args)
let result
= delOne(args)
return result
}
},
editOne: {
type: outputType,
description:
‘edit‘,
args: {
listObj: { type: inputType }
},
resolve: (value, args)
=> {
console.log(args)
let result
= editOne(args.listObj)
return result
}
},
addOne: {
type: outputType,
description:
‘add‘,
args: {
listObj: { type: inputType }
},
resolve: (value, args)
=> {
console.log(args.listObj)
let result
= addOne(args.listObj)
return result
}
},
tickOne: {
type: outputType,
description:
‘tick‘,
args: {
id: { type: GraphQLString },
checked: { type: GraphQLBoolean },
},
resolve: (value, args)
=> {
console.log(args)
let result
= tickOne(args)
return result
}
},
}),
});
export
default new GraphQLSchema({
query: queryType,
mutation: MutationType
})

 

c. 完善其余代码

在实现前端请求Graphql服务器时,最困扰我的就是参数以什么样的格式进行传递。后来在Graphql界面玩Graphql的query请求时发现了其中的诀窍…

关于前端请求格式进行一下说明:

技术图片

 

 

如上图,在玩Graphql的请求时,我们就可以直接在控制台network查看请求的格式了。这里我们只需要模仿这种格式,当做参数发送给Graphql服务器即可。

记得用反引号: `` ,来拼接参数格式。然后用data: {query: params}的格式传递参数,代码如下:


let data = {
query: `mutation{
addOne(listObj:{
id:
"${that.getUid()}",
desc:
"${that.params.desc}",
title:
"${that.params.title}",
date:
"${that.getTime(that.params.date)}",
checked:
false
}){
id,
success
}
}`
}
$.post(
‘/graphql‘, data).done((res) => {
console.log(res)
// do something
})

 


最后更改server.js,router/index.js,controllers/list.js,public/index.js改成github项目对应目录的文件代码即可。

完整项目的目录如下:

技术图片


五、后记
对于Vue开发者,可以使用vue-apollo使得前端传参更加优雅~

对上文有疑问或者有建议意见的,可以加我QQ:820327571,并备注:Graphql

六、参考文献



  • graphql官网教程

  • GraphQL.js

  • 30分钟理解GraphQL核心概念

  • 我的前端故事----我为什么用GraphQL

  • GraphQL 搭配 Koa 最佳入门实践


————————————————
版权声明:本文为CSDN博主「__Charming__」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41882147/article/details/82966783


推荐阅读
  • 实践指南:使用Express、Create React App与MongoDB搭建React开发环境
    本文详细介绍了如何利用Express、Create React App和MongoDB构建一个高效的React应用开发环境,旨在为开发者提供一套完整的解决方案,包括环境搭建、数据模拟及前后端交互。 ... [详细]
  • django项目中使用手机号登录
    本文使用聚合数据的短信接口,需要先获取到申请接口的appkey和模板id项目目录下创建ubtils文件夹,定义返回随机验证码和调取短信接口的函数function.py文件se ... [详细]
  • 本文总结了在使用React Native开发过程中遇到的一些常见问题及其解决方法,包括配置错误、依赖问题和特定组件的使用技巧。 ... [详细]
  • 本文介绍了一个使用Spring框架和Quartz调度器实现每周定时调用Web服务获取数据的小项目。通过详细配置Spring XML文件,展示了如何设置定时任务以及解决可能遇到的自动注入问题。 ... [详细]
  • 深入探索Node.js新框架:Nest.js第六篇
    在本文中,我们将深入探讨Node.js的新框架Nest.js,并通过一个完整的示例来展示其强大功能。我们将使用多个装饰器创建一个基本控制器,该控制器提供了多种方法来访问和操作内部数据,涵盖了常见的CRUD操作。此外,我们还将详细介绍Nest.js的核心概念和最佳实践,帮助读者更好地理解和应用这一现代框架。 ... [详细]
  • 本文介绍了如何在Anaconda3环境中正确安装TensorFlow和Keras框架,解决了默认环境缺乏所需库以及安装过程中常见的网络连接问题。通过创建特定Python版本的环境,并利用国内镜像加速下载,确保安装过程顺利。 ... [详细]
  • Nibblestutotials.net教程 – Blend  Silverlight1系列之Button Basic
    Basic:createonebutton文中三部分所用资源及代码下载:part1,part2,part3Buttonsbasicpart1:drawingNibbl ... [详细]
  • 本文探讨了在使用 ClickOnce 部署方式时遇到的自动更新失败问题,包括本地安装与服务器安装的不同表现,并提供了详细的解决方案。 ... [详细]
  • VSCode中实现大型项目函数跳转的方法
    在处理大型代码项目时,简单的C/C++插件往往无法满足需求。本文介绍如何通过配置GNU Global等工具,在VSCode中实现高效的函数跳转。 ... [详细]
  • PHP网站部署指南:从零开始搭建PHP网站
    本文提供了详细的步骤指导,帮助开发者在不同环境下成功部署PHP网站,包括在IIS和Apache服务器上的具体操作。 ... [详细]
  • 深入解析轻量级数据库 SQL Server Express LocalDB
    本文详细介绍了 SQL Server Express LocalDB,这是一种轻量级的本地 T-SQL 数据库解决方案,特别适合开发环境使用。文章还探讨了 LocalDB 与其他轻量级数据库的对比,并提供了安装和连接 LocalDB 的步骤。 ... [详细]
  • 本文探讨了一个Web工程项目的需求,即允许用户随时添加定时任务,并通过Quartz框架实现这些任务的自动化调度。文章将介绍如何设计任务表以存储任务信息和执行周期,以及如何通过一个定期扫描机制自动识别并加载新任务到调度系统中。 ... [详细]
  • 尽管在WPF中工作了一段时间,但在菜单控件的样式设置上遇到了一些基础问题,特别是关于如何正确配置前景色和背景色。 ... [详细]
  • 本文深入探讨了Go语言中的接口型函数,通过实例分析其灵活性和强大功能,帮助开发者更好地理解和运用这一特性。 ... [详细]
  • Android与JUnit集成测试实践
    本文探讨了如何在Android项目中集成JUnit进行单元测试,并详细介绍了修改AndroidManifest.xml文件以支持测试的方法。 ... [详细]
author-avatar
alive
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有