@tramvai/state 1.84.0 → 1.89.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/connect/Provider.d.ts +7 -1
- package/lib/connect/context.d.ts +1 -0
- package/lib/connect/types.d.ts +1 -0
- package/lib/index.es.js +47 -188
- package/lib/index.js +47 -188
- package/package.json +5 -3
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { ConsumerContext, ServerState } from './types';
|
|
3
|
+
export declare const Provider: ({ context, children, serverState, }: {
|
|
4
|
+
context: ConsumerContext;
|
|
5
|
+
children: ReactElement;
|
|
6
|
+
serverState?: ServerState;
|
|
7
|
+
}) => JSX.Element;
|
package/lib/connect/context.d.ts
CHANGED
package/lib/connect/types.d.ts
CHANGED
package/lib/index.es.js
CHANGED
|
@@ -4,15 +4,18 @@ import identity from '@tinkoff/utils/function/identity';
|
|
|
4
4
|
import pick from '@tinkoff/utils/object/pick';
|
|
5
5
|
import shallowEqual from '@tinkoff/utils/is/shallowEqual';
|
|
6
6
|
import strictEqual from '@tinkoff/utils/is/strictEqual';
|
|
7
|
+
import { jsx } from 'react/jsx-runtime';
|
|
8
|
+
import noop from '@tinkoff/utils/function/noop';
|
|
7
9
|
import hoistStatics from 'hoist-non-react-statics';
|
|
8
10
|
import invariant from 'invariant';
|
|
9
|
-
import React, { createContext, useContext, useMemo, useRef,
|
|
11
|
+
import React, { createContext, useContext, useMemo, useRef, useCallback } from 'react';
|
|
10
12
|
import { isValidElementType } from 'react-is';
|
|
13
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
11
14
|
import isArray from '@tinkoff/utils/is/array';
|
|
12
15
|
import { useShallowEqual, useIsomorphicLayoutEffect } from '@tinkoff/react-hooks';
|
|
13
16
|
export { useIsomorphicLayoutEffect } from '@tinkoff/react-hooks';
|
|
14
|
-
import noop from '@tinkoff/utils/function/noop';
|
|
15
17
|
import toArray from '@tinkoff/utils/array/toArray';
|
|
18
|
+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
|
|
16
19
|
import always from '@tinkoff/utils/function/always';
|
|
17
20
|
import mapObject from '@tinkoff/utils/object/map';
|
|
18
21
|
import isPlainObject from '@tinkoff/utils/is/plainObject';
|
|
@@ -652,6 +655,7 @@ class Subscription {
|
|
|
652
655
|
}
|
|
653
656
|
|
|
654
657
|
const ConnectContext = createContext(null);
|
|
658
|
+
const ServerStateContext = createContext(null);
|
|
655
659
|
|
|
656
660
|
const useConsumerContext = () => {
|
|
657
661
|
const context = useContext(ConnectContext);
|
|
@@ -674,10 +678,9 @@ function useActions(actions) {
|
|
|
674
678
|
|
|
675
679
|
function useStore(reducer) {
|
|
676
680
|
const context = useConsumerContext();
|
|
681
|
+
const serverState = useContext(ServerStateContext);
|
|
677
682
|
const reducerRef = useRef(reducer);
|
|
678
683
|
const addedReducerRef = useRef(null);
|
|
679
|
-
const unsubscribeRef = useRef(noop);
|
|
680
|
-
const [, forceRender] = useReducer((s) => s + 1, 0);
|
|
681
684
|
// если текущий редьюсер не зарегистрирован в диспетчере,
|
|
682
685
|
// регистрируем его вручную, что бы гарантировать работоспособность `context.getState(reducer)`,
|
|
683
686
|
// и сохраняем в `addedReducerRef`, что бы удалить при unmount
|
|
@@ -685,26 +688,14 @@ function useStore(reducer) {
|
|
|
685
688
|
context.registerStore(reducer);
|
|
686
689
|
addedReducerRef.current = reducer.storeName;
|
|
687
690
|
}
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
const subscribe = (updatedState) => {
|
|
691
|
-
// если состояние текущего редьюсера изменилось,
|
|
692
|
-
// обновляем локальное состояние и ререндерим компонент
|
|
693
|
-
if (stateRef.current !== updatedState) {
|
|
694
|
-
stateRef.current = updatedState;
|
|
695
|
-
forceRender();
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
// сразу обновляем состояние
|
|
699
|
-
subscribe(context.getState(reducer));
|
|
700
|
-
// и подписываемся на обновления редьюсера
|
|
701
|
-
unsubscribeRef.current = context.subscribe(reducer, subscribe);
|
|
691
|
+
const subscribe = useCallback((reactUpdate) => {
|
|
692
|
+
const unsubscribe = context.subscribe(reducer, reactUpdate);
|
|
702
693
|
// заменяем текущий редьюсер
|
|
703
694
|
reducerRef.current = reducer;
|
|
704
695
|
return () => {
|
|
705
696
|
// гарантируем отписку от обновлений текущего редьюсера,
|
|
706
697
|
// при анмаунте компонента
|
|
707
|
-
|
|
698
|
+
unsubscribe();
|
|
708
699
|
// если текущий редьюсер был зарегистрирован в диспетчере в этом хуке,
|
|
709
700
|
// удаляем его из диспетчера
|
|
710
701
|
if (addedReducerRef.current) {
|
|
@@ -713,7 +704,7 @@ function useStore(reducer) {
|
|
|
713
704
|
}
|
|
714
705
|
};
|
|
715
706
|
}, [reducer, context]);
|
|
716
|
-
return
|
|
707
|
+
return useSyncExternalStore(subscribe, () => context.getState(reducer), serverState ? () => serverState[reducer.storeName] : () => context.getState(reducer));
|
|
717
708
|
}
|
|
718
709
|
|
|
719
710
|
const contextExecution = typeof window !== 'undefined' ? window : global;
|
|
@@ -731,23 +722,29 @@ const schedule = scheduling();
|
|
|
731
722
|
function useSelector(storesOrStore, selector, equalityFn = shallowEqual) {
|
|
732
723
|
invariant(selector, `You must pass a selector to useSelectors`);
|
|
733
724
|
const context = useConsumerContext();
|
|
734
|
-
const
|
|
725
|
+
const serverState = useContext(ServerStateContext);
|
|
735
726
|
const renderIsScheduled = useRef(false);
|
|
736
727
|
const storesRef = useShallowEqual(storesOrStore);
|
|
737
728
|
const subscription = useMemo(() => new Subscription(toArray(storesRef).map(context.getStore)), [storesRef, context]);
|
|
738
729
|
const latestSubscriptionCallbackError = useRef();
|
|
739
|
-
const
|
|
740
|
-
|
|
730
|
+
const subscribe = useCallback((reactUpdate) => {
|
|
731
|
+
subscription.setOnStateChange(() => {
|
|
732
|
+
if (!renderIsScheduled.current) {
|
|
733
|
+
renderIsScheduled.current = true;
|
|
734
|
+
schedule(() => {
|
|
735
|
+
reactUpdate();
|
|
736
|
+
renderIsScheduled.current = false;
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
subscription.trySubscribe();
|
|
741
|
+
return () => {
|
|
742
|
+
return subscription.tryUnsubscribe();
|
|
743
|
+
};
|
|
744
|
+
}, [subscription]);
|
|
741
745
|
let selectedState;
|
|
742
746
|
try {
|
|
743
|
-
|
|
744
|
-
selector !== latestSelector.current ||
|
|
745
|
-
latestSubscriptionCallbackError.current) {
|
|
746
|
-
selectedState = selector(context.getState());
|
|
747
|
-
}
|
|
748
|
-
else {
|
|
749
|
-
selectedState = latestSelectedState.current;
|
|
750
|
-
}
|
|
747
|
+
selectedState = useSyncExternalStoreWithSelector(subscribe, context.getState, serverState ? () => serverState : context.getState, selector, equalityFn);
|
|
751
748
|
}
|
|
752
749
|
catch (err) {
|
|
753
750
|
let errorMessage = `An error occured while selecting the store state: ${err.message}.`;
|
|
@@ -757,46 +754,8 @@ function useSelector(storesOrStore, selector, equalityFn = shallowEqual) {
|
|
|
757
754
|
throw new Error(errorMessage);
|
|
758
755
|
}
|
|
759
756
|
useIsomorphicLayoutEffect(() => {
|
|
760
|
-
latestSelector.current = selector;
|
|
761
|
-
latestSelectedState.current = selectedState;
|
|
762
757
|
latestSubscriptionCallbackError.current = undefined;
|
|
763
758
|
});
|
|
764
|
-
useIsomorphicLayoutEffect(() => {
|
|
765
|
-
let didUnsubscribe = false;
|
|
766
|
-
function checkForUpdates() {
|
|
767
|
-
renderIsScheduled.current = false;
|
|
768
|
-
if (didUnsubscribe) {
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
try {
|
|
772
|
-
const newSelectedState = latestSelector.current(context.getState());
|
|
773
|
-
if (equalityFn(newSelectedState, latestSelectedState.current)) {
|
|
774
|
-
return;
|
|
775
|
-
}
|
|
776
|
-
latestSelectedState.current = newSelectedState;
|
|
777
|
-
}
|
|
778
|
-
catch (err) {
|
|
779
|
-
// we ignore all errors here, since when the component
|
|
780
|
-
// is re-rendered, the selectors are called again, and
|
|
781
|
-
// will throw again, if neither props nor store state
|
|
782
|
-
// changed
|
|
783
|
-
latestSubscriptionCallbackError.current = err;
|
|
784
|
-
}
|
|
785
|
-
forceRender();
|
|
786
|
-
}
|
|
787
|
-
subscription.setOnStateChange(() => {
|
|
788
|
-
if (!renderIsScheduled.current) {
|
|
789
|
-
renderIsScheduled.current = true;
|
|
790
|
-
schedule(checkForUpdates);
|
|
791
|
-
}
|
|
792
|
-
});
|
|
793
|
-
subscription.trySubscribe();
|
|
794
|
-
checkForUpdates();
|
|
795
|
-
return () => {
|
|
796
|
-
didUnsubscribe = true;
|
|
797
|
-
return subscription.tryUnsubscribe();
|
|
798
|
-
};
|
|
799
|
-
}, [subscription]);
|
|
800
759
|
return selectedState;
|
|
801
760
|
}
|
|
802
761
|
|
|
@@ -807,8 +766,6 @@ const useStoreSelector = (store, selector) => {
|
|
|
807
766
|
return useSelector(store, memoizedSelector);
|
|
808
767
|
};
|
|
809
768
|
|
|
810
|
-
// Define some constant arrays just to avoid re-creating these
|
|
811
|
-
const EMPTY_ARRAY = [];
|
|
812
769
|
const stringifyComponent = (Comp) => {
|
|
813
770
|
try {
|
|
814
771
|
return JSON.stringify(Comp);
|
|
@@ -817,11 +774,6 @@ const stringifyComponent = (Comp) => {
|
|
|
817
774
|
return String(Comp);
|
|
818
775
|
}
|
|
819
776
|
};
|
|
820
|
-
function storeStateUpdatesReducer(state, action) {
|
|
821
|
-
const [, updateCount] = state;
|
|
822
|
-
return [action.payload, updateCount + 1];
|
|
823
|
-
}
|
|
824
|
-
const initStateUpdates = () => [null, 0];
|
|
825
777
|
function connectAdvanced(
|
|
826
778
|
/*
|
|
827
779
|
selectorFactory is a func that is responsible for returning the selector function used to
|
|
@@ -875,10 +827,6 @@ pure = true,
|
|
|
875
827
|
wrappedComponentName,
|
|
876
828
|
WrappedComponent,
|
|
877
829
|
};
|
|
878
|
-
// If we aren't running in "pure" mode, we don't want to memoize values.
|
|
879
|
-
// To avoid conditionally calling hooks, we fall back to a tiny wrapper
|
|
880
|
-
// that just executes the given callback immediately.
|
|
881
|
-
const usePureOnlyMemo = pure ? useMemo : (callback) => callback();
|
|
882
830
|
const ConnectFunction = (props) => {
|
|
883
831
|
const [forwardedRef, wrapperProps] = useMemo(() => {
|
|
884
832
|
// Distinguish between actual "data" props that were passed to the wrapper component,
|
|
@@ -890,6 +838,7 @@ pure = true,
|
|
|
890
838
|
}, [props]);
|
|
891
839
|
// Retrieve the store and ancestor subscription via context, if available
|
|
892
840
|
const contextValue = useConsumerContext();
|
|
841
|
+
const serverState = useContext(ServerStateContext);
|
|
893
842
|
invariant(Boolean(contextValue), `Could not find context in ` +
|
|
894
843
|
`"${displayName}". Either wrap the root component in a <Provider>, ` +
|
|
895
844
|
`or pass a custom React context provider to <Provider> and the corresponding ` +
|
|
@@ -909,124 +858,34 @@ pure = true,
|
|
|
909
858
|
subscription.trySubscribe();
|
|
910
859
|
}
|
|
911
860
|
}, [subscription]);
|
|
912
|
-
// We need to force this wrapper component to re-render whenever a Redux store update
|
|
913
|
-
// causes a change to the calculated child component props (or we caught an error in mapState)
|
|
914
|
-
const [[previousStateUpdateResult], forceComponentUpdateDispatch] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates);
|
|
915
|
-
// Propagate any mapState/mapDispatch errors upwards
|
|
916
|
-
if (previousStateUpdateResult && previousStateUpdateResult.error) {
|
|
917
|
-
throw previousStateUpdateResult.error;
|
|
918
|
-
}
|
|
919
|
-
// Set up refs to coordinate values between the subscription effect and the render logic
|
|
920
|
-
const lastChildProps = useRef();
|
|
921
|
-
const lastWrapperProps = useRef(wrapperProps);
|
|
922
|
-
const childPropsFromStoreUpdate = useRef();
|
|
923
861
|
const renderIsScheduled = useRef(false);
|
|
924
|
-
const
|
|
925
|
-
// Tricky logic here:
|
|
926
|
-
// - This render may have been triggered by a Redux store update that produced new child props
|
|
927
|
-
// - However, we may have gotten new wrapper props after that
|
|
928
|
-
// If we have new child props, and the same wrapper props, we know we should use the new child props as-is.
|
|
929
|
-
// But, if we have new wrapper props, those might change the child props, so we have to recalculate things.
|
|
930
|
-
// So, we'll use the child props from store update only if the wrapper props are the same as last time.
|
|
931
|
-
if (childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current) {
|
|
932
|
-
return childPropsFromStoreUpdate.current;
|
|
933
|
-
}
|
|
934
|
-
// TODO We're reading the store directly in render() here. Bad idea?
|
|
935
|
-
// This will likely cause Bad Things (TM) to happen in Concurrent Mode.
|
|
936
|
-
// Note that we do this because on renders _not_ caused by store updates, we need the latest store state
|
|
937
|
-
// to determine what the child props should be.
|
|
862
|
+
const actualChildPropsSelector = useCallback(() => {
|
|
938
863
|
return childPropsSelector(contextValue.getState(), wrapperProps);
|
|
939
|
-
}, [contextValue,
|
|
940
|
-
// We need this to execute synchronously every time we re-render. However, React warns
|
|
941
|
-
// about useLayoutEffect in SSR, so we try to detect environment and fall back to
|
|
942
|
-
// just useEffect instead to avoid the warning, since neither will run anyway.
|
|
943
|
-
useIsomorphicLayoutEffect(() => {
|
|
944
|
-
// We want to capture the wrapper props and child props we used for later comparisons
|
|
945
|
-
lastWrapperProps.current = wrapperProps;
|
|
946
|
-
lastChildProps.current = actualChildProps;
|
|
947
|
-
// If the render was from a store update, clear out that reference and cascade the subscriber update
|
|
948
|
-
if (childPropsFromStoreUpdate.current) {
|
|
949
|
-
childPropsFromStoreUpdate.current = undefined;
|
|
950
|
-
}
|
|
951
|
-
});
|
|
864
|
+
}, [contextValue, wrapperProps, childPropsSelector]);
|
|
952
865
|
// Our re-subscribe logic only runs when the store/subscription setup changes
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
// Capture values for checking if and when this component unmounts
|
|
958
|
-
let didUnsubscribe = false;
|
|
959
|
-
let lastThrownError = null;
|
|
960
|
-
// We'll run this callback every time a store subscription update propagates to this component
|
|
961
|
-
const checkForUpdates = () => {
|
|
962
|
-
renderIsScheduled.current = false;
|
|
963
|
-
if (didUnsubscribe) {
|
|
964
|
-
// Don't run stale listeners.
|
|
965
|
-
// Redux doesn't guarantee unsubscriptions happen until next dispatch.
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
const latestStoreState = contextValue.getState();
|
|
969
|
-
let newChildProps;
|
|
970
|
-
let error;
|
|
971
|
-
try {
|
|
972
|
-
// Actually run the selector with the most recent store state and wrapper props
|
|
973
|
-
// to determine what the child props should be
|
|
974
|
-
newChildProps = childPropsSelector(latestStoreState, lastWrapperProps.current);
|
|
975
|
-
}
|
|
976
|
-
catch (e) {
|
|
977
|
-
error = e;
|
|
978
|
-
lastThrownError = e;
|
|
979
|
-
}
|
|
980
|
-
if (!error) {
|
|
981
|
-
lastThrownError = null;
|
|
982
|
-
}
|
|
983
|
-
// If the child props haven't changed, nothing to do here - cascade the subscription update
|
|
984
|
-
if (newChildProps !== lastChildProps.current) {
|
|
985
|
-
// Save references to the new child props. Note that we track the "child props from store update"
|
|
986
|
-
// as a ref instead of a useState/useReducer because we need a way to determine if that value has
|
|
987
|
-
// been processed. If this went into useState/useReducer, we couldn't clear out the value without
|
|
988
|
-
// forcing another re-render, which we don't want.
|
|
989
|
-
lastChildProps.current = newChildProps;
|
|
990
|
-
childPropsFromStoreUpdate.current = newChildProps;
|
|
991
|
-
// If the child props _did_ change (or we caught an error), this wrapper component needs to re-render
|
|
992
|
-
forceComponentUpdateDispatch({
|
|
993
|
-
type: 'STORE_UPDATED',
|
|
994
|
-
payload: {
|
|
995
|
-
latestStoreState,
|
|
996
|
-
error,
|
|
997
|
-
},
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
// Actually subscribe to the nearest connected ancestor (or store)
|
|
866
|
+
const subscribe = useCallback((reactUpdate) => {
|
|
867
|
+
if (!subscription) {
|
|
868
|
+
return noop;
|
|
869
|
+
}
|
|
1002
870
|
subscription.setOnStateChange(() => {
|
|
1003
871
|
if (!renderIsScheduled.current) {
|
|
1004
872
|
renderIsScheduled.current = true;
|
|
1005
|
-
schedule(
|
|
873
|
+
schedule(() => {
|
|
874
|
+
reactUpdate();
|
|
875
|
+
renderIsScheduled.current = false;
|
|
876
|
+
});
|
|
1006
877
|
}
|
|
1007
878
|
});
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
checkForUpdates();
|
|
1011
|
-
const unsubscribeWrapper = () => {
|
|
1012
|
-
didUnsubscribe = true;
|
|
1013
|
-
subscription.tryUnsubscribe();
|
|
1014
|
-
if (lastThrownError) {
|
|
1015
|
-
// It's possible that we caught an error due to a bad mapState function, but the
|
|
1016
|
-
// parent re-rendered without this component and we're about to unmount.
|
|
1017
|
-
// This shouldn't happen as long as we do top-down subscriptions correctly, but
|
|
1018
|
-
// if we ever do those wrong, this throw will surface the error in our tests.
|
|
1019
|
-
// In that case, throw the error from here so it doesn't get lost.
|
|
1020
|
-
throw lastThrownError;
|
|
1021
|
-
}
|
|
879
|
+
return () => {
|
|
880
|
+
return subscription.tryUnsubscribe();
|
|
1022
881
|
};
|
|
1023
|
-
|
|
1024
|
-
|
|
882
|
+
}, [subscription]);
|
|
883
|
+
const actualChildProps = useSyncExternalStore(subscribe, actualChildPropsSelector, serverState ? childPropsSelector(serverState, wrapperProps) : actualChildPropsSelector);
|
|
1025
884
|
// Now that all that's done, we can finally try to actually render the child component.
|
|
1026
885
|
// We memoize the elements for the rendered child component as an optimization.
|
|
1027
886
|
const renderedWrappedComponent = useMemo(
|
|
1028
887
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
1029
|
-
() =>
|
|
888
|
+
() => jsx(WrappedComponent, Object.assign({}, actualChildProps, { ref: forwardedRef }), void 0), [forwardedRef, actualChildProps]);
|
|
1030
889
|
return renderedWrappedComponent;
|
|
1031
890
|
};
|
|
1032
891
|
// If we're in "pure" mode, ensure our wrapper component only re-renders when incoming props have changed.
|
|
@@ -1036,7 +895,7 @@ pure = true,
|
|
|
1036
895
|
if (forwardRef) {
|
|
1037
896
|
const forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
|
|
1038
897
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
1039
|
-
return
|
|
898
|
+
return jsx(Connect, Object.assign({}, props, { forwardedRef: ref }), void 0);
|
|
1040
899
|
});
|
|
1041
900
|
forwarded.displayName = displayName;
|
|
1042
901
|
forwarded.WrappedComponent = WrappedComponent;
|
|
@@ -1300,8 +1159,8 @@ function finalPropsSelectorFactory(context, { initMapStateToProps, initMapContex
|
|
|
1300
1159
|
return selectorFactory(mapStateToProps, mapContextToProps, mergeProps, context, options);
|
|
1301
1160
|
}
|
|
1302
1161
|
|
|
1303
|
-
const Provider = ({ context, children }) => {
|
|
1304
|
-
return
|
|
1162
|
+
const Provider = ({ context, children, serverState, }) => {
|
|
1163
|
+
return (jsx(ConnectContext.Provider, Object.assign({ value: context }, { children: jsx(ServerStateContext.Provider, Object.assign({ value: serverState }, { children: children }), void 0) }), void 0));
|
|
1305
1164
|
};
|
|
1306
1165
|
|
|
1307
1166
|
/*
|
package/lib/index.js
CHANGED
|
@@ -8,14 +8,17 @@ var identity = require('@tinkoff/utils/function/identity');
|
|
|
8
8
|
var pick = require('@tinkoff/utils/object/pick');
|
|
9
9
|
var shallowEqual = require('@tinkoff/utils/is/shallowEqual');
|
|
10
10
|
var strictEqual = require('@tinkoff/utils/is/strictEqual');
|
|
11
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
12
|
+
var noop = require('@tinkoff/utils/function/noop');
|
|
11
13
|
var hoistStatics = require('hoist-non-react-statics');
|
|
12
14
|
var invariant = require('invariant');
|
|
13
15
|
var React = require('react');
|
|
14
16
|
var reactIs = require('react-is');
|
|
17
|
+
var shim = require('use-sync-external-store/shim');
|
|
15
18
|
var isArray = require('@tinkoff/utils/is/array');
|
|
16
19
|
var reactHooks = require('@tinkoff/react-hooks');
|
|
17
|
-
var noop = require('@tinkoff/utils/function/noop');
|
|
18
20
|
var toArray = require('@tinkoff/utils/array/toArray');
|
|
21
|
+
var withSelector = require('use-sync-external-store/shim/with-selector');
|
|
19
22
|
var always = require('@tinkoff/utils/function/always');
|
|
20
23
|
var mapObject = require('@tinkoff/utils/object/map');
|
|
21
24
|
var isPlainObject = require('@tinkoff/utils/is/plainObject');
|
|
@@ -29,11 +32,11 @@ var identity__default = /*#__PURE__*/_interopDefaultLegacy(identity);
|
|
|
29
32
|
var pick__default = /*#__PURE__*/_interopDefaultLegacy(pick);
|
|
30
33
|
var shallowEqual__default = /*#__PURE__*/_interopDefaultLegacy(shallowEqual);
|
|
31
34
|
var strictEqual__default = /*#__PURE__*/_interopDefaultLegacy(strictEqual);
|
|
35
|
+
var noop__default = /*#__PURE__*/_interopDefaultLegacy(noop);
|
|
32
36
|
var hoistStatics__default = /*#__PURE__*/_interopDefaultLegacy(hoistStatics);
|
|
33
37
|
var invariant__default = /*#__PURE__*/_interopDefaultLegacy(invariant);
|
|
34
38
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
35
39
|
var isArray__default = /*#__PURE__*/_interopDefaultLegacy(isArray);
|
|
36
|
-
var noop__default = /*#__PURE__*/_interopDefaultLegacy(noop);
|
|
37
40
|
var toArray__default = /*#__PURE__*/_interopDefaultLegacy(toArray);
|
|
38
41
|
var always__default = /*#__PURE__*/_interopDefaultLegacy(always);
|
|
39
42
|
var mapObject__default = /*#__PURE__*/_interopDefaultLegacy(mapObject);
|
|
@@ -674,6 +677,7 @@ class Subscription {
|
|
|
674
677
|
}
|
|
675
678
|
|
|
676
679
|
const ConnectContext = React.createContext(null);
|
|
680
|
+
const ServerStateContext = React.createContext(null);
|
|
677
681
|
|
|
678
682
|
const useConsumerContext = () => {
|
|
679
683
|
const context = React.useContext(ConnectContext);
|
|
@@ -696,10 +700,9 @@ function useActions(actions) {
|
|
|
696
700
|
|
|
697
701
|
function useStore(reducer) {
|
|
698
702
|
const context = useConsumerContext();
|
|
703
|
+
const serverState = React.useContext(ServerStateContext);
|
|
699
704
|
const reducerRef = React.useRef(reducer);
|
|
700
705
|
const addedReducerRef = React.useRef(null);
|
|
701
|
-
const unsubscribeRef = React.useRef(noop__default["default"]);
|
|
702
|
-
const [, forceRender] = React.useReducer((s) => s + 1, 0);
|
|
703
706
|
// если текущий редьюсер не зарегистрирован в диспетчере,
|
|
704
707
|
// регистрируем его вручную, что бы гарантировать работоспособность `context.getState(reducer)`,
|
|
705
708
|
// и сохраняем в `addedReducerRef`, что бы удалить при unmount
|
|
@@ -707,26 +710,14 @@ function useStore(reducer) {
|
|
|
707
710
|
context.registerStore(reducer);
|
|
708
711
|
addedReducerRef.current = reducer.storeName;
|
|
709
712
|
}
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
const subscribe = (updatedState) => {
|
|
713
|
-
// если состояние текущего редьюсера изменилось,
|
|
714
|
-
// обновляем локальное состояние и ререндерим компонент
|
|
715
|
-
if (stateRef.current !== updatedState) {
|
|
716
|
-
stateRef.current = updatedState;
|
|
717
|
-
forceRender();
|
|
718
|
-
}
|
|
719
|
-
};
|
|
720
|
-
// сразу обновляем состояние
|
|
721
|
-
subscribe(context.getState(reducer));
|
|
722
|
-
// и подписываемся на обновления редьюсера
|
|
723
|
-
unsubscribeRef.current = context.subscribe(reducer, subscribe);
|
|
713
|
+
const subscribe = React.useCallback((reactUpdate) => {
|
|
714
|
+
const unsubscribe = context.subscribe(reducer, reactUpdate);
|
|
724
715
|
// заменяем текущий редьюсер
|
|
725
716
|
reducerRef.current = reducer;
|
|
726
717
|
return () => {
|
|
727
718
|
// гарантируем отписку от обновлений текущего редьюсера,
|
|
728
719
|
// при анмаунте компонента
|
|
729
|
-
|
|
720
|
+
unsubscribe();
|
|
730
721
|
// если текущий редьюсер был зарегистрирован в диспетчере в этом хуке,
|
|
731
722
|
// удаляем его из диспетчера
|
|
732
723
|
if (addedReducerRef.current) {
|
|
@@ -735,7 +726,7 @@ function useStore(reducer) {
|
|
|
735
726
|
}
|
|
736
727
|
};
|
|
737
728
|
}, [reducer, context]);
|
|
738
|
-
return
|
|
729
|
+
return shim.useSyncExternalStore(subscribe, () => context.getState(reducer), serverState ? () => serverState[reducer.storeName] : () => context.getState(reducer));
|
|
739
730
|
}
|
|
740
731
|
|
|
741
732
|
const contextExecution = typeof window !== 'undefined' ? window : global;
|
|
@@ -753,23 +744,29 @@ const schedule = scheduling();
|
|
|
753
744
|
function useSelector(storesOrStore, selector, equalityFn = shallowEqual__default["default"]) {
|
|
754
745
|
invariant__default["default"](selector, `You must pass a selector to useSelectors`);
|
|
755
746
|
const context = useConsumerContext();
|
|
756
|
-
const
|
|
747
|
+
const serverState = React.useContext(ServerStateContext);
|
|
757
748
|
const renderIsScheduled = React.useRef(false);
|
|
758
749
|
const storesRef = reactHooks.useShallowEqual(storesOrStore);
|
|
759
750
|
const subscription = React.useMemo(() => new Subscription(toArray__default["default"](storesRef).map(context.getStore)), [storesRef, context]);
|
|
760
751
|
const latestSubscriptionCallbackError = React.useRef();
|
|
761
|
-
const
|
|
762
|
-
|
|
752
|
+
const subscribe = React.useCallback((reactUpdate) => {
|
|
753
|
+
subscription.setOnStateChange(() => {
|
|
754
|
+
if (!renderIsScheduled.current) {
|
|
755
|
+
renderIsScheduled.current = true;
|
|
756
|
+
schedule(() => {
|
|
757
|
+
reactUpdate();
|
|
758
|
+
renderIsScheduled.current = false;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
subscription.trySubscribe();
|
|
763
|
+
return () => {
|
|
764
|
+
return subscription.tryUnsubscribe();
|
|
765
|
+
};
|
|
766
|
+
}, [subscription]);
|
|
763
767
|
let selectedState;
|
|
764
768
|
try {
|
|
765
|
-
|
|
766
|
-
selector !== latestSelector.current ||
|
|
767
|
-
latestSubscriptionCallbackError.current) {
|
|
768
|
-
selectedState = selector(context.getState());
|
|
769
|
-
}
|
|
770
|
-
else {
|
|
771
|
-
selectedState = latestSelectedState.current;
|
|
772
|
-
}
|
|
769
|
+
selectedState = withSelector.useSyncExternalStoreWithSelector(subscribe, context.getState, serverState ? () => serverState : context.getState, selector, equalityFn);
|
|
773
770
|
}
|
|
774
771
|
catch (err) {
|
|
775
772
|
let errorMessage = `An error occured while selecting the store state: ${err.message}.`;
|
|
@@ -779,46 +776,8 @@ function useSelector(storesOrStore, selector, equalityFn = shallowEqual__default
|
|
|
779
776
|
throw new Error(errorMessage);
|
|
780
777
|
}
|
|
781
778
|
reactHooks.useIsomorphicLayoutEffect(() => {
|
|
782
|
-
latestSelector.current = selector;
|
|
783
|
-
latestSelectedState.current = selectedState;
|
|
784
779
|
latestSubscriptionCallbackError.current = undefined;
|
|
785
780
|
});
|
|
786
|
-
reactHooks.useIsomorphicLayoutEffect(() => {
|
|
787
|
-
let didUnsubscribe = false;
|
|
788
|
-
function checkForUpdates() {
|
|
789
|
-
renderIsScheduled.current = false;
|
|
790
|
-
if (didUnsubscribe) {
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
try {
|
|
794
|
-
const newSelectedState = latestSelector.current(context.getState());
|
|
795
|
-
if (equalityFn(newSelectedState, latestSelectedState.current)) {
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
latestSelectedState.current = newSelectedState;
|
|
799
|
-
}
|
|
800
|
-
catch (err) {
|
|
801
|
-
// we ignore all errors here, since when the component
|
|
802
|
-
// is re-rendered, the selectors are called again, and
|
|
803
|
-
// will throw again, if neither props nor store state
|
|
804
|
-
// changed
|
|
805
|
-
latestSubscriptionCallbackError.current = err;
|
|
806
|
-
}
|
|
807
|
-
forceRender();
|
|
808
|
-
}
|
|
809
|
-
subscription.setOnStateChange(() => {
|
|
810
|
-
if (!renderIsScheduled.current) {
|
|
811
|
-
renderIsScheduled.current = true;
|
|
812
|
-
schedule(checkForUpdates);
|
|
813
|
-
}
|
|
814
|
-
});
|
|
815
|
-
subscription.trySubscribe();
|
|
816
|
-
checkForUpdates();
|
|
817
|
-
return () => {
|
|
818
|
-
didUnsubscribe = true;
|
|
819
|
-
return subscription.tryUnsubscribe();
|
|
820
|
-
};
|
|
821
|
-
}, [subscription]);
|
|
822
781
|
return selectedState;
|
|
823
782
|
}
|
|
824
783
|
|
|
@@ -829,8 +788,6 @@ const useStoreSelector = (store, selector) => {
|
|
|
829
788
|
return useSelector(store, memoizedSelector);
|
|
830
789
|
};
|
|
831
790
|
|
|
832
|
-
// Define some constant arrays just to avoid re-creating these
|
|
833
|
-
const EMPTY_ARRAY = [];
|
|
834
791
|
const stringifyComponent = (Comp) => {
|
|
835
792
|
try {
|
|
836
793
|
return JSON.stringify(Comp);
|
|
@@ -839,11 +796,6 @@ const stringifyComponent = (Comp) => {
|
|
|
839
796
|
return String(Comp);
|
|
840
797
|
}
|
|
841
798
|
};
|
|
842
|
-
function storeStateUpdatesReducer(state, action) {
|
|
843
|
-
const [, updateCount] = state;
|
|
844
|
-
return [action.payload, updateCount + 1];
|
|
845
|
-
}
|
|
846
|
-
const initStateUpdates = () => [null, 0];
|
|
847
799
|
function connectAdvanced(
|
|
848
800
|
/*
|
|
849
801
|
selectorFactory is a func that is responsible for returning the selector function used to
|
|
@@ -897,10 +849,6 @@ pure = true,
|
|
|
897
849
|
wrappedComponentName,
|
|
898
850
|
WrappedComponent,
|
|
899
851
|
};
|
|
900
|
-
// If we aren't running in "pure" mode, we don't want to memoize values.
|
|
901
|
-
// To avoid conditionally calling hooks, we fall back to a tiny wrapper
|
|
902
|
-
// that just executes the given callback immediately.
|
|
903
|
-
const usePureOnlyMemo = pure ? React.useMemo : (callback) => callback();
|
|
904
852
|
const ConnectFunction = (props) => {
|
|
905
853
|
const [forwardedRef, wrapperProps] = React.useMemo(() => {
|
|
906
854
|
// Distinguish between actual "data" props that were passed to the wrapper component,
|
|
@@ -912,6 +860,7 @@ pure = true,
|
|
|
912
860
|
}, [props]);
|
|
913
861
|
// Retrieve the store and ancestor subscription via context, if available
|
|
914
862
|
const contextValue = useConsumerContext();
|
|
863
|
+
const serverState = React.useContext(ServerStateContext);
|
|
915
864
|
invariant__default["default"](Boolean(contextValue), `Could not find context in ` +
|
|
916
865
|
`"${displayName}". Either wrap the root component in a <Provider>, ` +
|
|
917
866
|
`or pass a custom React context provider to <Provider> and the corresponding ` +
|
|
@@ -931,124 +880,34 @@ pure = true,
|
|
|
931
880
|
subscription.trySubscribe();
|
|
932
881
|
}
|
|
933
882
|
}, [subscription]);
|
|
934
|
-
// We need to force this wrapper component to re-render whenever a Redux store update
|
|
935
|
-
// causes a change to the calculated child component props (or we caught an error in mapState)
|
|
936
|
-
const [[previousStateUpdateResult], forceComponentUpdateDispatch] = React.useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates);
|
|
937
|
-
// Propagate any mapState/mapDispatch errors upwards
|
|
938
|
-
if (previousStateUpdateResult && previousStateUpdateResult.error) {
|
|
939
|
-
throw previousStateUpdateResult.error;
|
|
940
|
-
}
|
|
941
|
-
// Set up refs to coordinate values between the subscription effect and the render logic
|
|
942
|
-
const lastChildProps = React.useRef();
|
|
943
|
-
const lastWrapperProps = React.useRef(wrapperProps);
|
|
944
|
-
const childPropsFromStoreUpdate = React.useRef();
|
|
945
883
|
const renderIsScheduled = React.useRef(false);
|
|
946
|
-
const
|
|
947
|
-
// Tricky logic here:
|
|
948
|
-
// - This render may have been triggered by a Redux store update that produced new child props
|
|
949
|
-
// - However, we may have gotten new wrapper props after that
|
|
950
|
-
// If we have new child props, and the same wrapper props, we know we should use the new child props as-is.
|
|
951
|
-
// But, if we have new wrapper props, those might change the child props, so we have to recalculate things.
|
|
952
|
-
// So, we'll use the child props from store update only if the wrapper props are the same as last time.
|
|
953
|
-
if (childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current) {
|
|
954
|
-
return childPropsFromStoreUpdate.current;
|
|
955
|
-
}
|
|
956
|
-
// TODO We're reading the store directly in render() here. Bad idea?
|
|
957
|
-
// This will likely cause Bad Things (TM) to happen in Concurrent Mode.
|
|
958
|
-
// Note that we do this because on renders _not_ caused by store updates, we need the latest store state
|
|
959
|
-
// to determine what the child props should be.
|
|
884
|
+
const actualChildPropsSelector = React.useCallback(() => {
|
|
960
885
|
return childPropsSelector(contextValue.getState(), wrapperProps);
|
|
961
|
-
}, [contextValue,
|
|
962
|
-
// We need this to execute synchronously every time we re-render. However, React warns
|
|
963
|
-
// about useLayoutEffect in SSR, so we try to detect environment and fall back to
|
|
964
|
-
// just useEffect instead to avoid the warning, since neither will run anyway.
|
|
965
|
-
reactHooks.useIsomorphicLayoutEffect(() => {
|
|
966
|
-
// We want to capture the wrapper props and child props we used for later comparisons
|
|
967
|
-
lastWrapperProps.current = wrapperProps;
|
|
968
|
-
lastChildProps.current = actualChildProps;
|
|
969
|
-
// If the render was from a store update, clear out that reference and cascade the subscriber update
|
|
970
|
-
if (childPropsFromStoreUpdate.current) {
|
|
971
|
-
childPropsFromStoreUpdate.current = undefined;
|
|
972
|
-
}
|
|
973
|
-
});
|
|
886
|
+
}, [contextValue, wrapperProps, childPropsSelector]);
|
|
974
887
|
// Our re-subscribe logic only runs when the store/subscription setup changes
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
// Capture values for checking if and when this component unmounts
|
|
980
|
-
let didUnsubscribe = false;
|
|
981
|
-
let lastThrownError = null;
|
|
982
|
-
// We'll run this callback every time a store subscription update propagates to this component
|
|
983
|
-
const checkForUpdates = () => {
|
|
984
|
-
renderIsScheduled.current = false;
|
|
985
|
-
if (didUnsubscribe) {
|
|
986
|
-
// Don't run stale listeners.
|
|
987
|
-
// Redux doesn't guarantee unsubscriptions happen until next dispatch.
|
|
988
|
-
return;
|
|
989
|
-
}
|
|
990
|
-
const latestStoreState = contextValue.getState();
|
|
991
|
-
let newChildProps;
|
|
992
|
-
let error;
|
|
993
|
-
try {
|
|
994
|
-
// Actually run the selector with the most recent store state and wrapper props
|
|
995
|
-
// to determine what the child props should be
|
|
996
|
-
newChildProps = childPropsSelector(latestStoreState, lastWrapperProps.current);
|
|
997
|
-
}
|
|
998
|
-
catch (e) {
|
|
999
|
-
error = e;
|
|
1000
|
-
lastThrownError = e;
|
|
1001
|
-
}
|
|
1002
|
-
if (!error) {
|
|
1003
|
-
lastThrownError = null;
|
|
1004
|
-
}
|
|
1005
|
-
// If the child props haven't changed, nothing to do here - cascade the subscription update
|
|
1006
|
-
if (newChildProps !== lastChildProps.current) {
|
|
1007
|
-
// Save references to the new child props. Note that we track the "child props from store update"
|
|
1008
|
-
// as a ref instead of a useState/useReducer because we need a way to determine if that value has
|
|
1009
|
-
// been processed. If this went into useState/useReducer, we couldn't clear out the value without
|
|
1010
|
-
// forcing another re-render, which we don't want.
|
|
1011
|
-
lastChildProps.current = newChildProps;
|
|
1012
|
-
childPropsFromStoreUpdate.current = newChildProps;
|
|
1013
|
-
// If the child props _did_ change (or we caught an error), this wrapper component needs to re-render
|
|
1014
|
-
forceComponentUpdateDispatch({
|
|
1015
|
-
type: 'STORE_UPDATED',
|
|
1016
|
-
payload: {
|
|
1017
|
-
latestStoreState,
|
|
1018
|
-
error,
|
|
1019
|
-
},
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
// Actually subscribe to the nearest connected ancestor (or store)
|
|
888
|
+
const subscribe = React.useCallback((reactUpdate) => {
|
|
889
|
+
if (!subscription) {
|
|
890
|
+
return noop__default["default"];
|
|
891
|
+
}
|
|
1024
892
|
subscription.setOnStateChange(() => {
|
|
1025
893
|
if (!renderIsScheduled.current) {
|
|
1026
894
|
renderIsScheduled.current = true;
|
|
1027
|
-
schedule(
|
|
895
|
+
schedule(() => {
|
|
896
|
+
reactUpdate();
|
|
897
|
+
renderIsScheduled.current = false;
|
|
898
|
+
});
|
|
1028
899
|
}
|
|
1029
900
|
});
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
checkForUpdates();
|
|
1033
|
-
const unsubscribeWrapper = () => {
|
|
1034
|
-
didUnsubscribe = true;
|
|
1035
|
-
subscription.tryUnsubscribe();
|
|
1036
|
-
if (lastThrownError) {
|
|
1037
|
-
// It's possible that we caught an error due to a bad mapState function, but the
|
|
1038
|
-
// parent re-rendered without this component and we're about to unmount.
|
|
1039
|
-
// This shouldn't happen as long as we do top-down subscriptions correctly, but
|
|
1040
|
-
// if we ever do those wrong, this throw will surface the error in our tests.
|
|
1041
|
-
// In that case, throw the error from here so it doesn't get lost.
|
|
1042
|
-
throw lastThrownError;
|
|
1043
|
-
}
|
|
901
|
+
return () => {
|
|
902
|
+
return subscription.tryUnsubscribe();
|
|
1044
903
|
};
|
|
1045
|
-
|
|
1046
|
-
|
|
904
|
+
}, [subscription]);
|
|
905
|
+
const actualChildProps = shim.useSyncExternalStore(subscribe, actualChildPropsSelector, serverState ? childPropsSelector(serverState, wrapperProps) : actualChildPropsSelector);
|
|
1047
906
|
// Now that all that's done, we can finally try to actually render the child component.
|
|
1048
907
|
// We memoize the elements for the rendered child component as an optimization.
|
|
1049
908
|
const renderedWrappedComponent = React.useMemo(
|
|
1050
909
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
1051
|
-
() =>
|
|
910
|
+
() => jsxRuntime.jsx(WrappedComponent, Object.assign({}, actualChildProps, { ref: forwardedRef }), void 0), [forwardedRef, actualChildProps]);
|
|
1052
911
|
return renderedWrappedComponent;
|
|
1053
912
|
};
|
|
1054
913
|
// If we're in "pure" mode, ensure our wrapper component only re-renders when incoming props have changed.
|
|
@@ -1058,7 +917,7 @@ pure = true,
|
|
|
1058
917
|
if (forwardRef) {
|
|
1059
918
|
const forwarded = React__default["default"].forwardRef(function forwardConnectRef(props, ref) {
|
|
1060
919
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
1061
|
-
return
|
|
920
|
+
return jsxRuntime.jsx(Connect, Object.assign({}, props, { forwardedRef: ref }), void 0);
|
|
1062
921
|
});
|
|
1063
922
|
forwarded.displayName = displayName;
|
|
1064
923
|
forwarded.WrappedComponent = WrappedComponent;
|
|
@@ -1322,8 +1181,8 @@ function finalPropsSelectorFactory(context, { initMapStateToProps, initMapContex
|
|
|
1322
1181
|
return selectorFactory(mapStateToProps, mapContextToProps, mergeProps, context, options);
|
|
1323
1182
|
}
|
|
1324
1183
|
|
|
1325
|
-
const Provider = ({ context, children }) => {
|
|
1326
|
-
return
|
|
1184
|
+
const Provider = ({ context, children, serverState, }) => {
|
|
1185
|
+
return (jsxRuntime.jsx(ConnectContext.Provider, Object.assign({ value: context }, { children: jsxRuntime.jsx(ServerStateContext.Provider, Object.assign({ value: serverState }, { children: children }), void 0) }), void 0));
|
|
1327
1186
|
};
|
|
1328
1187
|
|
|
1329
1188
|
/*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/state",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.89.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -20,11 +20,12 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@tinkoff/react-hooks": "0.0.24",
|
|
22
22
|
"@tinkoff/utils": "^2.1.2",
|
|
23
|
-
"@tramvai/types-actions-state-context": "1.
|
|
23
|
+
"@tramvai/types-actions-state-context": "1.89.1",
|
|
24
24
|
"@types/hoist-non-react-statics": "^3.3.1",
|
|
25
25
|
"invariant": "^2.2.4",
|
|
26
26
|
"react-is": ">=17",
|
|
27
|
-
"tslib": "^2.0.3"
|
|
27
|
+
"tslib": "^2.0.3",
|
|
28
|
+
"use-sync-external-store": "^1.0.0"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"hoist-non-react-statics": "^3.3.1",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"@reatom/core": "^1.1.5",
|
|
37
38
|
"@types/invariant": "^2.2.31",
|
|
38
39
|
"@types/react-is": "^17.0.0",
|
|
40
|
+
"@types/use-sync-external-store": "^0.0.3",
|
|
39
41
|
"redux": "^4.0.5"
|
|
40
42
|
},
|
|
41
43
|
"gitHead": "8e826a214c87b188fc4d254cdd8f2a2b2c55f3a8",
|