Redux Form의 RxJS 구현에 대한 자세한 소개(코드 예)

不言
풀어 주다: 2018-12-31 09:55:33
앞으로
4631명이 탐색했습니다.

이 기사는 RxJS를 사용한 Redux Form 구현에 대한 자세한 소개(코드 예제)를 제공합니다. 필요한 친구가 참고할 수 있기를 바랍니다.

이 글을 읽기 전에 다음 지식을 숙지해야 합니다:

  • React

  • RxJS (적어도 Subject가 무엇인지는 알아야 합니다)

Background

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></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 校验。

로그인 후 복사
           

存在的问题

  • 定制化很难。 比如不支持 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 <h2>React Rx Form</h2><p>感兴趣的同学可以先去看一下源码  https://github.com/reeli/reac...</p><h3>React 与 RxJS</h3><p>RxJS 是一个非常强大的数据管理工具,但它并不具备用户界面渲染的功能,而 React 却特别擅长处理界面。那何不将它们的长处结合起来?用 React 和 RxJS 来解决我们的 Form 难题。既然知道了它们各自的长处,所以分工也就比较明确了:</p><p><strong>RxJS 负责管理状态,React 负责渲染界面。</strong></p><h3>设计思路</h3><p>与 Redux Form 不同的是,我们不会将 form 的状态存储在 store 中,而是直接保存在 <code><form></form></code>  组件中。然后利用 RxJS 将数据通知给每一个 <code><field></field></code> ,然后 <code><field></field></code>  组件会根据数据去决定自己是否需要更新 UI,需要更新则调用 <code>setState</code> ,否则什么也不做。</p><p>举个例子,假设在一个 Form 中有三个 Field (如下),当只有 FieldA 的 value 发生变化时, 为了不让 </p>
로그인 후 복사
<form></form> 和
其子组件也 re-render,Redux Form 内部需要通过 shouldComponentUpdate() 去限制。
// 伪代码
로그인 후 복사
              

而 RxJS 能把组件更新的粒度控制到最小,换句话说,就是让真正需要 re-render 的 <field></field>

바로 이러한 특성 때문에 형태의 발전이 어려워지는 것입니다. 다음 장에서는 RxJS와 Form을 결합하여 이러한 문제를 더 잘 해결하는 데 도움을 줄 것입니다.

HTML Form

    자체 Form 구성 요소를 구현하기 전에 먼저 기본 HTML Form을 참조하겠습니다.
  1. 양식 상태 저장

  2. Form 컴포넌트의 경우 모든 양식 요소(값, 유효성 등)의 정보를 저장해야 하며 HTML Form은 예외는 없습니다.
  3. 그렇다면 HTML 양식은 양식 상태를 어디에 저장하나요? 양식 요소 정보를 어떻게 얻을 수 있나요?

    주로 다음과 같은 방법이 있습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#document.forms#🎜🎜#은 양식 노드. #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#HTMLFormElement.elements#🎜🎜# 모든 ​​양식 요소를 반환합니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#event.target.elements#🎜🎜#은 모든 양식 요소를 가져올 수도 있습니다. #🎜🎜##🎜🎜#
interface IFormState {
  [fieldName: string]: {
    dirty?: boolean;
    touched?: boolean;
    visited?: boolean;
    error?: TError;
    value: string;
  };
}
로그인 후 복사
로그인 후 복사
#🎜🎜#Validation#🎜🎜##🎜🎜#양식 유효성 검사 유형은 일반적으로 두 가지 유형으로 나뉩니다. #🎜🎜##🎜🎜##🎜🎜# # 🎜🎜#내장된 양식 유효성 검사. 기본적으로 양식이 제출되면 자동으로 트리거됩니다. novalidate 속성을 ​​설정하여 브라우저의 자동 유효성 검사를 끌 수 있습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜#JavaScript 유효성 검사. #🎜🎜##🎜🎜#
interface IFormState {
  fields: {
    [fieldName: string]: {
      dirty?: boolean;
      touched?: boolean;
      visited?: boolean;
      error?: string | undefined;
    };
  };
  values: {
    [fieldName: string]: any;
  };
}
로그인 후 복사
로그인 후 복사
#🎜🎜#문제#🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#맞춤화가 어렵습니다. #🎜🎜# 예를 들어 인라인 유효성 검사는 지원되지 않으며 양식은 제출할 때만 확인할 수 있으며 오류 메시지 스타일은 사용자 지정할 수 없습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#복잡한 장면을 다루기가 어렵습니다. #🎜🎜# 예를 들어 양식 요소 중첩 등 #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#입력 구성 요소가 일관되지 않게 동작하여 양식 요소의 값을 가져오기가 어렵습니다. #🎜🎜# 예를 들어 체크박스와 다중선택은 값을 구할 때 바로 값을 구할 수 없으며 추가적인 변환이 필요합니다. #🎜🎜##🎜🎜##🎜🎜#
type TValidator = (value: string | boolean) => string | undefined;

