前言
单例模式在许多工程中被大幅使用,其提供了一个全局访问点来操作一个实例,从而节省资源。在React中有许多知名第三方库也采用了这种设计模式,比如Redux。最近就碰到一个需求,要求实现的就是整个React项目中只能创建一个WebSocket实例,并且还要给不同组件调用。翻了翻React官方文档,发现使用useContext可以很好的满足该需求,于是记录下单例的一个固定写法。
构建单例
创建类
export default class Singleton{
#value;
constructor(){
this.#value=0;
}
get value(){
return this.#value;
}
set value(val){
this.#value=val;
}
}
使用createContext创建上下文,参数则为上下文初始化时的默认值
export const SingletonContext=createContext(null);
简单封装useContext钩子,便于函数式组件调用
export function useSingleton(){
return useContext(SingletonContext);
}
简单封装SingletonContext的Provider用于提供上下文的值,同时创建实例对象
export function SingletonProvider({children}){
const singleton=new Singleton();
return(
<SingletonContext.Provider value={singleton}>
{children}
</SingletonContext.Provider>
);
}
这样就创建了一个单例对象了
测试准备
因为函数式组件和类式组件调用方式不同,因此分别创建创建两个测试组件
函数式组件
import React from 'react';
export default function FunctionComponent() {
return (
<div>
FunctionComponent
</div>
);
}
类式组件
import React, { Component } from 'react';
export default class ClassComponent extends Component {
render() {
return (
<div>ClassComponent</div>
);
}
}
使用刚刚创建的SingletonProvider包裹需要读取上下文的所有组件即可
import ClassComponent from './components/ClassComponent';
import FunctionComponent from './components/FunctionComponent';
import "./App.css";
function App() {
return (
<div className='app'>
<SingletonProvider>
<FunctionComponent />
<ClassComponent />
</SingletonProvider>
</div>
);
}
export default App;
使用单例
函数式组件
函数式组件使用刚刚创建的useSingleton钩子即可,该函数的返回值便是之前创建的实例对象,并且添加按钮用于改变singleton.value的值
import React from 'react';
import { useSingleton } from '../singleton/context';
export default function FunctionComponent() {
const singleton=useSingleton();
const handleIncrease=()=>{
singleton.value++;
}
const handleShowValue=()=>{
console.log("rfc:",singleton.value);
}
return (
<div>
FunctionComponent
<button
onClick={handleShowValue}
>
show value
</button>
<button
onClick={handleIncrease}
>
increase
</button>
</div>
);
}
类式组件
类式组件调用单例相对函数式组件有点复杂,因为类式组件不能使用钩子函数。但是类式组件继承的父类:React.Component中有一个静态属性contextType用于设置组件的上下文,设置上下文之后便可以使用this.context来获取上下文的值了
React.Component的部分源码:
class Component<P, S> {
// tslint won't let me format the sample code in a way that vscode likes it :(
/**
* If set, `this.context` will be set at runtime to the current value of the given Context.
*
* Usage:
*
* ```ts
* type MyContext = number
* const Ctx = React.createContext<MyContext>(0)
*
* class Foo extends React.Component {
* static contextType = Ctx
* context!: React.ContextType<typeof Ctx>
* render () {
* return <>My context's value: {this.context}</>;
* }
* }
* ```
*
* @see https://react.dev/reference/react/Component#static-contexttype
*/
static contextType?: Context<any> | undefined;
/**
* If using the new style context, re-declare this in your class to be the
* `React.ContextType` of your `static contextType`.
* Should be used with type annotation or static contextType.
*
* ```ts
* static contextType = MyContext
* // For TS pre-3.7:
* context!: React.ContextType<typeof MyContext>
* // For TS 3.7 and above:
* declare context: React.ContextType<typeof MyContext>
* ```
*
* @see https://react.dev/reference/react/Component#context
*/
context: unknown;
// 其余代码...
}
在类式组件中操作单例
import React, { Component } from 'react';
import { SingletonContext } from '../singleton/context';
export default class ClassComponent extends Component {
static contextType=SingletonContext;
handleShowValue=()=>{
console.log("rcc:",this.context.value);
}
handleIncrease=()=>{
this.context.value++;
}
render() {
return (
<div>
ClassComponent
<button
onClick={this.handleShowValue}
>
show value
</button>
<button
onClick={this.handleIncrease}
>
increase
</button>
</div>
);
}
}
开始测试
首先分别查看两个组件中的单例初始值
点击类式组件的
increase按钮,再次查看单例初始值,两个组件中打印的值都变为了1
再次点击类式组件的
increase按钮进行测试
两个组件中的值都变为了2,由此可以说明它们使用的是同一个对象
需要注意的是上下文的值并不是响应式变量,不能触发页面更新!