百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>JS教程> RxJS实现Redux Form的具体介绍(代码示例)
分享文章到:

RxJS实现Redux Form的具体介绍(代码示例)

发布时间:09/01 来源:未知 浏览: 关键词:
本篇文章给大家带来的内容是关于RxJS实现Redux Form的具体介绍(代码示例),有必然的参照 价值,有需要的伴侣可以参照 一下,但愿对你有所帮忙。

看这篇文章此前,你需要把握的知识:

  • React

  • RxJS (至少需要知道 Subject 是啥)

背景

form 可以说是 web 开发中的最大的难题之一。跟一般的组件比拟,form 具有以下几个特点:

  1. 更多的会员交互。
    这意味着大概需要大量的自定义组件,比方 DataPicker,Upload,AutoComplete 等等。

  2. 频繁的状态改动。
    每当会员输入一个值,都大概会对利用状态造成改动,从而需要更新表单元素或者显示错误信息。

  3. 表单校验,也就是对会员输入数据的有效性停止验证。
    表单验证的情势也许多,比方边输入边验证,失去焦点后验证,或者在提交表单此前验证等等。

  4. 异步网络通讯。
    当会员输入和异步网络通讯同时存在时,需要思考的东西就更多了。就比方 AutoComplete,需要按照会员的输入去异步猎取响应的数据,假如会员每输入一次就发起一次恳求,会对资源造成很大白费。由于每一次输入都是异步猎取数据的,那么持续两次会员输入拿到的数据也有大概存在 "后来居上" 的问题。

正由于以上这些特点,使 form 的开发变得艰难重重。在接下来的章节中,我们会将 RxJS 和 Form 结合起来,帮忙我们更好的去解决这些问题。

HTML Form

在实现我们本人的 Form 组件此前,让我们先来参照 一下原生的 HTML Form。

留存表单状态

关于一个 Form 组件来说,需要留存所有表单元素的信息(如 value, validity 等),HTML Form 也不例外。
那么,HTML Form 将表单状态留存在什么地方?怎样才能猎取表单元素信息?

主要有以下几种办法:

  1. document.forms 会返回所有 <form> 表单节点。

  2. HTMLFormElement.elements 返回所有表单元素。

  3. event.target.elements 也能猎取所有表单元素。

document.forms[0].elements[0].value; // 猎取第一个 form 中第一个表单元素的值

const form = document.querySelector("form");
form.elements[0].value; 

form.addEventListener('submit', function(event) {
  console.log(event.target.elements[0].value);
});

Validation

表单校验的类型一样分为两种:

  1. 内置表单校验。默许会在提交表单的时候主动触发。通过设定 novalidate 属性可以关闭阅读器的主动校验。

  2. JavaScript 校验。

<form novalidate>
  <input name='username' required/>
  <input name='password' type='password' required minlength="6" maxlength="6"/>
  <input name='email' type='email'/>
  <input type='submit' value='submit'/>
</form>

存在的问题

  • 定制化很难。 比方不支撑 Inline Validation,只要 submit 时才能校验表单,且 error message 的样式不克不及自定义。

  • 难以应对复杂场景。 比方表单元素的嵌套等。

  • Input 组件的行动不统一,从而难以猎取表单元素的值。 比方 checkbox 和 multiple select,取值的时候不克不及直接取 value,还需要额外的转换。

var $form = document.querySelector('form');

function getFormValues(form) {
  var values = {};
  var elements = form.elements; // elemtns is an array-like object

  for (var i = 0; i < elements.length; i++) {
    var input = elements[i];
    if (input.name) {
      switch (input.type.toLowerCase()) {
        case 'checkbox':
          if (input.checked) {
            values[input.name] = input.checked;
          }
          break;
        case 'select-multiple':
          values[input.name] = values[input.name] || [];
          for (var j = 0; j < input.length; j++) {
            if (input[j].selected) {
              values[input.name].push(input[j].value);
            }
          }
          break;
        default:
          values[input.name] = input.value;
          break;
      }
    }

  }

  return values;
}

$form.addEventListener('submit', function(event) {
  event.preventDefault();
  getFormValues(event.target);
  console.log(event.target.elements);
  console.log(getFormValues(event.target));
});

