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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174 | 27x
18x
104x
18x
10x
27x
136x
112x
6x
106x
6x
2x
104x
91x
18x
18x
18x
18x
18x
10x
10x
10x
10x
10x
1x
1x
10x
1x
1x
1x
9x
1x
8x
10x
10x
10x
8x
8x
18x
16x
10x
11x
73x
8x
8x
8x
2x
2x
2x
6x
6x
4x
6x
8x
8x
4x
6x
65x
65x
20x
52x
51x
13x
13x
| import * as React from 'react';
export interface Context {
[key: string]: any;
}
interface PreactElement<P> {
attributes: P;
}
function getProps<P>(element: React.ReactElement<P> | PreactElement<P>): P {
return (element as React.ReactElement<P>).props || (element as PreactElement<P>).attributes;
}
function isReactElement(element: React.ReactNode): element is React.ReactElement<any> {
return !!(element as any).type;
}
function isComponentClass(Comp: React.ComponentType<any>): Comp is React.ComponentClass<any> {
return Comp.prototype && (Comp.prototype.render || Comp.prototype.isReactComponent);
}
function providesChildContext(
instance: React.Component<any>,
): instance is React.Component<any> & React.ChildContextProvider<any> {
return !!(instance as any).getChildContext;
}
// Recurse a React Element tree, running visitor on each element.
// If visitor returns `false`, don't call the element's render function
// or recurse into its child elements.
export function walkTree(
element: React.ReactNode,
context: Context,
visitor: (
element: React.ReactNode,
instance: React.Component<any> | null,
newContextMap: Map<any, any>,
context: Context,
childContext?: Context,
) => boolean | void,
newContext: Map<any, any> = new Map(),
) {
if (!element) {
return;
}
if (Array.isArray(element)) {
element.forEach(item => walkTree(item, context, visitor, newContext));
return;
}
// A stateless functional component or a class
if (isReactElement(element)) {
if (typeof element.type === 'function') {
const Comp = element.type;
const props = Object.assign({}, Comp.defaultProps, getProps(element));
let childContext = context;
let child;
// Are we are a react class?
if (isComponentClass(Comp)) {
const instance = new Comp(props, context);
// In case the user doesn't pass these to super in the constructor.
// Note: `Component.props` are now readonly in `@types/react`, so
// we're using `defineProperty` as a workaround (for now).
Object.defineProperty(instance, 'props', {
value: instance.props || props,
});
instance.context = instance.context || context;
// Set the instance state to null (not undefined) if not set, to match React behaviour
instance.state = instance.state || null;
// Override setState to just change the state, not queue up an update
// (we can't do the default React thing as we aren't mounted
// "properly", however we don't need to re-render as we only support
// setState in componentWillMount, which happens *before* render).
instance.setState = newState => {
Iif (typeof newState === 'function') {
// React's TS type definitions don't contain context as a third parameter for
// setState's updater function.
// Remove this cast to `any` when that is fixed.
newState = (newState as any)(instance.state, instance.props, instance.context);
}
instance.state = Object.assign({}, instance.state, newState);
};
if (Comp.getDerivedStateFromProps) {
const result = Comp.getDerivedStateFromProps(instance.props, instance.state);
Eif (result !== null) {
instance.state = Object.assign({}, instance.state, result);
}
} else if (instance.UNSAFE_componentWillMount) {
instance.UNSAFE_componentWillMount();
} else Iif (instance.componentWillMount) {
instance.componentWillMount();
}
Iif (providesChildContext(instance)) {
childContext = Object.assign({}, context, instance.getChildContext());
}
Iif (visitor(element, instance, newContext, context, childContext) === false) {
return;
}
child = instance.render();
} else {
// Just a stateless functional
Iif (visitor(element, null, newContext, context) === false) {
return;
}
child = Comp(props, context);
}
if (child) {
if (Array.isArray(child)) {
child.forEach(item => walkTree(item, childContext, visitor, newContext));
} else {
walkTree(child, childContext, visitor, newContext);
}
}
} else if ((element.type as any)._context || (element.type as any).Consumer) {
// A React context provider or consumer
Iif (visitor(element, null, newContext, context) === false) {
return;
}
let child;
if (!!(element.type as any)._context) {
// A provider - sets the context value before rendering children
// this needs to clone the map because this value should only apply to children of the provider
newContext = new Map(newContext);
newContext.set(element.type, element.props.value);
child = element.props.children;
} else {
// A consumer
let value = (element.type as any)._currentValue;
if (newContext.has((element.type as any).Provider)) {
value = newContext.get((element.type as any).Provider);
}
child = element.props.children(value);
}
Eif (child) {
if (Array.isArray(child)) {
child.forEach(item => walkTree(item, context, visitor, newContext));
} else {
walkTree(child, context, visitor, newContext);
}
}
} else {
// A basic string or dom element, just get children
Iif (visitor(element, null, newContext, context) === false) {
return;
}
if (element.props && element.props.children) {
React.Children.forEach(element.props.children, (child: any) => {
if (child) {
walkTree(child, context, visitor, newContext);
}
});
}
}
} else Eif (typeof element === 'string' || typeof element === 'number') {
// Just visit these, they are leaves so we don't keep traversing.
visitor(element, null, newContext, context);
}
// TODO: Portals?
}
|