前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >不要轻易使用 Alpine 镜像来构建 Docker 镜像,有坑!

不要轻易使用 Alpine 镜像来构建 Docker 镜像,有坑!

作者头像
米开朗基杨
发布于 2020-05-26 02:21:36
发布于 2020-05-26 02:21:36
27.2K00
代码可运行
举报
文章被收录于专栏:云原生实验室云原生实验室
运行总次数:0
代码可运行

本系列文章将分为三个部分:

第一部分着重介绍多阶段构建(multi-stage builds),因为这是镜像精简之路至关重要的一环。在这部分内容中,我会解释静态链接和动态链接的区别,它们对镜像带来的影响,以及如何避免那些不好的影响。中间会穿插一部分对 Alpine 镜像的介绍。链接:两个奇技淫巧,将 Docker 镜像体积减小 99%[1]

第二部分将会针对不同的语言来选择适当的精简策略,其中主要讨论 Go,同时也涉及到了 JavaNodePythonRubyRust。这一部分也会详细介绍 Alpine 镜像的避坑指南。什么?你不知道 Alpine 镜像有哪些坑?我来告诉你。

第三部分将会探讨适用于大多数语言和框架的通用精简策略,例如使用常见的基础镜像、提取可执行文件和减小每一层的体积。同时还会介绍一些更加奇特或激进的工具,例如 BazelDistrolessDockerSlimUPX,虽然这些工具在某些特定场景下能带来奇效,但大多情况下会起到反作用。

本文介绍第二部分。

1. Go 语言镜像精简

Go 语言程序编译时会将所有必须的依赖编译到二进制文件中,但也不能完全肯定它使用的是静态链接,因为 Go 的某些包是依赖系统标准库的,例如使用到 DNS 解析的包。只要代码中导入了这些包,编译的二进制文件就需要调用到某些系统库,为了这个需求,Go 实现了一种机制叫 cgo,以允许 Go 调用 C 代码,这样编译好的二进制文件就可以调用系统库。

也就是说,如果 Go 程序使用了 net 包,就会生成一个动态的二进制文件,如果想让镜像能够正常工作,必须将需要的库文件复制到镜像中,或者直接使用 busybox:glibc 镜像。

当然,你也可以禁止 cgo,这样 Go 就不会使用系统库,使用内置的实现来替代系统库(例如使用内置的 DNS 解析器),这种情况下生成的二进制文件就是静态的。可以通过设置环境变量 CGO_ENABLED=0 来禁用 cgo,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM golang
COPY whatsmyip.go .
ENV CGO_ENABLED=0
RUN go build whatsmyip.go

FROM scratch
COPY --from=0 /go/whatsmyip .
CMD ["./whatsmyip"]

由于编译生成的是静态二进制文件,因此可以直接跑在 scratch 镜像中 ?

当然,也可以不用完全禁用 cgo,可以通过 -tags 参数指定需要使用的内建库,例如 -tags netgo 就表示使用内建的 net 包,不依赖系统库:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ go build -tags netgo whatsmyip.go

这样指定之后,如果导入的其他包都没有用到系统库,那么编译得到的就是静态二进制文件。也就是说,只要还有一个包用到了系统库,都会开启 cgo,最后得到的就是动态二进制文件。要想一劳永逸,还是设置环境变量 CGO_ENABLED=0 吧。

2. Alpine 镜像探秘

上篇文章已经对 Alpine 镜像作了简要的介绍,并保证会在后面的文章中花很大的篇幅来讨论 Alpine 镜像,现在时候到了!

Alpine 是众多 Linux 发行版中的一员,和 CentOSUbuntuArchlinux 之类一样,只是一个发行版的名字,号称小巧安全,有自己的包管理工具 apk

CentOSUbuntu 不同,Alpine 并没有像 Red HatCanonical 之类的大公司为其提供维护支持,软件包的数量也比这些发行版少很多(如果只看开箱即用的默认软件仓库,Alpine 只有 10000 个软件包,而 Ubuntu、DebianFedora 的软件包数量均大于 50000。)