interface IFieldProps {
  children: (props: IFieldInnerProps)=> React.ReactNode;
  name: string;
  defaultValue?: any;
  validate?: TValidator | TValidator[];
}
로그인 후 복사
로그인 후 복사
#🎜🎜#React Rx Form#🎜🎜##🎜🎜#관심 있는 학생은 먼저 소스 코드를 살펴보세요 https://github.com/reeli/reac ..#🎜🎜##🎜🎜#React 및 RxJS#🎜🎜##🎜🎜#RxJS는 매우 강력한 데이터 관리 도구이지만 사용자 인터페이스 렌더링 기능은 없지만 React는 특히 처리에 능숙합니다. 인터페이스. 그렇다면 그들의 강점을 결합해 보는 것은 어떨까요? React와 RxJS를 사용하여 양식 퍼즐을 해결합니다. 이제 각각의 강점을 알았으므로 업무 분담이 상대적으로 명확해졌습니다. #🎜🎜##🎜🎜##🎜🎜#RxJS는 상태 관리를 담당하고 React는 인터페이스 렌더링을 담당합니다. #🎜🎜##🎜🎜##🎜🎜#Design Idea#🎜🎜##🎜🎜#Redux Form과 다르게 Form의 상태를 스토어에 저장하지 않고 에 직접 저장합니다&lt ; Form/> 구성 요소입니다. 그런 다음 RxJS를 사용하여 데이터의 각 <field></field>에 알리면 <field></field> 구성 요소가 UI를 기반으로 업데이트해야 하는지 여부를 결정합니다. 데이터를 업데이트하려면 setState를 호출하고, 그렇지 않으면 아무것도 하지 않습니다. #🎜🎜##🎜🎜#예를 들어, 아래 표시된 대로 Form에 3개의 필드가 있다고 가정합니다. <form></form> 또한 다시 렌더링하려면 Redux Form을 shouldComponentUpdate()를 통해 내부적으로 제한해야 합니다. #🎜🎜#
interface IRxFormProps {
  children: (props: IRxFormInnerProps) => React.ReactNode;
  initialValues?: {
      [fieldName: string]: any;
  }
}
로그인 후 복사
로그인 후 복사
#🎜🎜#그리고 RxJS는 구성 요소 업데이트의 세분성을 최소한으로 제어할 수 있습니다. 즉, 실제로 다시 렌더링해야 하는 <field></field>를 허용합니다. 다시 렌더링할 필요가 없는 구성 요소는 다시 렌더링되지 않습니다. #🎜🎜##🎜🎜#핵심은 주제#🎜🎜##🎜🎜#위의 디자인 아이디어에서 다음 두 가지 문제를 요약할 수 있습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜# Form과 Field는 일대다 관계이므로 Form의 상태를 여러 Field에 알려야 합니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜#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 的数据结构长什么样就可以了。

数据流

Redux Form의 RxJS 구현에 대한 자세한 소개(코드 예)

为了更好的理解数据流,让我们来看一个简单的例子。我们有一个 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$,每一次 Form State 的变化都会向 formSubject$ 上发送一个数据,每一个 Field 都会注册成为 formSubject$ 的观察者。也就是说 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></fielda>
            {!!formValues.A && <fieldb></fieldb>}
        >
    )}
</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>
        {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,否则无法保证最后打印出来的值的正确性,也就是说最后得到的结果可能不是打印的那一时刻的值。


위 내용은 Redux Form의 RxJS 구현에 대한 자세한 소개(코드 예)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:segmentfault.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