Antd之Form组件的实现

874 阅读4分钟

前言

不知道有没有小伙伴跟我一样,在使用antd之后,对其中的Form组件的实现很好奇。众所周知在react中是区分受控组件和非受控组件的,而要实时记录输入组件的value值,就需要用到受控组件同时结合state来保证数据更新后组件也会同时被重新渲染。而Form组件并不是使用state记录输入组件的value值的,那么它是如何实现更新数据后重新渲染组件的呢?下面我们就一起来讨论下这个问题。

forceUpdate

要使得组件重新渲染,除了更新state以外,class组件有一个强制更新当前组件的方法:forceUpdate。调用该方法会直接跳过shouldComponentUpdate生命周期,直接执行render函数重新渲染当前组件。

交出组件的控制权

用过Form组件的小伙伴肯定都知道,Form组件要获取表单值或者更新表单值都是通过form对象实现的(这里默认大家都用过了,如果没用过的话就请移步到antd-form熟悉一下Form的用法)。尽管有了forceUpdate方法可以让我们在不使用state储存数据的情况下也能重新渲染组件,但是却面临了下一个问题:在组件外部如何调用forceUpdate方法呢?请看下面的demo:

class Test {
    constructor(store) {
        store.registerInstance(this)
    }

    print() {
        console.log(Math.random())
    }
}

class Store {
    instances = []

    registerInstance(instance) {
        this.instances.push(instance)
    }

    print() {
        this.instances.forEach(i => {
            i.print()
        })
    }
}

const store = new Store()
new Test(store)
new Test(store)

store.print()
// 打印结果
// 0.44673102451121527
// 0.8664812722668469

如上代码所示,Store类的instances用于存储Test组件的实例,这样就相当于把Test类的控制权移交给了Store类,在Store中可以执行Test类的任何方法、可以读取/更新Test类的任何属性。React类组件也可以用同样的方法将当前组件的控制权移交给外部,从而达到在外部调用forceUpdate方法重新渲染组件。

具体代码实现的关键点

解决了以上两个问题我们就可以开始Form组件的实现了。首先我们将外部记录输入组件的value值、控制组件重新渲染的类叫做FormStore,而将注册到FormStore中的组件实例叫做字段实体(fieldEntity)。请看下面的代码:

export class FormStore {
    private values: StoreValue
    private initialValues: StoreValue
    private errors: Record<string, string>
    private fieldEntities: IFieldEntity[]
    
    constructor() {
        this.values = {}
        this.initialValues = {}
        this.errors = {}
        this.fieldEntities = []
    }
    
    getInternalHooks = (mark?: string): InternalHooks | null => {
        if (mark === HOOK_MARK) {
            return {
                registerField: this.registerField,
                unregisterField: this.unregisterField,
                getForm: this.getForm,
                setFieldError: this.setFieldError,
                setInitialValue: this.setInitialValue,
                removeFieldError: this.removeFieldError
            }
        }

        warning(false, '`getInternalHooks` is internal usage. Should not call directly.')
        return null
    }
    
    registerField = (fieldEntity: IFieldEntity): void => {
        this.fieldEntities.push(fieldEntity)
    }
}

export class Field extends Component {
    init = false

    componentDidMount(): void {
        if (!this.init) {
            const { name, initialValue } = this.props
            // FormStore是用过Context的形式在Form组件的上下文互相传递的
            const { registerField, setInitialValue } = this.context.getInternalHooks(HOOK_MARK)

            this.init = true
            registerField(this)

            if (name) {
                setInitialValue(name, initialValue)
            }
        }
    }
}

如上述代码所示,FormStore类有values用于存储实时的Form表单value值、fieldEntities存储组件的实例。接下来要做的就是将FormStore的values绑定到Form表单的输入组件上,如下:

export class Field extends Component {
    init = false

    componentDidMount(): void {
        if (!this.init) {
            const { name, initialValue } = this.props
            // FormStore是用过Context的形式在Form组件的上下文互相传递的
            const { registerField, setInitialValue } = this.context.getInternalHooks(HOOK_MARK)

            this.init = true
            registerField(this)

            if (name) {
                setInitialValue(name, initialValue)
            }
        }
    }
    
    reRender = (): void => {
        this.forceUpdate()
    }
    
    getControlled = (childProps: Record<string, any>): Record<string, any> => {
        const {
            name,
            valuePropName,
            changeMethodName,
            validateTrigger
        } = this.props
        const { getFieldValue, setFieldValue } = this.context

        if (!name) {
            return { ...childProps }
        }
        
        return {
            ...childProps,
            [valuePropName]: getFieldValue(name),
            [changeMethodName]: (value: any) => {
                setFieldValue(name, value)
                
                if (validateTrigger === 'onChange') {
                    this.validateRules(value)
                }

                childProps[changeMethodName]?.(value)
            },
            onBlur: (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
                if (validateTrigger === 'onBlur') {
                    this.validateRules(getFieldValue(name))
                }

                childProps.onBlur?.(e)
            }
        }
    }
}

以及FormStore的更新策略:

export class FormStore {
    private values: StoreValue
    private initialValues: StoreValue
    private errors: Record<string, string>
    private fieldEntities: IFieldEntity[]
    
    constructor() {
        this.values = {}
        this.initialValues = {}
        this.errors = {}
        this.fieldEntities = []
    }
    
    getInternalHooks = (mark?: string): InternalHooks | null => {
        if (mark === HOOK_MARK) {
            return {
                registerField: this.registerField,
                unregisterField: this.unregisterField,
                getForm: this.getForm,
                setFieldError: this.setFieldError,
                setInitialValue: this.setInitialValue,
                removeFieldError: this.removeFieldError
            }
        }

        warning(false, '`getInternalHooks` is internal usage. Should not call directly.')
        return null
    }
    
    registerField = (fieldEntity: IFieldEntity): void => {
        this.fieldEntities.push(fieldEntity)
    }
    
    private notifyObservers(
        prevValues: StoreValue,
        fields: string[] = [],
        force = false
    ) {
        this.fieldEntities.forEach(entity => {
            const shouldUpdate = entity.props.shouldUpdate

            if (force) {
                entity.reRender()
            } else if (fields.includes(entity.props.name as string)) {
                entity.reRender()
            } else if (typeof shouldUpdate === 'function' && shouldUpdate(prevValues, { ...this.values })) {
                entity.reRender()
            } else if (shouldUpdate) {
                entity.reRender()
            }
        })
    }
    
    setFieldValue(field: string, value: ValueType): void {
        if (field == null) {
            return
        }

        const prevValues = { ...this.values }
        this.values[field] = value
        this.notifyObservers(prevValues, [field])
    }
}

useForm

最后再说一下useForm的实现,useForm利用useRef来保证每次函数组件运行都只生成一个FormStore的实例,代码如下:

export function useForm<Values = ValueType>(): [FormInstance<Values>] {
    const formRef = useRef<FormInstance<Values>>()

    if (!formRef.current) {
        const formStore = new FormStore()
        formRef.current = formStore.getForm<Values>()
    }

    return [formRef.current]
}

总结

以上就是Form组件将数据存储到FormStore的同时又能重新渲染组件的实现原理,如有说的不好之处还请各位小伙伴多提提意见帮助我改进改进。 本文中只展示了部分关键的代码,如果想看完整代码请移步到我的组件库rn-element