本周开发监控项目,我发现了很多的 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}><inputname="name"type="text"value={this.state.name}onChange={this.handleChange}placeholder="Name"/><inputname="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}><inputname="name"type="text"value={this.state.name}onChange={this.handleChange}placeholder="Name"/><inputname="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}><inputname="name"type="text"value={values.name}onChange={handleChange}placeholder="Name"/><inputname="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}><inputname="name"type="text"value={values.name}onChange={handleChange}placeholder="Name"/><inputname="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 有着显著作用。