react 高阶组件hoc使用
1. 为什么使用高阶组件
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。
具体而言,高阶组件是参数为组件,返回值为新组件的函数。
原因:组件中出现重复的部分,可以使用高阶组件写重复的部分,再将和目标组件包裹,如下图所示
2. 具体使用
使用情景:在使用konva库的transformer时,对图形的拖拽、变形部分其实都是重复的代码,需要将重复的代码封装起来。
2.1原代码:
import React, { useRef, useEffect } from 'react';
import { Text, Transformer } from 'react-konva';
const KonvaText = (props: any) => {
const { shapeProps, isSelected, handleSelected, onChange } = props;
const textRef = useRef<any>();
const trRef = useRef<any>();
useEffect(() => {
if (isSelected) {
// we need to attach transformer manually
console.log('text isselected');
trRef.current.nodes([textRef.current]);
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
return (
<React.Fragment>
<Text
onClick={handleSelected}
onTap={handleSelected}
draggable
{...shapeProps}
ref={textRef}
onDragEnd={(e) => {
onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y(),
});
}}
onTransformEnd={(e) => {
const node = textRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
// we will reset it back
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
// set minimal value
width: Math.max(5, node.width() * scaleX),
height: Math.max(node.height() * scaleY),
});
}}></Text>
{isSelected && (
<Transformer
ref={trRef}
{...shapeProps}
boundBoxFunc={(oldBox, newBox) => {
// limit resize
if (newBox.width < 5 || newBox.height < 5) {
return oldBox;
}
const { width, height } = newBox;
// console.log('width', width);
// console.log('height', height);
return newBox;
}}
/>
)}
</React.Fragment>
);
};
export default KonvaText;
<KonvaText
key={text.id}
shapeProps={text}
isSelected={text.id === selectedId}
handleSelected={() => {
setSelectedId(text.id);
}}
onChange={(newAttrs: any) => {
const textlist = texts.slice();
textlist[i] = newAttrs;
setTexts(textlist);
}}
/>
))}
2.2 使用hoc封装后
注意事项:因为ref并不是props的一部分,所以需要使用React.forwardRef
来获取传递给它的 ref
import React, { useRef, useEffect } from 'react';
import { Transformer } from 'react-konva';
export const withTransform = (WrappedComponent: any) => {
const Inner = (props: {
isSelected: boolean;
handleSelected: () => void;
shapeProps: any; // 图形的属性
onChange: (newAttrs: any) => void;
}) => {
const { isSelected = false, handleSelected, shapeProps, onChange } = props;
const eleRef = useRef<any>();
const trRef = useRef<any>();
useEffect(() => {
if (isSelected && trRef) {
// @ts-ignore
trRef.current.nodes([eleRef.current]);
// @ts-ignore
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
const handleDragStart = () => {
handleSelected();
};
const handleDragEnd = (e: any) => {
onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y(),
});
};
const handleTransformEnd = (e: any) => {
// konva中transform只是改变了node的scale但并没有改变它的宽和高,所以我们需要改变宽和高,并保持scale不变,继续为1
const node = eleRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
// we will reset it back
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
width: Math.max(20, node.width() * scaleX),
height: Math.max(10, node.height() * scaleY),
});
};
return (
<>
<WrappedComponent
onDragEnd={handleDragEnd}
onTransformEnd={handleTransformEnd}
ref={eleRef}
isSelected={isSelected}
handleSelected={handleSelected}
onChange={onChange}
shapeProps={shapeProps}
/>
{isSelected && (
<Transformer
ref={trRef}
boundBoxFunc={(oldBox, newBox) => {
if (newBox.width < 20 || newBox.height < 10) {
return oldBox;
}
const { width, height } = newBox;
return newBox;
}}
/>
)}
</>
);
};
return Inner;
};
第二个参数 ref 只在使用 React.forwardRef
定义组件时存在。常规函数和 class 组件不接收 ref
参数,且 props
中也不存在 ref
。
import React from 'react';
import { Text } from 'react-konva';
import { withTransform } from '../hooks';
interface KonvaTextProps {
shapeProps: any; // konva的Text组件的属性对象
handleSelected: () => void;
onDragEnd: (e: any) => void;
onTransformEnd: (e: any) => void;
}
const KonvaText = React.forwardRef((props: KonvaTextProps, ref: any) => {
const { shapeProps, handleSelected, onDragEnd, onTransformEnd } = props;
return (
<React.Fragment>
<Text
onClick={handleSelected}
onTap={handleSelected}
draggable
ref={ref}
onDragEnd={onDragEnd}
onTransform={onTransformEnd}
{...shapeProps}></Text>
</React.Fragment>
);
});
export default withTransform(KonvaText);
<KonvaText
key={text.id}
shapeProps={text}
isSelected={text.id === selectedId}
handleSelected={() => {
setSelectedId(text.id);
}}
onChange={(newAttrs: any) => {
const textlist = texts.slice();
textlist[i] = newAttrs;
setTexts(textlist);
}}
/>