介绍
设计模式是对常见的,通用问题的可复用解决方案的归纳总结,通常被认为是解决该类问题的最佳实践,使用设计模式能帮助我们写出更容易维护,更健壮的代码。设计模式有很多,通常它们都会遵循一些共同的设计原则,接下来我们一起回顾下React社区里出现过的一些设计模式,以及它们所遵循的设计原则。
一些设计原则
- 单一职责原则(Single-responsibility?responsibility)?:?每个实体(class,?function,?module)只应该有一个职责。例如当一个组件接收了太多的props,我们应该考虑组件是不是做了太多的事情,有没有必要进行拆分。
- 开闭原则(Open-closed?principle):实体(class,?function,?module)?应该对扩展开放,但是对修改关闭。开闭原则意味着应该存在不直接修改的方式扩展实体的功能。
- 依赖反转原则(Dependency?inversion?principle):依赖于抽象,而不是具体的实现。依赖注入是一种实现依赖反转的方式。
- 不要自我重复?(Don't?repeat?yourself):重复代码会造成代码维护的困难。
- Composition?over?inheritance:?通过组合集成的两个组件是松耦合关系,通过props来约束。但是有继承关系的两个组件是强耦合关系,对父组件的修改可能会导致子组件的未预期的结果。
React设计模式
把业务组件划分成container组件和presentational组件。?Presentational?component中负责组件的ui渲染,Container?component负责数据的获取和事件的响应。
遵循的设计原则:
- 单一职责原则:?Presentational?component负责ui,Container?component负责数据和行为。
- Don't?repeat?yourself:?Presentational?component是纯ui组件,不包含业务逻辑,通常可以被复用。
示例
import React from "react";
export default function ImageList({ images, onClick }) {
return images.map((img, i) => <img src={img} key={i} onClick={onClick} />);
}
export default class ImageListContainer extends React.Component {
constructor() {
super();
this.state = {
images: []
};
}
componentDidMount() {
fetch("https://images.com")
.then(res => res.json())
.then(({ images }) => this.setState({ images }));
}
handleClick() {
}
render() {
return <ImageList images={this.state.images} onClick={handleClick} />;
}
}
Higher-order?component?是一个以组件为参数,返回一个新组件的函数,用于复用组件的逻辑,Redux的?connect?和?Relay的createFragmentContainer都有使用HOC模式。
遵循的设计原则:
- Don't?repeat?yourself:把可复用的逻辑放到HOC中,实现代码复用。
- Composition?over?inheritance:?hoc中传入的组件和返回的组件是组合的关系,?也可以把多个HOC进行多次的嵌套组合。
示例
import React from "react";
export default function withLoader(Component, url) {
return class HOC extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
data: {},
};
}
componentDidMount() {
fetch(url)
.then((res) => res.json())
.then(({ data }) => this.setState({ data }))
.finally(() => this.setState({loading: false}))
}
render() {
if (this.state.loading) {
return <div>Loading...</div>;
}
return <Component {...this.props} data={this.state.data} />;
}
};
}
Render?prop是指组件的使用者通过组件暴露的函数属性来参与定制渲染相关的逻辑。使用Render?prop模式的库包括:?React?Router,?Downshift?and?Formik.
遵循的设计原则:
- Don't?repeat?yourself:把可复用的逻辑放到组件中,实现代码复用。
- 依赖反转原则:通过render?prop注入渲染相关的实现。
- 开闭原则(Open-closed?principle):?通过render?prop暴露扩展点,而不是直接定制组件。
示例
import React from "react";
class Loader extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
data: {},
};
}
componentDidMount() {
fetch(url)
.then((res) => res.json())
.then(({ data }) => this.setState({ data }))
.finally(() => this.setState({ loading: false }));
}
render() {
if (this.state.loading) {
return <div>Loading...</div>;
}
return this.props.renderData(this.state.data);
}
}
Compound?components是指通过多个组件的组合来完成特定任务,这些组件通过共享的状态、逻辑进行关联。典型的例子是Select和Select.Option组件。使用Compound?components模式的库包括:semantic?ui;?
遵循的设计原则:
- 单一职责原则(Single-responsibility?responsibility):?拆分成多个组件,每个组件承担自己的职责。
- 开闭原则(Open-closed?principle):?需要迭代增强功能时,可以通过创建新的子组件的方式进行扩展。
示例
import React from "react";
const SelectContext = React.createContext({});
export function Select({ value, onChange, children }) {
const [open, setOpen] = React.useState(false);
const [val, setValue] = React.useState(value);
return (
<div className={`select`}>
<div
className="select-value"
onClick={() => {
setOpen(true);
}}
>
{val}
</div>
<SelectContext.Provider
value={{
value: val,
setOpen,
setValue: (newValue) => {
setValue(newValue);
if (value !== newValue) {
onChange(newValue);
}
},
}}
>
{open && children}
</SelectContext.Provider>
</div>
);
}
function Option({ children, value }) {
const {
setOpen,
setValue,
value: selectedValue,
} = React.useContext(SelectContext);
return (
<div
className={`select-option ${value === selectedValue ? "selected" : ""}`}
onClick={() => {
setValue(value);
setOpen(false);
}}
>
{children}
</div>
);
}
function OptionGroup({ children, label }) {
return (
<div className="select-option-group">
<div className="select-option-group-label">{label}</div>
{children}
</div>
);
}
Select.Option = Option;
Select.OptionGroup = OptionGroup;
function Demo() {
const [city, setCity] = React.useState("北京市");
return (
<Select value={city} onChange={setCity}>
<Select.Option value="北京市">北京市</Select.Option>
<Select.OptionGroup label="河北省">
<Select.Option value="石家庄市">石家庄市</Select.Option>
<Select.Option value="保定市">保定市</Select.Option>
</Select.OptionGroup>
</Select>
);
}
自定义hooks可以做到把与state和生命周期关联的可复用逻辑封装到独立的函数中,?上面的提及的一些模式都是基于组件的方案,自定义hooks是更细粒度的解决方案。
遵循的设计原则:
- Don't?repeat?yourself:把可复用的逻辑放到自定义hooks中,实现代码复用。
- 单一职责原则:每个自定义hooks是都是一个独立的逻辑单元。
示例:
import { useState, useEffect } from "react";
function useLoader(url) {
const [data, setData] = useState({});
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch(url)
.then((res) => res.json())
.then(({ data }) => {
setData({ data });
})
.finally(() => setLoading(false));
}, [url]);
return { data, loading };
}
结尾
上面提及的曾经在社区流行的设计模式,往往遵守了一些设计原则,从而能帮助开发者写出健壮,易维护的代码。但是我们需要能根据实际的场景做出判断,是否需要引入这些模式,毕竟还有一个设计原则是YAGNI?(You?aren't?gonna?need?it)。