Giter Club home page Giter Club logo

blog's Introduction

Hi there 👋, I'm Joe_Sky.

  • 🔭 I’m an ordinary Front-End engineer currently working on JD.com.
  • 🌱 I’m currently learning React, Typescript, Vue, Vite ...
  • 👯 I'm trying to create some projects(e.g. NornJ, jsx-sfc, Narrative, and other new projects) that are useful to the community and expect to be recognized by more people. I will keep working hard 💪.
  • 📫 How to reach me: [email protected]
  • ⚡ Fun fact: I should be a hardcore video game player. It's shocking that even consoles 20 years ago can still play games 😱.
All my game consoles and the year I bought them 🧐
  • Play Station (2000)
  • Play Station 2 (2002)
  • Play Station 3 (2014)
  • Play Station 4 (2020)
  • Play Station Portable (2007)
  • Play Station Portable Go (2011)
  • Play Station Vita (2016)
  • Play Station VR (2020)
  • Play Station Classic (2020)
  • Xbox (2003)
  • Xbox 360 (2009)
  • Xbox One X (2019)
  • Nintendo Switch OLED (2022)
  • Nintendo Switch (2019, 2020)
  • Nintendo Switch Lite (2020)
  • Nintendo WII (2008)
  • Nintendo 3DS LL (2013)
  • Nintendo DS (2017)
  • Nintendo DS Lite (2006)
  • Nintendo DS LL (2010)
  • Nintendo Game Cube (2005)
  • Nintendo Super Famicom (2018)
  • Nintendo Famicom (1993, 2018)
  • Game Boy (1998)
  • Game Boy Pocket (1998)
  • Game Boy Light (2017)
  • Game Boy Color (1999)
  • Game Boy Advance (2002)
  • Game Boy Advance SP (2004)
  • Game Boy Micro (2005, 2021)
  • Sega Game Gear (1999, 2017)
  • Sega Game Gear Micro (2021, 2022)
  • Sega Mega Drive (1996)
  • Sega Mega Drive Mini (2021)
  • Sega Mega CD 2 (2018)
  • Sega Genesis Nomad (1999)
  • Sega Genesis Mini 2 (2023)
  • Sega Saturn (2020)
  • Sega Dreamcast (2001)
  • PC Engine Core Grafx mini (2023)
  • Valve Steam Deck (2023)
  • ASUS Rog Ally (2023)
  • Analogue Pocket (2023)
  • SNK NEOGEO Mini (2024)

blog's People

Contributors

joe-sky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

web端的类excel表格组件调研

组件分类

有几种不同的方式都可以开发在线类excel表格的功能:

1. 深度开发的js表格组件

例如 Handsontable (点击查看demo),特点如下:

  • 各种表格相关功能非常全面,通常有免费版收费版
  • 一些高级功能在收费版中提供。

2. 通用型js表格组件

例如 ant-design的表格组件 (点击查看demo),特点如下:

  • 容易适配各种常规表格开发的场景,更容易做定制化
  • 但一些高级功能例如拖拽单元格计算则需要自行实现。

3. 操作真实excel的js库

例如 js-xlsx (点击查看demo),和上述js表格组件有本质区别:

  • 本质属于用js在页面上读取、展示或写入真实的excel文件;
  • 可以用在纯前端方式开发excel导入、导出功能。

上述三种组件开发在线excel功能对比

功能 Handsontable Ant-Design的Table组件 js-xlsx
单元格可编辑
单元格间公式计算 收费版可支持,也可自行实现 可自行实现 ✖️
合并单元格
拖拽排序列 可结合第三方组件实现
拖拽排序行 可结合第三方组件实现 ✖️
拖拽列宽 可结合第三方组件实现
列头菜单 可自行实现 ✖️
表头分多级 收费版可支持
单元格右键菜单 收费版可支持 可自行实现
按条件筛选列数据 收费版可支持
隐藏列 收费版可支持 可自行实现
拖拽单元格复制数据 可自行实现,但较困难 ✖️
导出excel文件 收费版可支持 可结合第三方组件实现
导入excel数据 可结合第三方组件实现 可结合第三方组件实现
前端框架支持 原生js、React、Vue、Angular React 原生js

