diff --git a/shared/hooks/useWhyDidYouUpdate.ts b/shared/hooks/useWhyDidYouUpdate.ts new file mode 100644 index 00000000..de6db5c9 --- /dev/null +++ b/shared/hooks/useWhyDidYouUpdate.ts @@ -0,0 +1,102 @@ +/** + * 检查组件变动情况(是什么导致了组件刷新) + * Fork from: https://github.com/devhubapp/devhub/blob/master/packages/components/src/hooks/use-why-did-you-update.ts + */ + +import { parse, stringify } from 'flatted'; +import _get from 'lodash/get'; +import _isEqual from 'lodash/isEqual'; +import { useEffect, useRef } from 'react'; + +interface UseWhyDidYouUpdateCallback { + onChangeFound?: (data: { + changesObj: Record< + string, + { + from: T; + to: T; + isDeepEqual: boolean; + changedKeys?: string[]; + } + >; + }) => void; + onNoChangeFound?: () => void; +} + +/** + * Quickly see which prop changed + * and caused a re-render by adding a single line to the component. + * + * USAGE: + * function MyComponent(props) { + * useWhyDidYouUpdate('MyComponent', props) + * + * return
+ * } + * + * OUTPUT: + * [why-did-you-update] MyComponent { myProp: { from 'oldvalue', to: 'newvalue' } } + * + * SHARE: + * This tip on Twitter: https://twitter.com/brunolemos/status/1090377532845801473 + * Also follow @brunolemos: https://twitter.com/brunolemos + */ +export function useWhyDidYouUpdate( + name: string, + props: Record, + { onChangeFound, onNoChangeFound }: UseWhyDidYouUpdateCallback = {} +) { + const latestProps = useRef(props); + + useEffect(() => { + if (!__DEV__) return; + + const allKeys = Object.keys({ ...latestProps.current, ...props }); + + const changesObj: Record< + string, + { + from: T; + to: T; + isDeepEqual: boolean; + changedKeys?: string[]; + } + > = {}; + allKeys.forEach((key) => { + if (latestProps.current[key] !== props[key]) { + changesObj[key] = { + from: latestProps.current[key], + to: props[key], + changedKeys: + props[key] && typeof props[key] === 'object' + ? Object.keys(latestProps.current[key]) + .map((k) => + _get(latestProps.current, [key, k]) === + _get(props, [key, k]) + ? '' + : k + ) + .filter(Boolean) + : undefined, + isDeepEqual: _isEqual(latestProps.current[key], props[key]), + }; + } + }); + + if (Object.keys(changesObj).length) { + if (onChangeFound) { + onChangeFound({ changesObj }); + } else { + // eslint-disable-next-line no-console + console.log('[why-did-you-update]', name, { + changes: parse(stringify(changesObj)), + props: { from: latestProps.current, to: props }, + }); + } + } else if (onNoChangeFound) { + onNoChangeFound(); + } + + latestProps.current = props; + }); +} diff --git a/shared/index.tsx b/shared/index.tsx index 3eca1506..c1a83a56 100644 --- a/shared/index.tsx +++ b/shared/index.tsx @@ -64,6 +64,7 @@ export { useMountedState } from './hooks/useMountedState'; export { usePrevious } from './hooks/usePrevious'; export { useRafState } from './hooks/useRafState'; export { useUpdateRef } from './hooks/useUpdateRef'; +export { useWhyDidYouUpdate } from './hooks/useWhyDidYouUpdate'; // manager export { buildRegFn } from './manager/buildRegFn'; diff --git a/shared/package.json b/shared/package.json index 4295c996..736f982b 100644 --- a/shared/package.json +++ b/shared/package.json @@ -12,6 +12,7 @@ "crc": "^3.8.0", "dayjs": "^1.10.6", "events": "^3.3.0", + "flatted": "^3.2.4", "formik": "^2.2.9", "i18next": "^20.3.2", "i18next-http-backend": "^1.2.6", diff --git a/yarn.lock b/yarn.lock index fef5a49a..d085f65f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5243,6 +5243,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.0.tgz#da07fb8808050aba6fdeac2294542e5043583f05" integrity sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A== +flatted@^3.2.4: + version "3.2.4" + resolved "https://registry.npmmirror.com/flatted/download/flatted-3.2.4.tgz?cache=0&sync_timestamp=1636473890680&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fflatted%2Fdownload%2Fflatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + flush-write-stream@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"