React 类似 Angular Service 的状态管理
Angular Service 功能
- 全局单例
- 后面 provide 的 Service 可以覆盖前面的
- 可以进行依赖注入
基于的 React 的原理
react 的 context.Provider 组件会在子组件创建一个一个上下文,函数子组件总可以通过 useContext 方法来获取 context 传递的数据
实现单例
新建一个 util.ts,然后创建一个 SERVICE_CONTEXT
util.ts
const SERVICE_CONTEXT = createContext(null);
实现使用 SERVICE_CONTEXT 的组件
util.ts
function createServiceProvider<C>(Comp: React.FC<C>, services: any[]) {
return React.memo((props: any) => {
let providers: any = {};
for (let service of services) {
providers[service.name] = new service();
}
return (
<SERVICE_CONTEXT.Provider value={providers}>
<Comp {...props}></Comp>
</SERVICE_CONTEXT.Provider>
);
});
}
创建 Total Service
npm i rxjs
total.service.ts
class TotalService {
count: number = 0;
count$ = new Subject<number>();
setCount(count: number) {
this.count = count;
this.count$.next(this.count);
}
}
自定义 Hook 函数
util.ts
function useServiceHook<T>(service: new () => T): T {
const providers: any = useContext(SERVICE_CONTEXT);
const provider = providers[(service as any).name];
return provider;
}
在 Total 组件中使用
total.tsx
export default createServiceProvider(Total, [TotalService]);
function Total() {
const totalService = useServiceHook(TotalService);
const [totalCount, setTotalCount] = useState(totalService.totalCount);
useEffect(() => {
const subscription = totalService.totalCount$.subscribe((totalCount) => {
setTotalCount(totalCount);
});
return () => {
subscription.unsubscribe();
};
}, []);
useEffect(() => {
const subscription = totalService.totalCount$.subscribe((totalCount) => {
setTotalCount(totalCount);
});
return () => {
subscription.unsubscribe();
};
}, []);
const addTotalCount = () => {
totalService.setTotalCount(totalCount + 1);
};
return (
<div>
<p>Total works! Total count is {totalCount}</p>
<button onClick={addTotalCount}>add total count</button>
</div>
);
}
从图中可以看出,Service 起作用了

实现 Service 覆盖
再新建一个 Part service
part.service.ts
export class PartService {
partCount: number = 0;
partCount$ = new Subject<number>();
setPartCount(partCount: number) {
this.partCount = partCount;
this.partCount$.next(this.partCount);
}
}
将 Part service 添加到 Total 组件中使用,形成全局单例
total.ts
export default createServiceProvider(Total, [TotalService, PartService]);
新建 Part 组件,并在 Total 组件中使用 Part 组件
点击 add {name} part count 会增加 PartService 的 partCount 值,同时会增加 TotalService 的 totalCount 值
part.ts
interface IProps {
name: string;
}
export default function Part({ name }: IProps) {
const totalService = useServiceHook(TotalService);
const partService = useServiceHook(PartService);
const [partCount, setPartCount] = useState(partService.partCount);
useEffect(() => {
const subscriptions: Subscription[] = [];
subscriptions.push(
partService.partCount$.subscribe((partCount) => {
setPartCount(partCount);
})
);
return () => {
subscriptions.forEach((i) => i.unsubscribe());
};
}, []);
const addPartCount = () => {
partService.setPartCount(partCount + 1);
totalService.setTotalCount(totalService.totalCount + 1);
};
return (
<div>
<p>{name} works!</p>
<p>
{name} part count is {partCount}
</p>
<button onClick={addPartCount}>add {name} part count</button>
</div>
);
}
total.ts
return (
<div>
<p>Total works! Total count is {totalCount}</p>
<button onClick={addTotalCount}>add total count</button>
<Part name="A"></Part>
<Part name="B"></Part>
</div>
);
info
现在我们来分析一下代码
我们在全局使用了 TotalService 和 PartService 形成了两个单例
TotalService 的 totalCount 在 Total 组件中展示了值
Total 组件使用了 Part 组件两次,实例化出了 PartA 和 PartB
PartA 和 PartB 都展示了 PartService 的 partCount 值
因为 PartService 是全局单例的,所以当我们点击 add {name} part count 时,PartA 和 PartB 中的 partCount 值会同时改变
从图中也可以看出,确实是同时改变

修改 createServiceProvider 方法
export function createServiceProvider<C>(Comp: React.FC<C>, services: any) {
return React.memo((props: any) => {
+ const outerContext = useContext(SERVICE_CONTEXT);
let providers: any = {};
+ if (outerContext) providers = Object.create(outerContext);
for (let service of services) {
providers[service.name] = new service();
}
return (
<SERVICE_CONTEXT.Provider value={providers}>
<Comp {...props}></Comp>
</SERVICE_CONTEXT.Provider>
);
});
}
在子组件使用 createServiceProvider 时,首先判断外层有没有 Providers(也就是 Service),如果有,通过 Object.create 方法将外层的 Providers 放到当前 Providers 的原型链上,之后,如果当前 Providers 有同名的 Service,就会直接覆盖
将 Part 用 createServiceProvider 生成
+ export default createServiceProvider(Part, [PartService]);
- export default function Part({ name }: IProps) {
+ function Part({ name }: IProps) {
......
}
最终结果
