一直以来,前端工程中的配置大多都是 .js 文件或者 .json 文件,最常见的比如:
创新互联IDC提供业务:服务器主机托管,成都服务器租用,服务器主机托管,重庆服务器租用等四川省内主机托管与主机租用业务;数据中心含:双线机房,BGP机房,电信机房,移动机房,联通机房。
这些配置对前端非常友好,因为都是我们熟悉的 JS 对象结构。一般静态化的配置会选择 json 文件,而动态化的配置,涉及到引入其他模块,因此会选择 js 文件。
还有现在许多新工具同时支持多种配置,比如 Eslint ,两种格式的配置任你选择:
后来不知道什么时候,突然出现了一种以 .yaml 或 .yml 为后缀的配置文件。一开始以为是某个程序的专有配置,后来发现这个后缀的文件出现的频率越来越高,甚至 Eslint 也支持了第三种格式的配置 .eslintrc.yml 。
既然遇到了,那就去 探索 它!
下面我们从 YAML 的 出现背景 , 使用场景 , 具体用法 , 高级操作 四个方面,看一下这个流行的现代化配置的神秘之处。
一个新工具的出现避免不了有两个原因:
YAML 这种新工具就属于后者。其实在 yaml 出现之前 js+json 用得也不错,也没什么特别难以处理的问题;但是 yaml 出现以后,开始觉得它好乱呀什么东西,后来了解它后,越用越喜欢,一个字就是优雅。
很多文章说选择 yaml 是因为 json 的各种问题,json 不适合做配置文件,这我觉得有些言过其实了。我更愿意将 yaml 看做是 json 的升级,因为 yaml 在格式简化和体验上表现确实不错,这个得承认。
下面我们对比 YAML 和 JSON,从两方面分析:
JSON 比较繁琐的地方是它严格的格式要求。比如这个对象:
在 JSON 中以下写法通通都是错的:
字符串的值必须 k-v 都是 "" 才行:
虽然是统一格式,但是使用上确实有不便利的地方。比如我在浏览器上测出了接口错误。然后把参数拷贝到 Postman 里调试,这时就我要手动给每个属性和值加 "" 号,非常繁琐。
YAML 则是另辟蹊径,直接把字符串符号干掉了。上面对象的同等 yaml 配置如下:
没错,就这么简单!
除了 "" 号,yaml 觉得 {} 和 [] 这种符号也是多余的,不如一起干掉。
于是呢,以这个对象数组为例:
转换成 yaml 是这样的:
对比一下这个精简程度,有什么理由不爱它?
说起增加的部分,最值得一提的,是 YAML 支持了 注释 。
用 JSON 写配置是不能有注释的,这就意味着我们的配置不会有备注,配置多了会非常凌乱,这是最不人性化的地方。
现在 yaml 支持了备注,以后配置可以是这样的:
把这种配置丢给新同事,还怕他看不懂配了啥吗?
除注释外,还支持配置复用的相关功能,这个后面说。
我接触的第一个 yaml 配置是 Flutter 项目的包管理文件 pubspec.yaml ,这个文件的作用和前端项目中的 package.json 一样,用于存放一些全局配置和应用依赖的包和版本。
看一下它的基本结构:
你看这个结构和 package.json 是不是基本一致? dependencies 下列出应用依赖和版本, dev_dependencies 下的则是开发依赖。
后来在做 CI/CD 自动化部署的时候,我们用到了 GitHub Action。它需要多个 yaml 文件来定义不同的工作流,这个配置可比 flutter 复杂得多。
其实不光 GitHub Action,其他流行的类似的构建工具如 GitLab CI/CD,circleci,全部都是齐刷刷的 yaml 配置,因此如果你的项目要做 CI/CD 持续集成,不懂 yaml 语法肯定是不行的。
还有,接触过 Docker 的同学肯定知道 Docker Compose,它是 Docker 官方的单机编排工具,其配置文件 docker-compose.yml 也是妥妥的 yaml 格式。现在 Docker 正是如日中天的时候,使用 Docker 必然免不了编排,因此 yaml 语法早晚也要攻克。
上面说的这 3 个案例,几乎都是现代最新最流行的框架/工具。从它们身上可以看出来,yaml 必然是下一代配置文件的标准,并且是 前端-后端-运维 的通用标准。
说了这么多,你跃跃欲试了吗?下面我们详细介绍 yaml 语法。
介绍 yaml 语法会对比 json 解释,以便我们快速理解。
先看一下 yaml 的几个特点:
相比于 JSON 来说,最大的区别是用 缩进 来表示层级,这个和 Python 非常接近。还有强化的一点是支持了注释,JSON 默认是不支持的(虽然 TS 支持),这也对配置文件非常重要。
YAML 支持以下几种数据结构:
先看对象,上一个 json 例子:
转换成 yaml:
对象是最核心的结构,key 值的表示方法是 [key]: ,注意这里 冒号后面有个空格,一定不能少 。value 的值就是一个 纯量 ,且默认不需要引号。
数组和对象的结构差不多,区别是在 key 前用一个 - 符号标识这个是数组项。注意这里 也有一个空格 ,同样也不能少。
转换成 JSON 格式如下:
了解了基本的对象和数组,我们再来看一个复杂的结构。
众所周知,在实际项目配置中很少有简单的对象或数组,大多都是对象和数组相互嵌套而成。在 js 中我们称之为对象数组,而在 yaml 中我们叫 复合结构 。
比如这样一个稍复杂的 JSON:
转换成复合结构的 YAML:
若你想尝试更复杂结构的转换,可以在 这个 网页中在线实践。
纯量比较简单,对应的就是 js 的基本数据类型,支持如下:
比较特殊的两个,null 用 ~ 符号表示,时间大多用 2021-12-21 这种格式表示,如:
转换成 JS 后:
在 yaml 实战过程中,遇到过一些特殊场景,可能需要一些特殊的处理。
在 shell 中我们常见到一些参数很多,然后特别长的命令,如果命令都写在一行的话可读性会非常差。
假设下面的是一条长命令:
在 linux 中可以这样处理:
就是在每行后加 符号标识换行。然而在 YAML 中更简单,不需要加任何符号,直接换行即可:
YAML 默认会把换行符转换成 空格 ,因此转换后 JSON 如下,正是我们需要的:
然而有时候,我们的需求是 保留换行符 ,并不是把它转换成空格,又该怎么办呢?
这个也简单,只需要在首行加一个 | 符号:
转换成 JSON 变成了这样:
获取配置是指,在 YAML 文件中定义的某个配置,如何在代码(JS)里获取?
比如前端在 package.json 里有一个 version 的配置项表示应用版本,我们要在代码中获取版本,可以这么写:
JSON 是可以直接导入的,YAML 可就不行了,那怎么办呢?我们分环境解析:
在浏览器中
浏览器中代码用 webapck 打包,因此加一个 loader 即可:
然后配置 loader:
在组件中使用:
在 Node.js 中
Node.js 环境下没有 Webpack,因此读取 yaml 配置的方法也不一样。
首先安装一个 js-yaml 模块:
然后通过模块提供的方法获取:
配置项复用的意思是,对于定义过的配置,在后面的配置直接引用,而不是再写一遍,从而达到复用的目的。
YAML 中将定义的复用项称为锚点,用 标识;引用锚点则用 * 标识。
对应的 JSON 如下:
但是锚点有个弊端,就是不能作为 变量 在字符串中使用。比如:
此时 key2 的值就是普通字符串 _my name is *name_,引用变得无效了。
其实在实际开发中,字符串中使用变量还是很常见的。比如在复杂的命令中多次使用某个路径,这个时候这个路径就应该是一个变量,在多个命令中复用。
GitHub Action 中有这样的支持,定义一个环境变量,然后在其他的地方复用:
这种实现方式与 webpack 中使用环境变量类似,在构建的时候将变量替换成对应的字符串。
如果本文对你有启发,请甩手一个赞
源码分析:
分析源码可得,TextField 是有状态 StatefulWidget,有丰富的属性,自定义化较高,实践中需要合理利用各种回调;
1、光标的相关属性;cursorColor 为光标颜色,cursorWidth 为光标宽度,cursorRadius 为光标圆角;其中 Radius 提供了 circle 圆角和 elliptical 非圆角两种;
2、textAlign 为文字起始位置,可根据业务光标居左/居右/居中等;注意只是文字开始方向;textDirection 问文字内容方向,从左向右或从右向左;
3、maxLength 为字符长度,设置时默认是展示一行,且右下角有编辑长度与整体长度对比;与 maxLengthEnforced 配合,maxLengthEnforced 为 true 时达到最大字符长度后不可编辑;为 false 时可继续编辑展示有差别;
4、设置 maxLength 之后右下角默认有字符计数器,设置 TextField.noMaxLength 即可只展示输入字符数;
5、maxLines 为允许展现的最大行数,在使用 maxLength 时内容超过一行不会自动换行,因为默认 maxLines=1,此时设置为 null 或固定展示行数即可自动换行;区别在于 null 会展示多行,而 maxLines 最多只展示到设置行数;
6、obscureText 是否隐藏编辑内容,常见的密码格式;
7、enableInteractiveSelection 长按是否出现【剪切/复制/粘贴】菜单;不可为空;
8、keyboardAppearance 为键盘亮度,包括 Brightness.dark/light 两种,但仅限于 iOS 设备;
9、textCapitalization 文字大小写;理论上 sentences 为每句话第一个字母大写;characters为每个字母大写;words 为每个单词首字母大写;但该属性仅限于 text keybord,和尚在本地更换多种方式并未实现,有待研究;
10、keyboardType 为键盘类型,和尚理解整体分为数字键盘和字母键盘等;根据设置的键盘类型,键盘会有差别;
a. 数字键盘
--1-- datetime 键盘上可随时访问 : 和 /;
--2-- phone 键盘上可随时访问 # 和 *;
--3-- number 键盘上可随时访问 + - * /
b. 字母键盘
--1-- emailAddress 键盘上可随时访问 @ 和 .;
--2-- url 键盘上可随时访问 / 和 .;
--3-- multiline 适用于多行文本换行;
--4-- text 默认字母键盘;
11、textInputAction 通常为键盘右下角操作类型,类型众多,建议多多尝试;
12、autofocus 是否自动获取焦点,进入页面优先获取焦点,并弹出键盘,若页面中有多个 TextField 设置 autofocus 为 true 则优先获取第一个焦点;
13、focusNode 手动获取焦点,可配合键盘输入等减少用户操作次数,直接获取下一个 TextField 焦点;
14、enabled 设为 false 之后 TextField 为不可编辑状态;
15、decoration 为边框修饰,可以借此来调整 TextField 展示效果;可以设置前置图标,后置图片,边框属性,内容属性等,会在后续集中尝试;若要完全删除装饰,将 decoration 设置为空即可;
16、inputFormatters 为格式验证,例如原生 Android 中通常会限制输入手机号或其他特殊字符,在 Flutter 中也可以借此来进行格式限制,包括正则表达式;使用时需要引入 package:flutter/services.dart;
a. LengthLimitingTextInputFormatter 限制最长字符;
b. WhitelistingTextInputFormatter 仅允许输入白名单中字符;如 digitsOnly 仅支持数字 [0-9];
c. BlacklistingTextInputFormatter 防止输入黑名单中字符;如 singleLineFormatter 强制输入单行;
分析源码 RegExp("[/]") 可以设置正则表达式;
17、onChanged 文本内容变更时回调,可实时监听 TextField 输入内容;
18、controller 文本控制器,监听输入内容回调;
19、onTap 点击 TextField时回调;
20、onEditingComplete 在提交内容时回调,通常是点击回车按键时回调;
21、onSubmit 在提交时回调,不可与 onEditingComplete 同时使用,区别在于 onSubmit 是带返回值的回调;
问题小结:
当 TextField 设置 enableInteractiveSelection 属性后长按会出现菜单,默认为英文,可通过设置 Flutter 国际化来处理;
(1)在 pubspec.yaml 中集成 flutter_localizations;
2)在 MaterialApp 中设置本地化代理和支持的语言类型;
(1)将 maxLength 设置为 null 仅使用 LengthLimitingTextInputFormatter 限制最长字符;
(2)设置 InputDecoration 中 decoration 属性为空;但是底部有空余,只是隐藏而并非消失;
流式布局在移动端是非常常见的,比如商品列表,瀑布流、标签页等等
Flutter 中提供了两种流式布局Wrap和Flow
Wrap可以进行水平方向或者垂直方向上的布局,在一行或者一列现实不完所有的widgets的时候,能够根据当前宽度或者高度自动换行。
alignment 不管设置什么属性都不能调整第一行的位置
包裹一个Container来看,当前Wrap没有占满全屏
把Container占满全屏才表现下面效果
我们一般很少会使用Flow,因为其过于复杂,需要自己实现子组件的位置转换,所以在很多场景下首先要考虑的是Wrap是否满足需求。Flow主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。
可以使用 SingleChildScrollView 包裹布局
这里还需要了解一个 Scaffold 中的一个属性 resizeToAvoidBottomInset
官方文档给出的解释就是处理键盘遮挡问题,默认是 true,如果不希望顶起需要设置为 false。
在 sdk 低版本的时候是使用 resizeToAvoidBottomPadding 需要将其设置为 false,现在已经弃用。但网上很多文章还没有改正,仍然用的 resizeToAvoidBottomPadding。
分两种情况
一种是使用系统的返回键,比如 android 底部导航自带的返回,
另一种是使用导航栏自定义的返回键
第一种情况需要在页面根布局使用 WillPopScope 在 onWillPop 中拦截返回处理。
原理都是通过判断输入框是否获取了焦点
当底部有固定的组件,比如提交按钮,我们在键盘弹起的时候希望按钮贴着键盘顶部固定,但是中间滚动视图可以自由滚动
可以在 SingleChildScrollView 外部再使用 Stack 包裹,悬浮按钮使用 Positioned 定位,
还要⚠️注意要给滚动组件底部留出距离防遮挡,同时还有动态加上 bottomBar 的高度,因为在 iphoneX 以上的手机,会有个虚拟按键,如果不加上该按键高度,同样会被遮挡
高度获取方法: MediaQuery.of(context).padding.bottom
在 showDialog 布局中使用 Scaffold 包裹,不要忘了将 backgroundColor 设为透明。
如果弹窗过高,还是需要将高度固定,然后使用 SingleChildScrollView ,弹窗中同样也可以在执行关闭的时候拦截,判断键盘是否弹起,如果弹起则要先关闭键盘。
给所有输入框绑定 FoucusNode
在 maxLines=1 的情况下,输入框不支持换行,换行按钮会变成 done
监听 onEditingComplete 方法
根布局使用 GestureDetector 或者 InkWell 包裹,点击的时候收起键盘。
最后要记得销毁
row : 在水平方向上排列子widget的列表.
Row 是继承 Flex = MultiChildRenderObjectWidget = RenderObjectWidget = Widget .
所以 Row 具有动态布局的特点,可以让子控件展开以填充可水平的可用空间,用一个 Expanded 的控件包裹子控件。
Row 不支持滚动(通常认为一行的子控件超过可以容纳空间是错误的)。如果有一行控件控件比较多,希望可以滑动的时候,可以采用 ListView 。
同样,如果是垂直方向方面和 Row 对应的是 Column 。
小demo
这里需要注意的地方:
因为在 Row 里面有text,但是因为text的长度过长,一行肯定是放不下的,所以这里考虑用Expanded包裹一个Text,这样文字会自动换行,填充剩余部分。
效果图如下
修改下Row的 内容:
效果如下:可见多个Expanded 具有平分剩余空间的功能。和android 的weight 属性很相像。
Text("long text...") 中文字过长会换行显示。
但如果放置在Row()中 Row(children:[Text("long text...")]) 文字就不会换行显示,还可能会报错某一侧长度溢出了多少像素。
这时给Text组件包裹一层Expanded就可以换行显示。
Row(children:[Expanded(child:Text("long text..."))])
或者用Flexible 也可以。
Row(children:[Flexible (child:Text("long text..."))])
Expanded/Flexible 会限制Row的宽度不要那么长。