SHERlocked93

SHERlocked93 查看完整档案

南京编辑北京理工大学  |  飞行器动力学与控制 编辑中电  |  前端打字员 编辑 github.com/SHERlocked93/blog/blob/master/README.md 编辑
编辑

来自南京的前端打字员,掘金优秀作者,慕课畅销专栏 <JavaScript 设计模式精讲> 作者,原创同步更新于 Github 个人博客 (求 star🤪 )

公众号 前端下午茶,欢迎关注 👏 ,分享前端相关的技术博客、精选文章,期待在这里和大家一起进步 ~

个人微信号: qianyu443033099 ,加我时请备注 sf

个人动态

SHERlocked93 发布了文章 · 9月22日

手摸手带你 Docker 从入门到实践

bg

在下最近遇到要在服务器上安装 Mysql、Nginx、EasyMock 等工具的场景,这里记录一下我使用 Docker 安装的过程,希望也能在类似的场景中帮助到大家~

本文前备知识需要一些 Linux 的一些基本命令,推介先看一下 <半小时搞会 CentOS 入门必备基础知识> 这篇文章。

CentOS 版本: 7.6

Nginx 版本: 1.16.1

Docker 版本: 19.03.12

你多学一样本事,就少说一句求人的话

1. 介绍

1.1 出现的原因

前后端开发到测试到生产的过程中,经常会遇到一个问题,明明我在本地跑没问题,为什么到测试环境或者生产环境就报错了了呢,常常这是因为开发、测试、生产的环境与配置不同导致的。

折腾过环境配置的人都明白其中麻烦,换一台系统、虚拟机、机器,就又要重来一次,费力费时。由于环境和配置的原因,各种奇奇怪怪因为环境和配置的 Bug,总是像打地鼠游戏里面的地鼠一样不断冒出来 🐹

Docker

Docker 对这个问题给出了一个很好的解决方案,通过镜像将除了系统之外所需要的系统环境由下而上打包,达到服务跨平台的无缝运作。也就是说,安装的时候,把特定的环境一模一样地搬过来,从而解决「在我的电脑上能跑,在 xx 环境就跑不了」的情况。

另外一个重要的原因,就是轻量,基于容器的虚拟化,Docker 的镜像仅包含业务运行所需的 runtime 环境,一个 CentOS/Ubuntu 基础镜像仅 170M,因为轻量一个宿主机可以轻松安装数百个容器。

1.2 是什么

Docker 是基于 Go 语言实现的云开源项目,从 2013 年发布到现在一直广受关注。Docker 可以让你像使用集装箱一样快速的组合成应用,并且可以像运输标准集装箱一样,尽可能的屏蔽代码层面的差异。它将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。

程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。

本文就不对比虚拟机跟 Docker 的区别和优劣了,每个文章都有,说烂了,想了解的话可以百度一下 😂,我这里就不多说了,下面直接看看怎么安装怎么用起来吧。

2. 安装 & 配置

2.1 Mac 下安装

在下直接使用 Homebrew Cask 来安装,Mac 下:

# Homebrew 安装
$ braw cask install docker

即可,安装完输入命令,直接报错!

➜  ~ docker
zsh: command not found: docker  # 报错

遇到这个报错别担心,安装完之后要在应用列表里面双击 Docker 应用,输入密码之后就可以使用这个命令了 😅。

2.2 CentOS 下安装

Docker 要求 CentOS 版本必须在 6.5 及以上才可以安装。

# 安装
$ sudo yum install yum-utils device-mapper-persistent-data lvm2
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce

# 开启 Docker
$ sudo systemctl start docker

在 Windows 上可以直接下载安装包来安装,或者 Mac 上不使用 Homebrew 也可以去官网直接下载安装包来安装,百度一下到处都是安装方法,其他的就不用多说。

3. 简单配置并跑起来

3.1 配置镜像加速

在 MacOS 的 Docker 配置 Perferences -> Docker Engine 或者 Windows 的 Settings -> Deamon 中的 JSON 中增加一项 registry-mirrors 如下

Docker镜像加速配置

配置完之后在命令行中 docker info 就可以查看到我们配置的镜像加速地址了。

➜  ~ sudo docker info
...
 Registry Mirrors:
  https://reg-mirror.qiniu.com/
  http://hub-mirror.c.163.com/
  https://registry.docker-cn.com/
...

如果你的系统的 Docker 没有客户端,比如 CentOS 中,可以直接修改 deamon 配置文件:

# 修改/创建 docker 的 deamon 配置文件
$ sudo vi /etc/docker/daemon.json

# 修改为如下配置
{
  "experimental": false,
  "debug": true,
  "registry-mirrors": [
    "https://reg-mirror.qiniu.com",
    "http://hub-mirror.c.163.com",
    "https://registry.docker-cn.com"
  ]
}

# 修改完 :wq 重启
$ sudo systemctl restart docker

3.2 Hello World !

然后就可以快乐跑起来我们第一个 Docker 指令 Hello World 了

Docker跑起Helloworld

Good start ! 🎉

4. 镜像 & 容器 & 仓库

镜像和容器的关系就像类和类的实例,一个镜像可以同时跑多个容器,单个容器实例又可以创建新的镜像。如下图:

镜像容器仓库

下面解释一下这个图里面出现的元素

