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>;
};
正如我们在这个(简单的)示例中看到的,我们有三层组件,并且只data.title在最后一层使用了 `<component>` 标签。这样,我们就不需要将 props 传递给所有层级了。
关于上下文语法的一些提示
我在使用上下文时总是采用这种语法。但是,当我再次编写它时,发现了一些问题:
- 对于“静态数据”(例如示例中的数据),我们实际上不需要这个
Provider函数,我们可以自己实现它:
let data = {
title: "Welcome"
};
const Context = createContext(data);
export default function App() {
return (
<div className="App">
<Card />
</div>
);
}
另一方面,我们可以使用 `the`Customer代替 ` useContext,像这样:
const Title = () => {
return (<Context.Consumer>
{(data) => <h1>{data.title}</h1>}
</Context.Consumer>);
};
展示和容器组件
这些组件(也称为组件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>;
}
Image.js:
import React from "react";
export default function Image(props) {
const { src, alt } = props || {};
return <img src={src} alt={alt} />;
}
Button.js:
import React from "react";
export default function Button(props) {
const { children, ...attributes } = props;
return <button {...attributes}>{children}</button>;
}
最后,我们可以构建容器文件夹组件,即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>
);
}
如果你想查看完整代码,请点击这里。
注意!你们中的许多人可能想知道为什么要分成不同的部分。直接把它们写在同一个部分里不就行了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>
);
}
- 渲染
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;
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>
);
}
- 现在,我们有一个带有以下属性的选择函数
onChangestyle。 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
