虽然React是世界上最流行和最常用的前端框架之一,但许多开发人员在重构代码以提高重用性时仍在挣扎。如果你曾发现自己在整个React应用中重复相同的代码片段,那么你就来对了文章。
在本教程中,我们将向你介绍三个最常见的指标,说明是时候建立一个可重用的React组件了。然后,我们将通过构建一个可重用的布局和两个令人兴奋的React钩子,继续看一些实用的演示。
当你读完时,你将能够自己弄清楚什么时候适合创建可重用的React组件,以及如何做到这一点。
本文假设你对React和React钩子有基本了解。如果你想了解这些主题,我建议你查看 "React入门"指南和 "React钩子入门"。
可重用的React组件的三大指标
首先,让我们看看什么时候你可能想这样做的一些迹象。
重复创建具有相同CSS样式的包装器
我最喜欢的知道何时创建可重用组件的标志是重复使用相同的CSS样式。现在,你可能会想,"等一下:为什么我不简单地给共享相同CSS样式的元素分配相同的类名呢?" 你说得很对。每次不同组件中的一些元素共享相同的样式时,创建可重复使用的组件并不是一个好主意。事实上,这可能会引入不必要的复杂性。所以你要再问自己一件事:这些常用的样式元素是包装器吗?
例如,考虑下面的登录和注册页面。
// Login.js
import './common.css';
function Login() {
return (
<div className='wrapper'>
<main>
{...}
</main>
<footer className='footer'>
{...}
</footer>
</div>
);
}
// SignUp.js
import './common.css';
function Signup() {
return (
<div className='wrapper'>
<main>
{...}
</main>
<footer className='footer'>
{...}
</footer>
</div>
);
}
同样的样式被应用于每个组件的容器(<div> 元素)和页脚。所以在这种情况下,你可以创建两个可重用的组件--<Wrapper /> 和<Footer /> --并把它们的孩子作为一个道具来传递。例如,登录组件可以被重构如下。
// Login.js
import Footer from "./Footer.js";
function Login() {
return (
<Wrapper main={{...}} footer={<Footer />} />
);
}
因此,你不再需要在多个页面中导入common.css ,也不再需要创建相同的<div> 元素来包装一切。
事件监听器的重复使用
要给一个元素附加一个事件监听器,你可以像这样在useEffect() 里面处理它。
// App.js
import { useEffect } from 'react';
function App() {
const handleKeydown = () => {
alert('key is pressed.');
}
useEffect(() => {
document.addEventListener('keydown', handleKeydown);
return () => {
document.removeEventListener('keydown', handleKeydown);
}
}, []);
return (...);
}
或者你可以像这样直接在你的JSX里面做,就像下面的按钮组件所演示的那样。
// Button.js
function Button() {
return (
<button type="button" onClick={() => { alert('Hi!')}}>
Click me!
</button>
);
};
当你想给document 或window 添加一个事件监听器时,你就必须采用第一种方法。然而,你可能已经意识到,第一种方法需要更多的代码,需要使用useEffect() 、addEventListener() 和removeEventListener() 。所以在这种情况下,创建一个自定义钩子将使你的组件更加简洁。
使用事件监听器有四种可能的情况。
- 同一事件监听器,同一事件处理程序
- 相同的事件监听器,不同的事件处理程序
- 不同的事件监听器,相同的事件处理程序
- 不同的事件监听器,不同的事件处理程序
在第一种情况下,你可以创建一个同时定义了事件监听器和事件处理程序的钩子。考虑一下下面这个钩子。
// useEventListener.js
import { useEffect } from 'react';
export default function useKeydown() {
const handleKeydown = () => {
alert('key is pressed.');
}
useEffect(() => {
document.addEventListener('keydown', handleKeydown);
return () => {
document.removeEventListener('keydown', handleKeydown);
}
}, []);
};
然后你可以在任何组件中使用这个钩子,如下所示。
// App.js
import useKeydown from './useKeydown.js';
function App() {
useKeydown();
return (...);
};
对于其他三种情况,我建议创建一个钩子,接收一个事件和一个事件处理函数作为道具。例如,我将把keydown 和handleKeydown 作为道具传递给我的自定义钩子。考虑一下下面这个钩子。
// useEventListener.js
import { useEffect } from 'react';
export default function useEventListener({ event, handler} ) {
useEffect(() => {
document.addEventListener(event, props.handler);
return () => {
document.removeEventListener(event, props.handler);
}
}, []);
};
然后你可以在任何组件中使用这个钩子,如下所示。
// App.js
import useEventListener from './useEventListener.js';
function App() {
const handleKeydown = () => {
alert('key is pressed.');
}
useEventListener('keydown', handleKeydown);
return (...);
};
重复使用相同的GraphQL脚本
当涉及到使GraphQL代码可重复使用时,你真的不需要寻找标志。对于复杂的应用程序,查询或突变的GraphQL脚本很容易占用30-50行的代码,因为有许多属性需要请求。如果你使用同一个GraphQL脚本不止一次或两次,我认为它应该有自己的自定义钩子。
考虑一下下面的例子。
import { gql, useQuery } from "@apollo/react-hooks";
const GET_POSTS = gql`
query getPosts {
getPosts {
user {
id
name
...
}
emojis {
id
...
}
...
}
`;
const { data, loading, error } = useQuery(GET_POSTS, {
fetchPolicy: "network-only"
});
你应该为这个特定的API创建一个React钩子,而不是在每个从后端请求帖子的页面上重复这段代码。
import { gql, useQuery } from "@apollo/react-hooks";
function useGetPosts() {
const GET_POSTS = gql`{...}`;
const { data, loading, error } = useQuery(GET_POSTS, {
fetchPolicy: "network-only"
});
return [data];
}
const Test = () => {
const [data] = useGetPosts();
return (
<div>{data?.map(post => <h1>{post.text}</h1>)}</div>
);
};
构建出三个可重复使用的React组件
现在我们已经看到了一些常见的迹象,即何时创建一个新的组件,你可以在你的react应用程序中共享,让我们把这些知识付诸实践,建立三个实用的演示。
1.布局组件
React通常用于构建复杂的网络应用。这意味着需要用React开发大量的页面,我怀疑一个应用的每个页面都会有不同的布局。例如,一个由30个页面组成的网络应用程序通常使用不到五个不同的布局。因此,建立一个灵活的、可重复使用的布局,可以在许多不同的页面中利用,这是至关重要的。这将为你节省非常多的代码行,从而节省大量的时间。
考虑一下下面的React功能组件。
// Feed.js
import React from "react";
import style from "./Feed.module.css";
export default function Feed() {
return (
<div className={style.FeedContainer}>
<header className={style.FeedHeader}>Header</header>
<main className={style.FeedMain}>
{
<div className={style.ItemList}>
{itemData.map((item, idx) => (
<div key={idx} className={style.Item}>
{item}
</div>
))}
</div>
}
</main>
<footer className={style.FeedFooter}>Footer</footer>
</div>
);
}
const itemData = [1, 2, 3, 4, 5];
这是一个典型的网页,它有一个<header> 、一个<main> 和一个<footer> 。如果还有30个这样的网页,你会很容易厌倦反复编写HTML标签和反复应用相同的样式。
相反,你可以创建一个布局组件,接收<header>,<main> 和<footer> 作为道具,如下面的代码。
// Layout.js
import React from "react";
import style from "./Layout.module.css";
import PropTypes from "prop-types";
export default function Layout({ header, main, footer }) {
return (
<div className={style.Container}>
<header className={style.Header}>{header}</header>
<main className={style.Main}>{main}</main>
<footer className={style.Footer}>{footer}</footer>
</div>
);
}
Layout.propTypes = {
main: PropTypes.element.isRequired,
header: PropTypes.element,
footer: PropTypes.element
};
这个组件不需要<header> 和<footer> 。因此,你可以对页面使用相同的布局,无论它们是否包含页眉或页脚。
使用这个布局组件,你可以把你的feed页面变成一个更复杂的代码块。
// Feed.js
import React from "react";
import Layout from "./Layout";
import style from "./Feed.module.css";
export default function Feed() {
return (
<Layout
header={<div className={style.FeedHeader}>Header</div>}
main={
<div className={style.ItemList}>
{itemData.map((item, idx) => (
<div key={idx} className={style.Item}>
{item}
</div>
))}
</div>
}
footer={<div className={style.FeedFooter}>Footer</div>}
/>
);
}
const itemData = [1, 2, 3, 4, 5];
继续阅读《创建可重用的React组件的实用指南》,请点击SitePoint。
