UNPKG

3.32 kBPlain TextView Raw
1import { forwardRef, memo } from "react"
2
3import { isUsingStaticRendering } from "./staticRendering"
4import { useObserver } from "./useObserver"
5
6export interface IObserverOptions {
7 readonly forwardRef?: boolean
8}
9
10export function observer<P extends object, TRef = {}>(
11 baseComponent: React.RefForwardingComponent<TRef, P>,
12 options: IObserverOptions & { forwardRef: true }
13): React.MemoExoticComponent<
14 React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>
15>
16
17export function observer<P extends object>(
18 baseComponent: React.FunctionComponent<P>,
19 options?: IObserverOptions
20): React.FunctionComponent<P>
21
22export function observer<
23 C extends React.FunctionComponent<any> | React.RefForwardingComponent<any>,
24 Options extends IObserverOptions
25>(
26 baseComponent: C,
27 options?: Options
28): Options extends { forwardRef: true }
29 ? C extends React.RefForwardingComponent<infer TRef, infer P>
30 ? C &
31 React.MemoExoticComponent<
32 React.ForwardRefExoticComponent<
33 React.PropsWithoutRef<P> & React.RefAttributes<TRef>
34 >
35 >
36 : never /* forwardRef set for a non forwarding component */
37 : C & { displayName: string }
38
39// n.b. base case is not used for actual typings or exported in the typing files
40export function observer<P extends object, TRef = {}>(
41 baseComponent: React.RefForwardingComponent<TRef, P> | React.FunctionComponent<P>,
42 options?: IObserverOptions
43) {
44 // The working of observer is explained step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307
45 if (isUsingStaticRendering()) {
46 return baseComponent
47 }
48
49 const realOptions = {
50 forwardRef: false,
51 ...options
52 }
53
54 const baseComponentName = baseComponent.displayName || baseComponent.name
55
56 const wrappedComponent = (props: P, ref: React.Ref<TRef>) => {
57 return useObserver(() => baseComponent(props, ref), baseComponentName)
58 }
59 wrappedComponent.displayName = baseComponentName
60
61 // memo; we are not interested in deep updates
62 // in props; we assume that if deep objects are changed,
63 // this is in observables, which would have been tracked anyway
64 let memoComponent
65 if (realOptions.forwardRef) {
66 // we have to use forwardRef here because:
67 // 1. it cannot go before memo, only after it
68 // 2. forwardRef converts the function into an actual component, so we can't let the baseComponent do it
69 // since it wouldn't be a callable function anymore
70 memoComponent = memo(forwardRef(wrappedComponent))
71 } else {
72 memoComponent = memo(wrappedComponent)
73 }
74
75 copyStaticProperties(baseComponent, memoComponent)
76 memoComponent.displayName = baseComponentName
77
78 return memoComponent
79}
80
81// based on https://github.com/mridgway/hoist-non-react-statics/blob/master/src/index.js
82const hoistBlackList: any = {
83 $$typeof: true,
84 render: true,
85 compare: true,
86 type: true
87}
88
89function copyStaticProperties(base: any, target: any) {
90 Object.keys(base).forEach(key => {
91 if (!hoistBlackList[key]) {
92 Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key)!)
93 }
94 })
95}