UNPKG

8.14 kBJavaScriptView Raw
1import { SYNC_RENDER, NO_RENDER, FORCE_RENDER, ASYNC_RENDER, ATTR_KEY } from '../constants';
2import options from '../options';
3import { extend } from '../util';
4import { enqueueRender } from '../render-queue';
5import { getNodeProps } from './index';
6import { diff, mounts, diffLevel, flushMounts, recollectNodeTree, removeChildren } from './diff';
7import { createComponent, collectComponent } from './component-recycler';
8import { removeNode } from '../dom/index';
9
10/** Set a component's `props` (generally derived from JSX attributes).
11 * @param {Object} props
12 * @param {Object} [opts]
13 * @param {boolean} [opts.renderSync=false] If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering.
14 * @param {boolean} [opts.render=true] If `false`, no render will be triggered.
15 */
16export function setComponentProps(component, props, opts, context, mountAll) {
17 if (component._disable) return;
18 component._disable = true;
19
20 if ((component.__ref = props.ref)) delete props.ref;
21 if ((component.__key = props.key)) delete props.key;
22
23 if (!component.base || mountAll) {
24 if (component.componentWillMount) component.componentWillMount();
25 }
26 else if (component.componentWillReceiveProps) {
27 component.componentWillReceiveProps(props, context);
28 }
29
30 if (context && context!==component.context) {
31 if (!component.prevContext) component.prevContext = component.context;
32 component.context = context;
33 }
34
35 if (!component.prevProps) component.prevProps = component.props;
36 component.props = props;
37
38 component._disable = false;
39
40 if (opts!==NO_RENDER) {
41 if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) {
42 renderComponent(component, SYNC_RENDER, mountAll);
43 }
44 else {
45 enqueueRender(component);
46 }
47 }
48
49 if (component.__ref) component.__ref(component);
50}
51
52
53
54/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account.
55 * @param {Component} component
56 * @param {Object} [opts]
57 * @param {boolean} [opts.build=false] If `true`, component will build and store a DOM node if not already associated with one.
58 * @private
59 */
60export function renderComponent(component, opts, mountAll, isChild) {
61 if (component._disable) return;
62
63 let props = component.props,
64 state = component.state,
65 context = component.context,
66 previousProps = component.prevProps || props,
67 previousState = component.prevState || state,
68 previousContext = component.prevContext || context,
69 isUpdate = component.base,
70 nextBase = component.nextBase,
71 initialBase = isUpdate || nextBase,
72 initialChildComponent = component._component,
73 skip = false,
74 rendered, inst, cbase;
75
76 // if updating
77 if (isUpdate) {
78 component.props = previousProps;
79 component.state = previousState;
80 component.context = previousContext;
81 if (opts!==FORCE_RENDER
82 && component.shouldComponentUpdate
83 && component.shouldComponentUpdate(props, state, context) === false) {
84 skip = true;
85 }
86 else if (component.componentWillUpdate) {
87 component.componentWillUpdate(props, state, context);
88 }
89 component.props = props;
90 component.state = state;
91 component.context = context;
92 }
93
94 component.prevProps = component.prevState = component.prevContext = component.nextBase = null;
95 component._dirty = false;
96
97 if (!skip) {
98 rendered = component.render(props, state, context);
99
100 // context to pass to the child, can be updated via (grand-)parent component
101 if (component.getChildContext) {
102 context = extend(extend({}, context), component.getChildContext());
103 }
104
105 let childComponent = rendered && rendered.nodeName,
106 toUnmount, base;
107
108 if (typeof childComponent==='function') {
109 // set up high order component link
110
111 let childProps = getNodeProps(rendered);
112 inst = initialChildComponent;
113
114 if (inst && inst.constructor===childComponent && childProps.key==inst.__key) {
115 setComponentProps(inst, childProps, SYNC_RENDER, context, false);
116 }
117 else {
118 toUnmount = inst;
119
120 component._component = inst = createComponent(childComponent, childProps, context);
121 inst.nextBase = inst.nextBase || nextBase;
122 inst._parentComponent = component;
123 setComponentProps(inst, childProps, NO_RENDER, context, false);
124 renderComponent(inst, SYNC_RENDER, mountAll, true);
125 }
126
127 base = inst.base;
128 }
129 else {
130 cbase = initialBase;
131
132 // destroy high order component link
133 toUnmount = initialChildComponent;
134 if (toUnmount) {
135 cbase = component._component = null;
136 }
137
138 if (initialBase || opts===SYNC_RENDER) {
139 if (cbase) cbase._component = null;
140 base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true);
141 }
142 }
143
144 if (initialBase && base!==initialBase && inst!==initialChildComponent) {
145 let baseParent = initialBase.parentNode;
146 if (baseParent && base!==baseParent) {
147 baseParent.replaceChild(base, initialBase);
148
149 if (!toUnmount) {
150 initialBase._component = null;
151 recollectNodeTree(initialBase, false);
152 }
153 }
154 }
155
156 if (toUnmount) {
157 unmountComponent(toUnmount);
158 }
159
160 component.base = base;
161 if (base && !isChild) {
162 let componentRef = component,
163 t = component;
164 while ((t=t._parentComponent)) {
165 (componentRef = t).base = base;
166 }
167 base._component = componentRef;
168 base._componentConstructor = componentRef.constructor;
169 }
170 }
171
172 if (!isUpdate || mountAll) {
173 mounts.unshift(component);
174 }
175 else if (!skip) {
176 // Ensure that pending componentDidMount() hooks of child components
177 // are called before the componentDidUpdate() hook in the parent.
178 // Note: disabled as it causes duplicate hooks, see https://github.com/developit/preact/issues/750
179 // flushMounts();
180
181 if (component.componentDidUpdate) {
182 component.componentDidUpdate(previousProps, previousState, previousContext);
183 }
184 if (options.afterUpdate) options.afterUpdate(component);
185 }
186
187 if (component._renderCallbacks!=null) {
188 while (component._renderCallbacks.length) component._renderCallbacks.pop().call(component);
189 }
190
191 if (!diffLevel && !isChild) flushMounts();
192}
193
194
195
196/** Apply the Component referenced by a VNode to the DOM.
197 * @param {Element} dom The DOM node to mutate
198 * @param {VNode} vnode A Component-referencing VNode
199 * @returns {Element} dom The created/mutated element
200 * @private
201 */
202export function buildComponentFromVNode(dom, vnode, context, mountAll) {
203 let c = dom && dom._component,
204 originalComponent = c,
205 oldDom = dom,
206 isDirectOwner = c && dom._componentConstructor===vnode.nodeName,
207 isOwner = isDirectOwner,
208 props = getNodeProps(vnode);
209 while (c && !isOwner && (c=c._parentComponent)) {
210 isOwner = c.constructor===vnode.nodeName;
211 }
212
213 if (c && isOwner && (!mountAll || c._component)) {
214 setComponentProps(c, props, ASYNC_RENDER, context, mountAll);
215 dom = c.base;
216 }
217 else {
218 if (originalComponent && !isDirectOwner) {
219 unmountComponent(originalComponent);
220 dom = oldDom = null;
221 }
222
223 c = createComponent(vnode.nodeName, props, context);
224 if (dom && !c.nextBase) {
225 c.nextBase = dom;
226 // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229:
227 oldDom = null;
228 }
229 setComponentProps(c, props, SYNC_RENDER, context, mountAll);
230 dom = c.base;
231
232 if (oldDom && dom!==oldDom) {
233 oldDom._component = null;
234 recollectNodeTree(oldDom, false);
235 }
236 }
237
238 return dom;
239}
240
241
242
243/** Remove a component from the DOM and recycle it.
244 * @param {Component} component The Component instance to unmount
245 * @private
246 */
247export function unmountComponent(component) {
248 if (options.beforeUnmount) options.beforeUnmount(component);
249
250 let base = component.base;
251
252 component._disable = true;
253
254 if (component.componentWillUnmount) component.componentWillUnmount();
255
256 component.base = null;
257
258 // recursively tear down & recollect high-order component children:
259 let inner = component._component;
260 if (inner) {
261 unmountComponent(inner);
262 }
263 else if (base) {
264 if (base[ATTR_KEY] && base[ATTR_KEY].ref) base[ATTR_KEY].ref(null);
265
266 component.nextBase = base;
267
268 removeNode(base);
269 collectComponent(component);
270
271 removeChildren(base);
272 }
273
274 if (component.__ref) component.__ref(null);
275}