五种高级React模式的概述,本篇文章包括了示例代码,优缺点以及公共库中的具体用法。
不清楚你在日常设计组件的时候有没有问过自己以下问题之一:
- 如何构建可重用组件以适应不同的用例?
- 如何使用简单的 API构建组件,使其易于使用?
- 如何在 UI 和功能方面构建可扩展的组件?
这些反复出现的问题导致了整个 React 社区的出现了一些高级模式。
在本文中,我们将概述 5 种不同的模式。为了便于比较,我们将对所有这些模式使用相同的结构:
我们将从一个简短的介绍开始,然后是一个真实的代码示例(基于简单的Counter组件)。
我们将列出各个模式的优点和缺点,然后以下面的两个标准进行评判:
- 控制翻转: 给予组件用户的灵活性和控制级别。
- 实现复杂性: 你和用户都难以使用该模式。
注意: 强烈建议阅读本片文章一定要边看源码边阅读,不然你看不懂!!!!!
源码: 快快点击这里!!
复合组件模式
这种模式更适用于设计灵活性较强的组件。而不需要不必要的Props传递。如果你想使你的组件更加具有灵活性,更加可定制,具有更好的关注点分离和更易理解的API,那么你就可以使用这种模式设计你的组件。
例子: 复合组件模式代码示例
import React from "react";
import Counter from "./Counter";
const Usage = () => {
const handleChangeCount = (count) => {
console.log("复合组件模式 -> count", count);
};
return (
<div>
<h1>复合组件模式 用例一</h1>
<Counter onChange={handleChangeCount}>
{/* 传递Props <Counter.Increment iconProps={{ spin: true }} /> */}
<Counter.Increment />
<Counter.Count max={10} />
<Counter.Decrement />
</Counter>
</div>
);
};
export default Usage;
复制代码
优点
- 降低 API 复杂性:这里不是将所有 props 塞进一个巨大的父组件中并将它们深入到子 UI 组件中,而是将每个 props 附加到最有意义的子组件。
- 灵活的标记结构:你的组件具有很大的 UI 灵活性,允许从单个组件创建各种案例。例如,用户可以更改子组件的顺序或定义应该显示哪一个。
- 关注点分离:大部分逻辑包含在主Counter组件中,然后使用React.Context来共享states和handlers所有子组件。我们得到了明确的责任分工。
缺点
- 过多的 UI 灵活性:灵活性伴随着引发意外行为的可能性(放置不需要的组件的子组件,使组件的子组件无序,忘记包含强制子组件)。
根据您希望用户如何使用您的组件,您可能不希望允许如此大的灵活性。
- 更重的 JSX:应用此模式将增加 JSX 行的数量,特别是如果你使用 linterEsLint 或 代码格式化程序如Prettier。
在单个组件规模上这似乎没什么大不了的,但当你看大局时,肯定会产生巨大的差异。
标准:
- 控制翻转: 1/4
- 实现复杂性: 1/4
使用该模式的公共库:
Props控制模式
此模式将您的组件转换为受控组件。外部状态被用作“单一事实来源”,允许用户插入自定义逻辑来修改默认组件行为。
例子: Props控制模式代码示例
import React, { useState } from "react";
import Counter from "./Counter";
const Usage = () => {
const [count, setCount] = useState(0);
const handleChangeCount = (count) => {
console.log("Props 控制模式 -> count", count);
setCount(count);
};
return (
<div>
<h1>Props 控制模式 用例二</h1>
<Counter onChange={handleChangeCount} value={count}>
{/* 传递Props <Counter.Increment iconProps={{ spin: true }} /> */}
<Counter.Increment />
<Counter.Count max={10} />
<Counter.Decrement />
</Counter>
</div>
);
};
export default Usage;
复制代码
优点
- 给予更多控制:由于状态暴露在你的组件之外,用户可以控制它,因此可以直接影响你的组件。
缺点
- 实现复杂性:以前,在一个地方进行一次集成 ( JSX) 就足以使您的组件正常工作。现在,这将是分布在3米不同的地方(JSX/ useState/ handleChange)。
标准:
- 控制翻转: 2/4
- 实现复杂性: 1/4
使用该模式的公共库:
自定义Hooks模式
让我们在“控制反转”中更进一步:现在将主要逻辑转移到自定义hook中。用户可以访问此hook并公开几个内部逻辑 ( States, Handlers),使他可以更好地控制你的组件。
import React, { useEffect } from "react";
import Counter from "./Counter";
import useCounter from "./useCounter";
const Usage = () => {
const { count, handleDecrement, handleIncrement } = useCounter(0);
useEffect(() => {
console.log("自定义 Hooks 模式 -> count", count);
}, [count]);
return (
<div>
<h1>自定义 Hooks 模式 用例三</h1>
<Counter value={count}>
{/* 传递Props <Counter.Increment iconProps={{ spin: true }} /> */}
<Counter.Increment onClick={handleIncrement} />
<Counter.Count max={10} />
<Counter.Decrement onClick={handleDecrement} />
</Counter>
</div>
);
};
export default Usage;
复制代码
优点
- 给予更多控制:用户可以在 hook 和 JSX 元素之间插入他自己的逻辑,允许他修改默认的组件行为。
缺点
- 实现复杂度:由于逻辑部分与渲染部分分离,必须由用户将两者联系起来。正确实现组件需要很好地理解组件的工作方式。
标准:
- 控制翻转: 2/4
- 实现复杂性: 2/4
使用该模式的公共库:
Props Getters 模式
Props Getters 模式提供了很好的控制,但也使你的组件更难集成,因为用户必须处理大量本地钩子的props并重新创建他的逻辑。该Props Getters 模式试图掩盖这种复杂性。我们提供了一个Props getters,而不是原生Props 。getter是一个返回许多 props 的函数,它有一个有意义的名称,允许用户自然地将它链接到正确的 JSX 元素。
import React, { useEffect } from "react";
import Counter from "./Counter";
import useCounter from "./useCounter";
const Usage = () => {
const { getCountProps, getDecrementProps, getIncrementProps } = useCounter({
initValue: 0,
max: 20,
});
const handleClickIncrement = () => {
console.log("handleIncrement -> Props Getters 模式");
};
const handleClickDecrement = () => {
console.log("handleIncrement -> Props Getters 模式");
};
return (
<div>
<h1>Props Getters 模式 用例四</h1>
<Counter {...getCountProps()}>
{/* <Counter.Increment
{...getIncrementProps({
onClick: handleClickIncrement,
iconProps: { spin: true },
})}
/> */}
<Counter.Increment
{...getIncrementProps({
onClick: handleClickIncrement,
})}
/>
<Counter.Count max={10} />
<Counter.Decrement
{...getDecrementProps({ onClick: handleClickDecrement })}
/>
</Counter>
</div>
);
};
export default Usage;
复制代码
优点
- 易用性:提供一种简单的方法来集成你的组件,隐藏复杂性,用户只需将正确的连接到正确getter的 JSX 元素。
缺点
- 缺乏可见性: 带来的抽象getters使你的组件更容易集成,但也更不透明。要正确覆盖您的组件,用户必须知道 getter 公开的 props 列表以及其中一个更改时的内部逻辑影响。
标准:
- 控制翻转: 3/4
- 实现复杂性: 3/4
使用该模式的公共库:
State Reducer 模式
控制反转方面最先进的模式。它为用户提供了一种高级方法来更改组件在内部的运行方式。
代码类似于自定义Hook模式,但除此之外,用户还定义了reducer传递给钩子的 。这reducer将使组件的任何内部操作重载。
import React, { useEffect } from "react";
import Counter from "./Counter";
import useCounter from "./useCounter";
const Usage = () => {
const reducer = (state, action) => {
switch (action.type) {
case "Decrement":
return {
count: Math.max(0, state.count - 2),
};
default:
return useCounter.reducer(state, action);
}
};
const { count, handleDecrement, handleIncrement } = useCounter(
{
initValue: 0,
max: 20,
},
reducer
);
useEffect(() => {
console.log("State Reducer 模式 -> count", count);
}, [count]);
return (
<div>
<h1>State Reducer 模式 用例五</h1>
<Counter value={count}>
<Counter.Increment onClick={handleIncrement} />
<Counter.Count max={10} />
<Counter.Decrement onClick={handleDecrement} />
</Counter>
</div>
);
};
export default Usage;
复制代码
优点
- 给予更多控制权:在最复杂的情况下,使用state reducers是将控制权留给用户的最佳方式。所有内部组件的操作现在都可以从外部访问并且可以被覆盖。
缺点
- 实现复杂性:对于您和用户来说,这种模式无疑是最复杂的。
- 缺乏可见性:由于任何reducer的动作都可以改变,因此需要很好地理解组件的内部逻辑。
标准:
- 控制翻转: 4/4
- 实现复杂性: 4/4
使用该模式的公共库:
总结
通过这 5 个高级 React 模式,我们已经看到了利用“控制反转”概念的不同方式。它们为你提供了一种创建灵活且适应性强的组件的强大方法。
但是,我们都知道“能力越大,责任越大”这句著名的谚语,你将控制权转移给用户的次数越多,你的组件就越远离“即插即用”的思维方式。作为开发人员,你的职责是根据正确的需求选择正确的模式。