容器崛起之前,Alpine 还是个无名之辈,可能是因为大家并不是很关心操作系统本身的大小,毕竟大家只关心业务数据和文档,程序、库文件和系统本身的大小通常可以忽略不计。

容器技术席卷整个软件产业之后,大家都注意到了一个问题,那就是容器的镜像太大了,浪费磁盘空间,拉取镜像的时间也很长。于是,人们开始寻求适用于容器的更小的镜像。对于那些耳熟能详的发行版(例如 Ubuntu、Debian、Fedora)来说,只能通过删除某些工具(例如 ifconfignetstat)将镜像体积控制在 100M 以下。而对于 Alpine 而言,什么都不用删除,镜像大小也就只有 5M 而已。

Alpine 镜像的另一个优势是包管理工具的执行速度非常快,安装软件体验非常顺滑。诚然,在传统的虚拟机上不需要太关心软件包的安装速度,同一个包只需要装一次即可,无需不停重复安装。容器就不一样了,你可能会定期构建新镜像,也可能会在运行的容器中临时安装某些调试工具,如果软件包的安装速度很慢,会很快消磨掉我们的耐心。

为了更直观,我们来做个简单的对比测试,看看不同的发行版安装 tcpdump 需要多长时间,测试命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
? → time docker run <image> <packagemanager> install tcpdump

测试结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Base image           Size      Time to install tcpdump
---------------------------------------------------------
alpine:3.11          5.6 MB      1-2s
archlinux:20200106   409 MB      7-9s
centos:8             237 MB      5-6s
debian:10            114 MB      5-7s
fedora:31            194 MB    35-60s
ubuntu:18.04          64 MB      6-8s

如果你想了解更多关于 Alpine 的内幕,可以看看 Natanel Copa 的演讲[2]

好吧,既然 Alpine 这么棒,为什么不用它作为所有镜像的基础镜像呢?别急,先一步一步来,为了趟平所有的坑,需要分两种情况来考虑:

  1. 使用 Alpine 作为第二构建阶段(run 阶段)的基础镜像
  2. 使用 ALpine 作为所有构建阶段(run 阶段和 build 阶段)的基础镜像

run 阶段使用 Alpine

带着激动的心情,将 Alpine 镜像加入了 Dockerfile:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM gcc AS mybuildstage
COPY hello.c .
RUN gcc -o hello hello.c

FROM alpine
COPY --from=mybuildstage hello .
CMD ["./hello"]

第一个坑来了,启动容器出现了错误:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
standard_init_linux.go:211: exec user process caused "no such file or directory"

这个报错在上篇文章已经见识过了,上篇文章的场景是使用 scratch 镜像作为 C 语言程序的基础镜像,错误的原因是 scratch 镜像中缺少动态库文件。可是为什么使用 Alpine 镜像也有报错,难道它也缺少动态库文件?

也不完全是,Alpine 使用的也是动态库,毕竟它的设计目标之一就是占用更少的空间。但 Alpine 使用的标准库与大多数发行版不同,它使用的是 musl libc,这个库相比于 glibc 更小、更简单、更安全,但是与大家常用的标准库 glibc 并不兼容。

你可能又要问了:『既然 musl libc 更小、更简单,还特么更安全,为啥其他发行版还在用 glibc?』

mmm。。。因为 glibc 有很多额外的扩展,并且很多程序都用到了这些扩展,而 musl libc 是不包含这些扩展的。详情可以参考 musl 的文档[3]

也就是说,如果想让程序跑在 Alpine 镜像中,必须在编译时使用 musl libc 作为动态库。

所有阶段使用 Alpine

为了生成一个与 musl libc 链接的二进制文件,有两条路:

  • 某些官方镜像提供了 Alpine 版本,可以直接拿来用。
  • 还有些官方镜像没有提供 Alpine 版本,我们需要自己构建。

golang 镜像就属于第一种情况,golang:alpine 提供了基于 Alpine 构建的 Go 工具链。

构建 Go 程序可以使用下面的 Dockerfile

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM golang:alpine
COPY hello.go .
RUN go build hello.go