技术选型建议

  • 综上所述,Handsontable 的功能是比较全的,完成度较高,样式也比较像专业的在线excel软件;但是一些核心功能需要购买收费版
  • Ant-Design的Table组件可自行定制化的能力很强,如投入一定开发资源,也可自行定制并实现功能较强的在线excel软件。

我本人曾使用Ant-Design的Table组件方案开发过有大量单元格计算的在线类excel表格项目。

  • js-xlsx 的功能相对上述两种比起来更死板一些,但是优势在于可读取操作真实excel文件。也可和上述表格组件结合使用,比如弥补导出、导入excel功能。

记一次将现有 multirepo 项目改造为 monorepo 的过程

什么是 monorepo 和 multirepo

  • Monorepo 的全称是 monolithic repository,即单体式仓库,把所有相关的 package 都放在一个仓库内进行管理。国内外很多知名的开源项目均采用 monorepo 方式管理仓库,如 ReactBabelUmiTaro 及近期放出的 Vue 3.0源码 等。

  • 与之对应的是 Multirepo(multiple repository),即传统的多仓库管理方式,每一个 package 都单独用一个仓库来进行管理,如 WebpackRollup

multirepo vs monorepo

背景

作者的一个项目是前端模板引擎(简称NornJ),它需要同时兼顾渲染 html 字符串与 React 组件功能。该项目的早期发展经历了如下几个阶段:

在初始阶段的v0.3版,代码量比较少且发布的包也只有一个nornj,所有的代码都放在同一个仓库中。

v0.4版的演进过程较为漫长。该版本中为了更好地进行职责分离,将渲染 React 组件的相关部分单独抽出了一个包(nornj-react);并后续开发了相关的 babel JSX 插件Webpack loadervscode 插件等。

也就是在本次改造前,该项目使用了传统的 multirepo 方式管理,以 package 来区分包含以下独立仓库:

然而 babel JSX 插件的代码是例外的,在v0.4版它放在了NornJ主仓库的 packages 目录下,结构与 monorepo很像 ;但该仓库还没有使用 lerna 等工具进行管理:

也有一些开源项目使用了这种方式,如 Vue 2.xHtm

问题

随着NornJ项目的代码量及 package 数逐渐增多,multirepo 方式渐渐显现出如下几个问题:

  • 多次发布

nornjnornj-reactnornj-loaderbabel-plugin-nornj-in-jsx等几个包在发布时原则上需要用统一的版本号,如这样 multirepo 下的各子包通常都要多次发布。由于该过程枯燥麻烦,有时就省去了这个流程,故上述几个子包间的版本号很难保证相同。

  • 调试困难

NornJ项目的部分 package 之间是有依赖关系的,比如 nornj-react 会依赖 nornj;babel-plugin-nornj-in-jsx 会依赖 nornj、nornj-react 等。这在调试的时候就会遇到依赖包如何更新的问题,当然我们可以使用npm link命令将上述子包链接到全局npm;但始终还是无法解决每个子包都要各自安装几百 MB 的 node_modules 目录:cold_sweat:。

  • 重复的工具配置文件

通常我们希望 multirepo 下的每个子包都使用统一的代码风格规范,这就需要在它们各自的目录下配置相同的eslint、stylelint、prettier等工具的配置文件。理想中这些配置文件的内容都是相同的,这就涉及更新时如何将各个子包都同步到,很不方便。

  • 无法统计多个子包的总测试覆盖率

nornjnornj-react两个包在 multirepo 下只能分别统计各自的测试覆盖率,这不是我们想要的,只是因为不得不这么做。这两个子包虽然职责有所不同,但都属于NornJ核心源码的一部分(其他的babel、webpack、vscode扩展属于插件),它们的覆盖率应该一起统计才正确。

目标