React Rx Form

感乐趣的同学可以先去看一下源码 https://github.com/reeli/reac...

React 与 RxJS

RxJS 是一个非常强大的数据治理工具,但它并不具备会员界面渲染的功效,而 React 却特殊擅长处置界面。那何不将它们的长处结合起来?用 React 和 RxJS 来解决我们的 Form 难题。既然知道了它们各自的长处,所以分工也就比力明白了:

RxJS 负责治理状态,React 负责渲染界面。

设计思绪

与 Redux Form 不一样的是,我们不会将 form 的状态储备在 store 中,而是直接留存在 <Form/> 组件中。然后利用 RxJS 将数据通知给每一个 <Field/> ,然后 <Field/> 组件会按照数据去决议本人可否需要更新 UI,需要更新则调取 setState ,不然什么也不做。

举个例子,假设在一个 Form 中有三个 Field (如下),当只要 FieldA 的 value 发生转变时, 为了不让 <Form/> 和
其子组件也 re-render,Redux Form 内部需要通过 shouldComponentUpdate() 去限制。

// 伪代码
<Form>
    <FieldA/>
    <FieldB/>
    <FieldC/>
</Form>

而 RxJS 能把组件更新的粒度操纵到最小,换句话说,就是让真正需要 re-render 的 <Field/> re-render,而不需要 re-render 的组件不从新渲染 。

中心是 Subject

从上面的设计思绪可以总结出以下两个问题:

  1. Form 和 Field 是一对多的关系,form 的状态需要通知给多个 Field。

  2. Field 需要按照数据去修改组件的状态。

第一个问题,需要的是一个 Observable 的功效,并且是能够支撑多播的 Observable。第二个问题需要的是一个 Observer 的功效。在 RxJS 中,既是 Observable 又是 Observer,并且还能实现多播的,不就是 Subject 么!因此,在实现 Form 时,会大量用到 Subject。

formState 数据构造

Form 组件中也需要一个 State,用来留存所有 Field 的状态,这个 State 就是 formState。

那么 formState 的构造应当怎样定义呢?

在最早的版本中,formState 的构造是长下面这个模样的:

interface IFormState {
  [fieldName: string]: {
    dirty?: boolean;
    touched?: boolean;
    visited?: boolean;
    error?: TError;
    value: string;
  };
}

formState 是一个对象,它以 fieldName 为 key,以一个 留存了 Field 状态的对象作为它的 value。

看起来没缺点对吧?

但是。。。。。

最后 formState 的构造却变成了下面这样:

interface IFormState {
  fields: {
    [fieldName: string]: {
      dirty?: boolean;
      touched?: boolean;
      visited?: boolean;
      error?: string | undefined;
    };
  };
  values: {
    [fieldName: string]: any;
  };
}

Note: fields 中不包括 filed value,只要 field 的一些状态信息。values 中只要 field values。

为什么呢???

其实在实现最根本的 Form 和 Field 组件时,以上两种数据构造都可行。

那问题到底出在哪儿?

这里先买个关子,当前你只需要知道 formState 的数据构造长什么样就可以了。

数据流

微信截图_20181231095236.png

为了更好的懂得数据流,让我们来看一个简便的例子。我们有一个 Form 组件,它的内部包括了一个 Field 组件,在 Field 组件内部又包括了一个 Text Input。数据流大概是像下面这样的:

  1. 会员在输入框中输入一个字符。

  2. Input 的 onChange 事件会被 Trigger。

  3. Field 的 onChange Action 会被 Dispatch。

  4. 按照 Field 的 onChange Action 对 formState 停止修改。

  5. Form State 更新之后会通知 Field 的视察者。

  6. Field 的视察者将当前 Field 的 State pick 出来,假如发明有更新则 setState ,假如没有更新则什么都不做。

  7. setState 会使 Field rerender ,新的 Field Value 就可以通知给 Input 了。

中心组件

第一,我们需要创立两个根本组件,一个 Field 组件,一个 Form 组件。

Field 组件