FROM alpine
COPY --from=0 /go/hello .
CMD ["./hello"]

生成的镜像大小为 7.5M,对于一个只打印 『hello world』的程序来说确实有点大了,但我们可以换个角度:

  • 即使程序很复杂,生成的镜像也不会很大。
  • 包含了很多有用的调试工具。
  • 即使运行时缺少某些特殊的调试工具,也可以迅速安装。

Go 语言搞定了,C 语言呢?并没有 gcc:alpine 这样的镜像啊。只能以 Alpine 镜像作为基础镜像,自己安装 C 编译器了,Dockerfile 如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FROM alpine
RUN apk add build-base
COPY hello.c .
RUN gcc -o hello hello.c

FROM alpine
COPY --from=0 hello .
CMD ["./hello"]

必须安装 build-base,如果安装 gcc,就只有编译器,没有标准库。build-base 相当于 Ubuntu 的 build-essentials,引入了编译器、标准库和 make 之类的工具。

最后来对比一下不同构建方法得到的 『hello world』镜像大小:

  • 使用基础镜像 golang 构建:805MB
  • 多阶段构建,build 阶段使用基础镜像 golang,run 阶段使用基础镜像 ubuntu:66.2MB
  • 多阶段构建,build 阶段使用基础镜像 golang:alpine,run 阶段使用基础镜像 alpine:7.6MB
  • 多阶段构建,build 阶段使用基础镜像 golang,run 阶段使用基础镜像 scratch:2MB

最终镜像体积减少了 99.75%,相当惊人了。再来看一个更实际的例子,上一节提到的使用 net 的程序,最终的镜像大小对比:

  • 使用基础镜像 golang 构建:810MB
  • 多阶段构建,build 阶段使用基础镜像 golang,run 阶段使用基础镜像 ubuntu:71.2MB
  • 多阶段构建,build 阶段使用基础镜像 golang:alpine,run 阶段使用基础镜像 alpine:12.6MB
  • 多阶段构建,build 阶段使用基础镜像 golang,run 阶段使用基础镜像 busybox:glibc:12.2MB
  • 多阶段构建,build 阶段使用基础镜像 golang 并使用参数 CGO_ENABLED=0,run 阶段使用基础镜像 ubuntu:7MB

镜像体积仍然减少了 99%

3. Java 语言镜像精简

Java 属于编译型语言,但运行时还是要跑在 JVM 中。那么对于 Java 语言来说,该如何使用多阶段构建呢?

静态还是动态?

从概念上来看,Java 使用的是动态链接,因为 Java 代码需要调用 JVM 提供的 Java API,这些 API 的代码都在可执行文件之外,通常是 JAR 文件或 WAR 文件。

然而这些 Java 库并不是完全独立于系统库的,某些 Java 函数最终还是会调用系统库,例如打开文件时需要调用 open(), fopen() 或它们的变体,因此 JVM 本身可能会与系统库动态链接。

这就意味着理论上可以使用任意的 JVM 来运行 Java 程序,系统标准库是 musl libc 还是 glibc 都无所谓。因此,也就可以使用任意带有 JVM 的基础镜像来构建 Java 程序,也可以使用任意带有 JVM 的镜像作为运行 Java 程序的基础镜像。

类文件格式

Java 类文件(Java 编译器生成的字节码)的格式会随着版本而变化,且大部分变化都是 Java API 的变化。还有一部分更改与 Java 语言本身有关,例如 Java 5 中添加了泛型,这种变化就可能会导致类文件格式的变化,从而破坏与旧版本的兼容性。

所以默认情况下,使用给定版本的 Java 编译器编译的类不能与更早版本的 JVM 兼容,但可以指定编译器的 -target (Java 8 及其以下版本)参数或者 --release (Java 9 及其以上版本)参数来使用较旧的类文件格式。--release 参数还可以指定类文件的路径,以确保程序运行在指定的 JVM 版本中(例如 Java 11),不会意外调用 Java 12 的 API。

JDK vs JRE

如果你对大多数平台上的 Java 打包方式很熟悉,那你应该知道 JDKJRE

