diff --git a/src/index.ts b/src/index.ts index dc6f3ee..0e838b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,12 +3,13 @@ import { IActions, IStoreArg, IStoreOptionsArg, - ITrackStore, IInstance, IStoreApi, - IArray, - IObject, -} from "./types" + AnyArray, + AnyObject, + IStoreProxy, + ObjectKey +} from './types' let instanceId = 0 let inDeepProxy = false @@ -18,24 +19,25 @@ function verifyActions(actions: IActions) { for (const key in actions) { const value = actions[key] - if (typeof value !== "function") { - throw new Error("actions 里只能放函数") + if (typeof value !== 'function') { + throw new Error('actions 里只能放函数') } } } function verifyState(state: IState) { - if (state === null || typeof state !== "object") { - throw new Error("state 必须是对象") + if (state === null || typeof state !== 'object') { + throw new Error('state 必须是对象') } } - - -function track(instance: IInstance, isEffect = false) { - function addSingleTrack(key: string, callback: Function) { +function track( + instance: IInstance, + isEffect = false +) { + function addSingleTrack(key: ObjectKey, callback: Function) { if (isEffect) { - const value = instance.state[key] + const value = instance.state[key as string] callback(key, value) } @@ -58,7 +60,9 @@ function track(instance: IInstance, isEffect = false) { } } -function deleteTrack(trackStore: ITrackStore) { +function deleteTrack({ + trackStore +}: IInstance) { function deleteSingleTrack(key: string, callback: Function) { const trackSet = trackStore[key] if (!trackSet) return @@ -77,7 +81,10 @@ function deleteTrack(trackStore: ITrackStore) { } } -function execute(instance: IInstance, rootKey: string) { +function execute( + instance: IInstance, + rootKey: string +) { const { trackStore } = instance const value = instance.state[rootKey] const trackSet = trackStore[rootKey] @@ -88,19 +95,24 @@ function execute(instance: IInstance, rootKey: string) { } } -function proxyStore(instance: IInstance, storeApi: IStoreApi) { +function proxyStore( + instance: IInstance, + storeApi: IStoreApi +): IStoreProxy { const { state, actions } = instance - return new Proxy(instance, { - get(_, prop: string) { - if (prop in storeApi) { - return storeApi[prop] + const proxyStoreRes = new Proxy>(instance as any, { + get(_, prop: any) { + if (prop in instance) { + return instance[prop as ObjectKey>] + } else if (prop in storeApi) { + return storeApi[prop as ObjectKey>] } else if (prop in state) { return state[prop] } else if (prop in actions) { return actions[prop] } else { - throw new Error(`没有找到 ${prop}`) + throw new Error(`不能获取 ${prop}`) } }, set(_, prop: string, value) { @@ -114,25 +126,28 @@ function proxyStore(instance: IInstance, storeApi: IStoreApi) { } else { throw new Error(`${prop} 请在创建 Store 时添加到 State 或 Actions 中`) } - }, + } }) + + return proxyStoreRes } -function proxyState( - instance: IInstance, - targetObj: IArray | IObject, +function proxyState( + instance: IInstance, + targetObj: AnyArray | AnyObject, rootKey: null | string = null ) { - const { sameValueExecuteWatch } = instance.options + const { isDeepWatch } = instance.options return new Proxy(targetObj, { set(target, prop: string, value) { - if (!sameValueExecuteWatch && target[prop] === value) return true + // 值不变就无需执行收集到的依赖 + if (target[prop] === value) return true if (inDeepProxy) { target[prop] = value return true - } else if (typeof value === "object" && value !== null) { + } else if (isDeepWatch && typeof value === 'object' && value !== null) { currentRootKey = rootKey ? rootKey : prop target[prop] = deepProxyState(instance, value) currentRootKey = null @@ -141,34 +156,32 @@ function proxyState( } if (rootKey) { - execute(instance, rootKey) + execute(instance, rootKey) } else { - execute(instance, prop) + execute(instance, prop) } return true - }, + } }) } -function deepProxyState( - instance: IInstance, - rawTarget: IArray | IObject, +function deepProxyState( + instance: IInstance, + rawTarget: AnyArray | AnyObject, isRootObj = false ) { + const { isDeepWatch } = instance.options // 设置根容器 - let rootContainer: IArray | IObject = { - } + let rootContainer: AnyArray | AnyObject = {} if (Array.isArray(rawTarget)) { rootContainer = [] } - inDeepProxy = true - function recursionProxy( - target: IObject | IArray, - upContainer: IArray | IObject, + target: AnyObject | AnyArray, + upContainer: AnyArray | AnyObject, isRoot = false ) { for (const key in target) { @@ -186,7 +199,7 @@ function deepProxyState( 2.普通类型 直接赋值给上一个容器 */ - if (typeof value === "object" && value !== null) { + if (typeof value === 'object' && value !== null) { let container = {} if (Array.isArray(value)) { @@ -194,7 +207,7 @@ function deepProxyState( } recursionProxy(value, container) - upContainer[key] = proxyState(instance, container, currentRootKey) + upContainer[key] = proxyState(instance, container, currentRootKey) } else { upContainer[key] = value } @@ -205,18 +218,26 @@ function deepProxyState( } } - recursionProxy(rawTarget, rootContainer, isRootObj) - - inDeepProxy = false + if (isDeepWatch) { + inDeepProxy = true + recursionProxy(rawTarget, rootContainer, isRootObj) + inDeepProxy = false + } else { + rootContainer = rawTarget + } return isRootObj - ? proxyState(instance, rootContainer) - : proxyState(instance, rootContainer, currentRootKey) + ? proxyState(instance, rootContainer) + : proxyState(instance, rootContainer, currentRootKey) } -function createStoreInstance(rawState: IState, actions: IActions, options: IStoreOptionsArg) { +function createStoreInstance( + rawState: S, + actions: A, + options: IStoreOptionsArg +) { // 创建实例对象 - const instance: IInstance = { + const instance: IInstance = { id: instanceId++, trackStore: {}, state: {}, @@ -226,36 +247,37 @@ function createStoreInstance(rawState: IState, actions: IActions, options: IStor // 实例对象初始化 // 给 state 进行代理 - instance.state = deepProxyState(instance, rawState, true) + instance.state = deepProxyState(instance, rawState, true) return instance } -function createStoreApi(instance: IInstance) { - const { trackStore } = instance - +function createStoreApi( + instance: IInstance +) { const storeApi = { - watch: track(instance), - watchEffect: track(instance, true), - deleteWatch: deleteTrack(trackStore), + watch: track(instance), + watchEffect: track(instance, true), + deleteWatch: deleteTrack(instance) } return storeApi } export default function xlStore( - store: IStoreArg, options: IStoreOptionsArg = {} -): S & A & IStoreApi { - const state = store.state ?? {} - const actions = store.actions ?? {} + store: IStoreArg, + options: IStoreOptionsArg = {} +): IStoreProxy { + const state = store.state + const actions = store.actions verifyState(state) verifyActions(actions) - const instance = createStoreInstance(state, actions, options) - const storeApi = createStoreApi(instance) + const instance = createStoreInstance(state, actions, options) + const storeApi = createStoreApi(instance) - const storeProxy = proxyStore(instance, storeApi) + const proxyStoreRes = proxyStore(instance, storeApi) - return storeProxy as unknown as S & A & IStoreApi + return proxyStoreRes } diff --git a/src/types.ts b/src/types.ts index 9d6047f..c03147a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,44 +1,60 @@ -interface IObject extends Object { +export interface AnyObject extends Object { [key: string]: any } -interface IArray extends Array { +export interface AnyArray extends Array { [key: string]: any } -interface IState extends Object { +/* Store Type */ +export type ObjectKey = keyof T + +export interface IState extends Object { [key: string]: any } -interface IActions extends Object { +export interface IActions extends Object { [key: string]: Function } -interface IStoreArg { - state?: IState - actions?: A & IActions & ThisType, -} - -interface IStoreOptionsArg { - sameValueExecuteWatch?: boolean +export type ITrackStore = { + [Props in keyof S]?: Set } -type ITrackStore = { - [key: string]: Set +export interface IStoreApi { + watch( + key: ObjectKey | ObjectKey[], + callback: (key: string, value: any) => any + ): any + watchEffect( + key: ObjectKey | ObjectKey[], + callback: (key: string, value: any) => any + ): any + deleteWatch( + key: ObjectKey | ObjectKey[], + callback: (key: string, value: any) => any + ): any } -interface IInstance { +export interface IInstance { id: number - trackStore: ITrackStore + trackStore: ITrackStore state: IState - actions: IActions, + actions: A & ThisType> options: IStoreOptionsArg } -interface IStoreApi { - watch(key: string | string[], callback: Function): any - watchEffect(key: string | string[], callback: Function): any - deleteWatch(key: string | string[], callback: Function): any - [key: string]: any +export interface IStoreArg { + state: S + actions: A & ThisType> +} + +export interface IStoreOptionsArg { + isDeepWatch?: boolean } -export { IObject, IArray, IState, IActions, IStoreArg, IStoreOptionsArg, ITrackStore, IInstance, IStoreApi } +export type IStoreProxy = S & + A & + IStoreApi & { + id: number + trackStore: ITrackStore + } diff --git a/test/test.ts b/test/test.ts index 1568b5d..b98ca38 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,23 +1,50 @@ -import xlStore from "../src/index.js" +import xlStore from '../src/index.js' -const myStore = xlStore({ - state: { - name: 'hxl', - age: 18 +const myStore = xlStore( + { + state: { + userInfo: { + name: 'hxl', + age: 18 + }, + books: [ + { id: 100, name: 'JS高级程序设计', price: 88 }, + { id: 101, name: 'Vue高级程序设计', price: 99 }, + { id: 102, name: 'React高级程序设计', price: 87 } + ] + }, + actions: { + changeUserInfoAction() { + this.userInfo = { + name: 'coderhxl', + age: 18 + } + }, + changeBookPriceAction() { + this.books[0].price = 99 + } + } }, - actions: { - }, -}) + { + isDeepWatch: true + } +) -function callback(key: string, value: any) { - console.log("watch", key, value) +function getUserInfo(key: string, value: any) { + console.log('getUserInfo', key, value) } +function getBooks(key: string, value: any) { + console.log('getBooks', key, value) +} -myStore.watchEffect(["name", "age"], callback) +console.log(myStore.userInfo) +console.log(myStore.books) -myStore.deleteWatch(['name'], callback) +myStore.watch('userInfo', getUserInfo) +myStore.watch('books', getBooks) -myStore.name = 'code' -myStore.age = 19 +console.log('------------------------------') +myStore.changeUserInfoAction() +myStore.changeBookPriceAction()