React设计模式(翻译)

170 阅读8分钟

介绍

React 开发人员可以通过使用设计模式来节省时间和精力,设计模式提供了一种使用经过测试和可信的解决方案快速解决问题方法。它们支持低耦合的内聚模块,从而帮助 React 开发人员创建可维护、可扩展且高效的应用程序。在本文中,我们将探索 React 设计模式并研究它们如何改进 React 应用程序的开发。

容器组件和UI组件

容器组件和UI组件模式是一种旨在将React代码中的展示逻辑与业务逻辑分离的模式,从而使其模块化、可测试并且遵循关注点分离原则。

大多数情况下,在 React 应用程序中,我们需要从后端/存储获取数据或计算逻辑并在 React 组件上展示该计算的结果。在这些情况下,容器组件和UI组件模式大放异彩,因为它可用于将组件分为两类,即:

  • 容器组件,充当负责数据获取或计算的组件。
  • UI组件,其工作是将获取的数据或计算值呈现在 UI(用户界面)上。

容器和展示模式的示例如下所示:

import React, { useEffect } from 'react';
import CharacterList from './CharacterList';

const StarWarsCharactersContainer:React.FC = () => {
    const [characters, setCharacters] = useState<Character>([])
    const [isLoading, setIsLoading] = useState<boolean>(false);
    const [error, setError] = useState<boolean>(false);

    const getCharacters = async () => {
        setIsLoading(true);
        try {
            const response = await fetch("https://akabab.github.io/starwars-api/api/all.json");
            const data = await response.json();
            setIsLoading(false);
            if (!data) return;
            setCharacters(data);
        } catch(err) {
            setError(true);
        } finally {
            setIsLoading(true);
        }
    };

    useEffect(() => {
        getCharacters();
    }, []);

    return <CharacterList loading={loading} error={error} characters={characters} />;
};

export default StarWarsCharactersContainer;
// the component is responsible for displaying the characters

import React from 'react';
import { Character } from './types';

interface CharacterListProps {
    loading: boolean;
    error: boolean;
    users: Character[];
}