JRE 即 Java 运行时环境(Java Runtime Environment),包含了运行 Java 程序所需要的环境,即 JVM

JDK 即 Java 开发工具包(Java Development Kit),既包含了 JRE,也包含了开发 Java 程序所需的工具,即 Java 编译器。

大多数 Java 镜像都提供了 JDK 和 JRE 两种标签,因此可以在多阶段构建的 build 阶段使用 JDK 作为基础镜像,run 阶段使用 JRE 作为基础镜像。

Java vs OpenJDK

推荐使用 openjdk,因为开源啊,更新勤快啊~~

也可以使用 amazoncorretto[4],这是 Amazon fork OpenJDK 后打了补丁的版本,号称企业级。

开始构建

说了那么多,到底该用哪个镜像呢?这里给出几个参考:

  • openjdk:8-jre-alpine(85MB)
  • openjdk:11-jre(267MB)或者 openjdk:11-jre-slim(204MB)
  • openjdk:14-alpine(338MB)

如果你想要更直观的数据,可以看我的例子,还是搬出屡试不爽的 『hello world』,只不过这次是 Java 版本:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class hello {
  public static void main(String [] args) {
    System.out.println("Hello, world!");
  }
}

不同构建方法得到的镜像大小:

  • 使用基础镜像 java 构建:643MB
  • 使用基础镜像 openjdk 构建:490MB
  • 多阶段构建,build 阶段使用基础镜像 openjdk,run 阶段使用基础镜像 openjdk:jre:479MB
  • 使用基础镜像 amazoncorretto 构建:390MB
  • 多阶段构建,build 阶段使用基础镜像 openjdk:11,run 阶段使用基础镜像 openjdk:11-jre:267MB
  • 多阶段构建,build 阶段使用基础镜像 openjdk:8,run 阶段使用基础镜像 openjdk:8-jre-alpine:85MB

所有的 Dockerfile 都可以在这个仓库[5]找到。

4. 解释型语言镜像精简

对于诸如 NodePythonRust 之类的解释型语言来说,情况就比较复杂一点了。先来看看 Alpine 镜像。

Alpine 镜像

对于解释型语言来说,如果程序仅用到了标准库或者依赖项和程序本身使用的是同一种语言,且无需调用 C 库和外部依赖,那么使用 Alpine 作为基础镜像一般是没有啥问题的。一旦你的程序需要调用外部依赖,情况就复杂了,想继续使用 Alpine 镜像,就得安装这些依赖。根据难度可以划分为三个等级:

  • 简单:依赖库有针对 Alpine 的安装说明,一般会说明需要安装哪些软件包以及如何建立依赖关系。但这种情况非常罕见,原因前面也提到了,Alpine 的软件包数量比大多数流行的发行版要少得多。
  • 中等:依赖库没有针对 Alpine 的安装说明,但有针对别的发行版的安装说明。我们可以通过对比找到与别的发行版的软件包相匹配的 Alpine 软件包(假如有的话)。
  • 困难:依赖库没有针对 Alpine 的安装说明,但有针对别的发行版的安装说明,但是 Alpine 也没有与之对应的软件包。这种情况就必须从源码开始构建!

最后一种情况最不推荐使用 Alpine 作为基础镜像,不但不能减小体积,可能还会适得其反,因为你需要安装编译器、依赖库、头文件等等。。。更重要的是,构建时间会很长,效率低下。如果非要考虑多阶段构建,就更复杂了,你得搞清楚如何将所有的依赖编译成二进制文件,想想就头大。因此一般不推荐在解释型语言中使用多阶段构建。

有一种特殊情况会同时遇到 Alpine 的绝大多数问题:Python 用于数据科学numpypandas 之类的包都被预编译成了 wheel[6]wheel 是 Python 新的打包格式,被编译成了二进制,用于替代 Python 传统的 egg 文件,可以通过 pip 直接安装。但这些 wheel 都绑定了特定的 C 库,这就意味着在大多数使用 glibc 的镜像中都可以正常安装,但 Alpine 镜像就不行,原因你懂得,前面已经说过了。如果非要在 Alpine 中安装,你需要安装很多依赖,重头构建,耗时又费力,有一篇文章专门解释了这个问题:使用 Alpine 构建 Pyhton 镜像会将构建速度拖慢 50 倍![7]

