From 33833440e0ae394ca5f3294b5b10d87ad2d50825 Mon Sep 17 00:00:00 2001 From: afc163 Date: Thu, 5 Mar 2026 10:43:08 +0800 Subject: [PATCH] refactor: convert class components to functional components - Convert Yuan.tsx to FC with useRef + useEffect - Convert MonitorMap to FC with useState + useEffect - Convert autoHeight HOC to functional component - Convert WaterWave to FC with proper animation cleanup Co-Authored-By: Claude Opus 4.6 --- .../components/Charts/WaterWave/index.tsx | 158 +++++------ src/pages/dashboard/analysis/utils/Yuan.tsx | 40 +-- .../components/Charts/WaterWave/index.tsx | 158 +++++------ .../monitor/components/Charts/autoHeight.tsx | 57 ++-- .../monitor/components/Map/index.tsx | 267 +++++++++--------- 5 files changed, 316 insertions(+), 364 deletions(-) diff --git a/src/pages/dashboard/analysis/components/Charts/WaterWave/index.tsx b/src/pages/dashboard/analysis/components/Charts/WaterWave/index.tsx index 62ee38db..b75f723d 100644 --- a/src/pages/dashboard/analysis/components/Charts/WaterWave/index.tsx +++ b/src/pages/dashboard/analysis/components/Charts/WaterWave/index.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { useEffect, useRef, useState } from 'react'; import autoHeight from '../../../../monitor/components/Charts/autoHeight'; /* eslint no-return-assign: 0 */ @@ -12,57 +12,25 @@ export type WaterWaveProps = { percent: number; style?: React.CSSProperties; }; -class WaterWave extends Component { - state = { - radio: 1, - }; - timer: number = 0; - root: HTMLDivElement | undefined | null = null; - node: HTMLCanvasElement | undefined | null = null; - componentDidMount() { - this.renderChart(); - this.resize(); - window.addEventListener( - 'resize', - () => { - requestAnimationFrame(() => this.resize()); - }, - { - passive: true, - }, - ); - } - componentDidUpdate(props: WaterWaveProps) { - const { percent } = this.props; - if (props.percent !== percent) { - // 不加这个会造成绘制缓慢 - this.renderChart('update'); - } - } - componentWillUnmount() { - cancelAnimationFrame(this.timer); - if (this.node) { - this.node.innerHTML = ''; - } - window.removeEventListener('resize', this.resize); - } - resize = () => { - if (this.root) { - const { height = 1 } = this.props; - const { offsetWidth } = this.root.parentNode as HTMLElement; - this.setState({ - radio: offsetWidth < height ? offsetWidth / height : 1, - }); - } - }; - renderChart(type?: string) { - const { percent, color = '#1890FF' } = this.props; + +const WaterWave: React.FC = ({ + title, + color = '#1890FF', + height = 1, + percent, +}) => { + const [radio, setRadio] = useState(1); + const timerRef = useRef(0); + const rootRef = useRef(null); + const nodeRef = useRef(null); + + const renderChart = (type?: string) => { const data = percent / 100; - cancelAnimationFrame(this.timer); - if (!this.node || (data !== 0 && !data)) { + cancelAnimationFrame(timerRef.current); + if (!nodeRef.current || (data !== 0 && !data)) { return; } - const canvas = this.node; + const canvas = nodeRef.current; const ctx = canvas.getContext('2d'); if (!ctx) { return; @@ -179,47 +147,69 @@ class WaterWave extends Component { sp += 0.07; drawSin(); } - this.timer = requestAnimationFrame(render); + timerRef.current = requestAnimationFrame(render); }; render(); - } - render() { - const { radio } = this.state; - const { percent, title, height = 1 } = this.props; - return ( + }; + + const resize = () => { + if (rootRef.current) { + const { offsetWidth } = rootRef.current.parentNode as HTMLElement; + setRadio(offsetWidth < height ? offsetWidth / height : 1); + } + }; + + useEffect(() => { + renderChart(); + resize(); + const handleResize = () => { + requestAnimationFrame(resize); + }; + window.addEventListener('resize', handleResize, { passive: true }); + + return () => { + window.removeEventListener('resize', handleResize); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + renderChart('update'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [percent]); + + useEffect(() => { + return () => { + cancelAnimationFrame(timerRef.current); + }; + }, []); + + return ( +
{ - this.root = n; + style={{ + width: height, + height, + overflow: 'hidden', }} + > + +
+
-
- { - this.node = n; - }} - width={height * 2} - height={height * 2} - /> -
-
- {title && {title}} -

{percent}%

-
+ {title && {title}} +

{percent}%

- ); - } -} +
+ ); +}; + export default autoHeight()(WaterWave); diff --git a/src/pages/dashboard/analysis/utils/Yuan.tsx b/src/pages/dashboard/analysis/utils/Yuan.tsx index a541e7b4..516d59fa 100644 --- a/src/pages/dashboard/analysis/utils/Yuan.tsx +++ b/src/pages/dashboard/analysis/utils/Yuan.tsx @@ -1,33 +1,17 @@ -import React from 'react'; +import { useEffect, useRef } from 'react'; import { yuan } from '../components/Charts'; -/** 减少使用 dangerouslySetInnerHTML */ -export default class Yuan extends React.Component<{ - children: string | number; -}> { - main: HTMLSpanElement | undefined | null = null; - - componentDidMount() { - this.renderToHtml(); - } - componentDidUpdate() { - this.renderToHtml(); - } +/** 减少使用 dangerouslySetInnerHTML */ +const Yuan: React.FC<{ children: string | number }> = ({ children }) => { + const spanRef = useRef(null); - renderToHtml = () => { - const { children } = this.props; - if (this.main) { - this.main.innerHTML = yuan(children); + useEffect(() => { + if (spanRef.current) { + spanRef.current.innerHTML = yuan(children); } - }; + }, [children]); + + return ; +}; - render() { - return ( - { - this.main = ref; - }} - /> - ); - } -} +export default Yuan; diff --git a/src/pages/dashboard/monitor/components/Charts/WaterWave/index.tsx b/src/pages/dashboard/monitor/components/Charts/WaterWave/index.tsx index 90ad3eab..81f76dcb 100644 --- a/src/pages/dashboard/monitor/components/Charts/WaterWave/index.tsx +++ b/src/pages/dashboard/monitor/components/Charts/WaterWave/index.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { useEffect, useRef, useState } from 'react'; import autoHeight from '../autoHeight'; /* eslint no-return-assign: 0 */ @@ -12,57 +12,25 @@ export type WaterWaveProps = { percent: number; style?: React.CSSProperties; }; -class WaterWave extends Component { - state = { - radio: 1, - }; - timer: number = 0; - root: HTMLDivElement | undefined | null = null; - node: HTMLCanvasElement | undefined | null = null; - componentDidMount() { - this.renderChart(); - this.resize(); - window.addEventListener( - 'resize', - () => { - requestAnimationFrame(() => this.resize()); - }, - { - passive: true, - }, - ); - } - componentDidUpdate(props: WaterWaveProps) { - const { percent } = this.props; - if (props.percent !== percent) { - // 不加这个会造成绘制缓慢 - this.renderChart('update'); - } - } - componentWillUnmount() { - cancelAnimationFrame(this.timer); - if (this.node) { - this.node.innerHTML = ''; - } - window.removeEventListener('resize', this.resize); - } - resize = () => { - if (this.root) { - const { height = 1 } = this.props; - const { offsetWidth } = this.root.parentNode as HTMLElement; - this.setState({ - radio: offsetWidth < height ? offsetWidth / height : 1, - }); - } - }; - renderChart(type?: string) { - const { percent, color = '#1890FF' } = this.props; + +const WaterWave: React.FC = ({ + title, + color = '#1890FF', + height = 1, + percent, +}) => { + const [radio, setRadio] = useState(1); + const timerRef = useRef(0); + const rootRef = useRef(null); + const nodeRef = useRef(null); + + const renderChart = (type?: string) => { const data = percent / 100; - cancelAnimationFrame(this.timer); - if (!this.node || (data !== 0 && !data)) { + cancelAnimationFrame(timerRef.current); + if (!nodeRef.current || (data !== 0 && !data)) { return; } - const canvas = this.node; + const canvas = nodeRef.current; const ctx = canvas.getContext('2d'); if (!ctx) { return; @@ -179,47 +147,69 @@ class WaterWave extends Component { sp += 0.07; drawSin(); } - this.timer = requestAnimationFrame(render); + timerRef.current = requestAnimationFrame(render); }; render(); - } - render() { - const { radio } = this.state; - const { percent, title, height = 1 } = this.props; - return ( + }; + + const resize = () => { + if (rootRef.current) { + const { offsetWidth } = rootRef.current.parentNode as HTMLElement; + setRadio(offsetWidth < height ? offsetWidth / height : 1); + } + }; + + useEffect(() => { + renderChart(); + resize(); + const handleResize = () => { + requestAnimationFrame(resize); + }; + window.addEventListener('resize', handleResize, { passive: true }); + + return () => { + window.removeEventListener('resize', handleResize); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + renderChart('update'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [percent]); + + useEffect(() => { + return () => { + cancelAnimationFrame(timerRef.current); + }; + }, []); + + return ( +
{ - this.root = n; + style={{ + width: height, + height, + overflow: 'hidden', }} + > + +
+
-
- { - this.node = n; - }} - width={height * 2} - height={height * 2} - /> -
-
- {title && {title}} -

{percent}%

-
+ {title && {title}} +

{percent}%

- ); - } -} +
+ ); +}; + export default autoHeight()(WaterWave); diff --git a/src/pages/dashboard/monitor/components/Charts/autoHeight.tsx b/src/pages/dashboard/monitor/components/Charts/autoHeight.tsx index c5af01b7..9c9fa3c4 100644 --- a/src/pages/dashboard/monitor/components/Charts/autoHeight.tsx +++ b/src/pages/dashboard/monitor/components/Charts/autoHeight.tsx @@ -1,8 +1,6 @@ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; -export type IReactComponent

