本周开发监控项目,我发现了很多的 React 类组件封装,发现出现了多次UI渲染的情况、代码辨识度也较差,对性能和维护都产生了挑战。这里多个场景的都是状态管理和逻辑复用需求,其实完全没有必要封装类组件。相反我通过引入 React 自定义 Hook,通过逻辑抽离的方式,不仅有效地找到了解决办法,也提高了代码复用性。
背景
通常情况下,我们习惯将业务逻辑和 UI 一起封装到组件中,然而随着项目需求的增加,某些复杂的状态管理和副作用逻辑经常需要在多个组件中复用。传统的组件封装方式在这种场景下复用性较差,且每次使用时都可能引入额外的 UI 代码,增加了维护成本。
为了解决这一问题,我们可以将一些常见的逻辑抽离出来,封装成自定义 Hook,供多个组件复用。下面我们来比较一下不使用自定义 Hooks 和使用自定义 Hooks 的代码。
场景复现对比
1、不使用自定义 Hooks
每个表单组件都需要单独管理 useState
和验证逻辑,代码会变得冗长且难以维护。
import React, { Component } from 'react';
class UserForm extends Component {
constructor(props) {
super(props);
this.state = { name: '', workcode: '' };
}
handleChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
console.log('handleSubmit:', this.state);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
name="name"
type="text"
value={this.state.name}
onChange={this.handleChange}
placeholder="Name"
/>
<input
name="workcode"
type="text"
value={this.state.workcode}
onChange={this.handleChange}
placeholder="Workcode"
/>
<button type="submit">Submit</button>
</form>
);
}
}
class AdminForm extends Component {
constructor(props) {
super(props);
this.state = { name: '', workcode: '' };
}
handleChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
console.log('handleSubmit:', this.state);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
name="name"
type="text"
value={this.state.name}
onChange={this.handleChange}
placeholder="Name"
/>
<input
name="workcode"
type="workcode"
value={this.state.workcode}
onChange={this.handleChange}
placeholder="workcode"
/>
<button type="submit">Submit</button>
</form>
);
}
}
这种不断重复地逻辑,每个表单组件都会有类似的状态管理,以及类似的逻辑操作。而且维护成本高,每次新增一个表单,开发者都需要重复编写几乎相同的代码。如果需求或逻辑变更,必须在多个地方修改代码。状态管理和表单验证逻辑完全被分散在各个组件中,不同组件之间无法共享这些逻辑。
2、使用自定义 Hooks
将表单的状态管理和逻辑提取到一个自定义 Hook 中,可以极大地简化代码,并实现逻辑的复用。
我们开发一个 useForm
自定义 Hook,用于管理表单的输入值、以及逻辑等。下面是简化的实现代码:
import React, { useState } from 'react';
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const handleChange = (event) => {
setValues({ ...values, [event.target.name]: event.target.value });
};
return { values, handleChange };
}
使用场景:
我们可以在多个表单组件中复用 useForm
,实现输入管理和逻辑的统一处理:
import React, { useState } from 'react';
import { useForm } form './component/customHooks'
function UserForm() {
const { values, handleChange } = useForm({ name: '', workcode: '' });
const handleSubmit = (event) => {
event.preventDefault();
console.log('handleSubmit:', values);
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
type="text"
value={values.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="workcode"
type="workcode"
value={values.workcode}
onChange={handleChange}
placeholder="workcode"
/>
<button type="submit">Submit</button>
</form>
);
}
function AdminForm() {
const { values, handleChange } = useForm({ name: '', workcode: '' });
const handleSubmit = (event) => {
event.preventDefault();
console.log('handleSubmit:', values);
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
type="text"
value={values.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="workcode"
type="workcode"
value={values.workcode}
onChange={handleChange}
placeholder="workcode"
/>
<button type="submit">Submit</button>
</form>
);
}
通过 useForm
,我们可以轻松管理多个表单的输入状态和逻辑,避免在每个组件中重复编写这些代码。那么自定义 Hook 到底适用哪些适用场景呢?
自定义 Hook 的使用场景
1. 封装通用的 API 逻辑
项目中多个组件需要调用同一 API,并对结果进行状态管理。以往我们需要在每个组件内重复实现状态管理逻辑,而通过自定义 Hook,可以将 API 调用和状态管理逻辑抽离出来,让组件更专注于 UI 渲染。
// 封装通用的 API 请求逻辑
function useFetchData(apiUrl) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(apiUrl);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchData();
}, [apiUrl]);
return { data, loading, error };
}
// 组件中使用自定义 Hook
function MyComponent() {
const { data, loading, error } = useFetchData('/api/data');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{JSON.stringify(data)}</div>;
}
2. 处理副作用
某些组件需要在不同的生命周期阶段执行异步操作,比如页面加载时请求数据或窗口尺寸变化时调整布局。通过 useEffect
的组合,我们将这些逻辑独立封装在 Hook 中,从而减少组件中的代码。
// 封装窗口尺寸变化的逻辑
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
// 组件中使用自定义 Hook
function LayoutComponent() {
const { width, height } = useWindowSize();
return <div>Window size: {width} x {height}</div>;
}
简洁高效,复用性还 Good~
总结
通过使用自定义 Hooks,能够有效解决 React 组件中的重复逻辑、状态管理和维护成本的问题。上述例子清晰地展示了从类组件到函数组件的转变,并通过自定义 Hooks 简化了表单处理的逻辑。对于项目,特别是需要频繁维护和扩展的项目,使用好自定义 Hooks 有着显著作用。