React 设计模式(第一部分)
本文最初发表于 bugfender.com:React设计模式(第 1 部分)。
经过几个月的 React 学习,我特别关注的一个主题是设计模式。在本文中,我将分享我的主要发现。
注意:有些模式侧重于状态管理概念,但我们可以避免Redux, Mobx使用第三方状态管理工具,因为它们与本文的主题无关。
渲染属性
React 文档非常丰富:
“渲染属性”是指一种在 React 组件之间共享代码的技术,它使用一个值为函数的属性。
简单来说,它就是一个带有函数值的 prop。这个函数是一个需要渲染的组件。你可能在以下地方见过它React Router:
<Route
path='/about'
render={(props) => (
<About {...props} isLoad={true} />
)}
/>
这种模式的主要目的是更新同级组件的属性。它使组件更易于重用,并帮助我们更轻松地实现“关注点分离”。
我们以以下场景为例:
- 我们需要开发一个
Form组件。 - 在里面
From我们有p和input。 - 这
input是用户输入的内容。 - 显示
p用户输入的内容。
我们可以简单地创建类似这样的东西:
import React, { useState } from "react";
export default function Input(props) {
return (
<>
<input
type="text"
value={props.value}
onChange={props.onChange}
/>
</>
);
}
export default function Form() {
const [value, setValue] = useState("");
return (
<form>
<Input onChange={e => setValue(e.target.value)}/>
<p>{value}</p>
</form>
);
}
这种方法存在两个问题:
1. 在这种情况下我们不使用“关注隔膜”的概念,因为 Input 应该控制的是 Value 而不是 Form。
2. 我们的组件可重复使用性和灵活性不高。
我们可以重构代码,并像这样使用渲染属性:
import React, { useState } from "react";
function Input(props) {
const [value, setValue] = useState("");
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
{props.render && props.render(value)}
</>
);
}
export default function Form() {
return (
<form>
<Input render={(value) => <p>{value}</p>} />
</form>
);
}
这样,Input组件就能控制值,而且更具可重用性(可以用不同的元素实现相同的功能)。
HOC - 高阶组件
Higher-Order Components本质上,它是一个函数,接收一个组件作为参数,并返回一个包含特定业务逻辑的新组件。你可能在 Redux 中见过它:
export default connect(mapStateToProps , mapDispatchToProps)(From);
借助此功能Higher-Order Components,您可以为应用程序的公共(全局)函数编写单独的功能,并在项目中的不同组件上重用它。
我们来看另一个场景:
- 我们需要开发两个
menu组件。 - 在第一个组件中,我们有一个
button需要阻止菜单点击事件的组件。 - 第二个组件也是一个
button,但这次我们需要 处理 菜单点击事件。
问题是我们需要两种菜单——一种带有stopPropagation功能,另一种不带功能。
我们可以Higher-Order Components这样使用:
import React from "react";
import "./style.css";
function stopPropagation(WrappedComponent) {
return function(){
const handleClick = event => {
event.stopPropagation();
WrappedComponent.handleClick()
};
return <WrappedComponent onClick={handleClick} />;
}
}
function Button(props){
const handleClick = () => console.log("button clicked!");
Button.handleClick = handleClick;
return <button onClick={props.onClick || handleClick}>Click Me</button>;
}
function Menu(props) {
const openMenu = () => console.log("menu opened!");
return (
<div onClick={openMenu} className="menu">
<h1>Menu</h1>
{props.children}
</div>
);
}
export default function App() {
const ButtonPropagation = stopPropagation(Button);
return (
<div>
<Menu>
<ButtonPropagation />
</Menu>
<Menu>
<Button />
</Menu>
</div>
);
}
让我们来分析一下这段代码:
- 该
App组件读取我们提到的两个值Menus。 - 该
Menu组件读取标题和子元素(在本例中为Button)。 Button有一个按钮元素,带有点击事件。**handleClick这是该事件的基本功能。** 我们需要使用导出此函数Button.handleClick= handleClick(在类组件中,您可以使用static)。- 这
stopPropagation是高阶组件。它接收一个组件(Button在我们的例子中),并将具有新能力的组件(在我们的例子中stopPropagation)发送回去。
这是一个简单的用法示例Higher-Order Components。我们可以直接使用它stopPropagation,而无需在不同的组件中重复编写代码。更重要的是,我们还可以创建其他“按钮”高阶组件,例如 preventDefault 和 queueClick。
好了,文章第一部分就到这里。在第二部分,我将讨论Context模式、Presentational and Container Components模式和compound components模式。
感谢阅读。希望您喜欢这篇教程并有所收获。如果您有任何补充,请留言。如果您想了解更多信息,以下是一些关于该主题的优秀资源:
- 掌握 React 设计模式 🔥(渲染属性和高阶组件)
render prop- 在本视频中,您将找到有关渲染属性和高阶组件的解释HOC。 - React Context & Hooks 教程- 这是一个包含 21 个视频的播放列表,解释了 React Context 和 Hooks 中的 React Context(其中一些视频解释了 Hooks 的一般概念 - 如果您了解 Hooks,可以跳过它们)。
- React 设计模式:展示型组件和容器组件- 本视频解释了什么是展示型组件和容器组件(也称为“智能组件和哑组件”)。
- 使用 React Hooks 构建灵活组件- 一篇优秀的博客文章,解释了复合组件模式。如果您想了解更多相关内容,可以观看这个视频:React Hooks:将复合组件重构为 Hooks。