const CharacterList: React.FC<CharacterListProps> = ({ loading, error, characters }) => {

    if (loading && !error) return <div>Loading...</div>;
    if (!loading && error) return <div>error occured.unable to load characters</div>;
    if (!characters) return null;

    return (
        <ul>
            {characters.map((user) => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
};

export default CharacterList;

与 Hooks 的组件组合

Hooks 是 React 16.8 中首次推出的一项全新功能。从那时起,他们在开发 React 应用程序中发挥了至关重要的作用。hook是基本函数,可授予功能组件访问状态和生命周期方法(以前仅可用于类组件)的功能。另一方面,hook可以专门设计来满足组件要求并具有其他用例。

我们现在可以隔离所有状态逻辑(一种需要响应状态变量的逻辑),并使用自定义hook在组件中组合或使用它。因此,代码更加模块化和可测试,因为钩子松散地绑定到组件,因此可以单独测试。

带有hook的组件组合示例如下所示:

// creating a custom hook that fetches star wars characters

export const useFetchStarWarsCharacters = () => {

    const [characters, setCharacters] = useState<Character>([])
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(false);
    const controller = new AbortController()

    const getCharacters = async () => {
        setIsLoading(true);
        try {
            const response = await fetch(
                "https://akabab.github.io/starwars-api/api/all.json", 
                {
                    method: "GET", 
                    credentials: "include",
                    mode: "cors",
                    headers: {
                        'Content-Type': 'application/json',
                        'Access-Control-Allow-Origin': '*'
                    },
                    signal: controller.signal
                }
            );
            const data = await response.json();
            setIsLoading(false);
            if (!data) return;
            setCharacters(data);
        } catch(err) {
            setError(true);
        } finally {
            setIsLoading(true);
        }
    };

    useEffect(() => {
        getCharacters();
        return () => {
            controller.abort();
        }
    }, []);

    return [
        characters,
        isLoading,
        error
    ];
};

创建自定义hook后,我们将其导入到 StarWarsCharactersContainer 组件中并使用它;

// importing the custom hook to a component and fetch the characters 

import React from 'react';
import { Character } from './types';
import { useFetchStarWarsCharacters } from './useFetchStarWarsCharacters';

const StarWarsCharactersContainer:React.FC = () => {
    const [ characters, isLoading, error ] = useFetchStarWarsCharacters();
    return <CharacterList loading={loading} error={error} characters={characters} />;
};

export default StarWarsCharactersContainer;

使用Reducer进行状态管理

大多数情况下,处理组件中的许多状态会导致许多未分组状态的问题,这可能是处理起来很麻烦且具有挑战性的。在这种情况下,Reducer模式可能是一个有用的选择。我们可以使用Reducer将状态分类为某些操作,这些操作在执行时可以更改分组的状态

此模式允许使用它的开发人员控制组件和/或hook的状态管理,让他们在发送事件时管理状态更改。

使用reducer模式的示例如下所示: ![[Pasted image 20231113150515.png]]

在上面的代码中,组件调度两个操作:

  • “登录” 操作类型会触发状态更改,该状态更改会影响三个状态值,即“loggedIn”、“user”、“token”。
  • “注销”操作只是将状态重置为其初始值。

提供者模式的数据管理

提供者模式对于数据管理非常有用,因为它利用上下文 API 通过应用程序的组件树传递数据。这种模式是一种有效的解决支柱钻井问题的方法,这一直是 React 开发中普遍关注的问题

为了实现提供者模式,我们首先创建一个提供者组件。 Provider 是 Context 对象提供给我们的一个高阶组件。我们可以利用React提供的createContext方法来构造一个Context对象。

export const ThemeContext = React.createContext(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = React.useState("light");

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

创建提供程序后,我们将使用创建的提供程序组件封装依赖于上下文 API 中的数据的组件。

为了从上下文 API 获取数据,我们调用 useContext 钩子,它接受上下文作为参数(在本例中为 ThemeContext)。

import { useContext } from 'react';
import { ThemeProvider, ThemeContext } from "../context";


const HeaderSection = () => {
  <ThemeProvider>
    <TopNav />
  </ThemeProvider>;
};


const TopNav = () => {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <div style={{ backgroundColor: theme === "light" ? "#fff" : "#000 " }}>
      ...
    </div>
  );
};

使用 HOC(高阶组件)增强组件

高阶组件将组件作为参数,并返回注入了附加数据或功能的增强组件。 React 中 HOC 的可能性是由于 React 更喜欢组合而不是继承。

高阶组件 (HOC) 模式提供了一种增加或修改组件功能的机制,促进组件重用和代码共享。

HOC 模式的示例如下所示:

import React from 'react'

const higherOrderComponent = Component => {
  return class HOC extends React.Component {
    state = {
      name: 'John Doe'
    }

    render() {
      return <Component name={this.state.name {...this.props} />
    }
 }


const AvatarComponent = (props) => {
  return (
    <div class="flex items-center justify-between">
      <div class="rounded-full bg-red p-4">
          {props.name}
      </div>
      <div>
          <p>I am a {props.description}.</p>
      </div>
    </div>
  )
}


const SampleHOC = higherOrderComponent(AvatarComponent);


const App = () => {
  return (
    <div>
      <SampleHOC description="Frontend Engineer" />
    </div>
  )
}

export default App;

在上面的代码中,由 highOrderComponent 提供 props,它将在内部使用。

复合组件

复合组件模式是一种 React 设计模式,用于管理由子组件组成的父组件。

这种模式背后的原理是将父组件分解为更小的组件,然后使用 props、上下文或其他react数据管理技术来管理这些较小组件之间的交互。

当需要创建由较小组件组成的可重用、多功能组件时,这种模式会派上用场。它使开发人员能够创建复杂的 UI 组件,这些组件可以轻松定制和扩展,同时保持清晰简单的代码结构。

复合组件模式的用例示例如下所示:

import React, { createContext, useState } from 'react';

const ToggleContext = createContext();

function Toggle({ children }) {
  const [on, setOn] = useState(false);
  const toggle = () => setOn(!on);

  return (
    <ToggleContext.Provider value={{ on, toggle }}>
      {children}
    </ToggleContext.Provider>
  );
}

Toggle.On = function ToggleOn({ children }) {
  const { on } = useContext(ToggleContext);
  return on ? children : null;
}

Toggle.Off = function ToggleOff({ children }) {
  const { on } = useContext(ToggleContext);
  return on ? null : children;
}

Toggle.Button = function ToggleButton(props) {
  const { on, toggle } = useContext(ToggleContext);
  return <button onClick={toggle} {...props} />;
}

function App() {
  return (
    <Toggle>
      <Toggle.On>The button is on</Toggle.On>
      <Toggle.Off>The button is off</Toggle.Off>
      <Toggle.Button>Toggle</Toggle.Button>
    </Toggle>
  );
}

Prop组合

这需要从几个相关的 props 创建一个对象,并将其作为单个 props 传递给组件。

这种模式允许我们清理代码并使管理 props 变得更简单,当我们想要将大量相关属性传递给组件时,它特别有用。

import React from 'react';

function P(props) {
  const { color, size, children, ...rest } = props;
  return (
    <p style={{ color, fontSize: size }} {...rest}>
      { children }
    </p>
  );
}

function App() {
  const paragraphProps = {
    color: "red",
    size: "20px",
    lineHeight: "22px"
  };
  return <P {...paragraphProps}>This is a P</P>;
}

受控输入

受控输入模式可用于处理输入字段。此模式涉及使用事件处理程序在输入字段的值发生更改时更新组件状态,以及将输入字段的当前值存储在组件状态中。

由于 React 控制组件的状态和行为,因此该模式使代码比不受控制的输入模式更具可预测性和可读性,后者不使用组件的状态,而是直接通过 DOM(文档对象模型)对其进行控制。

受控输入模式的用例示例如下所示:

import React, { useState } from 'react';

function ControlledInput() {
  const [inputValue, setInputValue] = useState('');

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <input type="text" value={inputValue} onChange={handleChange} />
  );
}

使用forwardRefs管理自定义组件

称为 ForwardRef 的高阶组件将另一个组件作为输入并输出一个传递原始组件引用的新组件。通过这样做,子组件的 ref(可用于检索底层 DOM 节点或组件实例)可供父组件访问。

当创建与第三方库或应用程序中的另一个自定义组件交互的自定义​​组件时,在工作流程中包含 ForwardRef 模式非常有帮助。通过授予对库的 DOM 节点或另一个组件的 DOM 实例的访问权限,它有助于将此类组件的控制权转移给您。

forwardRef 模式的用例示例如下所示:

import React from "react";

const CustomInput = React.forwardRef((props, ref) => (
  <input type="text" {...props} ref={ref} />
));

const ParentComponent = () => {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <CustomInput ref={inputRef} />;
};

<CustomInput/> from our component <ParentComponent/> using forwardRefs.
在上面的代码中,我们使用 forwardRefs 从组件 <ParentComponent/> 触发了另一个组件 <CustomInput/> 的焦点。

结论

我们在本文中讨论了 React 设计模式,包括高阶组件、容器呈现组件模式、复合组件、受控组件等等。通过将这些设计模式和最佳实践合并到您的 React 项目中,您可以提高代码质量,促进团队协作,并使您的应用程序更具可扩展性、灵活性和可维护性。