Field 组件是连接 Form 组件和表单元素的中心层。它的作用是让 Input 组件的职责更简单。有了它之后,Input 只需要做显示就可以了,不需要再关怀其他复杂逻辑(validate/normalize等)。况且,关于 Input 组件来说,不仅可以用在 Form 组件中,也可以用在 Form 组件之外的地方(有些地方大概并不需要 validate 等逻辑),所以 Field 这一层的抽象还是非常重要的。

  • 拦截和转换。 format/parse/normalize。

  • 表单校验。 参照 HTML Form 的表单校验,我们可以把 validation 放在 Field 组件上,通过组合验证规则来顺应不一样的需求。

  • 触发 field 状态的 改动(如 touched,visited)

  • 给子组件供给所需信息。 向下供给 Field 的状态 (error, touched, visited...),乃至用于表单元素绑定事件的回调函数 (onChange,onBlur...)。

利用 RxJS 的特性来操纵 Field 组件的更新,减少不必要的 rerender。

与 Form 停止通讯。 当 Field 状态发生转变时,需要通知 Form。在 Form 中改动了某个 Field 的状态,也需要通知给 Field。

Form 组件

  • 治理表单状态。 Form 组件将表单状态供给给 Field,当 Field 发生转变时通知 Form。

  • 供给 formValues。

  • 在表单校验失败的时候,阻挠表单的提交。

    通知 Field 每一次 Form State 的转变。 在 Form 中会创立一个 formSubject&dollar;,每一次 Form State 的转变都会向 formSubject&dollar; 上发送一个数据,每一个 Field 都会注册成为 formSubject&dollar; 的视察者。也就是说 Field 知道 Form State 的每一次转变,因此可以决议在恰当的时候停止更新。
    当 FormAction 发生转变时,通知给 Field。 比方 startSubmit 的时候。


组件之间的通讯

  1. Form 和 Field 通讯。

    Context 主要用于跨级组件通讯。在实际开发中,Form 和 Field 之间大概会跨级,因此我们需要用 Context 来包管 Form 和 Field 的通讯。Form 通过 context 将其 instance 办法和 formState 供给给 Field。

  2. Field 和 Form 通讯。

    Form 组件会向 Field 组件供给一个 d__ispatch__ 办法,用于 Field 和 Form 停止通讯。所有 Field 的状态和值都由 Form 统一治理。假如盼望更新某个 Field 的状态或值,必需 dispatch 响应的 action。

  3. 表单元素和 Field 通讯

    表单元素和 Field 通讯主如果通过回调函数。Field 会向表单元素供给 onChange,onBlur 等回调函数。

接口的设计

关于接口的设计来说,简便清楚是很重要的。所以 Field 只保存了必要的属性,没有将表单元素需要的其他属性通过 Field 透传下去,而是交给表单元素本人去定义。

通过 Child Render,将对应的状态和办法供给给子组件,构造和层级愈加清楚了。

Field:

type TValidator = (value: string | boolean) => string | undefined;

interface IFieldProps {
  children: (props: IFieldInnerProps)=> React.ReactNode;
  name: string;
  defaultValue?: any;
  validate?: TValidator | TValidator[];
}

Form:

interface IRxFormProps {
  children: (props: IRxFormInnerProps) => React.ReactNode;
  initialValues?: {
      [fieldName: string]: any;
  }
}

到这里,一个最最根本的 Form 就完成了。接下来我们会在它的根基上停止一些扩展,以知足更多复杂的业务场景。

Enhance

FieldArray

FieldArray 主要用于渲染多组 Fields。

回到我们此前的阿谁问题,为什么要把 formState 的构造分为 fileds 和 values?

其实问题就出在 FieldArray,

  • 初始长度由 initLength 或者 formValues 决议。

  • formState 团体更新。

FormValues

通过 RxJS,我们将 Field 更新的粒度操纵到了最小,也就是说假如一个 Field 的 Value 发生转变,不会致使 Form 组件和其他 Feild 组件 rerender。

既然 Field 只能感知本人的 value 转变,那么问题就来了,怎样实现 Field 之间的联动?

于是 FormValues 组件就应运而生了。