针对以上问题,NornJ项目在v5.0版发布前需要提出下面几个改造目标,力求最大力度地简化调试、发布、测试流程:

  1. 优化发布流程:一键发布,并保证多个子包使用同一版本;

  2. 优化调试流程:各子包中依赖自动管理并更新,且无需多次安装node_modules目录;

  3. 统一工具配置文件:eslint等工具在多个子包间共享同一个的配置文件;

  4. 优化测试覆盖率统计方式:使统计多个子包的总测试覆盖率成为可能。

方案的制定

切换为 monorepo 项目

为使NornJ项目达到上述一系列目标,作者经过一番调研决定采用类似于Vue 3.0源码中的monorepo方式,即lerna + yarn。npm的替代工具yarn目前内置了workspace功能,它可以与最流行的monorepo管理工具lerna协同工作,而提供更高效的依赖包安装及管理。

但如果切换为 monorepo ,NornJ项目还有较特殊的地方,就是它的每个子包并不都适合接入lerna + yarn的统一发布管理流程,具体如下:

nornj-compile-editor是一个用于测试NornJ模板编译代码的编辑器工具,其直接在静态页面中运行NornJ的umd包,自身并不发布版本只是用于本地调试,故不适合用lerna统一管理版本。但它的源码仍可放在nornj/packages/nornj-compile-editor目录。

nornj-highlight是vscode代码高亮及提示插件,它的源码遵循vscode插件规范且依赖也较特殊,并需要独立发布到vscode插件市场,故不适合用lerna统一管理。但我们还是可以创建nornj/packages/nornj-highlight目录,只是它是一个链接会直接跳转到独立发布的nornj-highlight仓库。

梳理前端工程化工具

新的 monorepo 仓库我们希望所使用的各种工程化工具如下:

  1. 代码编译及打包:使用Rollup结合Babel进行代码构建;

  2. 单元测试工具:主要使用Jest做单元测试;

  3. 持续集成服务(Continuous Integration, 即CI):接入Travis-ci;

  4. 规范git提交注释:使用Commit-lint约束git提交注释规范。

改造的实现

todo

迁移各子包代码

todo

一些踩到的坑

todo

总结

todo

京东前端开源项目介绍 & React vs Vue框架的一些对比分析

京东前端开源项目介绍

业界熟知的京东开源前端项目目前还比较少,下面介绍一些有一定对外影响力的框架(以下不包含未完全对外开源的框架,如JDReact等)。

基于React技术

Taro是一个由凹凸实验室开发,以React语法开发微信、百度、支付宝小程序为主,同时又能打通h5React-Native技术的多端统一开发框架。目前在前端社区有很高的人气,项目维护非常积极,正在快速迭代发展中。

Nerv也由凹凸实验室出品,它是一个浏览器兼容性可支持到IE8的类React语法框架。目前京东主站首页就是使用Nerv渲染的。

Taro UI是一款基于Taro的UI组件库,一套组件可以在微信小程序支付宝小程序百度小程序H5多端适配运行。

基于Vue技术

一款基于Vue.js 2.0的前端 UI 组件库,主要用于快速开发PC网站产品该项目目前看似已停止维护

一套移动端轻量级Vue组件库,刚在前端社区推广不久,未来预计可依赖mpvue(美团开源的Vue语法小程序框架)实现代码转微信小程序功能。

以上是京东目前较有影响力的前端开源项目,可以看出以移动端开发为主,较少涉及PC中后台开发。


下面的是一些在某些部门有一定使用量、开源且仍在持续维护,但还未对公司外推广的:

React技术

此项目由我们大部门开发及维护】一个基于ReactMobx-state-treeNornJ技术的前端命令行脚手架,支持命令行创建页面、组件、mobx store文件等,也包含EslintPrettier、自动化测试等工程化配置。目前主要支持创建基于Ant-Design(PC)及Ant-Design-Mobile(H5)的两套项目模板。

同样由凹凸实验室开源的AT-UINerv(类React框架)版。

一套基于React开发的移动端UI组件库,和上面的Nut UI较类似。

此项目由我们大部门开发及维护】一个基于ReactMobx的前端脚手架,各种工程化工具配置非常齐全。

Vue技术

Gaea构建工具是基于Node.js、Webpack模版工程等的Vue技术栈的整套解决方案,包含了开发、调试、打包上线完整的工作流程。