= - | React.ComponentClass

- | React.ClassicComponentClass

; +export type IReactComponent

= React.ComponentClass

| React.FC

; function computeHeight(node: HTMLDivElement) { const { style } = node; @@ -37,42 +35,35 @@ type AutoHeightProps = { function autoHeight() { return

( WrappedComponent: React.ComponentClass

| React.FC

, - ): React.ComponentClass

=> { - class AutoHeightComponent extends React.Component

{ - state = { - computedHeight: 0, - }; + ): React.FC

=> { + const AutoHeightComponent: React.FC

= (props) => { + const [computedHeight, setComputedHeight] = useState(0); + const rootRef = useRef(null); - root: HTMLDivElement | null = null; - - componentDidMount() { - const { height } = this.props; - if (!height && this.root) { - let h = getAutoHeight(this.root); - this.setState({ computedHeight: h }); + useEffect(() => { + const { height } = props; + if (!height && rootRef.current) { + let h = getAutoHeight(rootRef.current); + setComputedHeight(h); if (h < 1) { - h = getAutoHeight(this.root); - this.setState({ computedHeight: h }); + h = getAutoHeight(rootRef.current); + setComputedHeight(h); } } - } + }, [props]); + + const { height } = props; + const h = height || computedHeight; - handleRoot = (node: HTMLDivElement) => { - this.root = node; - }; + return ( +

+ {h > 0 && } +
+ ); + }; - render() { - const { height } = this.props; - const { computedHeight } = this.state; - const h = height || computedHeight; - return ( -
- {h > 0 && } -
- ); - } - } return AutoHeightComponent; }; } + export default autoHeight; diff --git a/src/pages/dashboard/monitor/components/Map/index.tsx b/src/pages/dashboard/monitor/components/Map/index.tsx index df155e1f..34be85f0 100644 --- a/src/pages/dashboard/monitor/components/Map/index.tsx +++ b/src/pages/dashboard/monitor/components/Map/index.tsx @@ -1,6 +1,6 @@ import { PageLoading } from '@ant-design/pro-components'; import { HeatmapLayer, MapboxScene, PointLayer } from '@antv/l7-react'; -import * as React from 'react'; +import { useEffect, useState } from 'react'; const colors = [ '#eff3ff', @@ -11,143 +11,140 @@ const colors = [ '#2171b5', '#084594', ]; -export default class MonitorMap extends React.Component { - state = { - data: null, - grid: null, - loading: false, - }; - public async componentDidMount() { - const [geoData, gridData] = await Promise.all([ - fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/c5dba875-b6ea-4e88-b778-66a862906c93.json', - ).then((d) => d.json()), - fetch( - 'https://gw.alipayobjects.com/os/bmw-prod/8990e8b4-c58e-419b-afb9-8ea3daff2dd1.json', - ).then((d) => d.json()), - ]); - this.setState({ - data: geoData, - grid: gridData, - loading: true, - }); - } +export default function MonitorMap() { + const [data, setData] = useState[] | null>(null); + const [grid, setGrid] = useState[] | null>(null); + const [loading, setLoading] = useState(false); - public render() { - const { data, grid, loading } = this.state; - return loading === false ? ( - - ) : ( - - {grid && ( - - )} - {data && [ - { + async function fetchData() { + const [geoData, gridData] = await Promise.all([ + fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/c5dba875-b6ea-4e88-b778-66a862906c93.json', + ).then((d) => d.json()), + fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/8990e8b4-c58e-419b-afb9-8ea3daff2dd1.json', + ).then((d) => d.json()), + ]); + setData(geoData); + setGrid(gridData); + setLoading(true); + } + fetchData(); + }, []); + + return loading === false ? ( + + ) : ( + + {grid && ( + + )} + {data && [ + , - { - return v > 2000; + size: { + field: 'cum_conf', + type: 'log', }, - }} - size={{ - values: 12, - }} - style={{ - opacity: 1, - strokeOpacity: 1, - strokeWidth: 0, - }} - />, - ]} - - ); - } + }, + }} + color={{ + field: 'cum_conf', + values: colors, + }} + shape={{ + values: 'circle', + }} + active={{ + option: { + color: '#0c2c84', + }, + }} + size={{ + field: 'cum_conf', + values: [0, 30], + }} + style={{ + opacity: 0.8, + }} + />, + { + return v > 2000; + }, + }} + size={{ + values: 12, + }} + style={{ + opacity: 1, + strokeOpacity: 1, + strokeWidth: 0, + }} + />, + ]} + + ); }