创建可重用的React组件的实用指南

108 阅读6分钟

A Practical Guide to Creating Reusable React Components

虽然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>
  );
};

当你想给documentwindow 添加一个事件监听器时,你就必须采用第一种方法。然而,你可能已经意识到,第一种方法需要更多的代码,需要使用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 (...);
};

对于其他三种情况,我建议创建一个钩子,接收一个事件和一个事件处理函数作为道具。例如,我将把keydownhandleKeydown 作为道具传递给我的自定义钩子。考虑一下下面这个钩子。

// 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