React和Vue技术都包含

塞伯坦(CYB)是面向前端模块化工程的构建工具。主要目的是帮助开发者统一前端开发模式和项目开发结构,提高功能扩展和降低维护成本,自动化前端工作流,提高开发效率和开发质量。

以上开源项目中包含不少前端脚手架,它们各自的侧重点又有所不同:比如NornJ-cli较为轻量易学,react-mobx-scaffold则各方面配置都比较全面。而它们又分别较为贴合各自部门的技术特点。


React vs Vue框架的一些对比分析

ReactVue作为两个当前最受欢迎的前端组件化框架,它们各自都有相当活跃且丰富的社区生态。以下是一些简单对比:

对比项 React Vue
github star数 124,817 131,413
npm周下载量 6,312,392 1,027,722
主框架包体积 React 16.2.0 + React DOM: 31.8K Vue 2.4.2:20.9K
国内开源的优秀PC端UI组件库 1️⃣ ant-design ★43958
2️⃣ rsuite ★2529
1️⃣ element-ui ★35990
2️⃣ iview ★20349
原生APP端方案 React-Native ★75279
RN是目前较成熟且稳定的js开发APP端解决方案。
Weex ★12003
Weex为阿里开源,相对于RN知名度较小,但也是Vue中较知名的APP端解决方案。
小程序跨端方案 Taro ★16568 1️⃣ Wepy ★17001
2️⃣ mpvue ★16506
3️⃣ uni-app ★4182
4️⃣ chameleon ★4121
vnode描述语言 JSX
html与js代码混写在一起,但语法噪音较小,其实上手也挺容易。也可使用模板方案替代JSX,如NornJ
vue模板
html、css、js分开编写,结构很清晰,上手容易。
数据流 1️⃣ Redux ★47603
函数式编程风格数据流架构,代码相对MobxVuex比较来说相对较复杂,适合大型项目。
2️⃣ Mobx ★18841
响应式编程风格数据流架构,相对Redux比较容易上手,配合Mobx-state-tree等状态容器方案也能适应大型项目。
Vuex ★19486
响应式编程风格数据流架构,Vue开发的标配,官方维护。
官方文档 React官方中文文档
React的官方文档今年刚被官方翻译为中文版本。不过之前已有不少国内翻译版。
Vue官方中文文档
Vue的官方文档质量很高,且一直提供官方中文版。

总结

无论选择React还是Vue,两个框架都没有相当大的差异。根据个人或团队的要求,这个决定其实是非常主观的。

  • 国内Vue的相关开源项目应是更多一些,每个领域基本都有多个选择。
  • React的国外开源生态相当多,而国内在每个领域也有顶级的生态项目。

使用 Custom hook 同步 location.hash 值到 React 组件状态

背景

最近一个实际的搜索页面需求,需要做到同步 location.hash 值到表单元素中,也就是要实现把xxx.com#keyword=查询&catalog=品类1中的keywordcatalog提取出来进行同步。

思路

使用 React custom hooks 实现其实是一个不错的选择,这样可以做到只与组件的状态有关而与表单元素无关。在使用 TS 开发设计时我们可能需要考虑到这几点:

  1. Custom hook 的入参对应到返回值的类型可以支持动态推导
  2. 如何序列化/反序列化 query string
  3. 如何实现 url 变化时与组件状态的同步

代码实现

具体实现代码如下(此版本可用,但可能还能优化):

import { useState, useEffect, useCallback } from 'react';
import qs from 'querystring';

type HashQs<T> = Partial<Record<keyof T, string>>;

