发布于 2026-01-06 0 阅读
0

React 设计模式(第二部分)

React 设计模式(第二部分)

替代文字

本文最初发表于 bugfender.com:React设计模式(第 2 部分)

本文是 React 设计模式系列文章的第二部分。如果您错过了第一部分,请前往阅读第一部分。

这次我们要讨论的是Context模式、Presentational and Container Components模式和Compound Components模式。

语境

根据React 文档

上下文提供了一种在组件树中传递数据的方法,而无需在每一层手动传递 props。

简单来说,如果你的全局状态需要经过多个组件层级,你可以使用 `require` Context。例如:如果你有一个theme影响所有组件的状态,`require`Context将简化流程。

注意:使用时需要注意一个潜在的问题Context:它会降低组件的可重用性。Context数据仅在作用域内可用Provider,因此无法在作用域外使用Provider。我找到一个很棒的视频,解释了这个问题,并告诉您如何避免“螺旋钻取”。

让我们来看一个 Context 实际应用的例子:

import React, { useContext, createContext } from "react";
import "./styles.css";
let data = {
  title: "Welcome"
};
const Context = createContext();

export default function App() {
  return (
    <Context.Provider value={data}>
      <div className="App">
        <Card />
      </div>
    </Context.Provider>
  );
}

const Card = () => {
  return (
    <div className="card">
      <CardItem />
    </div>
  );
};

const CardItem = () => {
  return (
    <div className="CardItem">
      <Title />
    </div>
  );
};

const Title = () => {
  const data = useContext(Context);
  return <h1>{data.title}</h1>;
};
Enter fullscreen mode Exit fullscreen mode

正如我们在这个(简单的)示例中看到的,我们有三层组件,并且只data.title在最后一层使用了 `<component>` 标签。这样,我们就不需要将 props 传递给所有层级了。

关于上下文语法的一些提示

我在使用上下文时总是采用这种语法。但是,当我再次编写它时,发现了一些问题:

  • 对于“静态数据”(例如示例中的数据),我们实际上不需要这个Provider函数,我们可以自己实现它:
let data = {
  title: "Welcome"
};
const Context = createContext(data);

