React 组件开发 5 种高级模式
经常碰到的问题
- 如何重构可复用组件来适合不同场景
- 如何使用简单的API构建组件,使其易于使用
- 如何在UI和功能方面构建可扩展组件
复合组件模式
import React from "react";
import { Counter } from "./Counter";
function Usage() {
const handleChangeCounter = (count) => {
console.log("count", count);
};
return (
<Counter onChange={handleChangeCounter}>
<Counter.Decrement icon="minus" />
<Counter.Label>Counter</Counter.Label>
<Counter.Count max={10} />
<Counter.Increment icon="plus" />
</Counter>
);
}
export { Usage };
优点
降低 API 复杂度,不是将所有 props 塞到父组件,而是为每个子组件添加有意义的props
灵活的标记结构,有很大的 UI 灵活性,例如可以更改顺序
关注点分离:大部分逻辑包含在主
Counter
组件,然后通过Context
共享 states 和 handlers
缺点
- 过多的UI灵活性,伴随着意外行外,比如放了不要的子组件,是组件无序
- 增加了 JSX 行数,变得更重
标准
- 控制反转 1/4
- 集成复杂度 1/4
案例
Control Props Pattern
将组件转换为受控组件,外部状态被用作“单一真实来源”,允许插入自定义逻辑来修改默认组件行为
import React, { useState } from "react";
import { Counter } from "./Counter";
function Usage() {
const [count, setCount] = useState(0);
const handleChangeCounter = (newCount) => {
setCount(newCount);
};
return (
<Counter value={count} onChange={handleChangeCounter}>
// ...
</Counter>
);
}
export { Usage };
优点
- 给予更多控制
缺点
- 使用起来复杂,需要写三处代码,
useState
handleChange
jsx
标准
- 控制反转 2/4
- 集成复杂度 1/4
案例
自定义 hooks
import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";
function Usage() {
const { count, handleIncrement, handleDecrement } = useCounter(0);
return (
<>
<Counter value={count}>
<Counter.Decrement onClick={handleDecrement} />
<Counter.Increment onClick={handleIncrement} />
</Counter>
</>
);
}
export { Usage };
优点
- 给予更多的控制
缺点
- 实现复杂度较高,需要更好的理解组件的工作方式
标准
- 控制反转 2/4
- 集成复杂度 2/4
案例
Props Getters Pattern
自定义 hook 提供了跟好的控制,但是使组件更难集成,用户必须处理大量本地hook。
改模式试图掩盖这种复杂性,
import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";
const MAX_COUNT = 10;
function Usage() {
const {
count,
getCounterProps,
getIncrementProps,
getDecrementProps
} = useCounter({
initial: 0,
max: MAX_COUNT
});
const handleBtn1Clicked = () => {
console.log("btn 1 clicked");
};
return (
<>
<Counter {...getCounterProps()}>
<Counter.Decrement icon={"minus"} {...getDecrementProps()} />
<Counter.Label>Counter</Counter.Label>
<Counter.Count />
<Counter.Increment icon={"plus"} {...getIncrementProps()} />
</Counter>
<button {...getIncrementProps({ onClick: handleBtn1Clicked })}>
Custom increment btn 1
</button>
<button {...getIncrementProps({ disabled: count > MAX_COUNT - 2 })}>
Custom increment btn 2
</button>
</>
);
}
export { Usage };
优点
- 易用性,隐藏了复杂性,用户只需将正确的 getter 连接到对应的 jsx 上
缺点
- 缺乏可见性,抽象的 getters 不透明,用户必须得知道 getters 以及对应的参数,以其被修改后的影响
标准
- 控制反转 3/4
- 集成复杂度 3/4
案例
state reducer parttern
代码类似于 custom hook pattern,除此之外还定义了 reducer
传递给 hook
import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";
const MAX_COUNT = 10;
function Usage() {
const reducer = (state, action) => {
switch (action.type) {
case "decrement":
return {
count: Math.max(0, state.count - 2) //The decrement delta was changed for 2 (Default is 1)
};
default:
return useCounter.reducer(state, action);
}
};
const { count, handleDecrement, handleIncrement } = useCounter(
{ initial: 0, max: 10 },
reducer
);
return (
<>
<Counter value={count}>
<Counter.Decrement icon={"minus"} onClick={handleDecrement} />
<Counter.Label>Counter</Counter.Label>
<Counter.Count />
<Counter.Increment icon={"plus"} onClick={handleIncrement} />
</Counter>
<button onClick={handleIncrement} disabled={count === MAX_COUNT}>
Custom increment btn 1
</button>
</>
);
}
export { Usage };
优点
- 给予了更多控制权
缺点
- 实现复杂
- 缺乏可见性,需要更好的理解组件内部逻辑
标准
- 控制反转 4/4
- 集成复杂度 4/4
总结
将控制权转移给用户越多,组件越远离 “即插即用”的思维方式