export default function useHashQs<T extends Record<string, string | true>, U extends HashQs<T>>(
  defaultHashValues: T
): [U, (values: U) => U] {
  const [hashValues, setHashs] = useState(() => {
    const hashQsValues = qs.parse(location.hash.substr(1));
    const values: Record<string, string> = {};

    for (let o in defaultHashValues) {
      const defaultValue = defaultHashValues[o];
      if (defaultValue === true) {
        values[o] = hashQsValues[o] as string;
      } else {
        values[o] = defaultValue as string;
      }
    }

    return values as U;
  });

  const setHashValues = useCallback((values: U, isInit: boolean = false) => {
    const hashQsValues = qs.parse(location.hash.substr(1));

    for (let o in values) {
      if (values[o] != null && values[o] !== '') {
        hashQsValues[o] = values[o];
      } else {
        delete hashQsValues[o];
      }
    }

    const qsStr = qs.stringify(hashQsValues);
    if (qsStr.length > 0) {
      location.hash = qsStr;
    } else {
      history.replaceState(null, null, ' ');
    }
    isInit && setHashs(values);

    return values;
  }, []);

  useEffect(() => {
    setHashValues(hashValues, true);

    const onHashChange = () => {
      const hashQsValues = qs.parse(location.hash.substr(1));
      setHashs(hashQsValues as U);
    };
    addEventListener('hashchange', onHashChange);
    return () => {
      removeEventListener('hashchange', onHashChange);
    };
  }, []);

  return [hashValues, setHashValues];
}

使用方法

const [hashQs, setHashQs] = useHashQs({
  type: true,       // 设置为 true 则自动获取当前 hash 的 type 值
  keyword: true,
  county: true,
  industry: true,
  catalog: '品类1',  // 设置为字符串则当做默认值写入到当前 hash 的 catalog值
  brand: '品牌1',
});

// 从 useHashQs 结果值 hashQs 中可动态推导出 type、keyword 等字段,都为字符串类型
console.log(hashQs.type)  // string
console.log(hashQs.keyword)  // string

// 执行修改 hash 值,url 变化为:xxx.com#keyword=查询&catalog=品类1
setHashQs({
  keyword: '查询',
  catalog: '品类1'
});

序列化/反序列化 query string 方式

这里使用了querystring库(注意是querystring,不是query-string),这个库体积非常小,浏览器兼容性好。除此外也可以考虑使用标准的URLSearchParams API,浏览器兼容性可能差一些。

实现 url 变化时与组件状态的同步

  • 首先在 useHashQs 内部首次执行的 useEffect 中注册监听 hash 事件,hash 变化时更新状态:
useEffect(() => {
  const onHashChange = () => {
    // 更新状态
  };
  addEventListener('hashchange', onHashChange);
  return () => {
    removeEventListener('hashchange', onHashChange);
  };
}, []);
  • 然后在外部调用 useHashQs 时,使用 useEffect 监听 hashQs 的各字段值,再执行对应逻辑即可:
useEffect(() => {
  if (!_.isEmpty(hashQs.type)) {
    ...
  } else {
    ...
  }

  if (!_.isEmpty(hashQs.keyword)) {
    ...
  } else {
    ...
  }

  ...
}, [hashQs.type, hashQs.keyword, hashQs.xxx, ...]);

参考资料

use-url-search-params:一个类似的支持 location.search 的 custom hook 实现

用 Babel JSX 扩展来创造响应式 Ant Design 表单解决方案

大家好,两年前我曾经发布过一篇文章《使用新一代js模板引擎NornJ提升React.js开发体验》,第一次尝试推广我创作的可扩展模板引擎 NornJ 。

Babel JSX 插件的新思路

在发表那篇文章后不到一周的时间,我仔细参考了jsx-control-statements,不自觉萌生出一个新的想法:

使用 Babel 提取含特殊信息的 JSX 标签,把它们转换为需运行时的渲染函数,是否能突破 JSX 现有的语法扩展能力?

这个想法随后就被实施:babel-plugin-nornj-in-jsx,并继续应用于公司部门内的多个实际项目中。Babel转换原理描述,请看这里

NornJ 下一代版本

有了上面的转换思路,并在繁忙业务中经过两年断断续续迭代,我在今年发布了重新设计后使用 JSX API 的 NornJ 正式版,并重写了文档,源码也用 Typescript 几乎完全重写:

github:https://github.com/joe-sky/nornj

文档(gitee.io):https://joe-sky.gitee.io/nornj

文档(github.io):https://joe-sky.github.io/nornj

