This repository has been archived by the owner on Dec 31, 2020. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 348
/
Copy pathobserver.js
149 lines (135 loc) · 5.28 KB
/
observer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import React, { Component, PureComponent, forwardRef } from "react"
import { createAtom, _allowStateChanges } from "mobx"
import {
observer as observerLite,
useStaticRendering as useStaticRenderingLite,
Observer
} from "mobx-react-lite"
import { newSymbol, shallowEqual } from "./utils/utils"
let isUsingStaticRendering = false
const skipRenderKey = newSymbol("skipRender")
const isForcingUpdateKey = newSymbol("isForcingUpdate")
// Using react-is had some issues (and operates on elements, not on types), see #608 / #609
const ReactForwardRefSymbol =
typeof forwardRef === "function" && forwardRef((_props, _ref) => {})["$$typeof"]
/**
* Helper to set `prop` to `this` as non-enumerable (hidden prop)
* @param target
* @param prop
* @param value
*/
function setHiddenProp(target, prop, value) {
if (!Object.hasOwnProperty.call(target, prop)) {
Object.defineProperty(target, prop, {
enumerable: false,
configurable: true,
writable: true,
value
})
} else {
target[prop] = value
}
}
export function useStaticRendering(useStaticRendering) {
isUsingStaticRendering = useStaticRendering
useStaticRenderingLite(useStaticRendering)
}
function observerSCU(nextProps, nextState) {
if (isUsingStaticRendering) {
console.warn(
"[mobx-react] It seems that a re-rendering of a React component is triggered while in static (server-side) mode. Please make sure components are rendered only once server-side."
)
}
// update on any state changes (as is the default)
if (this.state !== nextState) {
return true
}
// update if props are shallowly not equal, inspired by PureRenderMixin
// we could return just 'false' here, and avoid the `skipRender` checks etc
// however, it is nicer if lifecycle events are triggered like usually,
// so we return true here if props are shallowly modified.
return !shallowEqual(this.props, nextProps)
}
function makeObservableProp(target, propName) {
const valueHolderKey = newSymbol(`reactProp_${propName}_valueHolder`)
const atomHolderKey = newSymbol(`reactProp_${propName}_atomHolder`)
function getAtom() {
if (!this[atomHolderKey]) {
setHiddenProp(this, atomHolderKey, createAtom("reactive " + propName))
}
return this[atomHolderKey]
}
Object.defineProperty(target, propName, {
configurable: true,
enumerable: true,
get: function() {
getAtom.call(this).reportObserved()
return this[valueHolderKey]
},
set: function set(v) {
if (!this[isForcingUpdateKey] && !shallowEqual(this[valueHolderKey], v)) {
setHiddenProp(this, valueHolderKey, v)
setHiddenProp(this, skipRenderKey, true)
getAtom.call(this).reportChanged()
setHiddenProp(this, skipRenderKey, false)
} else {
setHiddenProp(this, valueHolderKey, v)
}
}
})
}
/**
* Observer function / decorator
*/
export function observer(componentClass) {
if (componentClass.isMobxInjector === true) {
console.warn(
"Mobx observer: You are trying to use 'observer' on a component that already has 'inject'. Please apply 'observer' before applying 'inject'"
)
}
// Unwrap forward refs into `<Observer>` component
// we need to unwrap the render, because it is the inner render that needs to be tracked,
// not the ForwardRef HoC
if (ReactForwardRefSymbol && componentClass["$$typeof"] === ReactForwardRefSymbol) {
const baseRender = componentClass.render
if (typeof baseRender !== "function")
throw new Error("render property of ForwardRef was not a function")
return forwardRef(function ObserverForwardRef() {
return <Observer>{() => baseRender.apply(undefined, arguments)}</Observer>
})
}
// Function component
if (
typeof componentClass === "function" &&
(!componentClass.prototype || !componentClass.prototype.render) &&
!componentClass.isReactClass &&
!Component.isPrototypeOf(componentClass)
) {
return observerLite(componentClass)
}
return makeClassComponentObserver(componentClass)
}
function makeClassComponentObserver(componentClass) {
const target = componentClass.prototype || componentClass
if (target.componentWillReact)
throw new Error("The componentWillReact life-cycle event is no longer supported")
if (componentClass.__proto__ !== PureComponent) {
if (!target.shouldComponentUpdate) target.shouldComponentUpdate = observerSCU
else if (target.shouldComponentUpdate !== observerSCU)
throw new Error(
"It is not allowed to use shouldComponentUpdate in observer based components."
)
}
makeObservableProp(target, "props")
makeObservableProp(target, "state")
const baseRender = target.render
target.render = function renderWrapper() {
if (!this.baseRender) {
// safe the closure, as it won't change!
const bound = baseRender.bind(this)
this.baseRender = () => bound()
}
return <Observer>{this.baseRender}</Observer>
}
return componentClass
}