既然 Alpine 镜像这么坑,那么是不是只要是 Python 写的程序就不推荐使用 Alpine 镜像来构建呢?也不能完全这么肯定,至少 Python 用于数据科学时不推荐使用 Alpine,其他情况还是要具体情况具体分析,如果有可能,还是可以试一试 Alpine 的。

:slim 镜像

如果实在不想折腾,可以选择一个折衷的镜像 xxx:slim。slim 镜像一般都基于 Debianglibc,删除了许多非必需的软件包,优化了体积。如果构建过程中需要编译器,那么 slim 镜像不适合,除此之外大多数情况下还是可以使用 slim 作为基础镜像的。

下面是主流的解释型语言的 Alpine 镜像和 slim 镜像大小对比:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Image            Size
---------------------------
node             939 MB
node:alpine      113 MB
node:slim        163 MB
python           932 MB
python:alpine    110 MB
python:slim      193 MB
ruby             842 MB
ruby:alpine       54 MB
ruby:slim        149 MB

再来举个特殊情况的例子,同时安装 matplotlibnumpypandas,不同的基础镜像构建的镜像大小如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Image and technique         Size
--------------------------------------
python                      1.26 GB
python:slim                  407 MB
python:alpine                523 MB
python:alpine multi-stage    517 MB

可以看到这种情况下使用 Alpine 并没有任何帮助,即使使用多阶段构建也无济于事。

但也不能全盘否定 Alpine,比如下面这种情况:包含大量依赖的 Django 应用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Image and technique         Size
--------------------------------------
python                      1.23 GB
python:alpine                636 MB
python:alpine multi-stage    391 MB

最后来总结一下:到底使用哪个基础镜像并不能盖棺定论,有时使用 Alpine 效果更好,有时反而使用 slim 效果更好,如果你对镜像体积有着极致的追求,可以这两种镜像都尝试一下。相信随着时间的推移,我们就会积累足够的经验,知道哪种情况该用 Alpine,哪种情况该用 slim,不用再一个一个尝试。

5. Rust 语言镜像精简

Rust 是最初由 Mozilla 设计的现代编程语言,并且在 Web 和基础架构领域中越来越受欢迎。Rust 编译的二进制文件动态链接到 C 库,可以正常运行于 UbuntuDebianFedora 之类的镜像中,但不能运行于 busybox:glibc 中。因为 Rust 二进制需要调用 libdl 库,busybox:glibc 中不包含该库。

还有一个 rust:alpine 镜像,Rust 编译的二进制也可以正常运行其中。

如果考虑编译成静态链接,可以参考 Rust 官方文档[8]。在 Linux 上需要构建一个特殊版本的 Rust 编译器,构建的依赖库就是 musl libc,你没有看错,就是 Alpine 中的那个 musl libc。如果你想获得更小的镜像,请按照文档中的说明进行操作,最后将生成的二进制文件扔进 scratch 镜像中就好了。

6. 总结

本系列文章的前两部分介绍了优化 Docker 镜像体积的常用方法,以及如何针对不同类型的语言运用这些方法。最后一部分将会介绍如何在减少镜像体积的同时,还能减少 I/O 和内存使用量,同时还会介绍一些虽然与容器无关但对优化镜像有帮助的技术。

脚注

[1]

两个奇技淫巧,将 Docker 镜像体积减小 99%: https://fuckcloudnative.io/posts/docker-images-part1-reducing-image-size/

[2]

Natanel Copa 的演讲: https://dockercon.docker.com/watch/6nK1TVGjuTpFfnZNKEjCEr

[3]

musl 的文档: https://wiki.musl-libc.org/functional-differences-from-glibc.html

[4]

amazoncorretto: https://hub.docker.com/_/amazoncorretto

[5]

这个仓库: https://github.com/jpetazzo/minimage

[6]

wheel: https://pythonwheels.com/