基于 Mobx 与 JSX 语法扩展的 Ant Design 表单解决方案

我们部门团队自2016年起一直主力使用 Mobx 作为 React 状态管理方案,几年来我们一直受益于它的响应式数据流开发体验十分高效,也很容易优化。

Mobx 适配 Antd 表单可能存在的痛点

虽然关于 Mobx 与 Redux 等谁更优不是本篇文章里要对比的,但是通过几年的使用经验,我总结出 Mobx 在配合国内最流行的 React 组件库 Ant Design 组件,特别是表单验证组件时可能存在的一些开发痛点:

  • Ant Design Form 组件推荐的数据存取方式,无法很顺畅地与 Mobx 的响应式数据流结合

Antd Form 组件原生方式使用 getFieldsValue 和 setFieldsValue (官方文档)来对数据进行存取,这在使用 Mobx 做数据流管理时会遇到一些比较尴尬的场景:

  1. 请求后端接口把返回的表单字段数据存储在了 Mobx observable 数据中,然后我们需要把这些数据用 setFieldsValue 方法放置到 Form 组件实例内,各表单组件数据会更新。但这个数据更新的过程没有用上 observable 响应式特性,感觉对使用 Mobx 来说有点浪费;

  2. 从 Form 组件中获取表单字段值时要用 getFieldsValue。这样取出来直接在 render (或 Mobx computed)中使用时,Mobx 的 observer 不会自动重渲染(重计算),可能与直觉不符:

const Demo = () => {
  const [form] = Form.useForm();

  return useObserver(() => (
    <div>
      <Form form={form} name="control-hooks">
        <Form.Item name="note" label="Note" rules={[{ required: true }]}>
          <Input />
        </Form.Item>
        <Form.Item name="gender" label="Gender" rules={[{ required: true }]}>
          <Select placeholder="Select a option and change input text above" allowClear>
            <Option value="male">male</Option>
            <Option value="female">female</Option>
            <Option value="other">other</Option>
          </Select>
        </Form.Item>
      </Form>
      //表单值更新时,以下文字不会更新
      <i>Note:{form.getFieldValue('note')}</i>
      <i>Gender:{form.getFieldValue('gender')}</i>
    </div>
  ));
};

当然,上述场景是有办法解决的。但是无论怎样解决,我们都会感觉到有两份数据存在:Mobx 状态的数据、以及表单自己的数据。对适应了 Mobx 响应式数据流的开发人员来说,可能会觉得麻烦。

  • 部分 Mobx 的 observable 数据,在传入 Ant Design Form 表单组件时需要执行 toJS 转换

这可能是 Mobx observable 这种包装数据类型的硬伤,但像 CheckBox.Group 组件这种,每次传入组件的值都手工执行一次 toJS 转换值为普通数组,也确实有点麻烦。

寻找 Mobx 环境的表单方案 - mobx-react-form

我们可以找到现有的解决方案:mobx-react-form

它与 Antd Form 基于组件内管理数据的思路是不一样的。mobx-react-form 把表单数据、验证状态等都交给一个含 Mobx observable 成员的特殊结构实例来管理,再通过 JSX 延展操作符 API 通知到 Form 相关组件。一个简单的例子:

import React from 'react';
import { observer } from 'mobx-react';
import MobxReactForm from 'mobx-react-form';

const fields = [{
  name: 'email',
  label: 'Email',
  placeholder: 'Insert Email',
  rules: 'required|email|string|between:5,25',
}, {
  name: 'password',
  label: 'Password',
  placeholder: 'Insert Password',
  rules: 'required|string|between:5,25',
}];
const myForm = new MobxReactForm({ fields });

export default observer(({ myForm }) => (
  <form onSubmit={myForm.onSubmit}>
    <label htmlFor={myForm.$('email').id}>
      {myForm.$('email').label}
    </label>
    <input {...myForm.$('email').bind()} />
    <p>{myForm.$('email').error}</p>
    <button type="submit" onClick={myForm.onSubmit}>Submit</button>
    <button type="button" onClick={myForm.onClear}>Clear</button>
    <button type="button" onClick={myForm.onReset}>Reset</button>
    <p>{myForm.error}</p>
  </form>
));

