import { IMaybeError, FormContext, IFormContext, BasicModel } from './formulr'; import { Subscription, combineLatest, merge } from 'rxjs'; import { map } from 'rxjs/operators'; import { FormError } from './Error'; import { Component } from 'react'; function equal(a: T[], b: T[]) { if (a === b) { return true; } if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i += 1) { if (a[i] !== b[i]) { return false; } } return true; } function pickError(errors: Array>): IMaybeError { for (let i = 0; i < errors.length; i += 1) { const error = errors[i]; if (error) { return error; } } return null; } export interface ICombineErrorsProps { /** * 当 `FormStrategy` 是 `View` 的时候传入字段名数组,用于显示哪些字段,这个字段不和 `models` 同时存在 */ names?: string[]; /** * 可以传入 `model` 数组,会把这些 `model` 的组合显示,这个字段不和 `names` 同时存在 */ models?: BasicModel[]; children?: (error: IMaybeError) => React.ReactNode; } export interface ICombineErrorState { error: IMaybeError; } /** * 将多个 model 的错误组合显示,只会显示收到的第一个错误 * Combines error of multiple models, only one at a moment. */ export class CombineErrors extends Component< ICombineErrorsProps, ICombineErrorState > { static contextType = FormContext; context!: IFormContext; private $: Subscription | null = null; private $parent: Subscription | null = null; state: ICombineErrorState = { error: null, }; private parentChildrenChange = (name: string) => { const { names } = this.props; if (!names || !names.includes(name)) { return; } this.unsubscribe(); this.subscribe(); }; setError = (error: IMaybeError) => { this.setState({ error, }); }; subscribe() { const { parent } = this.context; const { models, names } = this.props; const fields: BasicModel[] = []; if (names) { for (let i = 0; i < names.length; i += 1) { const name = names[i]; const model = parent.get(name); if (model) { fields.push(model); } } } if (models) { for (let i = 0; i < models.length; i += 1) { fields.push(models[i]); } } this.$ = combineLatest(fields.map(it => it.error$)) .pipe(map(pickError)) .subscribe(this.setError); } unsubscribe() { if (this.$) { this.$.unsubscribe(); this.$ = null; } } shouldComponentUpdate( nextProps: ICombineErrorsProps, nextState: ICombineErrorState ) { return nextProps !== this.props || nextState.error !== this.state.error; } componentDidMount() { const { parent } = this.context; this.subscribe(); this.$parent = merge(parent.childRegister$, parent.childRemove$).subscribe( this.parentChildrenChange ); } componentDidUpdate(prevProps: ICombineErrorsProps) { if (this.props !== prevProps) { if ( !equal(prevProps.models || [], this.props.models || []) || !equal(prevProps.names || [], this.props.names || []) ) { this.unsubscribe(); this.subscribe(); } } } componentWillUnmount() { this.unsubscribe(); if (this.$parent) { this.$parent.unsubscribe(); this.$parent = null; } } render() { const { children } = this.props; const { error } = this.state; if (children) { return children(error); } if (error === null) { return null; } return {error?.message}; } }