实战React 必会第三方插件 —— Cron 表达式生成器(qnn-react-cron)
Posted 程序边界
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实战React 必会第三方插件 —— Cron 表达式生成器(qnn-react-cron)相关的知识,希望对你有一定的参考价值。
文章目录
一、引子
Cron 表达式相关知识详见:【实战】Linux基础知识必学 —— 定时任务
qnn-react-cron 可以看做 react-cron-antd 的升级版(具体“渊源”可见文档),现有功能如下:
- 🎉 全面支持 cron:秒、分、时、日、月、周、年
- 🎉 日及周条件互斥,自动改变响应值
- 🎉 支持反解析 cron 表达式到 UI
- 🎉 可结合此组件与 Antd 的下拉及输入组件封装成下拉输入框
- 🎉 国际化支持
- 🎉 TypeScript 支持
直到现在作者依旧在维护(这篇文章之前最新库更新日期:2023.03.02)
二、配置使用
1.安装
yarn add qnn-react-cron | npm i qnn-react-cron
这一步遇到依赖不兼容问题可见后面内容:<三、常见问题及解决>
2.使用
- 引用:
import React from "react";
import Cron from "qnn-react-cron";
(1)直接调用
<Cron />
啊哈,有点简单,这样只能显示,进行被隔离的操作,若要与页面其他组件联动,接着往下看。
- 默认生成表达式并赋值到变量:
import React, useState from "react";
import Cron from "qnn-react-cron";
export default () =>
const [cronValue, setCronValue] = useState('')
return <Cron
value=cronValue
onOk=setCronValue
/>
<Cron onOk=setCronValue/>
是<Cron onOk=value => setCronValue(value)/>
的简写
(2)赋值到表单(Form)
或是使用了表单(Form),比如需要将表达式赋值到 input 框中:
import React from "react";
import Cron from "qnn-react-cron";
export default () =>
const getFieldValue, setFieldsValue = props.form
return <Cron
value=getFieldValue('cronValue')
onOk=value => setFieldsValue( cronValue: value)
/>
这样点击 生成 按钮即可赋值到 input 框中,在 input 框中修改也能同步到组件中。
(3)自定义功能按钮
但是组件默认带了两个按钮:解析到UI、生成,要想隐藏 解析到UI 按钮只能将两个按钮全部都走自定义(有其他想要的功能,比如 重置 也能使用下面的自定义按钮):
import React, useState from "react";
import Button from "antd";
import Cron from "qnn-react-cron";
export default () =>
const getFieldValue, setFieldsValue = props.form
const [fns, setFns] = useState()
return <Cron
value=getFieldValue('cronValue')
// 相当于 ref
getCronFns=setFns
// 自定义底部按钮后需要自行调用方法来或者值
footer=[
//默认值
<Button style= marginRight: 10 onClick=()=>fns.onParse()>解析到UI</Button>
<Button type="primary" onClick=()=>setFieldsValue( cronValue: fns.getValue()))>生成</Button>
]
/>
若是组件用在模态框(Modal)中,组件和模态框在右下方都有按钮,可以隐藏默认按钮并将按钮功能(解析到UI、生成)提取到其他地方,比如输入框(Input)的右侧:
import Cron from "qnn-react-cron";
import Form, Input, Button from "antd"
// import useState from "react";
const CronIndex = () =>
// const [cronRef, setCronRef] = useState()
let cronRef
const [ form ] = Form.useForm()
const getFieldValue, setFieldsValue = form
return <Form form=form>
<Form.Item label="任务周期" name="cronValue">
<Input addonAfter=(
<Button
type='primary'
style= margin: '-1px -12px', border: 'none'
onClick=() => setFieldsValue( cronValue: cronRef.getValue() )>生成</Button>
)/>
</Form.Item>
<Cron
value=getFieldValue('cronValue')
getCronFns=fns => cronRef = fns
// getCronFns=setCronRef
footer=[]
/>
</Form>
export default CronIndex
此时效果:<输入框> 实时同步 到<组件>,<组件>中变化在点击生成按钮时同步到<输入框>,如图:
getCronFns 中对于 cronRef 值的获取不建议使用 useState(代码中有相关示例,已注释掉,感兴趣的可以尝试一下) ,会引起报错:Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn’t have a dependency array, or one of the dependencies changes on every render.
(4)隐藏指定 Tab
- 配置面板的隐藏与显示,默认如下:
<Cron
// 配置面板的隐藏, false 即隐藏
panesShow=
second: true,
minute:true,
hour: true,
day: true,
month:true,
week:true,
year:true,
// 默认显示哪个面板, 默认为 second, 如果隐藏了 second 需要自行设置
defaultTab="second"
/>
隐藏秒,默认显示分钟的设置,如下:
<Cron panesShow= second: false defaultTab="minute" />
默认值是:* * * * * ? *,因此在隐藏某个面板时,要做好对该部分隐藏值的处理
(5)其他
- 博主这里没有用到<语言国际化配置>,如有需要请参考文末官方文档
三、常见问题及解决
1.兼容低版本 antd 或高版本 react
博主在记录这篇博文时,安装的版本是:qnn-react-cron@1.0.4,所支持主要依赖版本:
- antd@“^4.5.2”
- react@“^15.0.0 || ^16.0.0 || ^17.0.0”
若是项目时用的还是比较老的 antd
版本,或是 react
版本高于 qnn-react-cron
所依赖版本在安装依赖时会发生如下报错:
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: npm-test@0.1.0
npm ERR! Found: react@18.2.0
npm ERR! node_modules/react
npm ERR! react@"^18.2.0" from the root project
npm ERR! peer react@">=16.9.0" from antd@4.24.8
npm ERR! node_modules/antd
npm ERR! peer antd@"^4.5.2" from qnn-react-cron@1.0.4
npm ERR! node_modules/qnn-react-cron
npm ERR! qnn-react-cron@"*" from the root project
npm ERR! 1 more (react-dom)
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^15.0.0 || ^16.0.0 || ^17.0.0" from qnn-react-cron@1.0.4
npm ERR! node_modules/qnn-react-cron
npm ERR! qnn-react-cron@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
...
不要慌,解决办法就在报错日志中(顺便学习英语了,嘻嘻):
- 关键词:peer(匹配的,对等的);
- 关键句:Fix the upstream dependency conflict, or retry(修复上游依赖冲突,或重试)
- 原因:npm 7.x 之前的版本遇到依赖冲突会忽视依赖冲突,继续进行安装;npm 7.x 版本开始不会自动进行忽略,需要用户手动输入命令,两种选择:
- –force 无视冲突,强制获取远端npm库资源 (覆盖之前)
- –legacy-peer-deps 忽视依赖冲突,继续安装(不覆盖之前)
npm install vue-router --force
或者
npm install vue-router --legacy-peer-deps
2.useForm 相关报错
参见:【已解决】Instance created by useForm
is not connected to any Form element. Forget to pass form
prop
3.setState inside useEffect 死循环
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn’t have a dependency array, or one of the dependencies changes on every render.
详见二.2.(3)案例或:博主在大佬提的issues下再次提问:https://github.com/wangzongming/qnn-react-cron/issues/21
四、拓展学习
1.cron表达式翻译 —— cronstrue
2.其他 cron 相关 npm 库(包含 vue 相关)
react-cron-generator
- npm:https://www.npmjs.com/package/react-cron-generator
- demo:https://sojinantony01.github.io/react-cron-generator/
vue:vcrontab
vue-cron-2
- 搜索其他 vue cron 相关:https://www.npmjs.com/search?ranking=popularity&q=vue-cron
(排序条件:Optimal(匹配度);Popularity(受欢迎度);Quality(质量);Maintenance(维护时间))
3.在线工具
over
Java技术探索「开发实战专题」Lombok插件开发实践必知必会操作!
前言
首先我们还是看下 Lombok 官方的描述:
本文主要内容涉及如下:
- Lombok 插件安装
- Lombok 常用注解使用
环境支持:
正文
安装 Lombok
使用 Lombok 之前我们先要在所使用的 IDE 中进行集成安装,这里以 IDEA 为例,安装步骤十分简单:
-
前往
File -> Settings -> Plugin -> Marketplace
,搜索 Lombok -
选择搜索结果 Lombok ,点击 Install 安装。
- 安装完成后重启即可。
在 IDE 安装了Lombok 插件后,我们就可以在 pom.xml
文件中添加 Lombok 的依赖进行使用了。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
@Getter/@Setter
-
通常我们编写实体类无论多少个字段,都要为其提供
getter
和setter
方法,如下面的示例类User.java
- 我们常会遇到这种情况:某个实体类新增和修改某个字段,我们都需要单独处理调整,十分麻烦并且重复。这时候如果我们使用 Lombok 提供
@Getter/@Setter
注解就能帮我们省去 getter 和setter
方法的维护,由 Lombok 对User
类自动生成getter
和setter
方法,两者最终的字节码时一样的,而我们现在在User.java
上编写的代码仅仅7 行即可:
@Getter
@Setter
public class User {
private Integer id;
private String username;
private String password;
}
然后用测试类 UserTests.java
测试结果如下:
public class UserTests {
@Test
public void test() {
User user = new User();
user.setUsername("one");
user.setPassword("zxc123");
Assert.assertEquals(user.getUsername(), "one");
Assert.assertEquals(user.getPassword(), "zxc123");
}
}
@Getter/@Setter
注解不仅可以使用在类上,还可以使用在字段上,这样就是表示针对该字段自动生成 getter /setter
方法。
@Getter
@Setter
private String password;
这里该注解使用在类上,还是在字段上的区别就是,如果注解使用在类上,只针对这个类的非静态字段有效。
需要注意的一点是:如果 @Getter
注解修饰了 boolean
类型的变量,其生成的 getter
方法签名是 isXXX
形式,而不是 getXXX
形式。
除此之外,@Getter/@Setter
还提供访问权限控制的属性 lombok.AccessLevel value()
, 默认为 PUBLIC
,而其他选值都是枚举类型:MODULE, PROTECTED, PACKAGE, PRIVATE
@NonNull
顾名思义,@NonNull
用于标记类中不能允许为 null
的字段或者参数上,任何使用该字段的地方都生成空指针判断代码,若@NonNull
标记的变量为 null,抛出 NullPointException
(NPE) 异常。比如下面示例代码:
public class User {
private Integer id;
private String username;
private String password;
public User(Integer id, @NonNull String username, @NonNull String password) {
this.id = id;
this.username = username;
this.password = password;
}
}
使用了 @NonNull
注解之后我们可以获取到反编译之后的字节码信息如下,这就是 Lombok 给我们生成的最终的代码:
public class User {
private Integer id;
private String username;
private String password;
public User(Integer id, @NonNull String username, @NonNull String password) {
if (username == null) {
throw new NullPointerException("username is marked non-null but is null");
} else if (password == null) {
throw new NullPointerException("password is marked non-null but is null");
} else {
this.id = id;
this.username = username;
this.password = password;
}
}
}
构造器注解
再来看下平时经常会遇见的场景,为实体类编写构造器方法,Lombok 提供了三个不同构造器注解 @NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructor
分别对用不同构造器方法处理方式,接下来就一一描述。
-
@NoArgsConstructor
为实体类生成无参的构造器方法 -
@AllArgsConstructor
为实体类生成除了static
修饰的字段之外带有各参数的构造器方法。 -
@RequiredArgsConstructor
为实体类生成指定字段的构造器方法,而这些字段需要被final
,或者@NonNull
修饰。@RequiredArgsConstructor public class User3 { private Integer id; private final String username; @NonNull private String password; }
编译成功后使用构造器方法时就是这样的效果:
User3 user3 = new User3("user3", "zxc123");
@ToString
@ToString
会给类自动生成易阅读的 toString
方法,带上有所非静态字段的属性名称和值,这样就十分便于我们日常开发时进行的打印操作。
@Getter
@Setter
@AllArgsConstructor
@ToString
public class User2 {
private Integer id;
private String username;
private String password;
}
最终编译成字节码,反编译结果如下:
public class User2 {
private Integer id;
private String username;
private String password;
// 省去 setter/getter
public String toString() {
return "User2(id=" + this.getId() + ", username=" + this.getUsername() + ", password=" + this.getPassword() + ")";
}
}
另外,注解 @ToString
还支持设置指定哪些字段的日志化输出,哪些不需要出现在 toString
方法中。使用属性 @ToString.Exclude
排除不需要在 toString
中出现的字段,使用 @ToString.Include
标记需要出现在 toString
中的字段,具体用法可参见示例:
@Getter
@Setter
@AllArgsConstructor
@ToString
public class User2 {
@ToString.Exclude
private Integer id;
@ToString.Include
private String username;
@ToString.Include
private String password;
}
打印 User2
对象的日志效果就是:User2(username=user2, password=zcx123)
。
@EqualsAndHashCode
@EqualsAndHashCode
注解就是用于根据类所拥有的非静态字段自动重写 equals
方法和 hashCode 方法,方便我们用于对象间的比较。类似 @ToString
,@EqualsAndHashCode
还可以使用需要作为比较的字段和排除不需要比较的字段,具体用法可以看如下示例:
@Getter
@Setter
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class User4 {
@EqualsAndHashCode.Exclude
private Integer id;
@EqualsAndHashCode.Include
private String username;
@EqualsAndHashCode.Exclude
private String password;
}
写完实体类代码,我们编写测试方法试下效果:
@Test
public void testEqual() {
User4 user4 = new User4(1, "user4", "zxc");
User4 user4_2 = new User4(1, "user4", "123");
Assert.assertEquals(user4, user4_2); // ture
}
@Data/@Value
@Data/@Value
注解,提供了更综合的生成代码功能,等价于下面几个注解
@Getter
@Setter
@AllArgsConstructor
@ToString
@EqualsAndHashCode
两个注解都只能使用在类上,与 @Data
不同, @Value
用来修饰不可变的类上。一般实体类没有特别的限制的话,通常可以直接使用 @Data
注解修饰。
@Builder
@Builder
是一个非常强大的注解,提供了一种基于建造者模式的构建对象的 API。使用 @Builder
注解为给我们的实体类自动生成 builder()
方法,并且直接根据字段名称方法进行字段赋值,最后使用 build()
方法构建出一个实体对象。
@Data
@Builder
public class User6 {
private Integer id;
private String username;
private String password;
}
@Test
public void testBuilder() {
User6 user6 = User6.builder().id(1).username("user6").password("zxc123").build();
log.warn("testLog: {}", user6); // User6(id=1, username=user6, password=zxc123)
}
需要注意的是 @Builder
不支持父类字段的生成,当一个实体类存在父类时,@Builder
只能生成当前类的字段构建方法。若需要用到父类的字段方法时, Lombok 提供了新的注解 @SuperBuilder
来应对这种情况,下面是 @SuperBuilder
注解的使用方式:
@SuperBuilder
@Getter
@Setter
public class Parent {
private int id;
private String name;
}
@SuperBuilder
@Data
public class Child extends Parent {
private String childName;
}
调用示例:
Child child = Child.builder().id(1).name("父类名称").childName("子类名称").build();
System.out.println(child.getId());
也可以参考此文方式去处理继承的情况:https://reinhard.codes/2015/09/16/lomboks-builder-annotation-and-inheritance/
日志注解
正对程序类中常见不同框架 Logger 对象,Lombok 也提供了注解,来自动生成 Logger对象,实现优雅地输出日志,只需要在类上使用日志注解如 @Log
。当然 Lombok 支持了多个日志框架,并且提供对应的注解如下:
-
@CommonsLog
等价效果:private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
-
@Flogger
等价效果:private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass();
-
@JBosLog
等价效果:private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
-
@Log
等价效果:private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
-
@Log4j
等价效果:private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
-
@Log4j2
等价效果:private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
-
@Slf4j
等价效果:private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j
等价效果:private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
下面代码使用 @Slf4j
注解进行日志输出:
@Slf4j
public class UserTests {
// ....
@Test
public void testLog() {
User5 user5 = new User5();
user5.setId(1);
user5.setUsername("user5");
user5.setPassword("zxc123");
log.warn("testLog: {}", user5);
// 21:57:15.488 [main] WARN com.one.learn.lombok.UserTests - testLog: User5(id=1, username=user5, password=zxc123)
}
}
@Cleanup
@Cleanup
用于标记需要释放清理操作的资源对象变量,如 FileInputStream
, FileOutputStream
等,标记之后资源对象使用完毕后,就会被自动关闭和清理,实际上这里 Lombok 实现效果与 Java7 特性 try with resource
一样, 为我们屏蔽了关闭资源的模板代码,下面给出 @Cleanup
的使用示例:
public class CleanupExample {
public static void main(String[] args) throws IOException {
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream out = new FileOutputStream(args[1]);
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) {
break;
}
out.write(b, 0, r);
}
}
}
将 CleanupExample.java
编译生成的字节码反编译可以得到如下结果:
public class CleanupExample {
//...
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream(args[0]);
try {
FileOutputStream out = new FileOutputStream(args[1]);
try {
byte[] b = new byte[10000];
while(true) {
int r = in.read(b);
if (r == -1) {
return;
}
out.write(b, 0, r);
}
} finally {
if (Collections.singletonList(out).get(0) != null) {
out.close();
}
}
} finally {
if (Collections.singletonList(in).get(0) != null) {
in.close();
}
}
}
}
@SneakyThrows
@SneakyThrows
主要用于在没有 throws
关键字的情况下,隐蔽地抛出受检查异常,为我们平常开发中需要异常抛出时省去的 throw
操作,下面为使用 @SneakyThrows
的示例代码:
public class SneakyThrowsExample implements Runnable {
@SneakyThrows(UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}
@SneakyThrows
public void run() {
throw new Throwable();
}
}
最终编译成字节码,反编译结果如下:
public class SneakyThrowsExample implements Runnable {
public SneakyThrowsExample() {
}
public String utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException var3) {
throw var3;
}
}
public void run() {
try {
throw new Throwable();
} catch (Throwable var2) {
throw var2;
}
}
}
val/var
val/var
用于局部变量的修饰,有了这注解修饰后,变量的类型就会自动通过等号右边的表达式推断出来,这个功能借鉴于许多编程语言的自动类型推断的特性。 而 val
与 var
的区别在于, val
用于修饰不可变变量,var 修饰可变变量。当 val
修饰的变量被重新赋值时,编译器就会提示异常:Error: java: 无法为最终变量 X 分配值
。实际用法也比较简单,可参考下面代码:
@Slf4j
public class VarValExample {
public static void main(String[] args) {
val text = "abc";
// text = "1"; // Error: java: 无法为最终变量 text 分配值`。
var num = 1;
num = 2;
log.info("text:{},num:{}", text, num); // text:abc,num:2
}
}
结语
到这里我们学习了 Lombok 的近乎 80% 常用的注解,应用在我们的日常开发中已经是绰绰有余了,开始尝试 使用 Lombok 吧,慢慢地就会感受下效率的提升以及代码的优雅。
参考
-
Project Lombok: https://projectlombok.org/
-
https://stackabuse.com/project-lombok-reducing-java-boilerplate-code/
- Project Lombok: Reducing Java Boilerplate Code:https://stackabuse.com/project-lombok-reducing-java-boilerplate-code/
- Lombok常用注解的使用总结:https://segmentfault.com/a/1190000017459838
- Lombok @Builder with Inheritance: https://www.baeldung.com/lombok-builder
-
Lombok’s @Builder annotation and inheritance: https://reinhard.codes/2015/09/16/lomboks-builder-annotation-and-inheritance/
- Lombok:让JAVA代码更优雅:http://blog.didispace.com/java-lombok-1/
- Lombok 简介:https://mp.weixin.qq.com/s?__biz=MzIzNzYxNDYzNw==&mid=2247483823&idx=1&sn=ff10d5050e4c95d6724520a14a4f9528&chksm=e8c4a2eddfb32bfb8c9a11bca96461cfeb695914156c4ca1a06b52c99d80b9e6b7fe57eb817c&mpshare=1&scene=1&srcid=0601m7OKSdsR9JlWGGJPm3dX%23rd
以上是关于实战React 必会第三方插件 —— Cron 表达式生成器(qnn-react-cron)的主要内容,如果未能解决你的问题,请参考以下文章