1 | import { forwardRef, memo } from "react"
|
2 |
|
3 | import { isUsingStaticRendering } from "./staticRendering"
|
4 | import { useObserver } from "./useObserver"
|
5 |
|
6 | let warnObserverOptionsDeprecated = true
|
7 |
|
8 | const hasSymbol = typeof Symbol === "function" && Symbol.for
|
9 | const isFunctionNameConfigurable =
|
10 | Object.getOwnPropertyDescriptor(() => {}, "name")?.configurable ?? false
|
11 |
|
12 |
|
13 | const ReactForwardRefSymbol = hasSymbol
|
14 | ? Symbol.for("react.forward_ref")
|
15 | : typeof forwardRef === "function" && forwardRef((props: any) => null)["$$typeof"]
|
16 |
|
17 | const ReactMemoSymbol = hasSymbol
|
18 | ? Symbol.for("react.memo")
|
19 | : typeof memo === "function" && memo((props: any) => null)["$$typeof"]
|
20 |
|
21 | export interface IObserverOptions {
|
22 | readonly forwardRef?: boolean
|
23 | }
|
24 |
|
25 | export function observer<P extends object, TRef = {}>(
|
26 | baseComponent: React.ForwardRefRenderFunction<TRef, P>,
|
27 | options: IObserverOptions & { forwardRef: true }
|
28 | ): React.MemoExoticComponent<
|
29 | React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>
|
30 | >
|
31 |
|
32 | export function observer<P extends object, TRef = {}>(
|
33 | baseComponent: React.ForwardRefExoticComponent<
|
34 | React.PropsWithoutRef<P> & React.RefAttributes<TRef>
|
35 | >
|
36 | ): React.MemoExoticComponent<
|
37 | React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>
|
38 | >
|
39 |
|
40 | export function observer<P extends object>(
|
41 | baseComponent: React.FunctionComponent<P>,
|
42 | options?: IObserverOptions
|
43 | ): React.FunctionComponent<P>
|
44 |
|
45 | export function observer<
|
46 | C extends React.FunctionComponent<any> | React.ForwardRefRenderFunction<any>,
|
47 | Options extends IObserverOptions
|
48 | >(
|
49 | baseComponent: C,
|
50 | options?: Options
|
51 | ): Options extends { forwardRef: true }
|
52 | ? C extends React.ForwardRefRenderFunction<infer TRef, infer P>
|
53 | ? C &
|
54 | React.MemoExoticComponent<
|
55 | React.ForwardRefExoticComponent<
|
56 | React.PropsWithoutRef<P> & React.RefAttributes<TRef>
|
57 | >
|
58 | >
|
59 | : never /* forwardRef set for a non forwarding component */
|
60 | : C & { displayName: string }
|
61 |
|
62 |
|
63 | export function observer<P extends object, TRef = {}>(
|
64 | baseComponent:
|
65 | | React.ForwardRefRenderFunction<TRef, P>
|
66 | | React.FunctionComponent<P>
|
67 | | React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>,
|
68 |
|
69 | options?: IObserverOptions
|
70 | ) {
|
71 | if (process.env.NODE_ENV !== "production" && warnObserverOptionsDeprecated && options) {
|
72 | warnObserverOptionsDeprecated = false
|
73 | console.warn(
|
74 | `[mobx-react-lite] \`observer(fn, { forwardRef: true })\` is deprecated, use \`observer(React.forwardRef(fn))\``
|
75 | )
|
76 | }
|
77 |
|
78 | if (ReactMemoSymbol && baseComponent["$$typeof"] === ReactMemoSymbol) {
|
79 | throw new Error(
|
80 | `[mobx-react-lite] You are trying to use \`observer\` on a function component wrapped in either another \`observer\` or \`React.memo\`. The observer already applies 'React.memo' for you.`
|
81 | )
|
82 | }
|
83 |
|
84 |
|
85 | if (isUsingStaticRendering()) {
|
86 | return baseComponent
|
87 | }
|
88 |
|
89 | let useForwardRef = options?.forwardRef ?? false
|
90 | let render = baseComponent
|
91 |
|
92 | const baseComponentName = baseComponent.displayName || baseComponent.name
|
93 |
|
94 |
|
95 |
|
96 | if (ReactForwardRefSymbol && baseComponent["$$typeof"] === ReactForwardRefSymbol) {
|
97 | useForwardRef = true
|
98 | render = baseComponent["render"]
|
99 | if (typeof render !== "function") {
|
100 | throw new Error(
|
101 | `[mobx-react-lite] \`render\` property of ForwardRef was not a function`
|
102 | )
|
103 | }
|
104 | }
|
105 |
|
106 | let observerComponent = (props: any, ref: React.Ref<TRef>) => {
|
107 | return useObserver(() => render(props, ref), baseComponentName)
|
108 | }
|
109 |
|
110 |
|
111 | ;(observerComponent as React.FunctionComponent).displayName = baseComponent.displayName
|
112 |
|
113 | if (isFunctionNameConfigurable) {
|
114 | Object.defineProperty(observerComponent, "name", {
|
115 | value: baseComponent.name,
|
116 | writable: true,
|
117 | configurable: true
|
118 | })
|
119 | }
|
120 |
|
121 |
|
122 | if ((baseComponent as any).contextTypes) {
|
123 | ;(observerComponent as React.FunctionComponent).contextTypes = (
|
124 | baseComponent as any
|
125 | ).contextTypes
|
126 | }
|
127 |
|
128 | if (useForwardRef) {
|
129 |
|
130 |
|
131 |
|
132 | observerComponent = forwardRef(observerComponent)
|
133 | }
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | observerComponent = memo(observerComponent)
|
139 |
|
140 | copyStaticProperties(baseComponent, observerComponent)
|
141 |
|
142 | if ("production" !== process.env.NODE_ENV) {
|
143 | Object.defineProperty(observerComponent, "contextTypes", {
|
144 | set() {
|
145 | throw new Error(
|
146 | `[mobx-react-lite] \`${
|
147 | this.displayName || this.type?.displayName || this.type?.name || "Component"
|
148 | }.contextTypes\` must be set before applying \`observer\`.`
|
149 | )
|
150 | }
|
151 | })
|
152 | }
|
153 |
|
154 | return observerComponent
|
155 | }
|
156 |
|
157 |
|
158 | const hoistBlackList: any = {
|
159 | $$typeof: true,
|
160 | render: true,
|
161 | compare: true,
|
162 | type: true,
|
163 |
|
164 |
|
165 | displayName: true
|
166 | }
|
167 |
|
168 | function copyStaticProperties(base: any, target: any) {
|
169 | Object.keys(base).forEach(key => {
|
170 | if (!hoistBlackList[key]) {
|
171 | Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key)!)
|
172 | }
|
173 | })
|
174 | }
|