export default function App() {
  return (
    <div className="App">
      <Card />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

另一方面,我们可以使用 `the`Customer代替 ` useContext,像这样:

const Title = () => {
  return (<Context.Consumer>
            {(data) => <h1>{data.title}</h1>}
        </Context.Consumer>);
};
Enter fullscreen mode Exit fullscreen mode

展示和容器组件

这些组件(也称为组件Smart And Dumb Components)是 React 中最知名的模式之一。React 官方文档中没有提及它们,但 Dan Abramov 的文章提供了很好的指导。

简单来说,Presentational And Container Components就是将业务逻辑组件与用户界面视图分离。

我们来看另一种情况:

  • 我们需要构建一个 Card 组件。
  • 卡片内部还有三个其他组件:  Title, Image 和 Button
  • 点击按钮后,图片会发生变化。

在开始编写组件之前,我们先创建两个文件夹:“Presentational”和“Container”。现在,让我们来构建三个Presentational组件:

Title.js:

import React from "react";
export default function Title(props) {
  const { children, ...attributes } = props;
  return <h1 {...attributes}>{children}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Image.js:

import React from "react";
export default function Image(props) {
  const { src, alt } = props || {};
  return <img src={src} alt={alt} />;
}
Enter fullscreen mode Exit fullscreen mode

Button.js:

import React from "react";
export default function Button(props) {
  const { children, ...attributes } = props;
  return <button {...attributes}>{children}</button>;
}
Enter fullscreen mode Exit fullscreen mode

最后,我们可以构建容器文件夹组件,即Card.

Card.js:

import React, { useEffect, useState } from "react";
import Title from "../Presentational/Title";
import Image from "../Presentational/Image";
import Button from "../Presentational/Button";

export default function Card() {
  const [card, setCard] = useState({});
  const [srcIndex, setSrcIndex] = useState(0);

  useEffect(() => {
    setCard({
      title: "Card Title",
      image: {
        imagesArray: [
          "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTh87QN4DkF7s92IFSfm7b7S4IR6kTdzIlhbw&usqp=CAU",
          "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRjFnHdaH1i1m_xOaJfXTyq4anRFwRyCg1p1Q&usqp=CAU"
        ],
        alt: "card image"
      }
    });
  }, []);
  const { image } = card;
  const changeImage = () =>
    setSrcIndex((index) =>
      index < image.imagesArray.length - 1 ? index + 1 : index - 1
    );
  return (
    <div className="card">
      <Title className="title-black">{card.title && card.title}</Title>
      <Image
        src={image && image.imagesArray[srcIndex]}
        alt={image && image.alt}
      />
      <Button onClick={changeImage}>Change Picture</Button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

如果你想查看完整代码,请点击这里

注意!你们中的许多人可能想知道为什么要分成不同的部分。直接把它们写在同一个部分里不就行了Card吗?

当我们把组件分离出来后,就可以在任何地方重用它们。但更重要的是,这样更容易实现其他模式,比如 `<T>`HOC或 `<T>` Render Props

化合物成分

在我看来,这是最难理解的复杂模式之一,但我会尽量用最简单的方式来解释它。

当我们讨论 `<div>`和`<div>` 时Compound Components,最简单的方法是从 HTML 的角度来思考它们。你可以把它们看作是一组具有基本功能的组件。它们的状态可以由全局管理(例如 `<div>`模式),也可以由容器管理(例如 `<div>`模式)。selectoptioncontextpresentational and container

Compound components实际上,它们兼具这两者的特点。就好像它们各自拥有自己的领地,并在领地内部进行管理一样。

我们来看下一个场景:

  • 我们需要开发 Select 和 Option组件。
  • 我们希望画面 Option 生动,色彩丰富。
  • 颜色 Option 会影响 Select 颜色。

我们来看一个例子:

App.js

import React from "react";
import "./styles.css";
import Select from "./Select";
import Option from "./Option";

export default function App() {
  return (
    <div>
      <Select>
        <Option.Blue>option 1</Option.Blue>
        <Option.Red>option 2</Option.Red>
        <Option>option 3</Option>
      </Select>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • 渲染 App 和 Select 组件 Option 。
  • Option.Blue 并且 Option.Red 是“颜色分量”。

Option.js

sdsdimport React, { useEffect } from "react";

function Option(props) {
  const { children, style, value, setStyle } = props;
  useEffect(() => {
    if (setStyle) setStyle({ backgroundColor: style.backgroundColor });
  }, [setStyle, style]);
  return (
    <option value={value} style={style}>
      {children}
    </option>
  );
}

Option.Blue = function (props) {
  props.style.backgroundColor = "blue";
  return Option(props);
};

Option.Red = function (props) {
  props.style.backgroundColor = "red";
  return Option(props);
};
export default Option;
Enter fullscreen mode Exit fullscreen mode
  • Option.Blue 这里你可以看到`and` 的实现 Option.Red。显而易见,我们渲染 Option 组件并向 props 添加一个属性。
  • 该功能 setStyle 来自[此处应填写来源信息 Select]。它用于将选择框颜色更改为所选选项的颜色。

选择.js

import React, { useState } from "react";

export default function Select(props) {
  const { children } = props;
  const [style, setStyle] = useState({});

  const findOptionActive = (e) => {
    const index = e.target.value * 1;
    const optionStyle = { ...e.nativeEvent.target[index].style };
    if (optionStyle) setStyle({ backgroundColor: optionStyle.backgroundColor });
  };

  const childrenWithProps = React.Children.map(children, (child, index) => {
    return React.cloneElement(child, {
      ...child.props,
      value: index,
      setStyle:
        index === 0 && Object.keys(style).length === 0 ? setStyle : null,
      style: { backgroundColor: "white" }
    });
  });

  return (
    <select onChange={findOptionActive} style={style}>
      {childrenWithProps}
    </select>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • 现在,我们有一个带有以下属性的选择函数 onChange style
  • findOptionActive 获取选项的样式并相应地更改下拉列表的样式。
  • 真正的魔法发生在 `get` 方法中 childrenWithProps。通常情况下,当 Select `get` 方法接收到 `get` 属性时children,我们无法访问子属性——但借助 `get` 和 `get` 方法React.Children , React.cloneElement 我们可以做到这一点。正如你所看到的,我们可以将 `get`  value、  setStyle`get` 和 style `get` 作为属性传递。

要获取完整代码,请点击这里。

这个练习能让你得到很好的练习机会,如果你想自己尝试一下(也许可以用另一种图案),请在下面的评论中添加你的解决方案。

结论

本文旨在向您展示 React 中的不同设计模式。您完全可以不使用任何这些模式,但对于任何框架或语言的开发者来说,了解设计模式都很有益处,这有助于他们在面对新的代码库时理解不同的语法层次。

希望您喜欢这篇教程并有所收获。如果您知道其他模式或对文章中提到的任何主题有更多了解,请在下方留言。

文章来源:https://dev.to/tomeraitz/react-design-patterns-part-2-53bl