9
10
'use strict';
11
12
-import type {ReactNativeCSType} from './ReactNativeCSTypes';
13
-
12
+const {CSStatefulComponent} = require('CSStatefulComponent');
13
const ReactFiberReconciler = require('react-reconciler');
15
-// TODO: direct imports like some-package/src/* are bad. Fix me.
14
+const ReactVersion = require('shared/ReactVersion');
15
+
16
const {
17
injectInternals,
18
} = require('react-reconciler/src/ReactFiberDevToolsHook');
19
-const ReactGenericBatching = require('events/ReactGenericBatching');
20
-const ReactVersion = require('shared/ReactVersion');
21
-const emptyObject = require('fbjs/lib/emptyObject');
19
23
-export type Container = number;
24
-export type Instance = number;
25
-export type Props = Object;
26
-export type TextInstance = number;
20
+import type {ReactNodeList} from 'shared/ReactTypes';
21
+import type {ReactNativeCSType} from './ReactNativeCSTypes';
22
28
-function processProps(instance: number, props: Props): Object {
29
- const propsPayload = {};
30
- for (var key in props) {
31
- if (key === 'children') {
32
- // Skip special case.
33
- continue;
34
- }
35
- var value = props[key];
36
- if (typeof value === 'function') {
37
- value = {
38
- style: 'rt-event',
39
- event: key,
40
- tag: instance,
41
- };
42
- }
43
- propsPayload[key] = value;
44
- }
45
- return propsPayload;
46
-}
23
+const emptyObject = {};
24
48
-function arePropsEqual(oldProps: Props, newProps: Props): boolean {
25
+type Container = {
26
+ pendingChild: null | Instance | TextInstance,
27
+};
28
+type InstanceProps = Props & {children: Array<Instance>};
29
+type Instance = {
30
+ props: InstanceProps,
31
+ options: {key: string, ref: null},
32
+ data: {type: 'NATIVE', name: string},
33
+};
34
+type Props = Object;
35
+type TextInstance = Instance;
36
+
37
+// We currently don't actually return a new state. We only use state updaters to trigger a
38
+// rerender. Therefore our state updater is the identity functions. When we later deal
39
+// with sync scheduling and aborted renders, we will need to update the state in render.
40
+const identityUpdater = state => state;
41
+// We currently don't have a hook for aborting render. Will add one once it is in place
42
+// in React Native proper.
43
+const infiniteDeadline = {
44
+ timeRemaining: function() {
45
+ return Infinity;
46
+ },
47
+};
48
+
49
+const arePropsEqual = (oldProps: Props, newProps: Props): boolean => {
50
var key;
51
for (key in newProps) {
52
if (key === 'children') {
67
}
68
}
69
return true;
69
-}
70
+};
71
+
72
+// React doesn't expose its full keypath. To manage lifetime of instances, we instead use IDs.
73
+let nextComponentKey = 0;
74
+
75
+// Callback. Currently this is global. TODO: This should be per root.
76
+let scheduledCallback = null;
77
+// Updater. This is the CS updater we use to trigger the update. TODO: This should be per root.
78
+let scheduleUpdate = null;
79
80
const ReactNativeCSFiberRenderer = ReactFiberReconciler({
81
appendInitialChild(
82
parentInstance: Instance,
83
child: Instance | TextInstance,
75
- ): void {},
84
+ ): void {
85
+ if (parentInstance.props) {
86
+ parentInstance.props.children.push(child);
87
+ } else {
88
+ // CSCustom
89
+ (parentInstance: any).children.push(child);
90
+ }
91
+ },
92
93
createInstance(
94
type: string,
97
hostContext: {},
98
internalInstanceHandle: Object,
99
): Instance {
84
- return 0;
100
+ let key = '' + nextComponentKey++;
101
+ let ref = null; // TODO: Always create Ref object so that getPublicInstance can use it.
102
+ // We need a new props object so that we can represent flattened children.
103
+ let newProps = Object.assign({}, props);
104
+ newProps.children = [];
105
+ if (type === 'CSCustom') {
106
+ // Special cased type that treats the props as the object.
107
+ // Useful for custom children types like FlexItem.
108
+ return newProps;
109
+ }
110
+ return {
111
+ props: newProps,
112
+ options: {key, ref},
113
+ data: {type: 'NATIVE', name: type},
114
+ };
115
},
116
117
createTextInstance(
120
hostContext: {},
121
internalInstanceHandle: Object,
122
): TextInstance {
93
- return 0;
123
+ // Could auto-translate to CSText with some host context defined attributes.
124
+ throw new Error('Not yet implemented.');
125
},
126
127
finalizeInitialChildren(
141
return emptyObject;
142
},
143
113
- getPublicInstance(instance) {
114
- return instance;
144
+ getPublicInstance(instance: Instance) {
145
+ return instance.options.ref;
146
},
147
148
prepareForCommit(): void {},
154
newProps: Props,
155
rootContainerInstance: Container,
156
hostContext: {},
126
- ): null | Object {
157
+ ): null | InstanceProps {
158
if (arePropsEqual(oldProps, newProps)) {
159
return null;
160
}
130
- return processProps(instance, newProps);
161
+ return Object.assign({}, newProps);
162
},
163
164
resetAfterCommit(): void {},
167
return false;
168
},
169
139
- scheduleDeferredCallback: global.requestIdleCallback,
170
+ scheduleDeferredCallback(callback) {
171
+ scheduledCallback = callback;
172
+ if (scheduleUpdate !== null) {
173
+ scheduleUpdate(identityUpdater);
174
+ }
175
+ },
176
177
shouldSetTextContent(type: string, props: Props): boolean {
178
// TODO: Figure out when we should allow text content.
179
return false;
180
},
181
146
- useSyncScheduling: true,
182
+ useSyncScheduling: false,
183
184
now(): number {
185
// TODO: Enable expiration by implementing this method.
189
persistence: {
190
cloneInstance(
191
instance: Instance,
156
- updatePayload: null | Object,
192
+ updatePayload: null | InstanceProps,
193
type: string,
194
oldProps: Props,
195
newProps: Props,
197
keepChildren: boolean,
198
recyclableInstance: null | Instance,
199
): Instance {
164
- return 0;
200
+ let newInstanceProps = updatePayload;
201
+ if (newInstanceProps === null) {
202
+ newInstanceProps = Object.assign({}, newProps);
203
+ }
204
+ // We need a new props object so that we can represent flattened children.
205
+ newInstanceProps.children = keepChildren ? instance.props.children : [];
206
+ if (type === 'CSCustom') {
207
+ return newInstanceProps;
208
+ }
209
+ return {
210
+ props: newInstanceProps,
211
+ options: instance.options,
212
+ data: instance.data,
213
+ };
214
},
215
167
- createContainerChildSet(
168
- container: Container,
169
- ): Array<Instance | TextInstance> {
170
- return [];
216
+ createContainerChildSet(container: Container): Container {
217
+ // We'll only ever have one instance in the container.
218
+ container.pendingChild = null;
219
+ return container;
220
},
221
222
appendChildToContainerChildSet(
174
- childSet: Array<Instance | TextInstance>,
223
+ childSet: Container,
224
child: Instance | TextInstance,
176
- ): void {},
225
+ ): void {
226
+ if (childSet.pendingChild !== null) {
227
+ throw new Error(
228
+ 'CSReact does not support top level fragments. Wrap it in a primitve.',
229
+ );
230
+ }
231
+ childSet.pendingChild = child;
232
+ },
233
234
finalizeContainerChildren(
235
container: Container,
180
- newChildren: Array<Instance | TextInstance>,
236
+ newChildren: Container,
237
): void {},
238
239
replaceContainerChildren(
240
container: Container,
185
- newChildren: Array<Instance | TextInstance>,
241
+ newChildren: Container,
242
): void {},
243
},
244
});
245
190
-const roots = new Map();
191
-
192
-const ReactNativeCSFiber: ReactNativeCSType = {
193
- render(element: React$Element<any>, containerTag: any, callback: ?Function) {
194
- let root = roots.get(containerTag);
195
-
196
- if (!root) {
197
- // TODO (bvaughn): If we decide to keep the wrapper component,
198
- // We could create a wrapper for containerTag as well to reduce special casing.
199
- root = ReactNativeCSFiberRenderer.createContainer(containerTag, false);
200
- roots.set(containerTag, root);
201
- }
202
- ReactNativeCSFiberRenderer.updateContainer(element, root, null, callback);
203
-
204
- return ReactNativeCSFiberRenderer.getPublicRootInstance(root);
205
- },
206
-
207
- unmountComponentAtNode(containerTag: number) {
208
- const root = roots.get(containerTag);
209
- if (root) {
210
- // TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
211
- ReactNativeCSFiberRenderer.updateContainer(null, root, null, () => {
212
- roots.delete(containerTag);
213
- });
214
- }
215
- },
216
-
217
- unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
218
-
219
- flushSync: ReactNativeCSFiberRenderer.flushSync,
220
-};
221
-
246
injectInternals({
247
findHostInstanceByFiber: ReactNativeCSFiberRenderer.findHostInstance,
248
// This is an enum because we may add more (e.g. profiler build)
251
rendererPackageName: 'react-cs-renderer',
252
});
253
230
-module.exports = ReactNativeCSFiber;
254
+type ReactCSProps = {children: ReactNodeList};
255
+type ReactCSState = {
256
+ root: Object,
257
+ container: {
258
+ pendingChild: null | Instance | TextInstance,
259
+ },
260
+};
261
+
262
+const ReactCS = CSStatefulComponent({
263
+ getInitialState({props}: {props: ReactCSProps}): ReactCSState {
264
+ let container = {
265
+ pendingChild: null,
266
+ };
267
+ let root = ReactNativeCSFiberRenderer.createContainer(container, false);
268
+ return {root, container};
269
+ },
270
+ render({
271
+ props,
272
+ state,
273
+ stateUpdater,
274
+ }: {
275
+ props: ReactCSProps,
276
+ state: ReactCSState,
277
+ stateUpdater: (update: (oldState: ReactCSState) => ReactCSState) => void,
278
+ }) {
279
+ scheduleUpdate = stateUpdater;
280
+ // TODO: For a props rerender updateContainer will schedule an additional state
281
+ // update even though it is not necessary since we're already rendering.
282
+ // We should only call scheduleUpdate for a React setState, not a top level
283
+ // props update.
284
+ ReactNativeCSFiberRenderer.updateContainer(
285
+ props.children,
286
+ state.root,
287
+ null,
288
+ null,
289
+ );
290
+ if (scheduledCallback) {
291
+ const callback = scheduledCallback;
292
+ scheduledCallback = null;
293
+ callback(infiniteDeadline);
294
+ }
295
+ return state.container.pendingChild;
296
+ },
297
+ getInstance({state}: {state: ReactCSState}) {
298
+ return ReactNativeCSFiberRenderer.getPublicRootInstance(state.root);
299
+ },
300
+ // TODO: Unmount hook. E.g. finalizer.
301
+});
302
+
303
+module.exports = (ReactCS: ReactNativeCSType);