mobx-react-form 的数据管理思路无疑是更符合 Mobx 响应式数据流的。虽然官方没给例子,但它在加一些扩展后应也可适配 Antd Form 组件。但我们从上面代码不难看出,mobx-react-form 和 Antd Form 原生方式比,可能还有以下几个让人顾虑的方面:

  • 用 json 方式定义各表单字段属性,不及 Antd 的 JSX 语法更符合 React 环境的特色;
  • 用 JSX 延展操作符通知各表单组件,语法可读性可能不是太好;
  • 它的底层验证组件,并没有提供 Antd 采用的 async-validator。

基于 JSX 扩展的表单方案 - mobxFormData

参考了 mobx-react-form 的数据管理思路,我利用 NornJ 现有的 JSX 扩展能力,开发出了基于 async-validator 的解决方案:mobxFormData ,同时支持Antd v3 & v4,性能也不错。详细文档在这里

Codesandbox 示例(如果一次无法运行,多刷新几次就好)

使用方式很简单,安装 preset:

npm install babel-preset-nornj-with-antd

再配一下 Babel:

{
  "presets": [
    ...,
    "nornj-with-antd"  //通常放在所有 preset 的最后面
  ]
}

然后就可以在 JSX/TSX 内直接使用了:

import React from 'react';
import { Form, Input, Button, Checkbox } from 'antd';
import { useLocalStore, useObserver } from 'mobx-react-lite';
import 'nornj-react';

export default props => {
  const { formData } = useLocalStore(() => (
    <mobxFormData>
      <mobxFieldData name="userName" required message="Please input your username!" />
      <mobxFieldData name="password" required message="Please input your password!" />
      <mobxFieldData name="remember" />
    </mobxFormData>
  ));
  
  return useObserver(() => (
    <Form>
      <Form.Item mobxField={formData.userName} label="Username">
        <Input />
      </Form.Item>
      <Form.Item mobxField={formData.password} label="Password">
        <Input.Password />
      </Form.Item>
      <Form.Item mobxField={formData.remember}>
        <Checkbox>Remember me</Checkbox>
      </Form.Item>
    </Form>
  ));
};

如上,此方案的表单字段数据放在 <mobxFormData> 标签返回的 formData 实例中。与 mobx-react-form 思路类似,formData 是一个扁平化的 Mobx observable 数据类型,上面包含了各表单数据字段、以及各种表单数据操作 API,使用起来非常方便,可以很好地与 Mobx 数据流对接:

export default props => {
  const { formData } = useLocalStore(() => (
    <mobxFormData>
      <mobxFieldData name="userName" required message="Please input your username!" />
      <mobxFieldData name="password" required message="Please input your password!" />
    </mobxFormData>
  ));
  
  useEffect(() => {
    axios.get('/user', { params: { ID: 12345 } })
    .then(function (response) {
      const user = response.data;
      formData.userName = user.userName;
      formData.password = user.password;
    });
  }, []);
  
  //表单数据操作 api 都在 formData 实例上,可以把实例传递给其他组件
  const onSubmit = () =>
    formData
      .validate()
      .then(values => {
        console.log(values);
      })
      .catch(errorInfo => {
        console.log(errorInfo);
      });
  
  return useObserver(() => (
    <div>
      <Form>
        <Form.Item mobxField={formData.userName} label="Username">
          <Input />
        </Form.Item>
        <Form.Item mobxField={formData.password} label="Password">
          <Input.Password />
        </Form.Item>
        <Form.Item>
          <Button type="primary" onClick={onSubmit}>
            Submit
          </Button>
        </Form.Item>
      </Form>
      //表单值更新时,以下文字会实时更新
      <i>Username:{formData.userName}</i>
      <i>Password:{formData.password}</i>
    </div>
  ));
};
  • 这里用到的 mobxFormData 是一种 JSX 扩展:标签,它被 Babel 转换后的实际值并不是 React.createElement 方法,而只是返回了特殊的对象结构,供 Mobx 转换为 observable 类型,转换原理请看这里

  • 而 mobxField 是另一种 JSX 扩展:指令,使用它将 formData 实例与 Form.Item 组件建立双向数据绑定。在 mobxField 指令的底层实现中,通过配置对不同的 Antd 表单元素组件选取了特定的值属性、事件属性等进行自动更新,并且已经在该转换时调用过 Mobx 的 toJS 方法了,无需再手工 toJS。

