hox文档
import { HoxRoot } from './hox';const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement
);
root.render(
);
reportWebVitals();
=============================================
import { createGlobalStore } from '../hox';
import {useState} from 'react';function useStoreDemo(){const [count,setCount]=useState(0);return {count,setCount}
}export const [useCountDemo] = createGlobalStore(useStoreDemo);
==========================================
import React from 'react';
import { useEffect } from 'react';
import { useCountDemo } from './hooks/demo'function App() {const {count,setCount}=useCountDemo()return (在这计数:{count});
}export default App;
import { useState } from 'react'
import { createStore } from '../hox'export const [useAppleStore, AppleStoreProvider] = createStore(() => {const [banana, setBanana] = useState(['苹果'])return {banana,setBanana}
})===================================
import { useEffect } from 'react';
import { useCountDemo } from '../hooks/demo'
import {useAppleStore} from '../hooks/appleDemo';
// import {useBeerStore} from '../hooks/beerDemo';function App() {const {count,setCount}=useCountDemo()const {banana,setBanana} =useAppleStore()function changeCount(){setCount(count+1)}function changeApple(){setBanana(v=>[...v,'葡萄'])}return (demo1...{banana});
}export default App;
==================================================
import React from 'react';
import { useEffect } from 'react';
import { useCountDemo } from './hooks/demo'
import Demo1 from './components/demo1';
import Demo2 from './components/demo2'
import {AppleStoreProvider} from './hooks/appleDemo';function App() {const {count,setCount}=useCountDemo()return ( 在这计数:{count});
}export default App;
// 创建局部状态共享
export { createStore } from './create-store'
// 创建全局状态共享
export { createGlobalStore } from './create-global-store'
export { HoxRoot } from './hox-root'
// 兼容类组件,这里暂不做分析
export { withStore } from './with-store'export type { CreateStoreOptions } from './create-store'
type Subscriber = (data: T) => voidexport class Container}> {constructor(public hook: (props: P) => T) {}subscribers = new Set>()data!: Tnotify() {for (const subscriber of this.subscribers) {subscriber(this.data)}}
}
export const [useAppleStore, AppleStoreProvider] = createStore(() => {const [banana, setBanana] = useState(['苹果'])return {banana,setBanana}
})
//create-global-store.tsx
import React, { ComponentType, FC, PropsWithChildren } from 'react'
import { useSyncExternalStore } from 'use-sync-external-store/shim'let globalExecutors: ComponentType[] = []const listeners = new Set<() => void>()// 每创建一个全局store,就会调用一次该方法
export function registerGlobalExecutor(executor: ComponentType) {//用于收集重新render时能够触发获取新状态的组件globalExecutors = [...globalExecutors, executor]//当添加新的全局store时,调用收集到的onStoreChange//意味着可以创建多个全局store//通过useSyncExternalStore重新计算返回添加后的重新render时能够触发获取新状态的组件listeners.forEach(listener => listener())
}export const HoxRoot: FC}>> = props => {// 订阅发布,返回的内容是所有和全局store对应的重新render时能够触发获取新状态的组件const executors = useSyncExternalStore(onStoreChange => {listeners.add(onStoreChange)return () => {listeners.delete(onStoreChange)}},() => {return globalExecutors})return (<>{executors.map((Executor, index) => (index} />))}{props.children}>)
}
import { Container } from './container'
import { registerGlobalExecutor } from './hox-root'
import { useDataFromContainer } from './use-data-from-container'
import { DepsFn } from './types'
import { memo, useEffect, useState } from 'react'export function createGlobalStore(hook: () => T) {let container: Container | null = null// 获取传递给createStore自定义的hook对应的containerfunction getContainer() {if (!container) {throw new Error('Failed to retrieve data from global container. Please make sure you have rendered HoxRoot.')}return container}// 重新render时能够触发获取新状态的组件,传递给registerGlobalExecutor// 通过useEffect在setState触发render后,通知重新计算状态const GlobalStoreExecutor = memo(() => {// 构建传入的hook对应的containerconst [innerContainer] = useState(() => new Container(hook))container = innerContainer// 保存hook返回的状态innerContainer.data = hook()// 通过useEffect在setState触发render后,通知重新计算状态useEffect(() => {// 收集发生在use-datat-from-container中innerContainer.notify()})return null})// 将组件传递给HooxRoot进行创建registerGlobalExecutor(GlobalStoreExecutor)// useDataFromContainer进行收集订阅// depsFn是指定要获取的状态内容,不传返回全部function useGlobalStore(depsFn?: DepsFn): T {return useDataFromContainer(getContainer(), depsFn)}// 全局store状态的快照function getGlobalStore(): T | undefined {return getContainer().data}return [useGlobalStore, getGlobalStore] as const
}
//use-data-from-container.ts
import { useRef } from 'react'
import { Container } from './container'
import { DepsFn } from './types'
import { useSyncExternalStore } from 'use-sync-external-store/shim'export function useDataFromContainer(container: Container,depsFn?: DepsFn
): T {const depsFnRef = useRef(depsFn)depsFnRef.current = depsFn// 传入container.data获取老的stateconst depsRef = useRef(depsFnRef.current?.(container.data) || [])//useSyncExternalStore返回statereturn useSyncExternalStore(onStoreChange => {function subscribe() {// 这里做了优化,当只有指定的状态发生变化时,才会触发onStoreChange使得useSyncExternalStore返回新的状态if (!depsFnRef.current) {onStoreChange()} else {const oldDeps = depsRef.currentconst newDeps = depsFnRef.current(container.data)if (compare(oldDeps, newDeps)) {onStoreChange()}depsRef.current = newDeps}}container.subscribers.add(subscribe)return () => {container.subscribers.delete(subscribe)}},() => container.data)
}function compare(oldDeps: unknown[], newDeps: unknown[]) {if (oldDeps.length !== newDeps.length) {return true}for (const index in newDeps) {if (oldDeps[index] !== newDeps[index]) {return true}}return false
}
// create-store.tsx
import React, {createContext,FC,memo,PropsWithChildren,useContext,useEffect,useState,
} from 'react'
import { Container } from './container'
import { DepsFn } from './types'
import { useDataFromContainer } from './use-data-from-container'export type CreateStoreOptions = {memo?: boolean
}const fallbackContainer = new Container(() => {})export function createStore}>(hook: (props: P) => T,options?: CreateStoreOptions
) {const shouldMemo = options?.memo ?? true// TODO: forwardRefconst StoreContext = createContext>(fallbackContainer)const IsolatorContext = createContext({})const IsolatorOuter: FC}>> = props => {return ({}}>{props.children} )}const IsolatorInner = memo}>>(props => {useContext(IsolatorContext)return <>{props.children}>},() => true)//和全局store的render组件类似,不过这里是通过context拿到containerconst StoreExecutor = memo>(props => {const { children, ...p } = props// 每次都是重新生成一个container,所以能够做到数据隔离const [container] = useState(() => new Container(hook))container.data = hook(p as P)// 同意是在setState后触发订阅,获取store最新的状态useEffect(() => {container.notify()})return (container}>{props.children} )})// 暴露给外部的Providerconst StoreProvider: FC> = props => {return (...props}>{props.children} )}function useStore(depsFn?: DepsFn): T {// hook拿到container const container = useContext(StoreContext)if (container === fallbackContainer) {// TODOconsole.error("Failed to retrieve the store data from context. Seems like you didn't render a outer StoreProvider.")}// 这里的逻辑就和全局 store 一致return useDataFromContainer(container, depsFn)}return [useStore, shouldMemo ? memo(StoreProvider) : StoreProvider] as const
}