每当 formValues 发生转变,FormValues 组件会就把新的 formValues 通知给子组件。也就是说假如你使用了 FormValues 组件,那么每一次 formValues 的转变都会致使 FormValues 组件乃至它的子组件 rerender,因此不倡议大范畴使用,不然大概带来机能问题。

总之,在使用 FormValues 的时候,最好把它放到一个影响范畴最小的地方。也就是说,当 formValues 发生转变时,让尽大概少的组件 rerender。

鄙人面的代码中,FieldB 的显示与否需要按照 FieldA 的 value 来推断,那么你只需要将 FormValues 作用于 FIeldA 和 FieldB 就可以了。

<FormValues>
    {({ formValues, updateFormValues }) => (
        <>
            <FieldA name="A" />
            {!!formValues.A && <FieldB name="B" />}
        </>
    )}
</FormValues>

FormSection

FormSection 主如果用于将一组 Fields group 起来,以便在复用在多个 form 中复用。主如果通过给 name增加前缀来实现的。

那么怎样给 Field 和 FieldArray 的 name 增加前缀呢?

我第一想到的是通过 React.Children 拿到子组件的 name,再和 FormSection 的 name 拼接起来。

但是,FormSection 和 Field 有大概不是父子关系!由于 Field 组件还可以被抽成一个独立的组件。因此,存在跨级组件通讯的问题。

没错!跨级组件通讯我们还是会用到 context。不外这里我们需要先从 FormConsumer 中拿到对应的 context value,再通过 Provider 将 prefix 供给给 Consumer。这时 Field/FieldArray 通过 Consumer 拿到的就是 FormSection 中的 Provider 供给的值,而不再是由 Form 组件的 Provider 所供给。由于 Consumer 会消耗离本人比来的阿谁 Provider 供给的值。

<FormConsumer>
  {(formContextValue) => {
    return (
      <FormProvider
        value={{
          ...formContextValue,
          fieldPrefix: `${formContextValue.fieldPrefix || ""}${name}.`,
        }}
      >
        {children}
      </FormProvider>
    );
  }}
</FormConsumer>

测试

Unit Test

主要用于工具类办法。

Integration Test

主要用于 Field,FieldArray 等组件。由于它们不克不及离开 Form 独立存在,所以没法对其使用单元测试。

Note: 在测试中,没法直接修改 instance 上的某一个属性,认为 React 将 props 上面的节点都设定成了 readonly (通过 Object.defineProperty 办法)。 但是可以通过团体设定 props 绕过。

instance.props = {
  ...instance.props,
  subscribeFormAction: mockSubscribeFormAction,
  dispatch: mockDispatch,
};

Auto Fill Form Util

假如项目中的表单过多,那么关于 QA 测试来说无疑是一个肩负。这个时候我们但愿能够有一个主动填表单的工具,来帮忙我们提高测试的效力。

在写这个工具的时候,我们需要模拟 Input 事件。

input.value = 'v';
const event = new Event('input', {bubbles: true});
input.dispatchEvent(event);

我们的盼望是,通过上面的代码去模拟 DOM 的 input 事件,然后触发 React 的 onChange 事件。但是 React 的 onChange 事件却没有被触发。因此没法给 input 元素设定 value。

由于 ReactDOM 在模拟 onChange 事件的时候有一个逻辑:只要当 input 的 value 改动,ReactDOM 才会发生 onChange 事件。

React 16+ 会覆写 input value setter,详细可以参照 ReactDOM 的 inputValueTracking。因此我们只需要拿到原始的 value setter,call 调取就行了。

const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, "v");

const event = new Event("input", { bubbles: true});
input.dispatchEvent(event);

Debug

打印 Log

在 Dev 环境中,可以通过 Log 来停止 Debug。当前在 Dev 环境下会主动打印 Log,其他环境则不会打印 Log。
Log 的信息主要包罗: prevState, action, nextState。

Note: 由于 prevState, action, nextState 都是 Object,所以别忘了在打印的时候调取 cloneDeep,不然没法包管最后打印出来的值的准确性,也就是说最后得到的结果大概不是打印的那一时刻的值。

以上就是RxJS实现Redux Form的具体介绍(代码示例)的具体内容,更多请关注百分百源码网其它相关文章!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有153人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板