概念说明
Docker 镜像 Images用于创建 Docker 容器的只读模板,比如 Ubuntu 16.04系统、Nginx 1.16.0 等,是一个特殊的文件系统,包括容器运行时需要的程序、库、资源、参数等,但不包含任何动态数据,内容在构建后也不会被改变,一个镜像可以创建多个容器
Docker 容器 Container容器是独立运行、相互隔离的一个或一组应用,是镜像创建的运行实例,实质是进程,可以看作为一个简易版的 Linux 环境 + 运行在其中的应用程序
Docker 客户端 Client客户端通过命令行或者其他工具使用 Docker SDK (https://docs.docker.com/devel... 与 Docker 的守护进程通信
Docker 主机 Host一个物理或者虚拟的机器用于执行 Docker 守护进程和容器
Docker 仓库 Repository集中存放镜像文件的地方,分为公有仓库和私有仓库。
Docker 注册服务器 Registry是一个集中存储、分发镜像的服务,官方的叫 Docker Hub。一个 Docker Registry 中可包含多个仓库,每个仓库可以包含多个标签 Tag 的镜像,不同的标签对应不同的版本
Docker MachineDocker Machine 是一个简化 Docker 安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如 VirtualBox、 Digital Ocean、Microsoft Azure

容器的生命周期图示

容器的生命周期

容器的五个核心状态,也就是图中色块表示的:Created、Running、Paused、Stopped、Deleted:

  1. Created:容器已经被创建,容器所需的相关资源已经准备就绪,但容器中的程序还未处于运行状态。
  2. Running:容器正在运行,也就是容器中的应用正在运行。
  3. Paused:容器已暂停,表示容器中的所有程序都处于暂停 ( 不是停止 ) 状态。
  4. Stopped:容器处于停止状态,占用的资源和沙盒环境都依然存在,只是容器中的应用程序均已停止。
  5. Deleted:容器已删除,相关占用的资源及存储在 Docker 中的管理信息也都已释放和移除。

本文主要关注于使用,就不太赘述这些状态的切换等,下面直接上手。

5. 基本使用

5.1 操作命令

# 开启 Docker 开机自启动
$ sudo systemctl enable docker

# 关闭 Docker 开机自启动
$ sudo systemctl disable docker

5.2 镜像命令

# 去下载镜像,先从本地找,没有去镜像,最后没有去 hub,标签不写默认为 lastest
$ docker pull [镜像名]:[标签Tag]

# 列出本机的所有 image 文件,-a 显示本地所有镜像(包括中间镜像),-q 只显示镜像ID,--digests 显示镜像的摘要信息
$ docker image ls
$ docker images

# 删除 image 文件, -f 强制删除镜像
$ docker rmi [镜像名][:标签Tag]
$ docker rmi [镜像名1][:标签Tag] [镜像名2][:标签Tag]    # 删多个
$ docker rmi $(docker ps -a -q)    # 删全部,后面是子命令

# 查询镜像名称,--no-trunc 显示完整的镜像描述,--filter=stars=30 列出star不少于指定值的镜像,--filter=is-automated=true 列出自动构建类型的镜像
$ docker search [关键字]

# 下载镜像,标签 tag 不写默认为 lastest,也可以自己加比如 :3.2.0
$ docker pull [镜像名][:标签Tag]

5.3 容器命令

# 列出本机正在运行的容器,-a 列出本机所有容器包括终止运行的容器,-q 静默模式只显示容器编号,-l 显示最近创建的容器
$ docker container ls     # 等价于下面这个命令
$ docker ps

# 新建并启动容器
$ docker run [option] [容器名] 

# 启动容器
$ docker start [容器ID]/[容器Names]

# 重启容器
$ docker restart [容器ID]/[容器Names]

# 终止容器运行
$ docker kill [容器ID]  # 强行终止,相当于向容器里面的主进程发出 SIGKILL 信号,那些正在进行中的操作会全部丢失
$ docker kill $(docker ps -a -q) # 强行终止所有容器
$ docker stop [容器ID]  # 从容终止,相当于向容器里面的主进程发出 SIGTERM 信号,然后过一段时间再发出 SIGKILL 信号
$ docker stop $(docker ps -a -q) # 终止所有容器

# 终止运行的容器文件,依然会占据硬盘空间,可以使用 docker container rm 命令删除,-f 强制删除可以删除正在运行的容器
$ docker rm [容器ID]
$ docker rm `docker ps -aq`    # 删除所有已经停止的容器,因为没停止的rm删不了需要加-f

# 查看容器的输出,-t加入时间戳,-f跟随最新日志打印,--tail数字显示最后多少条,如果docker run时,没有使用-it,就要用这个命令查看输出
$ docker logs [容器ID]

# 查看容器进程信息
$ docker top [容器ID]/[容器Names]
$ docker port [容器ID]/[容器Names]

# 退出容器
$ exit           # 容器退出
ctrl + p + q     # 容器退出,快捷键

# 进入容器
$ docker attach [容器ID]      # 退出容器时会让容器停止,本机的输入直接输到容器中
$ docker exec -it [容器ID]    # 退出容器时不会让容器停止,在已运行的容器中执行命令,不创建和启动新的容器

# 设置容器在docker启动时自动启动
$ docker container update --restart=always [容器名字]

这里要特别说一下 docker runoption,因为最常用:

  1. --name 为容器指定一个名称;
  2. -d 容器启动后进入后台,并返回容器 ID,即启动守护式容器;
  3. -P 随机端口映射;
  4. -p 80:8080 将本地 80 端口映射到容器的 8080 端口;
  5. bash 容器启动以后,内部第一个执行的命令。这里启动 bash,保证用户可以使用 Shell;
  6. -i 以交互模式运行容器,通常与 -t 同时使用;
  7. -t 为容器重新分配一个伪输入终端,容器的 Shell 会映射到当前的 Shell,然后在本机窗口输入的命令,就会传入容器,通常与 -i 同时使用;
  8. --rm 在容器终止运行后自动删除容器文件;
  9. --restart=always 设置容器自启动;
  10. -v /xxx:/yyy 映射命令,把本机的 xxx 目录映射到容器中的 yyy 目录,也就是说改变本机的 xxx 目录下的内容, 容器 yyy 目录中的内容也会改变;

比如我在 CentOS 下跑起来一个 CentOS 的 Docker 容器:

# 下载
$ docker pull centos

# 在上面下载的 centos 镜像基础上,新建一个容器名为 mycentos0901 的 centos 实例,并进入这个容器的 bash
$ docker run -it --name mycentos0901 0d120b6ccaa8

[root@169c9fffeecd /]   # 进入容器,下面输入命令,注意这里 root 后面的一串 ID
$ ls       # 可以看到centos的根目录文件列表
$ docker   # bash: docker: command not found 这个容器没有安装docker

是不是很神奇,我们可以在一开始的 CentOS 下面执行 docker ps 来查看容器列表:

image-20200901225909737

你会发现上面那个 ID,正是下面列表中跑起来的这个容器的 ID,镜像的 ID 也是我们前面 pull 下来的 CentOS 镜像 ID,名字也是我们起的 mycentos0901

如果 docker run 之后报 Conflict. The container name "xxxx" is already in use by container 就直接运行 docker rm $(docker ps -a -q) 删除已停止的容器,或者精确删除 docker rm [containerID] 也可以,就可以了。

5.4 几个常见场景的命令使用

守护式启动容器

使用 centos 以后台模式启动一个容器 docker run -d --name mycentos0903 0d120b6ccaa8,启动之后 docker ps -a 查看,发现容器并不在运行中,这是因为 Docker 的运行机制:Docker 容器后台运行,必须有一个前台进程

容器运行的命令如果不是那些一直挂起的命令,比如 toptail ,运行结束会自动退出。所以为了让容器持续在后台运行,那么需要将运行的程序以前台进程的形式运行。

比如这里在后台运行一个命令,这个命令一直在打印 docker run -d centos /bin/sh -c "while true; do echo hello zzyy; sleep 2; done",然后我们 logs 查看一下:

docker_logs

退出容器后对容器操作

退出容器后可以通过 exec 方法对正在运行的容器进行操作:

image-20200911142617186

在容器中拷贝文件到外部

拷贝文件使用 cp 命令

$ docker cp [容器ID]/[容器Names]:[要拷贝的文件目录] [本机目录]   # 容器文件拷贝到本机
$ docker cp [本机目录] [容器ID]/[容器Names]:[要拷贝的文件目录]   # 本机文件拷贝到容器

cp 不仅能把容器中的文件/文件夹拷贝到本机,也可以把本机中的文件/文件夹拷贝到容器。

演示一下,这里先到容器里面创建一个无聊的文件 xixi.txt,然后拷贝到本机:

image-20200921210352644

实用的时候,我们可以拷贝配置、日志等文件到本地。

6. 安装 MySQL

# 查询镜像
$ docker search mysql

# 下载镜像,实测没配置镜像加速的时候会比较慢,配置了就好一些
$ docker pull mysql

# 查看镜像
$ docker images

# 创建并运行容器
$ docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=888888 -v /Users/sherlocked93/Personal/configs/mysql.d:/etc/mysql/conf.d --name localhost-mysql mysql

稍微解释一下上面的参数:

  1. -p 3307:3306 将本机的 3307 端口映射到 mysql 容器的 3306 端口,根据需要自行更改;
  2. -e MYSQL_ROOT_PASSWORD=<string> 设置远程登录的 root 用户密码;
  3. --name <string> 可选,设置容器别名;
  4. -v xxx/mysql.d:/etc/mysql/conf.d 将本地目录下设置文件夹映射到容器的 /etc/mysql/conf.d
  5. -v xxx/logs:/logs 将本机指定目录下的 logs 目录挂载到容器的 /logs
  6. -v xxx/data:/var/lib/mysql 将主机制定目录下的 data 目录挂载到容器的 /var/lib/mysql

运行截图:

安装Mysql

然后去 Navicat 中就可以连接到 MySQL 了。

这也太爽了!真的是几行命令就装好了啊,比之前真是快乐多了 😂

7. 安装 Nginx

Nginx 的安装和其他的类似,如果你还不太了解 Nginx 如何使用,可以参看 <Nginx 从入门到实践,万字详解> 这篇文章,看完基本就了解如何使用和配置了。

# 查询/下载镜像
$ docker search nginx
$ docker pull nginx

image-20200922203203685

然后创建一个临时的容器,目的是把默认配置拷贝到本机,我这里把配置文件放到 /mnt 目录下,主要是三个配置文件夹:

  1. /etc/nginx 放置 Nginx 配置文件;
  2. /var/log/nginx/ 放置 Nginx 日志文件;
  3. /usr/share/nginx/html/ 放置 Nginx 前端静态文件都放在这个文件夹;

分别把这几个目录都拷贝到本机的 /mnt 文件夹下的 nginxnginx_logshtml 文件夹。

刚刚创建的临时容器没用了 docker rm -f [临时容器ID] 把临时容器干掉,然后 docker run 重新创建 Nginx 容器:

$ docker run -d --name localhost-nginx -p 8082:80 \
-v /mnt/nginx:/etc/nginx \
-v /mnt/nginx_logs:/var/log/nginx \
-v /mnt/html:/usr/share/nginx/html \
--privileged=true nginx

--privileged=true 表示容器内部对挂载的目录拥有读写等特权。

其他配置刚刚上面之前已经讲过,应该不用讲了。

image-20200922204931582

然后在你自己浏览器上就可以访问了,如果是云服务器,记得开放对应端口。

8. 安装 Easy Mock

因为 Easy Mock 依赖 Redis 和 MongoDB,因此本地环境使用 docker-compose 来搭建 Easy Mock 应该算是最佳实践了。

安装 docker-compose

官方文档:https://docs.docker.com/compose/install/

首先你得确定拥有 docker 环境,如果你是 Windows / Mac 用户,那么安装客户端,就会自带 docker-compose 了。

因为本次我们是在云服务器 CentOS7.6 上搭建,所以我们需要自行安装 docker-compose,运行如下命令,下载当前稳定版本的 docker-compose

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

修改文件权限为可执行文件

$ sudo chmod +x /usr/local/bin/docker-compose

验证是否安装成功

$ docker-compose version

编写 docker-compose.yml 配置文件

可以参考官方文档给出的部署文档,也可以参考我下面的配置过程。

首先新建文件 docker-compose.yml 并将下面 docker-compose 文件内容复制进入 docker-compose.yml,然后将内容中注释位置替换为自己需要的本地地址

version: '3'

services:
  mongodb:
    image: mongo:3.4.1
    volumes:
      #  /apps/easy-mock/data/db 是数据库文件存放地址,根据需要修改为本地地址
      - '/apps/easy-mock/data/db:/data/db'
    networks:
      - easy-mock
    restart: always

  redis:
    image: redis:4.0.6
    command: redis-server --appendonly yes
    volumes:
      #  /apps/easy-mock/data/redis 是 redis 数据文件存放地址,根据需要修改为本地地址
      - '/apps/easy-mock/data/redis:/data'
    networks:
      - easy-mock
    restart: always

  web:
    image: easymock/easymock:1.6.0
    # easy-mock 官方给出的文件,这里是 npm start,这里修改为 npm run dev
    command: /bin/bash -c "npm run dev:server"
    ports:
      - 7300:7300  # 改为你自己期望的映射
    volumes:
      # 日志地址,根据需要修改为本地地址
      - '/apps/easy-mock/logs:/home/easy-mock/easy-mock/logs'
    networks:
      - easy-mock
    restart: always

networks:
  easy-mock:

启动 Easy Mock

在 docker-compose 文件目录下,运行如下命令:

$ docker-compose up -d

如果遇到 easymock docker 实例报文件权限错误

Error: EACCES: permission denied....

要在项目根目录执行以下命令

$ chmod 777 /yourfile/logs

然后就可以通过浏览器上的 你的域名.com:7300 访问到 easy-mock 了!

如果你觉得域名后面跟着端口号挺难看的,你可以通过配置 Nginx 的二级域名来访问你部署的 easy-mock,配置二级域名的方法参见 这篇文章

9. 可视化管理

关于可视化查询工具,这里就简单推介一个 LazyDocker,由于是在终端运行的,而且支持键盘操作和鼠标点击,就挺骚气的,有了这个一些查询语句可以少打几次了。

lzd

安装比较简单,运行下面的命令:

$ docker run --rm -it -v \
/var/run/docker.sock:/var/run/docker.sock \
-v ~/.config/lazydocker:/.config/jesseduffield/lazydocker \
lazyteam/lazydocker

可以设置一个终端的 alias

$ alias lzd='docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock -v ~/.config/lazydocker:/.config/jesseduffield/lazydocker lazyteam/lazydocker'

然后你在终端输入 lzd 就可以浏览你的镜像、容器、日志、配置、状态等等内容了。

10. 结语

由于在下目前使用 Docker 的主要场景是 MySQL、Nginx 之类工具的安装,所以本文所介绍的内容也大多属于这个场景。

篇幅原因 Docker 还有一些内容本文没有介绍,但上面的内容已基本满足日常的使用,其他 Docker 的内容可以关注一下在下的后续文章~


网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~

参考文档:

  1. Empowering App Development for Developers | Docker 官方网站
  2. Docker核心技术(基础篇)
  3. Docker安装mysql
  4. Docker文档
  5. Docker-compose文档
  6. 使用 docker 运行 easy-mock - 知乎
  7. docker-compose easy-mock - 简书
  8. 使用 docker 运行 easy-mock | CodingDiary
  9. easymock官方docker仓库:easy-mock/easy-mock-docker
  10. 使用docker安装nginx

作者其他高赞文章:

  1. JS 中可以提升幸福度的小技巧
  2. Vue 使用中的小技巧
  3. Nginx 从入门到实践,万字详解!
  4. 半小时搞会 CentOS 入门必备基础知识
  5. 手摸手 Webpack 多入口配置实践
  6. 前端路由跳转基本原理

PS:本人博客地址 Github - SHERlocked93/blog,也欢迎大家关注我的公众号【前端下午茶】,一起加油吧~

查看原文

赞 22 收藏 17 评论 2

SHERlocked93 收藏了文章 · 9月17日

程序员:写作能收获什么?

简介: 很多程序员已经通过自己的个人博客或者公众号来进行技术沉淀,记录自己的成长。越来越多的程序员们也开始意识到了写作的重要性。程序员为什么需要写作?写作能带来什么收获?又有哪些额外的惊喜?本文介绍三位长期坚持写作的程序员,分享他们在写作道路上的心得和收获,希望对同学们有所启发。

image.png
你有写作的习惯吗?很多程序员的回答是:我为什么要写作呢?很多人觉得写作是一件有难度的事情,其实写作的动机就藏在日常工作中,那些在酝酿中的奇思妙想,那些稍纵即逝的编程思路,那些金光闪闪的 debug 瞬间……都是写作的素材。

输出是最好的输入,养成写作的习惯,对技术提升和个人成长都有很大的帮助。改变世界的程序员,同样需要写作记录世界。如果你还没有开始写作,那你可能需要重新思考“写东西”这件事的意义。

今天,我们采访了 3 位坚持写作的程序员,看看写作给他们带来了什么。

  • 寒雁:阿里巴巴前端技术专家,连续 5 年更新博客
  • Hollis:阿里巴巴技术专家,20 万粉丝公众号号主
  • Frank: Wuhan2020 开源项目发起人,个人博主

我为什么要开始写作?

Hollis: 写作让我思考,与志同道合的朋友讨论技术

2015 年毕业后,我加入了阿里巴巴从事后台开发工作,也是这一年,我写了第一篇文章,内容是我参加阿里校招之后总结的“面经”。因为在找工作之前复习的阶段,我看了很多其他人的面试总结,给了我很大的帮助。写这篇文章一方面想要对自己的校招做一个总结,另一方面也希望帮助到其他人。

从这篇文章以后,我收到了很多评论,还有很多人私下找到我讨论技术,我发现写作给我带来了很多的乐趣。通过写作我可以进行自我思考、自我总结,也可以和志同道合的朋友们一起讨论技术,所以我开始坚持写作。

最开始写的内容都比较简单,只是记录一些工作中遇到的问题的总结,渐渐的我开始主动去学习一些东西,然后文章内容逐渐演变成原理分析、最佳实践等。

一开始文章只是发表在自己的博客中(hollischuang.com),后来一次偶然的机会,我发现公众号上面的读者可以有更多的互动,于是就把自己的文章同步到公众号(Hollis)中,现在公众号已经积累了将近 20 万的读者。

去年还把自己写过的一些内容整理出来,和朋友一起出本了一本书《程序员的三门课》,在书中写了很多自己的经验和思考。

寒雁:写作是我的工作日志,可以帮助产品带来用户

作为程序员,我们每天都会遇到各种各样的技术问题,而我在遇到棘手一点的问题时,并不会急着去解决问题,而是会把问题记录清楚,包括代码、报错日志、截图,甚至解决问题的过程和一些参考链接。这些内容再加上一些原理层面的知识点,一篇记录问题的工作日志其实也就是一篇博客。所以,我刚开始写的博客,也就是这种类似于工作日志的内容,还是挺简单的。

后来,研究生毕业后,我选择了和朋友一起创业。前期不太懂运营,用户增长不知道怎么做。后来发现自己写博客还是挺擅长的,能给产品带来不少用户,于是就养成了写作的习惯。

翻译过不少博客,也原创了不少,写过一些挺受欢迎的博客,也写过一些很幼稚、类似于标题党的内容。不过整理来看,写作水平一直有在提高。来阿里之后,我写了一篇《写作的意义》,也在团队做了一次关于写作的分享《关于写作的那些事:寒雁聊聊 10 万+背后的思考》。我是真的挺喜欢写作的,也觉得写作收获蛮大的。

Frank: 我用写作记录开发“黑科技”,分享我的想法

时间回退到四年前,毕业后成为了一名游戏开发工程师,进入了一个全新的领域,每天都在接触新的东西,而且游戏开发中有大量互联网常规开发中难以见到的“黑科技”,令人目不暇接。从那时起,其实就一直有写作的习惯,由于很多技术细节并不适合对外,所以当时是使用很多笔记类软件进行记录的,例如印象笔记。

后来开始在开源圈中进行一些开发工作,依然保持着印象笔记来记录自己工作内容的习惯,但由于开源的开放性,很多时候也非常希望可以把这些想法和内容分享出来,于是博客就成了一个更好的选择,也是为什么现在选用博客 (blog.frankzhao.cn) 来做写作记录的原因。

程序员写作有什么好处?

寒雁:写作是对自己的长期投资,也是最佳个人品牌

写作是一件具有长期价值的事情,这一点类似于健身与读书。我想大多数人都认同,不管工作再忙,也应该坚持健身,坚持读书,因为这是对自己的长期投资,不少人也是这么做的。在我看来,写作其实也一样,只是很少有人会意识到这一点,更少人可以做到这一点。

提升工作效率

写作最重要的职业技能。我挺喜欢写工作日志的,从另一个角度理解,我每天的工作并不只是在写代码,而是在写工作日志,比如技术问题、技术方案、沟通备忘录、会议纪要等一切与工作相关的内容我都会记下来。在与同事沟通或者寻求帮助之前,我都会写一个完整的文档,这样沟通会高效很多。

写作即是学习

写作是最有效的学习方式。这里原理是费曼学习法,通过输出倒逼输入。因为我们在写作过程中会发现自己的一些知识盲点以及思维盲区,如果可以静下心研究清楚,然后用最通俗易懂的语言表达出来,这其实是很好的学习和提升自己的机会。写作其实挺锻炼思考能力的,因为表达一个观点相对简单,如何将观点论述地清晰、完整、深刻,结构化地表达出来,取决于我们是否真的想清楚了。

创建个人品牌

写作是最佳的个人品牌。互联网已经 30 岁了,但是它的游戏规则其实没变:通过流量变现。文章写得好,有读者就有流量,有流量就可以变现。自媒体时代很多“草根”作者崛起,也是这个道理。现在是视频时代,表达内容的媒介变了,但是本质没变,因为视频内容的含金量取决于文案。作为程序员,没有必要去靠写作赚钱,但是通过写作打造个人品牌还是挺重要的,这对于求职、招聘、同行交流以及未来创业都很有帮助。

Frank:写作让你换一个角度发现问题的全貌

就我本人而言,工作的前几年都以记录技术为主,但后来,尤其是近一年读博的期间,可能更多的写作发生的社科类学科上。坚持写作有诸多的好处:

记录技术成长

写作可以让自己更好的记录技术成长的历程,时常回顾会有更多的成长。虽然我本人现在已经不再做游戏相关的开发工作,但我很庆幸自己当初有大量的笔记可以让我回顾一些技术细节和设计理念,这些理念事实上在很多场景下都是通用的,可以很好的指导之后其他领域的开发工作。

换一个角度发现问题的全貌

很多时候你以为你明白了一个技术要点,但当你用文字去表达的时候你会发现有很多的盲点你可能都忽略了。例如你解决的是工作中的一个具体问题,当你解决了这个问题时,你以为你明白了。但当你用文字记录时,尤其是你把自己放在一个读者的角度去阅读时,才会发现你忽略的东西,例如具体的环境、版本,出现问题的情景、依赖等,当用文字去记录时,就会刻意补足这些内容,而这些才构成了解决这个问题的全景。而且就我个人感觉,记录过程中的成长可能要比单纯解决问题中的成长大得多。

提升写作能力和逻辑编排能力

写作能力绝不仅仅是一个文字工作者需要,尤其在这个愈加开放的时代,写作是通过的基础。练习写作能力,不仅可以帮助你更好的与他人沟通,而且也是一种梳理逻辑的过程。好的技术文章同样需要有优秀的逻辑编排,由浅入深,层层递进。而且相信我,这是任何工作,也包括一般的程序员工作中非常重要的一种能力。

分享让你的文章“升值”

如果你写的文章与他人分享,则这个文章的“价格”会比个人获得的更多,帮到其他的人的感觉会更好。在研究生之前,我曾有机会修改 JavaMail 的源码,使其支持需要基本认证的 HTTP 代理服务器进行邮件操作,而当时的 JavaMail 还仅支持 Socks 代理服务器。直到现在,我还是会偶尔收到有人邮件询问我实现细节,我能感受到我在真正的帮助他人。但可惜当时不了解开源,否则应该可以帮助到更多的人。

Hollis:写作提升技术能力,可以帮助更多人

技术提升

写文章的过程中,自己会想办法保证写出来的内容都是正确的,所以就会查阅很多资料,这个过程中,自己就会学习到很多知识,可以很好的提升自己的技术能力。尤其是写系列文章的时候,可以很好的完善自己的知识体系。正所谓“教是最好的学”。

不断纠错

没有人写出来的东西都是完全对的,所以有的时候写完的文章会收到一些不同的观点,这时候就可以帮助自己纠错,不断的提升自己。

帮助自己更好地记忆

很多人都会发现有一种现象,就是一个知识自己学过之后过段时间就忘了。有了博客之后就可以解决这个问题,可以把知识以自己的理解写到博客中,一方面可以加强自己的理解与记忆,另外也方便日后回头翻看与复习。

提升个人影响力

因为自己写作,可以大大提升自己在行业内的影响力,因为自己写了很多文章,有很多程序员都看过我的文章,我曾经大致统计过,我的技术文章,在全网的阅读量有数千万。最近几年,经常有公司的同事过来问我:你是不是Hollis?原来你就是Hollis?我看过你的文章等等。

帮助他人成就自己

在自己刚刚接触 Java 不久的时候,一直想找到一份学习路径,但是一直都没有找到,于是自己就利用业务时间总结了一份 Java 工程师的学习路径——《Java工程师成神之路》。这篇文章现在上百万人读过,我也接到很多留言,都说对他们帮助很大。最近两年,经常有读者在我的公众号和博客后台留言,说自己因为看了我的文章找到了某大厂的工作等等的好消息。看到自己的一点点努力,可以帮助到很多人,开始很有成就感的。

写作给你带来了什么额外惊喜?

寒雁:更好的职业发展以及对世界的认知

我之所以来阿里,也是因为我的文章,因为是主管看了我的博客,了解了我做的产品,然后邀请我来面试的。其实我自己去招人也是这样,如果你的博客写得足够好,我也会特别留意。

写作让我的阅读能力也明显提高了,在信息爆炸的时代,如何甄别真正值得阅读的内容还挺重要的,我可以在极短时间内判断一篇文章的层次,然后决定是否认真阅读。另外,因为我自己熟悉写作的套路,因此知道哪些话是真正有价值的,哪些话只是作者的话术,哪些要点是作者漏掉了。

Hollis:交友、招聘以及出书

因为写作,我认识了很多志同道合的朋友,他们很多人都是做程序员的,同时也是专业书籍的作者、知名博客的博主等。还有很多读者来自于各个互联网公司,有着不同的背景,有些都是工作经验比较丰富的大牛,和他们交流的过程中,自己也能学到很多东西。

因为我有自己有博客和公众号,又积累了很多读者,每次发布招聘信息都能收到很多简历,最近帮助团队招聘到了几个新的同事。因为我在一些招聘文章上面的昵称也是 Hollis,所以我遇到过几次,我在招聘网站上面“勾搭”候选人,都被人问:你是不是有个公众号?

因为自己写博客有很多读者,所以经常有出版社的编辑找到我询问出版书籍的事情。之前也有朋友找到我想要一起出书的等。2019 年和朋友共同出版了一本《程序员的三门课》。在那之后,自己还出了多本免费的电子书:《Java 开发手册灵魂 13 问》、《Java 开发手册灵魂 15 问》、《Java工程师成神之路——基础篇》等。

Frank:走上开源研究道路

我个人可能是程序员中不太常见的比较喜欢社科类记录的同学,要说惊喜,不如说是潜移默化的改变。

通过写作记录,逐渐加深对于社会科学的理解,对于人类写作历史的理解,是我最终走上开源研究道路很重要的原因。回头来看,每一个微小的习惯在长年累月中都会重新塑造自己,使你的人生走向一个完全不同的方向。而写作,或者说写作背后所代表的一种终身学习的态度,会给所有人都带来无限的可能。

每一个程序员都应该尝试技术写作,一旦开始,你就会发现自己成长的速度在增快,你看待文字背后的世界会变得不同。每一个程序员也不应该仅仅局限于技术写作,对于世界、历史、人文的理解同样需要记录和分享,希望写作让你面向屏幕时,不止面向代码,更是面向星辰大海。

原文链接
本文为阿里云原创内容,未经允许不得转载。

查看原文

SHERlocked93 赞了文章 · 9月17日

程序员:写作能收获什么?

简介: 很多程序员已经通过自己的个人博客或者公众号来进行技术沉淀,记录自己的成长。越来越多的程序员们也开始意识到了写作的重要性。程序员为什么需要写作?写作能带来什么收获?又有哪些额外的惊喜?本文介绍三位长期坚持写作的程序员,分享他们在写作道路上的心得和收获,希望对同学们有所启发。

image.png
你有写作的习惯吗?很多程序员的回答是:我为什么要写作呢?很多人觉得写作是一件有难度的事情,其实写作的动机就藏在日常工作中,那些在酝酿中的奇思妙想,那些稍纵即逝的编程思路,那些金光闪闪的 debug 瞬间……都是写作的素材。

输出是最好的输入,养成写作的习惯,对技术提升和个人成长都有很大的帮助。改变世界的程序员,同样需要写作记录世界。如果你还没有开始写作,那你可能需要重新思考“写东西”这件事的意义。

今天,我们采访了 3 位坚持写作的程序员,看看写作给他们带来了什么。

  • 寒雁:阿里巴巴前端技术专家,连续 5 年更新博客
  • Hollis:阿里巴巴技术专家,20 万粉丝公众号号主
  • Frank: Wuhan2020 开源项目发起人,个人博主

我为什么要开始写作?

Hollis: 写作让我思考,与志同道合的朋友讨论技术

2015 年毕业后,我加入了阿里巴巴从事后台开发工作,也是这一年,我写了第一篇文章,内容是我参加阿里校招之后总结的“面经”。因为在找工作之前复习的阶段,我看了很多其他人的面试总结,给了我很大的帮助。写这篇文章一方面想要对自己的校招做一个总结,另一方面也希望帮助到其他人。

从这篇文章以后,我收到了很多评论,还有很多人私下找到我讨论技术,我发现写作给我带来了很多的乐趣。通过写作我可以进行自我思考、自我总结,也可以和志同道合的朋友们一起讨论技术,所以我开始坚持写作。

最开始写的内容都比较简单,只是记录一些工作中遇到的问题的总结,渐渐的我开始主动去学习一些东西,然后文章内容逐渐演变成原理分析、最佳实践等。

一开始文章只是发表在自己的博客中(hollischuang.com),后来一次偶然的机会,我发现公众号上面的读者可以有更多的互动,于是就把自己的文章同步到公众号(Hollis)中,现在公众号已经积累了将近 20 万的读者。

去年还把自己写过的一些内容整理出来,和朋友一起出本了一本书《程序员的三门课》,在书中写了很多自己的经验和思考。

寒雁:写作是我的工作日志,可以帮助产品带来用户

作为程序员,我们每天都会遇到各种各样的技术问题,而我在遇到棘手一点的问题时,并不会急着去解决问题,而是会把问题记录清楚,包括代码、报错日志、截图,甚至解决问题的过程和一些参考链接。这些内容再加上一些原理层面的知识点,一篇记录问题的工作日志其实也就是一篇博客。所以,我刚开始写的博客,也就是这种类似于工作日志的内容,还是挺简单的。

后来,研究生毕业后,我选择了和朋友一起创业。前期不太懂运营,用户增长不知道怎么做。后来发现自己写博客还是挺擅长的,能给产品带来不少用户,于是就养成了写作的习惯。

翻译过不少博客,也原创了不少,写过一些挺受欢迎的博客,也写过一些很幼稚、类似于标题党的内容。不过整理来看,写作水平一直有在提高。来阿里之后,我写了一篇《写作的意义》,也在团队做了一次关于写作的分享《关于写作的那些事:寒雁聊聊 10 万+背后的思考》。我是真的挺喜欢写作的,也觉得写作收获蛮大的。

Frank: 我用写作记录开发“黑科技”,分享我的想法

时间回退到四年前,毕业后成为了一名游戏开发工程师,进入了一个全新的领域,每天都在接触新的东西,而且游戏开发中有大量互联网常规开发中难以见到的“黑科技”,令人目不暇接。从那时起,其实就一直有写作的习惯,由于很多技术细节并不适合对外,所以当时是使用很多笔记类软件进行记录的,例如印象笔记。

后来开始在开源圈中进行一些开发工作,依然保持着印象笔记来记录自己工作内容的习惯,但由于开源的开放性,很多时候也非常希望可以把这些想法和内容分享出来,于是博客就成了一个更好的选择,也是为什么现在选用博客 (blog.frankzhao.cn) 来做写作记录的原因。

程序员写作有什么好处?

寒雁:写作是对自己的长期投资,也是最佳个人品牌

写作是一件具有长期价值的事情,这一点类似于健身与读书。我想大多数人都认同,不管工作再忙,也应该坚持健身,坚持读书,因为这是对自己的长期投资,不少人也是这么做的。在我看来,写作其实也一样,只是很少有人会意识到这一点,更少人可以做到这一点。

提升工作效率

写作最重要的职业技能。我挺喜欢写工作日志的,从另一个角度理解,我每天的工作并不只是在写代码,而是在写工作日志,比如技术问题、技术方案、沟通备忘录、会议纪要等一切与工作相关的内容我都会记下来。在与同事沟通或者寻求帮助之前,我都会写一个完整的文档,这样沟通会高效很多。

写作即是学习

写作是最有效的学习方式。这里原理是费曼学习法,通过输出倒逼输入。因为我们在写作过程中会发现自己的一些知识盲点以及思维盲区,如果可以静下心研究清楚,然后用最通俗易懂的语言表达出来,这其实是很好的学习和提升自己的机会。写作其实挺锻炼思考能力的,因为表达一个观点相对简单,如何将观点论述地清晰、完整、深刻,结构化地表达出来,取决于我们是否真的想清楚了。

创建个人品牌

写作是最佳的个人品牌。互联网已经 30 岁了,但是它的游戏规则其实没变:通过流量变现。文章写得好,有读者就有流量,有流量就可以变现。自媒体时代很多“草根”作者崛起,也是这个道理。现在是视频时代,表达内容的媒介变了,但是本质没变,因为视频内容的含金量取决于文案。作为程序员,没有必要去靠写作赚钱,但是通过写作打造个人品牌还是挺重要的,这对于求职、招聘、同行交流以及未来创业都很有帮助。

Frank:写作让你换一个角度发现问题的全貌

就我本人而言,工作的前几年都以记录技术为主,但后来,尤其是近一年读博的期间,可能更多的写作发生的社科类学科上。坚持写作有诸多的好处:

记录技术成长

写作可以让自己更好的记录技术成长的历程,时常回顾会有更多的成长。虽然我本人现在已经不再做游戏相关的开发工作,但我很庆幸自己当初有大量的笔记可以让我回顾一些技术细节和设计理念,这些理念事实上在很多场景下都是通用的,可以很好的指导之后其他领域的开发工作。

换一个角度发现问题的全貌

很多时候你以为你明白了一个技术要点,但当你用文字去表达的时候你会发现有很多的盲点你可能都忽略了。例如你解决的是工作中的一个具体问题,当你解决了这个问题时,你以为你明白了。但当你用文字记录时,尤其是你把自己放在一个读者的角度去阅读时,才会发现你忽略的东西,例如具体的环境、版本,出现问题的情景、依赖等,当用文字去记录时,就会刻意补足这些内容,而这些才构成了解决这个问题的全景。而且就我个人感觉,记录过程中的成长可能要比单纯解决问题中的成长大得多。

提升写作能力和逻辑编排能力

写作能力绝不仅仅是一个文字工作者需要,尤其在这个愈加开放的时代,写作是通过的基础。练习写作能力,不仅可以帮助你更好的与他人沟通,而且也是一种梳理逻辑的过程。好的技术文章同样需要有优秀的逻辑编排,由浅入深,层层递进。而且相信我,这是任何工作,也包括一般的程序员工作中非常重要的一种能力。

分享让你的文章“升值”

如果你写的文章与他人分享,则这个文章的“价格”会比个人获得的更多,帮到其他的人的感觉会更好。在研究生之前,我曾有机会修改 JavaMail 的源码,使其支持需要基本认证的 HTTP 代理服务器进行邮件操作,而当时的 JavaMail 还仅支持 Socks 代理服务器。直到现在,我还是会偶尔收到有人邮件询问我实现细节,我能感受到我在真正的帮助他人。但可惜当时不了解开源,否则应该可以帮助到更多的人。

Hollis:写作提升技术能力,可以帮助更多人

技术提升

写文章的过程中,自己会想办法保证写出来的内容都是正确的,所以就会查阅很多资料,这个过程中,自己就会学习到很多知识,可以很好的提升自己的技术能力。尤其是写系列文章的时候,可以很好的完善自己的知识体系。正所谓“教是最好的学”。

不断纠错

没有人写出来的东西都是完全对的,所以有的时候写完的文章会收到一些不同的观点,这时候就可以帮助自己纠错,不断的提升自己。

帮助自己更好地记忆

很多人都会发现有一种现象,就是一个知识自己学过之后过段时间就忘了。有了博客之后就可以解决这个问题,可以把知识以自己的理解写到博客中,一方面可以加强自己的理解与记忆,另外也方便日后回头翻看与复习。

提升个人影响力

因为自己写作,可以大大提升自己在行业内的影响力,因为自己写了很多文章,有很多程序员都看过我的文章,我曾经大致统计过,我的技术文章,在全网的阅读量有数千万。最近几年,经常有公司的同事过来问我:你是不是Hollis?原来你就是Hollis?我看过你的文章等等。

帮助他人成就自己

在自己刚刚接触 Java 不久的时候,一直想找到一份学习路径,但是一直都没有找到,于是自己就利用业务时间总结了一份 Java 工程师的学习路径——《Java工程师成神之路》。这篇文章现在上百万人读过,我也接到很多留言,都说对他们帮助很大。最近两年,经常有读者在我的公众号和博客后台留言,说自己因为看了我的文章找到了某大厂的工作等等的好消息。看到自己的一点点努力,可以帮助到很多人,开始很有成就感的。

写作给你带来了什么额外惊喜?

寒雁:更好的职业发展以及对世界的认知

我之所以来阿里,也是因为我的文章,因为是主管看了我的博客,了解了我做的产品,然后邀请我来面试的。其实我自己去招人也是这样,如果你的博客写得足够好,我也会特别留意。

写作让我的阅读能力也明显提高了,在信息爆炸的时代,如何甄别真正值得阅读的内容还挺重要的,我可以在极短时间内判断一篇文章的层次,然后决定是否认真阅读。另外,因为我自己熟悉写作的套路,因此知道哪些话是真正有价值的,哪些话只是作者的话术,哪些要点是作者漏掉了。

Hollis:交友、招聘以及出书

因为写作,我认识了很多志同道合的朋友,他们很多人都是做程序员的,同时也是专业书籍的作者、知名博客的博主等。还有很多读者来自于各个互联网公司,有着不同的背景,有些都是工作经验比较丰富的大牛,和他们交流的过程中,自己也能学到很多东西。

因为我有自己有博客和公众号,又积累了很多读者,每次发布招聘信息都能收到很多简历,最近帮助团队招聘到了几个新的同事。因为我在一些招聘文章上面的昵称也是 Hollis,所以我遇到过几次,我在招聘网站上面“勾搭”候选人,都被人问:你是不是有个公众号?

因为自己写博客有很多读者,所以经常有出版社的编辑找到我询问出版书籍的事情。之前也有朋友找到我想要一起出书的等。2019 年和朋友共同出版了一本《程序员的三门课》。在那之后,自己还出了多本免费的电子书:《Java 开发手册灵魂 13 问》、《Java 开发手册灵魂 15 问》、《Java工程师成神之路——基础篇》等。

Frank:走上开源研究道路

我个人可能是程序员中不太常见的比较喜欢社科类记录的同学,要说惊喜,不如说是潜移默化的改变。

通过写作记录,逐渐加深对于社会科学的理解,对于人类写作历史的理解,是我最终走上开源研究道路很重要的原因。回头来看,每一个微小的习惯在长年累月中都会重新塑造自己,使你的人生走向一个完全不同的方向。而写作,或者说写作背后所代表的一种终身学习的态度,会给所有人都带来无限的可能。

每一个程序员都应该尝试技术写作,一旦开始,你就会发现自己成长的速度在增快,你看待文字背后的世界会变得不同。每一个程序员也不应该仅仅局限于技术写作,对于世界、历史、人文的理解同样需要记录和分享,希望写作让你面向屏幕时,不止面向代码,更是面向星辰大海。

原文链接
本文为阿里云原创内容,未经允许不得转载。

查看原文

赞 6 收藏 3 评论 1

SHERlocked93 关注了专栏 · 8月25日

SegmentFault 行业快讯

第一时间为开发者提供行业相关的实时热点资讯

关注 23857

SHERlocked93 发布了文章 · 8月25日

强烈推介的几个微信小程序开发小技巧,简单又实用

Eu_62tl0bcU

前段时间在下开发了个微信小程序,开发过程中总结了一些我觉得对我有用的小技巧,提炼出来,相当于一个总结复盘,也希望可以帮助到大家。如果对大家确实有帮助,别忘了点赞哦 🌟 ~

  1. 微信开发者工具版本:1.03.2006090(2020-06-19)
  2. 基础库版本: v2.12.1 (2020-08-04)

1. 开发中可能遇到的坑以及 Tips

本来想写个小技巧的,结果我总结了一堆坑,没上手之前完全想象不到微信小程序的开发体验是如此之差、如此之烂,从微信开发者工具到所谓的「全新语言」,都有一种浓浓的半成品的 five 即视感,实在让我 emmm.... 另外我发现网上的小程序文章大部分都是如何使用和如何避坑的实用文,而不是技巧文,这也从侧面反映了小程序的坑多。

在微信小程序原生开发过程中,我不断发出这样的疑问「为什么堂堂技术人才多如牛毛的腾讯,会推出如此 laji」,很多弱智反人类的地方,在两三年前社区就已经提出来,官方回复已经反馈正在修复中,但几年过去了,还是没有音信,官方回复仍然是一句冷冰冰的「已反馈」 😤

  1. 微信开发者工具经常热更新不起作用甚至白屏,重新编译也不行,只能强行退出后再次打开;
  2. 跟上一条类似,有时候一点样式出错,预览整个都白屏,调试器里也不说哪里的问题,直接就给你弃疗不显示,重新编译也无法解决问题,只能强行退出后再次打开;
  3. 跟上一条类似,调试器里报的错经常没什么用,驴头不对马嘴,让人很难定位问题;
  4. Android 端自定义 Tabbar 在下拉刷新的时候,也会跟着屏幕一起往下移,而且是无法绕过的 Bug,自定义 Tabbar 样式都写好了的我又改成自带的 Tabbar 了!
  5. import 的路径不支持绝对路径,比如你希望引用 utils/fetch.js,在不管多深的组件里面你都要慢慢 ../ 点到根目录,同样 .wxss 文件 @import 导入文件时也只能使用相对路径,所以就会出现 ../../../../../../utils/fetch.js 这种东西;
  6. 静态资源路径不能有汉字,有汉字就无法加载;
  7. .wxs 文件不支持 ES6,只能使用蹩脚的 ES5 写法;
  8. .wxml 中只能引入 .wxs 文件不能引入 .js 文件???
  9. 模板 {{}} 中连方法都不能执行,只能处理简单的运算如 + - * /,如果遇到数据需要 filter 的场景,需要在 .js 文件中预先格式化好再一个个 setData,比如经常写的 [2,3,4].includes(type),居然都跑不起来!
  10. .wxs 文件中无法使用 Date 对象,所以不能 new Date(),只能使用蹩脚的 getDate 方法,正则也是一样,生成正则对象需要使用 getRegExp 函数 getRegExp(pattern[, flags])
  11. .wxs 中可以调用其它 .wxs 文件,并且只能 require 调用 .wxs 文件,引入的文件必须使用相对路径;
  12. setData 连一个对象合并都懒得做,如果 data: {a: {b: 1, c: 1}},那么 setData({a: {b: 2}}) 就会丢失 a.c 的值,真是让人火冒三丈啊,还要 setData({['a.b': 2]}) 这样才行;
  13. IOS 上 Date 对象获取任意时间参数比如 getDaygetTime 都为 NaN,是因为 IOS 的 Date 构造函数不支持 2018-04-26 这种格式的日期,必须转换为 2018/04/26 这种格式才会显示正常;
  14. 开发版小程序有时候请求莫名其妙发不出去,右上角三个点 enable debug 打开「开发调试」之后就莫名其妙能发出去请求了,在多部手机上都是这样,不明真相。

2. 微信请求 Promise 化

2.1 使用现成的库

安装 Promise 库 wx-promise-pro,记得一定要带 -s--production,要不然无法构建成功。

npm i -S wx-promise-pro

然后在 app.js 中:

import { promisifyAll } from 'wx-promise-pro'

promisifyAll()  // promisify all wx api

App({ ... })

之后就可以正常使用了:

wx.pro.showLoading({
    title: '加载中',
    mask: true
})
  .then(() => console.log('in promise ~'))

2.2 自己实现

其实我们可以自己来实现一个这样的库,原理很简单,以原生 API 的 wx.request 为例:

// 原生 API 使用方式
wx.request({
    url: '',     // 请求的 url
    data: {},    // 参数
    method: '',  // post、get
    success: res => {
        // 请求成功回调函数,res为回调参数
    },
    fail: res => {
        // 请求失败回调函数,res为回调参数
    }
})

如果我们将其 Promise 化,应该的调用方式希望是:

// Promise 化后的期望使用方式
wx.pro.request({
    url: '',     // 请求的 url
    data: {},    // 参数
    method: ''   // post、get
})
  .then(res => {
      // 请求成功回调函数,res为回调参数
  })
  .catch(res => {
      // 请求失败回调函数,res为回调参数
  })

并且 then 函数返回的是一个 Promise 对象,让这个函数可以不断链式调用下去,所以首先需要 new 出来一个 Promise 对象:

function request(opt) {
    return new Promise((resolve, reject) => {
        wx.request({
            ...opt,
            success: res => { resolve(res)},
            fail: res => {reject(res)}
        })
    })
}

这里代码我们可以进一步改进,由于 successfail 这里传入的参数只是由 resolvereject 方法执行了下,所以可以直接传入 resolvereject 方法即可。

另外,由于其他小程序原生 API 格式一致,所以我们可以使用柯里化方法,来将其他需要进行 Promise 化的 API 进行处理:

function promisify(api) {
    return (opt = {}) => {
        return new Promise((resolve, reject) => {
            api({
                ...opt,
                fail: reject,
                success: resolve
            })
        })
    }
}

然后,将柯里化方法执行的结果作为新的 Promise 化的 API 挂载到 wx.pro 对象上:

// 将指定 API 进行 Promise 化
wx.pro.request = promisify(wx.request)

// 使用
wx.pro.request({...})
    .then(...)

然后为了方便我们使用其他方法,可以循环将 wx 对象上可以被 Promise 化的方法比如 requestscanCodeshowToastgetUserInfo 等一一挂载到 wx.pro 对象上,使用时可以直接 wx.pro.xx,由于这个方法执行返回的是一个 Promise 对象,因此可以像其它 Promise 化的对象那样使用。

事实上,不知不觉,我们就自己实现了 wx-promise-pro 的源码,这个库的核心代码也就是上面那这几行 🥳

2.3 在项目中使用

有了上面的工具后,我们可以将其使用在项目中,为了不在项目中遍布 wx.requestwx.pro.request 这里可以简单进行封装,新建两个文件如下:

// utils/api/fetch.js 封装请求方法、请求拦截器

const app = getApp()

const BaseUrl = 'http://172.0.0.1:7300/mock'

const TokenWhiteList = [
    '/app/user/get-by-code'     // 不需要鉴权的api手动添加到这里
]

/**
 * 设置请求拦截器
 * @param params 请求参数
 */
const fetch = (params = {}) => {
    // 拦截器逻辑
    if (!TokenWhiteList.includes(params.url)) {
        params.header = {
            'content-type': 'application/json',             // 默认值
            'token': app.globalData.token || ''
        }
    }

    if (params.url.startsWith('/')) {    // 拼接完整URL
        params.url = BaseUrl + params.url
    }

    // 返回promise
    return wx.pro.request({ ...params })
      .then(({ data: { code, message, data } }) => {
          // ... 各种异常情况的逻辑处理
          // 与后端约定 code 20000 时正常返回
          if (code === 20000) return Promise.resolve(data)
          return Promise.reject(message)
      })
}

export { fetch }

然后再将所有 API 封装到单独的文件中集中管理:

// utils/api/apis.js 封装所有请求 API

import { fetch } from './fetch'

/* 根据微信code获取用户信息 */
const appUserGetByCode = ({ code } = {}) => fetch({
    url: '/app/user/get-by-code',
    data: { code }
})

/* 扫码登录 */
const appUserQrLogin = ({ qrCode } = {}) => fetch({
    method: 'POST',
    url: '/app/user/qr-login',
    data: { qrCode }
})

/* 个人信息 */
const appUserInfo = () => fetch({
    url: '/app/user/info'
})

/* 系统参数获取,数据字典 */
const appSysParamListByParam = () => fetch({
    url: '/app/sys-param/list-by-param'
})

/* 数据字典所有 */
const appSysParamListAll = () => fetch({
    url: '/app/sys-param/list-all'
})

export {
    appSysParamListAll,   // 数据字典所有
    appSysParamListByParam,   // 系统参数获取,数据字典
    appUserGetByCode,   // 根据微信code获取用户信息
    appUserQrLogin,   // 扫码登录
    appUserInfo   // 个人信息
}

在要使用 API 的地方就可以这样引入:

import * as Api from '../../utils/api/apis.js'   // 相对路径

// 使用方式
Api.appSysParamListAll()
  .then(({ dataList }) => this.upData({ sysParamList: dataList }))
  .then(() => {
      const keyList = this.data.sysParamList.map(T => T.key)
      this.upData({
          keyList,
          formData: { keys: keyList }
      })
  })

使用方式就很舒服,这里使用到了 upData,就是下面我要介绍的内容,是在下非常推介的小程序工具~ 🥳

3. setState 修改 data 中想修改对象的属性

在小程序中,data 是不能直接操作的,需要使用 setData 函数。鉴于微信小程序开发时 setData 的使用体验十分蹩脚,我使用了个库函数 wx-updata,这个库函数在开发的时候对我很有帮助,这里特意推介给大家。

3.1 为什么要使用 wx-updata

你在使用 setData 的时候,是不是有时候觉得很难受,举个简单的例子:

// 你的 data
data: {
    name: '蜡笔小新',
    info: { height: 140, color: '黄色' }
}

如果要修改 info.height 为 155,使用 setData 要怎么做呢:

// 这样会把 info 里其他属性整不见了
this.setData({ info: { height: 155 } })

// 你需要取出 info 对象,修改后整个 setData
const { info } = this.data
info.height = 155
this.setData({ info })

似乎并不太复杂,但如果 data 是个很大的对象,要把比较深且不同的对象、数组项挨个改变:

data: {
    name: '蜡笔小新',
    info: {
        height: 140, color: '黄色',
        desc: [{ age: 8 }, '最喜欢大象之歌', '靓仔', { dog: '小白', color: '白色' }]
    }
}

比如某个需求,需要把 info.height 改为 155,同时改变 info.desc 数组的第 0 项的 age 为 12,第 3 项的 color 为灰色呢?

// 先取出要改变的对象,改变数字后 setData 回去
const { info } = this.data
info.height = 155
info.desc[0].age = 12
info.desc[3].color = '灰色'
this.setData({ info })

// 或者像某些文章里介绍的,这样可读性差,也不太实用
this.setData({
    'info.height': 155,
    'info.desc[0].age': 12,
    'info.desc[3].color': '灰色'
})

上面这两种方法,是我们平常小程序里经常用的,和其他 Web 端的框架相比,就很蹩脚,一种浓浓的半成品感扑面而来,有没有这样一个方法:

this.upData({
    info: {
        height: 155,
        desc: [{ age: 12 }, , , { color: '灰色' }]
    }
})

这个方法会帮我们深度改变嵌套对象里对应的属性值,跳过数组项里不想改变的,只设置我们提供了的属性值、数组项,岂不是省略了一大堆蹩脚的代码,而且可读性也极佳呢。

这就是为什么我在上线的项目中使用 wx-updata,而不是 setData

wx-updata 的原理其实很简单,举个例子:

this.upData({
    info: {
        height: 155,
        desc: [{ age: 12 }]
    }
})

// 会被自动转化为下面这种格式,
// this.setData({
//    'info.height': 155,
//    'info.desc[0].age': 12,
// })

原来这个转化工作是要我们自己手动来做,现在 wx-updata 帮我们做了,岂不美哉!

3.2 wx-updata 使用方式

在一般情况下,我们可以将方法直接挂载到 Page 构造函数上,这样就可以在 Page 实例中像使用 setData 一样使用 upData 了:

// app.js 中挂载
import { updataInit } from './miniprogram_npm/wx-updata/index'  // 你的库文件路径

App({
    onLaunch() {
        Page = updataInit(Page, { debug: true })
    }
})

// 页面代码中使用方式
this.upData({
    info: { height: 155 },
    desc: [{ age: 13 }, '帅哥'],
    family: [, , [, , , { color: '灰色' }]]
})

有的框架可能在 Page 对象上进行了进一步修改,直接替换 Page 的方式可能就不太好了,wx-updata 同样暴露了工具方法,用户可以在页面代码中直接使用工具方法进行处理:

// 页面代码中
import { objToPath } from './miniprogram_npm/wx-updata/index'  // 你的库文件路径

Page({
    data: { a: { b: 2}, c: [3,4,5]},

    // 自己封装一下
    upData(data) {
        return this.setData(objToPath(data))
    },

    // 你的方法中或生命周期函数
    yourMethod() {
        this.upData({ a: { b: 7}, c: [8,,9]})
    }
})

针对修改数组指定项的时候,可能存在的跳过数组空位的情况,wx-updata 提供了 Empty 的 Symbol 类型替位符,还有数组的对象路径方式,感兴趣可以看看 wx-updata 的文档,也可以参考 <开发微信小程序,我为什么放弃 setData,使用 upData> 这篇介绍文章。

另外,使用了 wx-updata 也还可以使用原来的 setData,特别是有时候要清空数组时,灵活使用,可以获得更好的小程序开发体验,祝大家小程序开发愉快 🤣

4. 使用 scss 写样式

4.1 Webstorm 配置方法

关于蹩脚的 .wxss 样式,我使用 webstorm 的 file watcher 工具把 scss 文件监听改动并实时编译成 .wxss 文件,感觉比较好用,这里给大家分享一下我的配置:

然后记得在 .gitignore 文件中加入要忽略的样式:

*.scss
*.wxss.map

这样在上传到 git 的时候,就不会上传 scss 文件了~ 当然如果你的团队成员需要 scss 的话,还是建议 git 上传的时候也加上 scss 文件。

这样设置之后,一个组件在本地的会是下面这样

本地文件

其中我们需要关注的就是 .js.json.scss.wxml 文件,另外的文件 .wxss 会在你改动 .scss 文件之后自动生成并更新,而 .wxss.map 是插件自动生成的映射关系,不用管。

如果不是使用 webstorm,可以直接执行命令 sass --watch index.scss:index.wxss -s expanded,命令行如果关闭,sass 命令就不会监听文件的变动然后编译,所以最好用编辑器的插件。

同理,也可以使用 less、stylus 等预编译语言。

4.2 Visual Studio Code 配置方法

万能的 VSC 当然也可以做到这个功能,搜索并下载插件 easy sass,然后在 setting.json 中修改/增加配置:

"easysass.formats": [
  {
    "format": "expanded",
    "extension": ".wxss"
  },
  {
    "format": "compressed",
    "extension": ".min.wxss"
  }
]

上面 expanded 是编译生成的 .wxss 文件,下面 compressed 是压缩之后的 .wxss 样式文件,下面这个用不到可以把下面这个配置去掉,然后在 .gitignore 文件中加入要忽略的中间样式:

*.scss

当然也可以不添加,如果你的同事也是实用 scss 来开发小程序的话,其他跟上面一样,至此你就可以在小程序开发中快乐使用 scss 了~

5. 使用 iconfont 图标字体

在 Web 开发中 iconfont 可谓是最常用的灵活图标字体工具了,这里介绍一下如何在微信小程序中引入 iconfont 图标。

首先找到你想使用的图标们,点击购物车之后下载到本地。

下载icon

下载到本地是一个压缩包,解压缩之后将 iconfont.css 文件复制到微信小程序的 styles 文件夹中 (在下的习惯,也可以放到你想放的地方比如 fonts),将后缀改为 .wxss

放到本地

app.wxss 中引入样式:

@import "styles/iconfont.wxss";

然后在 .wxml 中就可以使用刚刚你添加的图标了,Web 使用 i 标签,小程序中使用 text 标签:

<text class="iconfont icon-my-edit" style="color: blue"></text>

如果后面要加新的图标,要下载新的 iconfont.css 的文件到本地重命名并覆盖,重新走一遍这个流程。

当然,如果你使用的样式库提供的一些 icon 能满足你的要求,那更好,就不用引入外部图标字体文件了,不过大部分情况下是不满足的 🤣


网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出,如果本文帮助到了你,别忘了点赞支持一下哦(收藏不点赞,都是耍流氓 🤣)~

参考文档:

  1. youngjuning/wx-promise-pro: ✨强大、优雅的微信小程序异步库🚀
  2. 小程序开发坑之-IOS时间显示为NaN - 漠小飞
  3. 【微信小程序】性能优化
  4. 微信小程序使用Promise - 简书
  5. 开发微信小程序,我为什么放弃 setData,使用 upData

PS:本人博客地址 Github - SHERlocked93/blog,也欢迎大家关注我的公众号【前端下午茶】,一起加油吧~

另外可以加入「前端下午茶交流群」微信群,长按识别下面二维码即可加我好友,备注加群,我拉你入群~

查看原文

赞 47 收藏 33 评论 3

SHERlocked93 关注了问题 · 8月5日

webstorm 不能拖拽文件和代码了

不知道按错什么快捷键了,webstorm 突然不能拖拽文件个代码了,想把文件放到别的地方只能剪切了,不能直接拖过去了.好烦躁

关注 1 回答 1

SHERlocked93 回答了问题 · 8月5日

webstorm 不能拖拽文件和代码了

主要是这个配置

image.png

如果你需要打开这个设置,可以按住 Alt 键就可以拖动

关注 1 回答 1

SHERlocked93 关注了用户 · 8月4日

写Bug @t_co_b

关注 1987

SHERlocked93 关注了用户 · 8月3日

京东设计中心JDC @jingdongshejizhongxin

致力为京东零售消费者提供完美的购物体验。以京东零售体验设计为核心,为京东集团各业务条线提供设计支持, 包括线上基础产品体验设计、营销活动体验设计、品牌创意设计、新媒体传播设计、内外部系统产品设计、企业文 化设计传播以及体验创新研究等;部门专业领域涉及用户研究、交互设计、视觉设计以及前端开发。成员分布于北 京、深圳、上海。

关注 974

SHERlocked93 发布了文章 · 7月22日

开发微信小程序,我为什么放弃 setData,使用 upData

2020-07-22-HC2B7ingmKo

鉴于在下使用微信小程序开发时使用 setData 的蹩脚体验,开发了个库函数 wx-updata,项目上线之后,我把这个自用的库函数整理放到 Github 上开源出来 wx-updata,这个库函数在开发的时候对我很有帮助,希望也可以帮到大家 👏

如果大家在使用中遇到了问题,可以给我提 pr,提 issue,一起来改善小程序开发体验~

  1. wx-updata 版本 0.0.10
  2. Github 地址: https://github.com/SHERlocked...
  3. 小程序代码片段预览地址: https://developers.weixin.qq....
  4. 小程序代码片段代码地址: https://github.com/SHERlocked...

1. setData 不方便的地方

你在使用 setData 的时候,是不是有时候觉得很难受,举个简单的例子:

// 你的 data
data: {
    name: '蜡笔小新',
    info: { height: 140, color: '黄色' }
}

如果要修改 info.height 为 155,使用 setData 要怎么做呢:

// 这样会把 info 里其他属性整不见了
this.setData({ info: { height: 155 } })

// 你需要取出 info 对象,修改后整个 setData
const { info } = this.data
info.height = 155
this.setData({ info })

似乎并不太复杂,但如果 data 是个很大的对象,要把比较深且不同的对象、数组项挨个改变:

data: {
    name: '蜡笔小新',
    info: {
        height: 140, color: '黄色',
        desc: [{ age: 8 }, '最喜欢大象之歌', '靓仔', { dog: '小白', color: '白色' }]
    }
}

比如某个需求,需要把 info.height 改为 155,同时改变 info.desc 数组的第 0 项的 age 为 12,第 3 项的 color 为灰色呢?

// 先取出要改变的对象,改变数字后 setData 回去
const { info } = this.data
info.height = 155
info.desc[0].age = 12
info.desc[3].color = '灰色'
this.setData({ info })

// 或者像某些文章里介绍的,这样可读性差,也不太实用
this.setData({
    'info.height': 155,
    'info.desc[0].age': 12,
    'info.desc[3].color': '灰色'
})

上面这两种方法,是我们平常小程序里经常用的,和其他 Web 端的框架相比,就很蹩脚,一种浓浓的半成品感扑面而来,有没有这样一个方法:

this.upData({
    info: {
        height: 155,
        desc: [{ age: 12 }, , , { color: '灰色' }]
    }
})

这个方法会帮我们深度改变嵌套对象里对应的属性值,跳过数组项里不想改变的,只设置我们提供了的属性值、数组项,岂不是省略了一大堆蹩脚的代码,而且可读性也极佳呢。

这就是为什么我在上线的项目中使用 wx-updata,而不是 setData

wx-updata 的原理其实很简单,举个例子:

this.upData({
    info: {
        height: 155,
        desc: [{ age: 12 }]
    }
})

// 会被自动转化为下面这种格式,
// this.setData({
//    'info.height': 155,
//    'info.desc[0].age': 12,
// })

原来这个转化工作是要我们自己手动来做,现在 wx-updata 帮我们做了,岂不美哉!

下面介绍一下 wx-updata 的优点和主要使用方法~

2. wx-updata 的优点

  1. 支持 setData 对象自动合并,不用写蹩脚的对象路径了 🥳
  2. 支持对象中嵌套数组,数组中嵌套对象;
  3. 如果数组的某个值你不希望覆盖,请使用数组空位来跳过这个数组项,比如 [1,,3] 这个数组中间就是数组空位;
  4. 如果数组空位你的 Eslint 报错,可以使用 wx-updata 提供的 Empty 来代替: [1, Empty, 3]
  5. 如果数组空位你不习惯,或者不乐意数逗号个数,可以试试数组的对象路径方式 [1,,3] -> {'[0]': 1, '[2]': 3}

3. wx-updata 安装

你也可以直接把 dist 目录下的 wx-updata.js 拷贝到项目里使用

使用 npmyarn 安装方式:

$ npm i -S wx-updata
# or
$ yarn add wx-updata

然后:

  1. 把微信开发者工具面板右侧的 详情 - 本地设置 - 使用npm模块 按钮打开;
  2. 点击微信开发者工具面板工具栏的 工具 - 构建npm

构建后成功生成 miniprogram_npm 文件夹就可以正常使用了

4. wx-updata 使用方法

使用方式一

可以使用直接挂载到 Page 上的方式,这样就可以在 Page 实例中像使用 setData 一样使用 upData

// app.js
import { updataInit } from './miniprogram_npm/wx-updata/index'  // 你的库文件路径

App({
    onLaunch() {
        Page = updataInit(Page, { debug: true })
    }
})
// 页面代码中

this.upData({
    info: { height: 155 },
    desc: [{ age: 13 }, '帅哥'],
    family: [, , [, , , { color: '灰色' }]]
})

使用方式二

有的框架可能在 Page 对象上进行了进一步修改,直接替换 Page 的方式可能就不太好了,wx-updata 同样暴露了工具方法,用户可以在页面代码中直接使用工具方法进行处理:

// 页面代码中

import { objToPath } from './miniprogram_npm/wx-updata/index'  // 你的库文件路径

Page({
    data: { a: { b: 2}, c: [3,4,5]},

    // 自己封装一下
    upData(data) {
        return this.setData(objToPath(data))
    },

    // 你的方法中或生命周期函数
    yourMethod() {
        this.upData({ a: { b: 7}, c: [8,,9]})
    }
})

使用 Empty 代替数组空位

可以使用 wx-updata 提供的 Empty 来代替数组空位,由于 Empty 本质上是一个 Symbol,所以只能使用 wx-updata 导出的,而不能自己新建。

// 页面代码中
import { Empty } from './miniprogram_npm/wx-updata/index'

this.upData({
    info: { height: 155 },
    desc: [{ age: 13 }, '帅哥'],
    family: [Empty, Empty, [Empty, Empty, Empty, { color: '灰色' }]]
})

数组的对象路径方式

如果数组空位你不习惯,或者不乐意数逗号个数,可以试试数组的对象路径方式,需要传递 config 的配置 {arrObjPath: true}

// 页面代码中
import { Empty } from './miniprogram_npm/wx-updata/index'

// 原来的方式
this.upData({
    info: { height: 155 },
    desc: [, '靓仔'],
    family: [, , [, , , { color: '灰色' }]]
})

// 使用数组路径方式
this.upData({
    info: { height: 155 },
    desc: {'[1]': '靓仔'},
    family: { '[2]': { '[3]': { color: '灰色' }
})

5. wx-updata 相关 API

Page.prototype.upData(Object data, Function callback)

  1. data: 你希望设置的 data
  2. callback: 跟 setData) 第二个参数一样,引起界面更新渲染完毕后的回调函数

updataInit(Page, config)

  1. Page: 页面对象,需要在 app.js 中调用;
  2. config 配置

    • 配置参数 { debug: true },会将路径化后的 data 打印出来,帮助用户进行调试,默认 false 不开启;
    • 配置参数 { arrObjPath: true },会开启数组的对象路径方式功能,默认 false 不开启;

objToPath(Object data, Object config)

  1. data: 你希望设置的 data 对象
  2. config 配置

    • 配置参数 { arrObjPath: true },会开启数组的对象路径方式功能,默认 false 不开启;

网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~

参考文档:

  1. 小程序开发实用技巧——扩展 Page 页面对象 - 掘金

PS:本人博客地址 Github - SHERlocked93/blog,也欢迎大家关注我的公众号【前端下午茶】,一起加油吧~

另外可以加入「前端下午茶交流群」微信群,长按识别下面二维码即可加我好友,备注加群,我拉你入群~

查看原文

赞 10 收藏 6 评论 5

认证与成就

  • 获得 1550 次点赞
  • 获得 17 枚徽章 获得 0 枚金徽章, 获得 7 枚银徽章, 获得 10 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • progress-catalog

    Generate a progress catalog by html tag (h1, h2, h3, h4, h5, h6). 😜

  • wx-updata

    官方 setData 最佳替代品 ✈️

注册于 2016-12-02
个人主页被 5k 人浏览