[7]

使用 Alpine 构建 Pyhton 镜像会将构建速度拖慢 50 倍!: https://pythonspeed.com/articles/alpine-docker-python/

[8]

Rust 官方文档: https://doc.rust-lang.org/1.9.0/book/advanced-linking.html#static-linking

原文链接:https://www.ardanlabs.com/blog/2020/02/docker-images-part2-details-specific-to-different-languages.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云原生实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Rc-form: 消失的“Ta”
那是一个艳阳高照的早上,临近中休时间,小 H 正准备动身去吃午餐,突然,钉钉弹出了一条新消息:(登登登~)“您有一个新的 bug:表单点击提交按钮没反应”。自信的小 H 心想:这期的需求我不就给表单多加了几个字段嘛,怎么会影响到表单的提交功能呢?应该是提错 bug 了吧。于是,小 H 按照 bug 的描述复现起了场景:
政采云前端团队
2023/09/01
2440
Rc-form: 消失的“Ta”
Error: must set key for <rc-animate> children
在使用antd-design中的select的组件时候,报了这样的一个错误: Error: must set key for <rc-animate> children <FormItem {...formItemLayout} label={item.label} key={index}> { getFieldDecorator(item.paramName, { initialValue: [], })( <Select size={confi
杭州前端工程师
2018/06/15
1.6K0
Form 表单在数栈的应用(下):深入篇
后文中所提到的 Form 表单均为 Antd 3.x 中的 Form 组件,以下简称为 Form 表单。在 Form 表单在数栈的应用(上): 校验篇 中提到,我们生在一个最好的时代,其实是别人造好轮子帮我们做了一些事情,那我们今天看一看,别人的轮子是怎么造的,我们自己能不能实现。 留心过 Antd 的同学可能有印象,Antd 是基于 react-component 组件进行了 UI 封装,文章会以 react-component/form 的代码为主。
袋鼠云数栈
2022/01/19
9070
antd-design Form,Select联合使用 placeholder 不起作用问题
Contents 1 antd-design Form,Select联合使用 placeholder 不起作用问题 1.1 起因 1.2 排查 1.3 补充: antd-design Form,Sel
w候人兮猗
2020/07/01
2.1K0
数栈技术文章分享:你居然是这样的initialValue
先说一下写着篇文章的契机,是因为回显,复杂表单的回显,让我觉得我对initialValue这个属性是有误解的。
袋鼠云数栈
2021/05/14
1K0
数栈技术文章分享:你居然是这样的initialValue
React后台管理前端系统(基于开源框架开发)起步式
这个系统的搭建背景是这样的,有一个朋友想看到现有系统中的一些,用户数据,新闻数据,只需要看到,短期不需要增删改功能,让我搭建一个简单的后台系统给他看.接到任务作为一个有四年开发经验的人来说这也太简单了吧,开始干吧,最简单的办法是直接使用开源项目https://github.com/PanJiaChen/vue-element-admin 下载下来,把不要的路由去掉,新建几个表格路由,页面直接复制过来就用了.
拿我格子衫来
2022/01/24
2K0
React后台管理前端系统(基于开源框架开发)起步式
疫情期间,写的两个场景
嗯~这种实现的方式还是和舒服的,不用自己布局,不用自己再次思考逻辑;如果你想自己捣鼓一个,那你是真的闲,还不如花点时间捣鼓其他非编程的东西。
Jimmy_is_jimmy
2020/04/01
1.1K0
疫情期间,写的两个场景
最熟悉的陌生人 rc-form
我们也许会经常使用例如 Ant Design、Element UI、Vant 等第三方组件库来快速在项目中完成页面的布局效果和简单的交互功能。
政采云前端团队
2021/07/19
1.1K0
Form 表单在数栈的应用(上): 校验篇
本文的重点为 Form 表单的校验及在数栈中的应用,偏向于应用总结与心得分享。众所周知,我们生在一个最好的时代,antd 已经帮我们把绝大多数功能封装好了,即开即用, API 详尽,但即便如此,antd 开发人员依然在当前基础上一遍又一遍地做优化和探索,所以,笔者希望通过本文不仅能带给大家业务上的小技巧,还希望能带给大家一些思路上的启发。
袋鼠云数栈
2022/01/12
2.2K1
React 16.x折腾记 - (6) 基于React 16.x+ Antd 3.x封装的一个声明式的查询组件(实用强大)
根据ctype渲染的控件有Input,Button,Select,DatePicker,Cascader,Radio
CRPER
2024/02/06
2080
React 16.x折腾记 - (6)  基于React 16.x+ Antd 3.x封装的一个声明式的查询组件(实用强大)
React 折腾记 - (6) 基于React 16.6 + Antd 3.10.7封装的一个声明式的查询组件
根据ctype渲染的控件有Input,Button,Select,DatePicker,Cascader,Radio
CRPER
2018/12/12
2.7K0
猿实战09——实现你设计的类目系统
上两个章节,猿人君教会了你如何通过设计落地实现了属性库,今天我们继续来实现系统的另一个基石地位的模块——后台类目。
山旮旯的胖子
2020/09/07
7770
猿实战09——实现你设计的类目系统
详细剖析|袋鼠云数栈前端框架Antd 3.x 升级 4.x 的踩坑之路
袋鼠云数栈从 2016 年发布第⼀个版本开始,就始终坚持着以技术为核⼼、安全为底线、提效为⽬标、中台为战略的思想,坚定不移地⾛国产化信创路线,不断推进产品功能迭代、技术创新、服务细化和性能升级。
袋鼠云数栈
2023/03/06
4.2K0
React 实现一个markdown[2]
theme: channing-cyan highlight: a11y-light
用户4793865
2023/02/03
1.2K0
Python 数据可视化,常用看这一篇就够了
如果你想要用 Python 进行数据分析,就需要在项目初期开始进行探索性的数据分析,这样方便你对数据有一定的了解。其中最直观的就是采用数据可视化技术,这样,数据不仅一目了然,而且更容易被解读。
全栈程序员站长
2022/09/07
2.1K0
React 16.8.6 升级指南(react-hooks篇)
从官方的态度可以很容易看出是十分重视hooks这个特性的,并且官方直言我们期望 Hook 能够成为人们编写 React 组件的主要方式。并且从笔者的实践过程来看hooks不仅仅是一种新玩法,更重要的意义是可以帮助开发者做减法,减少代码量,减少维护成本,甚至减少理解成本。
腾讯IVWEB团队
2020/06/28
2.8K0
React源码解析之HostComponent的更新(下)
在上篇 React源码解析之HostComponent的更新(上) 中,我们讲到了多次渲染阶段的更新,本篇我们讲第一次渲染阶段的更新
进击的小进进
2020/03/18
2.8K0
React源码解析之HostComponent的更新(下)
精读《怎么用 React Hooks 造轮子》
上周的 精读《React Hooks》 已经实现了对 React Hooks 的基本认知,也许你也看了 React Hooks 基本实现剖析(就是数组),但理解实现原理就可以用好了吗?学的是知识,而用的是技能,看别人的用法就像刷抖音一样(哇,饭还可以这样吃?),你总会有新的收获。
黄子毅
2022/03/14
2.5K0
EngineerCMS核心代码
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/details/53367020
hotqin888
2018/09/11
1.4K0
一篇看懂 React Hooks
React Hooks 是 React 16.7.0-alpha 版本推出的新特性,想尝试的同学安装此版本即可。
前端迷
2019/08/05
3.8K0
推荐阅读
相关推荐
Rc-form: 消失的“Ta”
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 1. Go 语言镜像精简
  • 2. Alpine 镜像探秘
    • run 阶段使用 Alpine
    • 所有阶段使用 Alpine
  • 3. Java 语言镜像精简
    • 静态还是动态?
    • 类文件格式
    • JDK vs JRE
    • Java vs OpenJDK
    • 开始构建
  • 4. 解释型语言镜像精简
    • Alpine 镜像
    • :slim 镜像
  • 5. Rust 语言镜像精简
  • 6. 总结
    • 脚注
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档