前言
在使用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源码分析中会介绍到这几个钩子。敬请期待 ~ ~ ~