Frames

Untitled

0
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import invariant from 'shared/invariant';
9import warningWithoutStack from 'shared/warningWithoutStack';
10import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
11
12import ReactCurrentOwner from './ReactCurrentOwner';
13
14const hasOwnProperty = Object.prototype.hasOwnProperty;
15
16const RESERVED_PROPS = {
17 key: true,
18 ref: true,
19 __self: true,
20 __source: true,
21};
22
23let specialPropKeyWarningShown, specialPropRefWarningShown;
24
25function hasValidRef(config) {
26 if (__DEV__) {
27 if (hasOwnProperty.call(config, 'ref')) {
28 const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
29 if (getter && getter.isReactWarning) {
30 return false;
31 }
32 }
33 }
34 return config.ref !== undefined;
35}
36
37function hasValidKey(config) {
38 if (__DEV__) {
39 if (hasOwnProperty.call(config, 'key')) {
40 const getter = Object.getOwnPropertyDescriptor(config, 'key').get;
41 if (getter && getter.isReactWarning) {
42 return false;
43 }
44 }
45 }
46 return config.key !== undefined;
47}
48
49function defineKeyPropWarningGetter(props, displayName) {
50 const warnAboutAccessingKey = function() {
51 if (!specialPropKeyWarningShown) {
52 specialPropKeyWarningShown = true;
53 warningWithoutStack(
54 false,
55 '%s: `key` is not a prop. Trying to access it will result ' +
56 'in `undefined` being returned. If you need to access the same ' +
57 'value within the child component, you should pass it as a different ' +
58 'prop. (https://fb.me/react-special-props)',
59 displayName,
60 );
61 }
62 };
63 warnAboutAccessingKey.isReactWarning = true;
64 Object.defineProperty(props, 'key', {
65 get: warnAboutAccessingKey,
66 configurable: true,
67 });
68}
69
70function defineRefPropWarningGetter(props, displayName) {
71 const warnAboutAccessingRef = function() {
72 if (!specialPropRefWarningShown) {
73 specialPropRefWarningShown = true;
74 warningWithoutStack(
75 false,
76 '%s: `ref` is not a prop. Trying to access it will result ' +
77 'in `undefined` being returned. If you need to access the same ' +
78 'value within the child component, you should pass it as a different ' +
79 'prop. (https://fb.me/react-special-props)',
80 displayName,
81 );
82 }
83 };
84 warnAboutAccessingRef.isReactWarning = true;
85 Object.defineProperty(props, 'ref', {
86 get: warnAboutAccessingRef,
87 configurable: true,
88 });
89}
90
91/**
92 * Factory method to create a new React element. This no longer adheres to
93 * the class pattern, so do not use new to call it. Also, no instanceof check
94 * will work. Instead test $$typeof field against Symbol.for('react.element') to check
95 * if something is a React Element.
96 *
97 * @param {*} type
98 * @param {*} props
99 * @param {*} key
100 * @param {string|object} ref
101 * @param {*} owner
102 * @param {*} self A *temporary* helper to detect places where `this` is
103 * different from the `owner` when React.createElement is called, so that we
104 * can warn. We want to get rid of owner and replace string `ref`s with arrow
105 * functions, and as long as `this` and owner are the same, there will be no
106 * change in behavior.
107 * @param {*} source An annotation object (added by a transpiler or otherwise)
108 * indicating filename, line number, and/or other information.
109 * @internal
110 */
111const ReactElement = function(type, key, ref, self, source, owner, props) {
112 const element = {
113 // This tag allows us to uniquely identify this as a React Element
114 $$typeof: REACT_ELEMENT_TYPE,
115
116 // Built-in properties that belong on the element
117 type: type,
118 key: key,
119 ref: ref,
120 props: props,
121
122 // Record the component responsible for creating this element.
123 _owner: owner,
124 };
125
126 if (__DEV__) {
127 // The validation flag is currently mutative. We put it on
128 // an external backing store so that we can freeze the whole object.
129 // This can be replaced with a WeakMap once they are implemented in
130 // commonly used development environments.
131 element._store = {};
132
133 // To make comparing ReactElements easier for testing purposes, we make
134 // the validation flag non-enumerable (where possible, which should
135 // include every environment we run tests in), so the test framework
136 // ignores it.
137 Object.defineProperty(element._store, 'validated', {
138 configurable: false,
139 enumerable: false,
140 writable: true,
141 value: false,
142 });
143 // self and source are DEV only properties.
144 Object.defineProperty(element, '_self', {
145 configurable: false,
146 enumerable: false,
147 writable: false,
148 value: self,
149 });
150 // Two elements created in two different places should be considered
151 // equal for testing purposes and therefore we hide it from enumeration.
152 Object.defineProperty(element, '_source', {
153 configurable: false,
154 enumerable: false,
155 writable: false,
156 value: source,
157 });
158 if (Object.freeze) {
159 Object.freeze(element.props);
160 Object.freeze(element);
161 }
162 }
163
164 return element;
165};
166
167/**
168 * https://github.com/reactjs/rfcs/pull/107
169 * @param {*} type
170 * @param {object} props
171 * @param {string} key
172 */
173export function jsx(type, config, maybeKey) {
174 let propName;
175
176 // Reserved names are extracted
177 const props = {};
178
179 let key = null;
180 let ref = null;
181
182 // Currently, key can be spread in as a prop. This causes a potential
183 // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
184 // or <div key="Hi" {...props} /> ). We want to deprecate key spread,
185 // but as an intermediary step, we will use jsxDEV for everything except
186 // <div {...props} key="Hi" />, because we aren't currently able to tell if
187 // key is explicitly declared to be undefined or not.
188 if (maybeKey !== undefined) {
189 key = '' + maybeKey;
190 }
191
192 if (hasValidKey(config)) {
193 key = '' + config.key;
194 }
195
196 if (hasValidRef(config)) {
197 ref = config.ref;
198 }
199
200 // Remaining properties are added to a new props object
201 for (propName in config) {
202 if (
203 hasOwnProperty.call(config, propName) &&
204 !RESERVED_PROPS.hasOwnProperty(propName)
205 ) {
206 props[propName] = config[propName];
207 }
208 }
209
210 // Resolve default props
211 if (type && type.defaultProps) {
212 const defaultProps = type.defaultProps;
213 for (propName in defaultProps) {
214 if (props[propName] === undefined) {
215 props[propName] = defaultProps[propName];
216 }
217 }
218 }
219
220 return ReactElement(
221 type,
222 key,
223 ref,
224 undefined,
225 undefined,
226 ReactCurrentOwner.current,
227 props,
228 );
229}
230
231/**
232 * https://github.com/reactjs/rfcs/pull/107
233 * @param {*} type
234 * @param {object} props
235 * @param {string} key
236 */
237export function jsxDEV(type, config, maybeKey, source, self) {
238 let propName;
239
240 // Reserved names are extracted
241 const props = {};
242
243 let key = null;
244 let ref = null;
245
246 // Currently, key can be spread in as a prop. This causes a potential
247 // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
248 // or <div key="Hi" {...props} /> ). We want to deprecate key spread,
249 // but as an intermediary step, we will use jsxDEV for everything except
250 // <div {...props} key="Hi" />, because we aren't currently able to tell if
251 // key is explicitly declared to be undefined or not.
252 if (maybeKey !== undefined) {
253 key = '' + maybeKey;
254 }
255
256 if (hasValidKey(config)) {
257 key = '' + config.key;
258 }
259
260 if (hasValidRef(config)) {
261 ref = config.ref;
262 }
263
264 // Remaining properties are added to a new props object
265 for (propName in config) {
266 if (
267 hasOwnProperty.call(config, propName) &&
268 !RESERVED_PROPS.hasOwnProperty(propName)
269 ) {
270 props[propName] = config[propName];
271 }
272 }
273
274 // Resolve default props
275 if (type && type.defaultProps) {
276 const defaultProps = type.defaultProps;
277 for (propName in defaultProps) {
278 if (props[propName] === undefined) {
279 props[propName] = defaultProps[propName];
280 }
281 }
282 }
283
284 if (key || ref) {
285 const displayName =
286 typeof type === 'function'
287 ? type.displayName || type.name || 'Unknown'
288 : type;
289 if (key) {
290 defineKeyPropWarningGetter(props, displayName);
291 }
292 if (ref) {
293 defineRefPropWarningGetter(props, displayName);
294 }
295 }
296
297 return ReactElement(
298 type,
299 key,
300 ref,
301 self,
302 source,
303 ReactCurrentOwner.current,
304 props,
305 );
306}
307
308/**
309 * Create and return a new ReactElement of the given type.
310 * See https://reactjs.org/docs/react-api.html#createelement
311 */
312export function createElement(type, config, children) {
313 let propName;
314
315 // Reserved names are extracted
316 const props = {};
317
318 let key = null;
319 let ref = null;
320 let self = null;
321 let source = null;
322
323 if (config != null) {
324 if (hasValidRef(config)) {
325 ref = config.ref;
326 }
327 if (hasValidKey(config)) {
328 key = '' + config.key;
329 }
330
331 self = config.__self === undefined ? null : config.__self;
332 source = config.__source === undefined ? null : config.__source;
333 // Remaining properties are added to a new props object
334 for (propName in config) {
335 if (
336 hasOwnProperty.call(config, propName) &&
337 !RESERVED_PROPS.hasOwnProperty(propName)
338 ) {
339 props[propName] = config[propName];
340 }
341 }
342 }
343
344 // Children can be more than one argument, and those are transferred onto
345 // the newly allocated props object.
346 const childrenLength = arguments.length - 2;
347 if (childrenLength === 1) {
348 props.children = children;
349 } else if (childrenLength > 1) {
350 const childArray = Array(childrenLength);
351 for (let i = 0; i < childrenLength; i++) {
352 childArray[i] = arguments[i + 2];
353 }
354 if (__DEV__) {
355 if (Object.freeze) {
356 Object.freeze(childArray);
357 }
358 }
359 props.children = childArray;
360 }
361
362 // Resolve default props
363 if (type && type.defaultProps) {
364 const defaultProps = type.defaultProps;
365 for (propName in defaultProps) {
366 if (props[propName] === undefined) {
367 props[propName] = defaultProps[propName];
368 }
369 }
370 }
371 if (__DEV__) {
372 if (key || ref) {
373 const displayName =
374 typeof type === 'function'
375 ? type.displayName || type.name || 'Unknown'
376 : type;
377 if (key) {
378 defineKeyPropWarningGetter(props, displayName);
379 }
380 if (ref) {
381 defineRefPropWarningGetter(props, displayName);
382 }
383 }
384 }
385 return ReactElement(
386 type,
387 key,
388 ref,
389 self,
390 source,
391 ReactCurrentOwner.current,
392 props,
393 );
394}
395
396/**
397 * Return a function that produces ReactElements of a given type.
398 * See https://reactjs.org/docs/react-api.html#createfactory
399 */
400export function createFactory(type) {
401 const factory = createElement.bind(null, type);
402 // Expose the type on the factory and the prototype so that it can be
403 // easily accessed on elements. E.g. `<Foo />.type === Foo`.
404 // This should not be named `constructor` since this may not be the function
405 // that created the element, and it may not even be a constructor.
406 // Legacy hook: remove it
407 factory.type = type;
408 return factory;
409}
410
411export function cloneAndReplaceKey(oldElement, newKey) {
412 const newElement = ReactElement(
413 oldElement.type,
414 newKey,
415 oldElement.ref,
416 oldElement._self,
417 oldElement._source,
418 oldElement._owner,
419 oldElement.props,
420 );
421
422 return newElement;
423}
424
425/**
426 * Clone and return a new ReactElement using element as the starting point.
427 * See https://reactjs.org/docs/react-api.html#cloneelement
428 */
429export function cloneElement(element, config, children) {
430 invariant(
431 !(element === null || element === undefined),
432 'React.cloneElement(...): The argument must be a React element, but you passed %s.',
433 element,
434 );
435
436 let propName;
437
438 // Original props are copied
439 const props = Object.assign({}, element.props);
440
441 // Reserved names are extracted
442 let key = element.key;
443 let ref = element.ref;
444 // Self is preserved since the owner is preserved.
445 const self = element._self;
446 // Source is preserved since cloneElement is unlikely to be targeted by a
447 // transpiler, and the original source is probably a better indicator of the
448 // true owner.
449 const source = element._source;
450
451 // Owner will be preserved, unless ref is overridden
452 let owner = element._owner;
453
454 if (config != null) {
455 if (hasValidRef(config)) {
456 // Silently steal the ref from the parent.
457 ref = config.ref;
458 owner = ReactCurrentOwner.current;
459 }
460 if (hasValidKey(config)) {
461 key = '' + config.key;
462 }
463
464 // Remaining properties override existing props
465 let defaultProps;
466 if (element.type && element.type.defaultProps) {
467 defaultProps = element.type.defaultProps;
468 }
469 for (propName in config) {
470 if (
471 hasOwnProperty.call(config, propName) &&
472 !RESERVED_PROPS.hasOwnProperty(propName)
473 ) {
474 if (config[propName] === undefined && defaultProps !== undefined) {
475 // Resolve default props
476 props[propName] = defaultProps[propName];
477 } else {
478 props[propName] = config[propName];
479 }
480 }
481 }
482 }
483
484 // Children can be more than one argument, and those are transferred onto
485 // the newly allocated props object.
486 const childrenLength = arguments.length - 2;
487 if (childrenLength === 1) {
488 props.children = children;
489 } else if (childrenLength > 1) {
490 const childArray = Array(childrenLength);
491 for (let i = 0; i < childrenLength; i++) {
492 childArray[i] = arguments[i + 2];
493 }
494 props.children = childArray;
495 }
496
497 return ReactElement(element.type, key, ref, self, source, owner, props);
498}
499
500/**
501 * Verifies the object is a ReactElement.
502 * See https://reactjs.org/docs/react-api.html#isvalidelement
503 * @param {?object} object
504 * @return {boolean} True if `object` is a ReactElement.
505 * @final
506 */
507export function isValidElement(object) {
508 return (
509 typeof object === 'object' &&
510 object !== null &&
511 object.$$typeof === REACT_ELEMENT_TYPE
512 );
513}
514