1.什么是设计模式
设计模式(Design Pattern)是软件工程中面临的一般问题的解决方案。它不是成熟的代码,而是原则和思路。设计模式在不同的语言和工具中会有不同的实现方式,但核心思想是不变的。设计模式的主要目的是代码复用和减少系统的复杂性。它通常会解决一类问题,而不是一个具体问题。设计模式允许使用户更加相信系统,它使可维护性较高且方便扩展。
设计模式的类型主要分为三类:
1. 创建型模式:这些设计模式提供用于创建对象的某种机制,它们帮助创建对象,同时隐藏创建细节。比如工厂方法模式、抽象工厂模式、构建者模式、原型模式等。
2. 结构型模式:这些设计模式关注类和对象的组合。通过继承和组合,它们形成了更大的结构。比如适配器模式、装饰者模式、代理模式、外观模式、桥接模式等。
3. 行为型模式:这些设计模式特别关注对象之间的通信。它们处理更多的是对象之间的问题,比如观察者模式、迭代器模式、命令模式、备忘录模式、状态模式等。
总之,设计模式是编程和应用设计中常见的最佳实践。掌握设计模式可以增强语言的表达能力,帮助开发人员解决常见问题,设计清晰、健壮、可重用的代码。
2.设计模式及应用
JavaScript 的主要设计模式有以下几种:
- 单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点。
- 观察者模式:定义对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。
- 代理模式:控制对对象的访问,通常用于延迟加载costly objects。
- 工厂模式:通过工厂方法创建对象,而不需要指定对象的具体类型。
- 装饰器模式:动态地给一个对象添加一些额外的功能,继承的替代方案。
- 外观模式:提供一个统一的接口,用来访问子系统中的一组接口,外观定义了一个高层接口,让子系统更易使用。
- 迭代器模式:顺序访问一个聚合对象中的各个元素,而又不需要知道聚合对象的底层表示。
- 发布-订阅模式:又称观察者模式,订阅者建立自己感兴趣的事件,一旦发布者发布消息,对应的订阅者就会得到通知。
- 中介者模式:通过一个中间人封装一系列对象相互作用,中介者使各对象不必显式地相互调用,从而使其耦合松散。
- 组合模式:组合多个对象形成树形结构以表示对象的全部或者部分层次,使用户可以统一对待单个对象和组合对象。
下面详细介绍一下上述10种设计模式
2.1单例模式(Singleton Pattern)
单例模式的核心思想在于保证一个类只有一个实例,并提供一个访问它的全局访问点。在 React 中,单例模式的应用场景非常多,如 Redux 中的 store 只需要在应用中存在一个实例,而不是每个组件都重新创建一个。以下是一个 React 中实现单例模式的示例:
const MyComponent = (() => {
let instance; // 保证一个实例
function init() {
// 初始化组件
}
return () => {
if (!instance) {
// 当实例不存在时才进行初始化
instance = init();
}
return instance;
};
})();
function App() {
const component = MyComponent();
return (
<div>
{component}
{component} {/* 两次调用返回的相同组件实例 */}
</div>
);
}
2.2 观察者模式(Observer Pattern)
观察者模式是定义对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并被自动更新。在 React 中,组件的生命周期就是一个非常好的例子,当父组件的状态或属性改变时,子组件会自动重新渲染。以下是一个 React 中使用观察者模式实现组件监听属性的示例:
import React, { useState, useEffect } from "react";
function ObserverComponent(props) {
const [observedProps, setObservedProps] = useState(props.observedProps);
useEffect(() => {
function handlePropsChange() {
setObservedProps(props.observedProps);
}
props.observedProps.on("propChange", handlePropsChange); // 监听属性变化
return () => {
props.observedProps.removeListener("propChange", handlePropsChange); // 移除监听
};
}, [props.observedProps]);
return (
<div>
{/* 显示监听的属性 */}
{observedProps.prop1}
{observedProps.prop2}
{observedProps.prop3}
</div>
);
}
class ObservedProps {
constructor() {
this.prop1 = 0;
this.prop2 = "";
this.prop3 = [];
this.listeners = {};
}
setProp(propName, newValue) {
this[propName] = newValue;
this.emit("propChange"); // 触发属性变化事件
}
on(eventName, listener) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName].push(listener);
}
removeListener(eventName, listenerToRemove) {
if (!this.listeners[eventName]) {
return;
}
this.listeners[eventName] = this.listeners[eventName].filter(
(listener) => listener !== listenerToRemove
);
}
emit(eventName) {
if (!this.listeners[eventName]) {
return;
}
this.listeners[eventName].forEach((listener) => listener());
}
}
function App() {
const [observedProps] = useState(new ObservedProps());
return (
<div>
{/* 修改属性 */}
<button onClick={() => observedProps.setProp("prop1", 1)}>Set Prop 1</button>
<button onClick={() => observedProps.setProp("prop2", "abc")}>Set Prop 2</button>
<button onClick={() => observedProps.setProp("prop3", [1, 2, 3])}>Set Prop 3</button>
{/* 观察属性变化 */}
<ObserverComponent observedProps={observedProps} />
</div>
);
}
2.3 代理模式(Proxy Pattern)
代理模式的核心思想在于控制对对象的访问,通常用于延迟加载
import React, { useState } from "react";
// 创建被代理的组件
function RealComponent(props) {
return <div>{props.text}</div>;
}
// 创建代理组件,通过判断某些条件加载被代理组件
function ProxyComponent(props) {
const [loaded, setLoaded] = useState(false);
function handleLoadRealComponent() {
setLoaded(true);
}
if (!loaded) {
return <button onClick={handleLoadRealComponent}>Load Real Component</button>;
}
return <RealComponent {...props} />;
}
function App() {
return <ProxyComponent text="Hello, World!" />;
}
2.4 工厂模式(Factory Pattern)
工厂模式通过工厂方法创建对象,而不需要指定对象的具体类型。在 React 中,常见的是通过工厂方法创建相似的组件,如数据列表中每行的组件可使用工厂方法创建,并通过传入属性区分不同行的数据。
import React from "react";
// 工厂方法
function createTableRowComponent(data) {
return function TableRowComponent() {
return (
<tr>
<td>{data.name}</td>
<td>{data.age}</td>
<td>{data.gender}</td>
</tr>
);
};
}
function App() {
const tableData = [
{ name: "Tom", age: 20, gender: "Male" },
{ name: "Jerry", age: 21, gender: "Female" },
{ name: "Felix", age: 22, gender: "Male" },
];
// 通过工厂方法创建组件,传入不同的数据
const TableRowComponents = tableData.map((data) => createTableRowComponent(data));
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Gender</th>
</tr>
</thead>
<tbody>
{/* 渲染组件列表 */}
{TableRowComponents.map((Component, index) => (
<Component key={index} />
))}
</tbody>
</table>
);
}
2.5装饰器模式(Decorator Pattern)
装饰器模式动态地给一个对象添加一些额外的功能,是继承的替代方案。在 React 中,高阶组件就是一个很好的装饰器模式的例子,通过将一个组件传入另一个组件中作为参数,返回一个新的、添加了额外功能的组件。
import React from "react";
// 定义高阶组件
function withDecoration(Component) {
return function DecoratedComponent(props) {
return (
<div>
<h1>Decoration Start</h1>
<Component {...props} />
<h1>Decoration End</h1>
</div>
);
};
}
// 创建原始组件
function OriginalComponent() {
return <div>Original Component</div>;
}
// 使用高阶组件装饰原始组件
const DecoratedComponent = withDecoration(OriginalComponent);
function App() {
return <DecoratedComponent />;
}
2.6 外观模式(Facade Pattern)
外观模式提供一个统一的接口,用来访问子系统中的一组接口,外观定义了一个高层接口,让子系统更易使用。在 React 中,可以使用外观模式来库化组件,将多个相关的组件封装在一起,对外暴露一个统一的接口,让使用者不需要进行复杂的组件选择和配置。
import React from "react";
// 将多个相关的组件封装在一起,对外暴露一个统一的接口
function LibraryComponent(props) {
return (
<div>
<ButtonComponent text={props.buttonText} />
<InputComponent value={props.inputValue} onChange={props.onInputChange} />
<ListComponent items={props.listItems} />
</div>
);
}
// 下面分别是 ButtonComponent、InputComponent 和 ListComponent 组件的定义和使用
function ButtonComponent(props) {
return <button>{props.text}</button>;
}
function InputComponent(props) {
return <input value={props.value} onChange={props.onChange} />;
}
function ListComponent(props) {
return (
<ul>
{props.items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
function App() {
const listItems = ["item1", "item2", "item3"];
return (
<LibraryComponent
buttonText="Click Me!"
inputValue=""
onInputChange={() => {}}
listItems={listItems}
/>
);
}
2.7迭代器模式(Iterator Pattern)
迭代器模式通过顺序访问聚合对象中的各个元素,而又不需要知道聚合对象的底层表示。在 React 中,迭代器模式常见的应用场景之一是在列表渲染时进行过滤。
import React, { useState } from "react";
function FilteredListComponent(props) {
const [filter, setFilter] = useState(""); // 搜索关键字
function handleFilterInputChange(event) {
setFilter(event.target.value);
}
return (
<div>
<input value={filter} onChange={handleFilterInputChange} />
<ul>
{/* 列表渲染 */}
{props.listItems
.filter((item) => item.includes(filter)) // 过滤
.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
function App() {
const listItems = ["item1", "item2", "item3"];
return <FilteredListComponent listItems={listItems} />;
}
2.8 发布-订阅模式(Publisher-Subscriber Pattern)
发布-订阅模式(又称观察者模式)订阅者会建立自己感兴趣的事件,一旦发布者发布消息,对应的订阅者就会得到通知。在 React 中,发布-订阅模式的实现基于全局的事件机制。一个组件可以订阅一个事件并在事件被触发时更新自己。
import React, { useState } from "react";
const eventEmitter = {
listeners: {},
on(eventName, listener) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName].push(listener);
},
removeListener(eventName, listenerToRemove) {
if (!this.listeners[eventName]) {
return;
}
this.listeners[eventName] = this.listeners[eventName].filter(
(listener) => listener !== listenerToRemove
);
},
emit(eventName, data) {
if (!this.listeners[eventName]) {
return;
}
this.listeners[eventName].forEach((listener) => listener(data));
},
};
function SubscriberComponent(props) {
const [counter, setCounter] = useState(0);
// 订阅事件
eventEmitter.on("counterIncrease", (increment) => {
setCounter((prevCounter) => prevCounter + increment);
});
return <div>Counter: {counter}</div>;
}
function PublisherComponent(props) {
function handleButtonClick() {
// 触发事件
eventEmitter.emit("counterIncrease", props.increment);
}
return <button onClick={handleButtonClick}>Increase Counter by {props.increment}</button>;
}
function App() {
return (
<div>
<SubscriberComponent />
<PublisherComponent increment={1} />
</div>
);
}
2.9中介者模式
中介者模式是一种行为型设计模式,其目的是降低组件之间的耦合性,它通过提供中介组件,使得其他组件不需要直接依赖彼此,而是通过中介组件来通信。
在 React 中,中介者模式的一个应用场景是将逻辑和状态抽离出来,减少组件之间的直接关系,从而降低代码的复杂度。在这个场景下,中介组件可以扮演状态管理中心的角色,负责管理组件之间的通信和状态管理。
让我们来看一个具体的例子:
import React, { useState } from "react";
const UserList = ({ users, activeUser, onUserSelect }) => (
<ul>
{users.map((user) => (
<li
key={user.id}
onClick={() => onUserSelect(user)}
style={{
backgroundColor: user.id === activeUser.id ? "#eee" : "auto",
}}
>
{user.name}
</li>
))}
</ul>
);
const UserDetails = ({ user }) => (
<div>
<h3>{user.name}</h3>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
<p>Website: {user.website}</p>
</div>
);
const UserContainer = ({ users }) => {
const [activeUser, setActiveUser] = useState(users[0]);
const handleUserSelect = (user) => {
setActiveUser(user);
};
return (
<div>
<UserList
users={users}
activeUser={activeUser}
onUserSelect={handleUserSelect}
/>
<UserDetails user={activeUser} />
</div>
);
};
export default UserContainer;
2.10组合模式
组合模式是一种结构型设计模式,通过将对象组合成树形结构,使得单个对象和组合对象可以被同等对待。它是一种将对象组织成树形结构的方法,使得客户端通过统一的方式操作对象和组合对象。
在 React 中,组合模式的一个应用场景是组件的嵌套和组织,使得每个组件可以作为单个组件或组合组件来使用。在这个场景下,组件可以根据需要嵌套其他组件,从而实现组件之间的复合和组织。
让我们来看一个具体的例子:
import React from "react";
const List = ({ items }) => (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
const Header = ({ text }) => <h3>{text}</h3>;
const Paragraph = ({ text }) => <p>{text}</p>;
const Page = ({ title, header, body }) => (
<div>
<Header text={title} />
{header && <Header text={header} />}
{body && <Paragraph text={body} />}
</div>
);
const About = () => (
<Page
title="About Us"
header="Our Vision"
body="We are a team of passionate developers who believe in building a great product that can change the world."
/>
);
const Home = () => (
<Page title="Welcome">
<List items={["Item 1", "Item 2", "Item 3"]} />
</Page>
);
const App = () => (
<div>
<About />
<Home />
</div>
);
export default App;
上面的代码中,我们定义了五个组件:
- List:渲染列表;
- Header:渲染标题;
- Paragraph:渲染段落;
- Page:扮演组合容器的角色,根据 props 决定渲染哪些子组件;
- About 和 Home:分别是两个示例页面,以 Page 作为容器组件,并嵌套其他组件。
在这个示例中,我们使用 Page 组件作为组合容器,将其他组件进行了组合。在 Home 页面中,我们使用 List 组件作为子组件进行嵌套,在 About 页面中,我们使用 Header 和 Paragraph 组件作为子组件进行组合。
这样的设计有一个很明显的优点,那就是组件的嵌套和组合非常容易,我们可以根据需要组合不同的子组件,从而实现非常灵活的组件组合和组织。同时,也符合开闭原则,容易进行组件的扩展和修改。