fusion中表单验证的一种解决方案(useImperativeHandle + forwardRef)

1,215 阅读3分钟

前言

在使用fusion组件库的时候我们经常会使用表单,必然会涉及到表单验证,当然有好几种方案,下面我们讲一下其中一种方案的用法,即useImperative + forwardRef + Field。这种方案本人感觉比较麻烦,仅作为参考。

正文

铺垫(钩子的作用)

forwardRef

  • forwardRef(英文释义为:引用传递)接受一个函数式组件并且含有额外的ref参数,forwardRef才可以实现在父组件中通过组件向子组件传递ref,也就是子组件才可以获得该(ref)参数,比较简单的情况下我们可能不会使用,但对于一些重复使用的组件就有用武之地了。可以将forwardRef理解成中转站,父组件要想获得子组件中绑定的ref对象,就需要通过forwardRef的帮助(把在父组件中创建的ref实例对象传递给子组件然后绑定给子组件中需要获得的目标上)。

ps: ref的使用场景:处理焦点、文本选择或者媒体的控制; 触发必要的动画; 集成第三方DOM库。

场景1: 我们想从父组件中获得子组件中绑定的input输入框的实例对象。

代码:


import React, { Component, createRef, forwardRef } from 'react';
//子组件
const SonInput = forward((props, ref) => {
	<input type="text" ref={ ref }/>
})

// 父组件
class Father extends Component {
	constructor(props) {
    	super(props);
        this.ref = createRef();
    }
    componentDidMount() {
    	const { current } = this.ref;
        current.focus();
    }
    render() {
    	return (
        	<div>
            	<p>晴天</p>
                <SonInput ref={ this.ref }/>
            </div>
        )
    }
}

export default Father;

场景2: 高阶组件

如果子组件被forward包裹了,那么对应的高阶函数也需要使用React.forwardRef,高阶函数将传入的组件加工成新的组件再给他人使用,那么他人也要使用未加工前组件中的ref,因此需要高阶函数这个“工厂”也被React.forwardRef包裹一下。

import React, { Component, createRef } from 'react';

const SonInput = React.forwardRef((props, ref) => 
	<input type="text" ref={ ref } />
)

const FactoryHoc = (WrappedComponent) => {
    const ConverRef = (props) => {
    	const { forwardRef, ...other } = props;
        return <WrappedComponent { ...other } ref={forwardRef} />;
    }
    
    return React.forwardRef((props, ref) => 
    <ConverRef {...props} forwardRef = {ref} /> ) 
    //之所以这里传递ref的时候不用ref={ref},是因为ref是保留字,我们只有这样才能将ref像属性一样传递
}

const FactoryWithRef = FactoryHoc(SonInput);

//父组件
class Father extends Component {
	constructor(props) {
    	super(props);
        this.ref = createRef();
    }
    
    componentDidMount() {
    	const { current } = this.ref;
        current.focus();
    }
    
    render() {
    	return (
            <div>
            	<p>今天阳光很明媚</p>
                <FactoryWithRef ref = { this.ref } />
            </div>
        )
    }
}

useImperactiveHandle

  • useImperactiveHandle是自定义ref对象的一个react钩子,可以将其理解为“setRef” 使我们在使用ref的时候可以自定义ref的值

先举一个简单的例子(来自简书)

function App() {
    const buttonRef = userRef(null);
    useEffect(() => {
    	console.log(buttonRef.current);
    })
    return (
    	<div className="App">
            <Button2 ref={ buttonRef }>按钮</Button2>
            <button
            className="close"
            onClick={() => {
            	buttonRef.current.x();
            }}
            >
            x
            </button>
        </div>
    )
}

const Button2 = React.forwardRef((props, ref) => {
    const realButton = createRef(null);
    const setRef = useImperativeRef;
    setRef(ref, () => {
    	return {
          x: () => {
            realButton.current.remove();
          },
          realButton: realButton
        }
    })
    return <button ref={ realButton } { ...props } />;
})

实现

下面我们开始进入正题: fusion中表单验证的一种解决方案(useImperativeHandle + forwardRef) 想必看到这里,下面的也就很简单了。

// 父组件中需要控制表单的展示
function ConfirmModel(record, refresh) {
    let form = null;
    const dialog = Dialog.show({
    	title: "",
        className: "",
        content: (<DialogContent ref={ ref => form = ref } record={ record }/>)
        ...
        onOk: () => {
        	// 在这里进行验证、调用接口
        }
    })
}

//子组件
import React, { forwardRef, useImperativeHandle, useState } from 'react';
import {Field, ...} from 'fusion'

function ConfirmModel(props, ref) {
    let field = Field.useField();
    useImperativeHandle(ref, () => {
    	submit(successCb) {
        	field.validate((errors, values) => {
            	if(errors) return;
                successCb(values);
            })
        }
    })
}
export default forwardRef(ConfirmModel);

总结: 实际开发中表单校验并不推荐该实现方式,但也应因时度世,我们主要是要学会这种思路,以备不时之需,之后的react源码分析中会介绍到这几个钩子。敬请期待 ~ ~ ~