mobxFormData 方案的语法整体看起来,和 React JSX 环境感觉也比较契合,IDE 语法提示也是完整的。除了语法,它的各方面功能其实也挺全面,Antd 原生 Form 能实现的它也几乎都能实现。具体可以看它的文档和示例

mobxFormData 的各种表单示例文档

为了更好地服务于开发者,mobxFormData 方案按照 antd v4 版官方文档,重写了其中10多个可运行示例文档,并使用 Dumi 部署在 NornJ 的文档站点中:mobxFormData 表单示例文档

大家可以拿它和 antd 官方表单示例文档 做下对比,其实可以看出在同样功能的情况下,mobxFormData 的代码量通常会更少一些。

mobxFormData 能用于生产环境吗

mobxFormData 方案在我司大部门内已有多个线上实际项目在用,所以我觉得如果您认为它对您的开发体验有好处,或有兴趣尝试,则可以用于生产环境。作者也会一直坚持更新这个项目,如果发现问题非常欢迎您的反馈。

关于 JSX 扩展,一些作者的经验

最后,依作者的实践经验,总结出一些作者认为的目前 JSX 扩展方案可行经验,在此分享给大家:

经验一:JSX 扩展其实能支持 IDE 代码提示

在一些文章评论中,我记得不只一次看到过有人提过: Babel 做的 JSX 扩展是否会无法与现有的 Eslint 与 IDE 语法提示环境融合。这里可以给出一个结论:JSX 扩展其实绝大多数都可以支持 IDE 语法提示

而方法就是使用 Typescript,只要掌握一些 TS 重写类型的知识即可,定义在 global.d.ts 内。例如:

const Test = () => <customDiv id="test">test</customDiv>

为上面的 customDiv 标签补上 TS 类型,只要这样:

interface ICustomDiv {
  id: string;
}

declare namespace JSX {
  interface IntrinsicElements {
    /**
     * customDiv tag
     */
    customDiv: ICustomDiv;
  }
}

指令的话,例如:

const Test = () => <div customId="test">test</div>

TS 这样写就可以:

declare namespace JSX {
  interface IntrinsicAttributes {
    /**
     * customId directive
     */
    customId?: string;  //因为每个组件都可能用到,为不影响类型检查,所以定义为可选的
  }
}

NornJ 项目所有的预置 JSX 扩展都是这样来定义类型,代码可以看这里。Eslint 的话,如果 TS 类型定义好了它通常不会受影响,但可能用到未使用的变量等,这时也不难处理简单加个配置就好,配置方法可以看这里

经验二:React 用双向数据绑定的场景其实不等于用指令语法

还有些观点觉得 “双向绑定” 这个概念,似乎在 React 环境中出现会是一种不合时宜的场景。

双向绑定的含义理解起来是视图组件和数据模型之间建立的绑定关系,它们会双向同步更新。这种场景 React 中也可能会存在,像 Antd 的 Form 组件,从早期版本直到最新的 V4 版,在我看来它的数据管理方式其实一直都类似于双向数据绑定,但并没有用指令方式 API 实现。从它的官方文档中,也一直可以看到对双向绑定的描述

对于指令的实现,不同的 Babel JSX 扩展项目的实现也不同,大多数是语法糖转换;也有比较特殊的,比如 NornJ 的mobxBind 指令,它的实现其实是一个React 高阶组件。所以说 API 只是形式,并不一定代表底层实现。

经验三:目前有哪些现存的 JSX 语法扩展方案

这个领域确实比较偏,以下是作者这些年来见过的几个 Babel JSX 扩展项目,它们都提供了流程控制等常见 JSX 扩展:

目前作者已知的可扩展 Babel JSX 插件:

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.