anu-verzum 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -10
- package/dist/core/components/Connector.js +3 -1
- package/dist/core/components/Context.js +2 -0
- package/dist/core/components/History.js +3 -4
- package/dist/core/domUtils.d.ts +1 -0
- package/dist/core/domUtils.js +5 -5
- package/dist/core/reconciler.js +101 -47
- package/dist/server-api/server-api.js +16 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -171,7 +171,10 @@ npm run format # Format all source files with Prettier
|
|
|
171
171
|
<a href="#avoiding-unnecessary-wrapper-elements">Avoiding unnecessary wrapper elements</a>
|
|
172
172
|
</li>
|
|
173
173
|
<li>
|
|
174
|
-
<a href="#
|
|
174
|
+
<a href="#list-keys">Keys for dynamic lists</a>
|
|
175
|
+
</li>
|
|
176
|
+
<li>
|
|
177
|
+
<a href="#refs">The refs</a>
|
|
175
178
|
</li>
|
|
176
179
|
<li>
|
|
177
180
|
<a href="#rendering-application">Rendering application</a>
|
|
@@ -546,14 +549,14 @@ It takes <strong>ONE</strong> argument which can either be:
|
|
|
546
549
|
// Explicit form:
|
|
547
550
|
const ElemList = ({ somethingToLoop }: ListProps): AnuElement => (
|
|
548
551
|
<Anu.Fragment>
|
|
549
|
-
{somethingToLoop.map(prop => <li>{prop}</li>)}
|
|
552
|
+
{somethingToLoop.map((prop, i) => <li key={`list-item-${i}`}>{prop}</li>)}
|
|
550
553
|
</Anu.Fragment>
|
|
551
554
|
);
|
|
552
555
|
|
|
553
556
|
// Shorthand form — identical result:
|
|
554
557
|
const ElemList = ({ somethingToLoop }: ListProps): AnuElement => (
|
|
555
558
|
<>
|
|
556
|
-
{somethingToLoop.map(prop => <li>{prop}</li>)}
|
|
559
|
+
{somethingToLoop.map((prop, i) => <li key={`list-item-${i}`}>{prop}</li>)}
|
|
557
560
|
</>
|
|
558
561
|
);
|
|
559
562
|
|
|
@@ -571,6 +574,40 @@ It takes <strong>ONE</strong> argument which can either be:
|
|
|
571
574
|
};
|
|
572
575
|
```
|
|
573
576
|
|
|
577
|
+
<h3 id="list-keys">Keys for dynamic lists</h3>
|
|
578
|
+
|
|
579
|
+
- Add a <code>key</code> prop to each element in a dynamically rendered list. Keys allow the reconciler to match elements across renders by identity rather than position, so items that move, are inserted, or are removed do not cause unrelated siblings to lose their state or be recreated unnecessarily.
|
|
580
|
+
- A key must be unique among siblings. Use a stable, unique identifier from your data (such as a database ID). Only fall back to the array index when the list never reorders and items are never inserted in the middle.
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
interface Item {
|
|
584
|
+
id: string;
|
|
585
|
+
label: string;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
interface ItemListProps extends Props {
|
|
589
|
+
items: Item[];
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Stable IDs — preferred:
|
|
593
|
+
const ItemList = ({ items }: ItemListProps): AnuElement => (
|
|
594
|
+
<ul>
|
|
595
|
+
{items.map(({ id, label }) => (
|
|
596
|
+
<li key={id}>{label}</li>
|
|
597
|
+
))}
|
|
598
|
+
</ul>
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
// Index fallback — only when order never changes:
|
|
602
|
+
const StaticList = ({ items }: ItemListProps): AnuElement => (
|
|
603
|
+
<ul>
|
|
604
|
+
{items.map(({ label }, i) => (
|
|
605
|
+
<li key={`list-item-${i}`}>{label}</li>
|
|
606
|
+
))}
|
|
607
|
+
</ul>
|
|
608
|
+
);
|
|
609
|
+
```
|
|
610
|
+
|
|
574
611
|
<h3 id="refs">The refs</h3>
|
|
575
612
|
|
|
576
613
|
- In most cases, there is no need to use refs to manage (HTML) sub-components from the parent (class) component.
|
|
@@ -665,7 +702,7 @@ when you want to imperatively modify a child outside of the typical dataflow.
|
|
|
665
702
|
/>
|
|
666
703
|
<ul>
|
|
667
704
|
{files.length > 0 && files.map(({ name, file }) => (
|
|
668
|
-
<li>
|
|
705
|
+
<li key={name}>
|
|
669
706
|
<img src={name} alt={`Uploaded file name: ${file.name}, type: ${file.type}, size: ${file.size}`} />
|
|
670
707
|
</li>
|
|
671
708
|
))}
|
|
@@ -775,6 +812,7 @@ when you want to imperatively modify a child outside of the typical dataflow.
|
|
|
775
812
|
todoList.forEach(task => {
|
|
776
813
|
todoStatus[task.status].push(
|
|
777
814
|
<div
|
|
815
|
+
key={task.name}
|
|
778
816
|
draggable
|
|
779
817
|
className="todoListItem"
|
|
780
818
|
onDragStart={event => this.handleDragStart(event, task.name)}
|
|
@@ -879,7 +917,7 @@ when you want to imperatively modify a child outside of the typical dataflow.
|
|
|
879
917
|
<div className="container">
|
|
880
918
|
<div style={{ minHeight: '800px' }}>
|
|
881
919
|
{this.state.photos.map(user => (
|
|
882
|
-
<img src={user.url} height="100px" width="200px" />
|
|
920
|
+
<img key={user.url} src={user.url} height="100px" width="200px" />
|
|
883
921
|
))}
|
|
884
922
|
</div>
|
|
885
923
|
<div ref={this.loadingRef} style={loadingCSS}>
|
|
@@ -1205,13 +1243,13 @@ when you want to imperatively modify a child outside of the typical dataflow.
|
|
|
1205
1243
|
<h2>Topics</h2>
|
|
1206
1244
|
<ul>
|
|
1207
1245
|
{items.map(({ name, slug }) => (
|
|
1208
|
-
<li>
|
|
1246
|
+
<li key={slug}>
|
|
1209
1247
|
<Anu.History.Link to={`${match.url}/${slug}`}>{name}</Anu.History.Link>
|
|
1210
1248
|
</li>
|
|
1211
1249
|
))}
|
|
1212
1250
|
</ul>
|
|
1213
1251
|
{items.map(({ name, slug }) => (
|
|
1214
|
-
<Anu.History.Route path={`${match.path ?? ''}/${slug}`} render={() => (
|
|
1252
|
+
<Anu.History.Route key={slug} path={`${match.path ?? ''}/${slug}`} render={() => (
|
|
1215
1253
|
<Topic topicId={name} />
|
|
1216
1254
|
)} />
|
|
1217
1255
|
))}
|
|
@@ -1284,6 +1322,30 @@ when you want to imperatively modify a child outside of the typical dataflow.
|
|
|
1284
1322
|
|
|
1285
1323
|
The <code>Anu.ServerAPI</code> is basically built on top of <code>Promise</code> and currently has 5 methods <code>get()</code>, <code>post()</code>, <code>put()</code>, <code>delete()</code> and <code>file()</code>.
|
|
1286
1324
|
|
|
1325
|
+
The <code>.catch()</code> callback receives <code>{ status, response }</code> where <code>response</code> is always <code>null</code> on failure. <code>status</code> is the HTTP status code for server-side errors (e.g. <code>404</code>, <code>500</code>) and <code>0</code> for network-level failures such as no internet connection, DNS error, or a CORS rejection (i.e. no HTTP response was received at all).
|
|
1326
|
+
|
|
1327
|
+
```typescript
|
|
1328
|
+
Anu
|
|
1329
|
+
.ServerAPI
|
|
1330
|
+
.get<MyItem>('/app/my-server-url/1234')
|
|
1331
|
+
.then(({ response }) => {
|
|
1332
|
+
// response is MyItem | null
|
|
1333
|
+
})
|
|
1334
|
+
.catch(({ status }) => {
|
|
1335
|
+
if (status === 0) {
|
|
1336
|
+
// Network-level failure — no response was received.
|
|
1337
|
+
// Show a "check your connection" message to the user.
|
|
1338
|
+
} else if (status === 401) {
|
|
1339
|
+
// Server responded with 401 Unauthorized — redirect to login.
|
|
1340
|
+
Anu.History.goTo('/login');
|
|
1341
|
+
} else if (status === 404) {
|
|
1342
|
+
// Server responded with 404 Not Found.
|
|
1343
|
+
} else {
|
|
1344
|
+
// Any other HTTP error (403, 500, etc.).
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1287
1349
|
<h3 id="get-and-delete-methods">The <strong>GET</strong> and <strong>DELETE</strong> HTTP methods</h3>
|
|
1288
1350
|
|
|
1289
1351
|
- The <code>Anu.ServerAPI.get()</code> and <code>Anu.ServerAPI.delete()</code> perform a <strong>GET</strong> or <strong>DELETE</strong> HTTP method respectively to get data from the server or delete a specific data from the server
|
|
@@ -1787,12 +1849,12 @@ it should return that part, with the updated desired values:
|
|
|
1787
1849
|
|
|
1788
1850
|
<h3 id="creating-the-store">Creating the store</h3>
|
|
1789
1851
|
|
|
1790
|
-
- The store object stores the global state object (that can be reached using the <code>store.getState()</code>
|
|
1791
|
-
You will likely use the <code>store.getState()</code> and <code>store.dispatch()</code> methods only because subscribing and unsubscribing functionalities are "wired" into the <code><Anu.Connector.Provider /></code> and the "
|
|
1852
|
+
- The store object stores the global state object (that can be reached using the <code>store.getState()</code> method) and it also has the <code>store.dispatch()</code>, <code>store.subscribe()</code> and <code>store.unsubscribe()</code> methods.
|
|
1853
|
+
You will likely use the <code>store.getState()</code> and <code>store.dispatch()</code> methods only because subscribing and unsubscribing functionalities are "wired" into the <code><Anu.Connector.Provider /></code> and the "connected"
|
|
1792
1854
|
(also known as "container") component(s) created by using the <code>Anu.Connector.connect()</code> - see <a href="#connector-api">Connecting components to the global state - The Connector API</a> section.
|
|
1793
1855
|
- Create a store object using <code>Anu.store.createStore()</code>:
|
|
1794
1856
|
- The first argument is the <code>rootReducer</code> which is always needed (can be either a "single" reducer or a combination of ("single" and/or combined) reducers),
|
|
1795
|
-
- The second <code>initialState</code> argument is
|
|
1857
|
+
- The second <code>initialState</code> argument is required. Pass the initial state object for your store here.
|
|
1796
1858
|
This argument is useful if you want to have initialized values for your application before dispatching your first action.
|
|
1797
1859
|
- The third argument is optional, you can use it if you want to apply middleware functionalities like dispatching asynchronous actions (e.g. AJAX calls or delayed calls).
|
|
1798
1860
|
In this case, the built-in <code>Anu.store.middleware.applyMiddleware()</code> can be passed:
|
|
@@ -82,11 +82,13 @@ class Subscription {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
removeNestedSub(listener) {
|
|
85
|
-
this.tryUnsubscribe();
|
|
86
85
|
const index = this.listeners.indexOf(listener);
|
|
87
86
|
if (index >= 0) {
|
|
88
87
|
this.listeners.splice(index, 1);
|
|
89
88
|
}
|
|
89
|
+
if (this.listeners.length === 0) {
|
|
90
|
+
this.tryUnsubscribe();
|
|
91
|
+
}
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
const connectHOC = (mapStateToProps, mapDispatchToProps) => WrappedComponent => {
|
|
@@ -35,6 +35,7 @@ const createContext = context => {
|
|
|
35
35
|
providerContext.value = {
|
|
36
36
|
...pureProps
|
|
37
37
|
};
|
|
38
|
+
providerContext.__notifySub = true;
|
|
38
39
|
this.setState();
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -56,6 +57,7 @@ const createContext = context => {
|
|
|
56
57
|
class ContextConsumer extends _Component.Component {
|
|
57
58
|
componentDidUpdate() {
|
|
58
59
|
if (providerContext.__notifySub) {
|
|
60
|
+
providerContext.__notifySub = false;
|
|
59
61
|
this.setState();
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -52,7 +52,8 @@ const matchPath = (pathname, options) => {
|
|
|
52
52
|
isExact: true
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
-
const
|
|
55
|
+
const escapedPath = path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
56
|
+
const match = new RegExp(`^${escapedPath}`).exec(pathname);
|
|
56
57
|
if (!match) {
|
|
57
58
|
return null;
|
|
58
59
|
}
|
|
@@ -130,11 +131,9 @@ class HistoryLink extends _Component.Component {
|
|
|
130
131
|
const {
|
|
131
132
|
to,
|
|
132
133
|
children,
|
|
134
|
+
ariaLabel,
|
|
133
135
|
...restProps
|
|
134
136
|
} = this.props;
|
|
135
|
-
const {
|
|
136
|
-
ariaLabel
|
|
137
|
-
} = restProps;
|
|
138
137
|
return (0, _elements.createElement)('a', {
|
|
139
138
|
href: to,
|
|
140
139
|
ariaLabel: `historyLink${ariaLabel ? `-${ariaLabel}` : ''}`,
|
package/dist/core/domUtils.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AnuElement, Props } from './elements';
|
|
2
|
+
export declare const getHTMLValidSvgTag: (fiberType: string) => string;
|
|
2
3
|
export declare const SVG_ELEMENT_LIST: readonly string[];
|
|
3
4
|
export declare const updateDomProperties: (dom: HTMLElement | SVGElement | Text, prevProps: Props, nextProps: Props, isSvgElement?: boolean) => void;
|
|
4
5
|
export declare const createDomElement: (fiber: AnuElement & {
|
package/dist/core/domUtils.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.updateDomProperties = exports.createDomElement = exports.SVG_ELEMENT_LIST = void 0;
|
|
6
|
+
exports.updateDomProperties = exports.getHTMLValidSvgTag = exports.createDomElement = exports.SVG_ELEMENT_LIST = void 0;
|
|
7
7
|
var _elements = require("./elements");
|
|
8
8
|
const getHTMLValidSvgTag = fiberType => {
|
|
9
9
|
switch (fiberType) {
|
|
@@ -17,6 +17,7 @@ const getHTMLValidSvgTag = fiberType => {
|
|
|
17
17
|
return fiberType;
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
+
exports.getHTMLValidSvgTag = getHTMLValidSvgTag;
|
|
20
21
|
const SVG_ELEMENT_LIST = exports.SVG_ELEMENT_LIST = ['anchor', 'animate', 'animateMotion', 'animateTransform', 'circle', 'clipPath', 'desc', 'discard', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'foreignObject', 'g', 'hatch', 'hatchpath', 'image', 'line', 'linearGradient', 'marker', 'mask', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'stop', 'svgStyle', 'svg', 'switch', 'symbol', 'text', 'textPath', 'svgTitle', 'tspan', 'unknown', 'use', 'view'];
|
|
21
22
|
const updateDomProperties = (dom, prevProps, nextProps, isSvgElement = false) => {
|
|
22
23
|
const isEvent = name => {
|
|
@@ -33,13 +34,12 @@ const updateDomProperties = (dom, prevProps, nextProps, isSvgElement = false) =>
|
|
|
33
34
|
dom.removeEventListener(eventType, prevProps[name]);
|
|
34
35
|
});
|
|
35
36
|
Object.keys(prevProps).filter(isAttribute).filter(isGone(prevProps, nextProps)).forEach(name => {
|
|
36
|
-
const el = dom;
|
|
37
37
|
if (name === 'className') {
|
|
38
|
-
|
|
38
|
+
dom.removeAttribute('class');
|
|
39
39
|
} else if (name === 'htmlFor') {
|
|
40
|
-
|
|
40
|
+
dom.removeAttribute('for');
|
|
41
41
|
} else {
|
|
42
|
-
|
|
42
|
+
dom.removeAttribute(name);
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
Object.keys(nextProps).filter(isAttribute).filter(isNew(prevProps, nextProps)).forEach(name => {
|
package/dist/core/reconciler.js
CHANGED
|
@@ -28,6 +28,9 @@ const resetNextUnitOfWork = () => {
|
|
|
28
28
|
if (!update) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
+
if (update.from === CLASS_COMPONENT && !update.instance.__fiber) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
31
34
|
if (update.partialState) {
|
|
32
35
|
update.instance.__fiber.partialState = update.partialState;
|
|
33
36
|
}
|
|
@@ -90,22 +93,20 @@ const updateHostComponent = wipFiber => {
|
|
|
90
93
|
const newChildElements = wipFiber.props.children;
|
|
91
94
|
reconcileChildrenArray(wipFiber, newChildElements);
|
|
92
95
|
};
|
|
93
|
-
const createFunctionComponent = fiber => {
|
|
94
|
-
const instance = fiber.type(fiber.props);
|
|
95
|
-
instance.__fiber = fiber;
|
|
96
|
-
return instance;
|
|
97
|
-
};
|
|
98
96
|
const updateFunctionComponent = wipFiber => {
|
|
99
97
|
let instance = wipFiber.stateNode || null;
|
|
100
98
|
if (instance === null) {
|
|
101
|
-
instance = wipFiber.stateNode =
|
|
99
|
+
instance = wipFiber.stateNode = {
|
|
100
|
+
props: wipFiber.props,
|
|
101
|
+
render: wipFiber.type
|
|
102
|
+
};
|
|
102
103
|
} else if (wipFiber.props === instance.props && !wipFiber.partialState) {
|
|
103
104
|
cloneChildFibers(wipFiber);
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
106
107
|
instance.props = wipFiber.props;
|
|
107
|
-
|
|
108
|
-
const newChildElements =
|
|
108
|
+
instance.render = wipFiber.type;
|
|
109
|
+
const newChildElements = instance.render(wipFiber.props);
|
|
109
110
|
reconcileChildrenArray(wipFiber, newChildElements);
|
|
110
111
|
};
|
|
111
112
|
const createInstance = fiber => {
|
|
@@ -147,6 +148,9 @@ const updateClassComponent = wipFiber => {
|
|
|
147
148
|
} else {
|
|
148
149
|
nextState = wipFiber.partialStateCallback(instance.state, nextProps);
|
|
149
150
|
}
|
|
151
|
+
wipFiber.prevState = {
|
|
152
|
+
...instance.state
|
|
153
|
+
};
|
|
150
154
|
instance.props = nextProps;
|
|
151
155
|
instance.state = nextState;
|
|
152
156
|
wipFiber.partialState = undefined;
|
|
@@ -155,62 +159,100 @@ const updateClassComponent = wipFiber => {
|
|
|
155
159
|
reconcileChildrenArray(wipFiber, newChildElements);
|
|
156
160
|
};
|
|
157
161
|
const arrify = val => !val ? [] : Array.isArray(val) ? val : [val];
|
|
162
|
+
const getTag = element => {
|
|
163
|
+
if (typeof element.type === 'string') {
|
|
164
|
+
return HOST_COMPONENT;
|
|
165
|
+
}
|
|
166
|
+
if (typeof element.type === 'function' && !element.type.isAnuComponent && !element.type.prototype?.render) {
|
|
167
|
+
return FUNCTION_COMPONENT;
|
|
168
|
+
}
|
|
169
|
+
return CLASS_COMPONENT;
|
|
170
|
+
};
|
|
158
171
|
const reconcileChildrenArray = (wipFiber, newChildElements) => {
|
|
159
172
|
const elements = arrify(newChildElements);
|
|
160
|
-
|
|
173
|
+
const oldFiberMap = new Map();
|
|
161
174
|
let oldFiber = wipFiber.alternate ? wipFiber.alternate.child : undefined;
|
|
162
|
-
let
|
|
163
|
-
while (
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
175
|
+
let oldIndex = 0;
|
|
176
|
+
while (oldFiber) {
|
|
177
|
+
const mapKey = oldFiber.props.key ?? oldIndex;
|
|
178
|
+
oldFiberMap.set(mapKey, oldFiber);
|
|
179
|
+
oldFiber = oldFiber.sibling;
|
|
180
|
+
oldIndex++;
|
|
181
|
+
}
|
|
182
|
+
let prevFiber;
|
|
183
|
+
for (let i = 0; i < elements.length; i++) {
|
|
184
|
+
const element = elements[i];
|
|
185
|
+
const mapKey = element.props.key ?? i;
|
|
186
|
+
const matchedOldFiber = oldFiberMap.get(mapKey);
|
|
187
|
+
const sameType = matchedOldFiber !== undefined && element.type === matchedOldFiber.type;
|
|
188
|
+
let newFiber;
|
|
189
|
+
if (sameType && matchedOldFiber) {
|
|
168
190
|
newFiber = {
|
|
169
|
-
type:
|
|
170
|
-
tag:
|
|
171
|
-
stateNode:
|
|
191
|
+
type: matchedOldFiber.type,
|
|
192
|
+
tag: matchedOldFiber.tag,
|
|
193
|
+
stateNode: matchedOldFiber.stateNode,
|
|
172
194
|
props: element.props,
|
|
173
195
|
parent: wipFiber,
|
|
174
|
-
alternate:
|
|
175
|
-
partialState:
|
|
196
|
+
alternate: matchedOldFiber,
|
|
197
|
+
partialState: matchedOldFiber.partialState,
|
|
176
198
|
effectTag: UPDATE
|
|
177
199
|
};
|
|
178
|
-
if (
|
|
179
|
-
newFiber.partialStateCallback =
|
|
200
|
+
if (matchedOldFiber.partialStateCallback) {
|
|
201
|
+
newFiber.partialStateCallback = matchedOldFiber.partialStateCallback;
|
|
180
202
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
} else {
|
|
189
|
-
tag = CLASS_COMPONENT;
|
|
203
|
+
oldFiberMap.delete(mapKey);
|
|
204
|
+
} else {
|
|
205
|
+
if (matchedOldFiber) {
|
|
206
|
+
matchedOldFiber.effectTag = DELETION;
|
|
207
|
+
wipFiber.effects = wipFiber.effects || [];
|
|
208
|
+
wipFiber.effects.push(matchedOldFiber);
|
|
209
|
+
oldFiberMap.delete(mapKey);
|
|
190
210
|
}
|
|
191
211
|
newFiber = {
|
|
192
212
|
type: element.type,
|
|
193
|
-
tag,
|
|
213
|
+
tag: getTag(element),
|
|
194
214
|
props: element.props,
|
|
195
215
|
parent: wipFiber,
|
|
196
216
|
effectTag: PLACEMENT
|
|
197
217
|
};
|
|
198
218
|
}
|
|
199
|
-
if (
|
|
200
|
-
oldFiber.effectTag = DELETION;
|
|
201
|
-
wipFiber.effects = wipFiber.effects || [];
|
|
202
|
-
wipFiber.effects.push(oldFiber);
|
|
203
|
-
}
|
|
204
|
-
if (oldFiber) {
|
|
205
|
-
oldFiber = oldFiber.sibling;
|
|
206
|
-
}
|
|
207
|
-
if (index === 0) {
|
|
219
|
+
if (i === 0) {
|
|
208
220
|
wipFiber.child = newFiber;
|
|
209
|
-
} else if (prevFiber
|
|
221
|
+
} else if (prevFiber) {
|
|
210
222
|
prevFiber.sibling = newFiber;
|
|
211
223
|
}
|
|
212
|
-
|
|
224
|
+
prevFiber = newFiber;
|
|
225
|
+
}
|
|
226
|
+
oldFiberMap.forEach(fiber => {
|
|
227
|
+
fiber.effectTag = DELETION;
|
|
228
|
+
wipFiber.effects = wipFiber.effects || [];
|
|
229
|
+
wipFiber.effects.push(fiber);
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
const getFirstHostNode = fiber => {
|
|
233
|
+
if (fiber.tag === HOST_COMPONENT) {
|
|
234
|
+
return fiber.stateNode ? fiber.stateNode : null;
|
|
235
|
+
}
|
|
236
|
+
let child = fiber.child;
|
|
237
|
+
while (child) {
|
|
238
|
+
const node = getFirstHostNode(child);
|
|
239
|
+
if (node) {
|
|
240
|
+
return node;
|
|
241
|
+
}
|
|
242
|
+
child = child.sibling;
|
|
213
243
|
}
|
|
244
|
+
return null;
|
|
245
|
+
};
|
|
246
|
+
const getNextHostNode = (fiber, domParent) => {
|
|
247
|
+
let sibling = fiber.sibling;
|
|
248
|
+
while (sibling) {
|
|
249
|
+
const node = getFirstHostNode(sibling);
|
|
250
|
+
if (node && node.parentNode === domParent) {
|
|
251
|
+
return node;
|
|
252
|
+
}
|
|
253
|
+
sibling = sibling.sibling;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
214
256
|
};
|
|
215
257
|
const cloneChildFibers = parentFiber => {
|
|
216
258
|
const oldFiber = parentFiber.alternate;
|
|
@@ -268,7 +310,7 @@ const flushComponentLifecyclesQueue = () => {
|
|
|
268
310
|
}
|
|
269
311
|
};
|
|
270
312
|
const commitAllWork = fiber => {
|
|
271
|
-
fiber.effects.forEach((effect, index, effects) => {
|
|
313
|
+
(fiber.effects || []).forEach((effect, index, effects) => {
|
|
272
314
|
commitWork(effect);
|
|
273
315
|
if (index === effects.length - 1) {
|
|
274
316
|
flushComponentLifecyclesQueue();
|
|
@@ -289,7 +331,12 @@ const commitWork = effect => {
|
|
|
289
331
|
const domParent = domParentFiber.stateNode;
|
|
290
332
|
if (effect.effectTag === PLACEMENT) {
|
|
291
333
|
if (effect.tag === HOST_COMPONENT) {
|
|
292
|
-
|
|
334
|
+
const nextDomSibling = getNextHostNode(effect, domParent);
|
|
335
|
+
if (nextDomSibling) {
|
|
336
|
+
domParent.insertBefore(effect.stateNode, nextDomSibling);
|
|
337
|
+
} else {
|
|
338
|
+
domParent.appendChild(effect.stateNode);
|
|
339
|
+
}
|
|
293
340
|
if (effect.props.ref) {
|
|
294
341
|
effect.props.ref.current = effect.stateNode;
|
|
295
342
|
}
|
|
@@ -304,6 +351,12 @@ const commitWork = effect => {
|
|
|
304
351
|
} else if (effect.effectTag === UPDATE) {
|
|
305
352
|
if (effect && effect.stateNode && effect.alternate && effect.alternate.props && effect.props) {
|
|
306
353
|
if (effect.tag === HOST_COMPONENT) {
|
|
354
|
+
const nextDomSibling = getNextHostNode(effect, domParent);
|
|
355
|
+
if (nextDomSibling) {
|
|
356
|
+
domParent.insertBefore(effect.stateNode, nextDomSibling);
|
|
357
|
+
} else {
|
|
358
|
+
domParent.appendChild(effect.stateNode);
|
|
359
|
+
}
|
|
307
360
|
(0, _domUtils.updateDomProperties)(effect.stateNode, effect.alternate.props, effect.props, _domUtils.SVG_ELEMENT_LIST.indexOf(effect.type) > -1);
|
|
308
361
|
if (effect.props.ref) {
|
|
309
362
|
effect.props.ref.current = effect.stateNode;
|
|
@@ -313,8 +366,8 @@ const commitWork = effect => {
|
|
|
313
366
|
componentLifecyclesQueue.push({
|
|
314
367
|
fn: effect.stateNode.componentDidUpdate,
|
|
315
368
|
params: {
|
|
316
|
-
prevProps: effect.
|
|
317
|
-
prevState: effect.
|
|
369
|
+
prevProps: effect.alternate?.props,
|
|
370
|
+
prevState: effect.prevState
|
|
318
371
|
}
|
|
319
372
|
});
|
|
320
373
|
}
|
|
@@ -325,6 +378,7 @@ const commitWork = effect => {
|
|
|
325
378
|
if (effect.stateNode.componentWillUnmount) {
|
|
326
379
|
effect.stateNode.componentWillUnmount();
|
|
327
380
|
}
|
|
381
|
+
effect.stateNode.__fiber = null;
|
|
328
382
|
}
|
|
329
383
|
commitDeletion(effect, domParent);
|
|
330
384
|
}
|
|
@@ -9,6 +9,18 @@ const CLIENT_ERROR_STATUS_CODES = [400, 401, 402, 403, 404, 405, 406, 407, 408,
|
|
|
9
9
|
const SERVER_ERROR_STATUS_CODES = [500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511];
|
|
10
10
|
const _setXHR = (successHandler, errorHandler) => {
|
|
11
11
|
const XHR = new XMLHttpRequest();
|
|
12
|
+
XHR.onerror = () => {
|
|
13
|
+
errorHandler({
|
|
14
|
+
status: 0,
|
|
15
|
+
response: null
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
XHR.ontimeout = () => {
|
|
19
|
+
errorHandler({
|
|
20
|
+
status: 0,
|
|
21
|
+
response: null
|
|
22
|
+
});
|
|
23
|
+
};
|
|
12
24
|
XHR.onload = () => {
|
|
13
25
|
const {
|
|
14
26
|
status,
|
|
@@ -42,7 +54,7 @@ const _serverGetAPI = (url, params = {}) => (successHandler, errorHandler) => {
|
|
|
42
54
|
const urlParamKeys = Object.keys(params);
|
|
43
55
|
if (urlParamKeys.length > 0) {
|
|
44
56
|
urlParamKeys.forEach((key, index) => {
|
|
45
|
-
urlWithParams += index === 0 ? `?${key}=${params[key]}` : `&${key}=${params[key]}`;
|
|
57
|
+
urlWithParams += index === 0 ? `?${encodeURIComponent(key)}=${encodeURIComponent(params[key])}` : `&${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
|
|
46
58
|
});
|
|
47
59
|
}
|
|
48
60
|
XHR.open('GET', urlWithParams, true);
|
|
@@ -52,11 +64,10 @@ const _serverPostAPI = (url, data) => (successHandler, errorHandler) => {
|
|
|
52
64
|
if (!data) {
|
|
53
65
|
return;
|
|
54
66
|
}
|
|
55
|
-
const urlParams = `data=${JSON.stringify(data)}`;
|
|
56
67
|
const XHR = _setXHR(successHandler, errorHandler);
|
|
57
68
|
XHR.open('POST', url, true);
|
|
58
|
-
XHR.setRequestHeader('Content-type', 'application/
|
|
59
|
-
XHR.send(
|
|
69
|
+
XHR.setRequestHeader('Content-type', 'application/json; charset=utf-8');
|
|
70
|
+
XHR.send(JSON.stringify(data));
|
|
60
71
|
};
|
|
61
72
|
const _serverPutAPI = (url, data) => (successHandler, errorHandler) => {
|
|
62
73
|
if (!data) {
|
|
@@ -73,7 +84,7 @@ const _serverDeleteAPI = (url, params = {}) => (successHandler, errorHandler) =>
|
|
|
73
84
|
const urlParamKeys = Object.keys(params);
|
|
74
85
|
if (urlParamKeys.length > 0) {
|
|
75
86
|
urlParamKeys.forEach((key, index) => {
|
|
76
|
-
urlWithParams += index === 0 ? `?${key}=${params[key]}` : `&${key}=${params[key]}`;
|
|
87
|
+
urlWithParams += index === 0 ? `?${encodeURIComponent(key)}=${encodeURIComponent(params[key])}` : `&${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
|
|
77
88
|
});
|
|
78
89
|
}
|
|
79
90
|
XHR.open('DELETE', urlWithParams, true);
|
package/package.json
CHANGED