mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
@@ -20,6 +20,7 @@
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
|
||||
import type { HostChildren } from 'ReactFiberReconciler';
|
||||
|
||||
var ReactFiberReconciler = require('ReactFiberReconciler');
|
||||
@@ -153,30 +154,61 @@ var ReactNoop = {
|
||||
return;
|
||||
}
|
||||
|
||||
var bufferedLog = [];
|
||||
function log(...args) {
|
||||
bufferedLog.push(...args, '\n');
|
||||
}
|
||||
|
||||
function logHostInstances(children: Array<Instance>, depth) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
console.log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
|
||||
log(' '.repeat(depth) + '- ' + child.type + '#' + child.id);
|
||||
logHostInstances(child.children, depth + 1);
|
||||
}
|
||||
}
|
||||
function logContainer(container : Container, depth) {
|
||||
console.log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
|
||||
log(' '.repeat(depth) + '- [root#' + container.rootID + ']');
|
||||
logHostInstances(container.children, depth + 1);
|
||||
}
|
||||
|
||||
function logUpdateQueue(updateQueue : UpdateQueue, depth) {
|
||||
log(
|
||||
' '.repeat(depth + 1) + 'QUEUED UPDATES',
|
||||
updateQueue.isReplace ? 'is replace' : '',
|
||||
updateQueue.isForced ? 'is forced' : ''
|
||||
);
|
||||
log(
|
||||
' '.repeat(depth + 1) + '~',
|
||||
updateQueue.partialState,
|
||||
updateQueue.callback ? 'with callback' : ''
|
||||
);
|
||||
var next;
|
||||
while (next = updateQueue.next) {
|
||||
log(
|
||||
' '.repeat(depth + 1) + '~',
|
||||
next.partialState,
|
||||
next.callback ? 'with callback' : ''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function logFiber(fiber : Fiber, depth) {
|
||||
console.log(
|
||||
log(
|
||||
' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'),
|
||||
'[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']'
|
||||
);
|
||||
if (fiber.updateQueue) {
|
||||
logUpdateQueue(fiber.updateQueue, depth);
|
||||
}
|
||||
const childInProgress = fiber.progressedChild;
|
||||
if (childInProgress && childInProgress !== fiber.child) {
|
||||
console.log(' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority);
|
||||
log(' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.progressedPriority);
|
||||
logFiber(childInProgress, depth + 1);
|
||||
if (fiber.child) {
|
||||
console.log(' '.repeat(depth + 1) + 'CURRENT');
|
||||
log(' '.repeat(depth + 1) + 'CURRENT');
|
||||
}
|
||||
} else if (fiber.child && fiber.updateQueue) {
|
||||
log(' '.repeat(depth + 1) + 'CHILDREN');
|
||||
}
|
||||
if (fiber.child) {
|
||||
logFiber(fiber.child, depth + 1);
|
||||
@@ -186,10 +218,12 @@ var ReactNoop = {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('HOST INSTANCES:');
|
||||
log('HOST INSTANCES:');
|
||||
logContainer(rootContainer, 0);
|
||||
console.log('FIBERS:');
|
||||
log('FIBERS:');
|
||||
logFiber((root.stateNode : any).current, 0);
|
||||
|
||||
console.log(...bufferedLog);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
|
||||
import type { TypeOfWork } from 'ReactTypeOfWork';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
|
||||
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
@@ -76,6 +77,12 @@ export type Fiber = Instance & {
|
||||
pendingProps: any, // This type will be more specific once we overload the tag.
|
||||
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
|
||||
memoizedProps: any, // The props used to create the output.
|
||||
// A queue of local state updates.
|
||||
updateQueue: ?UpdateQueue,
|
||||
// The state used to create the output. This is a full state object.
|
||||
memoizedState: any,
|
||||
// Linked list of callbacks to call after updates are committed.
|
||||
callbackList: ?UpdateQueue,
|
||||
// Output is the return value of this fiber, or a linked list of return values
|
||||
// if this returns multiple values. Such as a fragment.
|
||||
output: any, // This type will be more specific once we overload the tag.
|
||||
@@ -151,6 +158,9 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
|
||||
|
||||
pendingProps: null,
|
||||
memoizedProps: null,
|
||||
updateQueue: null,
|
||||
memoizedState: null,
|
||||
callbackList: null,
|
||||
output: null,
|
||||
|
||||
nextEffect: null,
|
||||
@@ -192,6 +202,8 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
|
||||
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
|
||||
alt.ref = fiber.ref;
|
||||
alt.pendingProps = fiber.pendingProps; // TODO: Pass as argument.
|
||||
alt.updateQueue = fiber.updateQueue;
|
||||
alt.callbackList = fiber.callbackList;
|
||||
alt.pendingWorkPriority = priorityLevel;
|
||||
|
||||
alt.child = fiber.child;
|
||||
@@ -217,6 +229,8 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
|
||||
// pendingProps is here for symmetry but is unnecessary in practice for now.
|
||||
// TODO: Pass in the new pendingProps as an argument maybe?
|
||||
alt.pendingProps = fiber.pendingProps;
|
||||
alt.updateQueue = fiber.updateQueue;
|
||||
alt.callbackList = fiber.callbackList;
|
||||
alt.pendingWorkPriority = priorityLevel;
|
||||
|
||||
alt.memoizedProps = fiber.memoizedProps;
|
||||
|
||||
@@ -14,14 +14,18 @@
|
||||
|
||||
import type { ReactCoroutine } from 'ReactCoroutine';
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { HostConfig } from 'ReactFiberReconciler';
|
||||
import type { Scheduler } from 'ReactFiberScheduler';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
|
||||
|
||||
var {
|
||||
reconcileChildFibers,
|
||||
reconcileChildFibersInPlace,
|
||||
cloneChildFibers,
|
||||
} = require('ReactChildFiber');
|
||||
var { LowPriority } = require('ReactPriorityLevel');
|
||||
var ReactTypeOfWork = require('ReactTypeOfWork');
|
||||
var {
|
||||
IndeterminateComponent,
|
||||
@@ -37,8 +41,15 @@ var {
|
||||
NoWork,
|
||||
OffscreenPriority,
|
||||
} = require('ReactPriorityLevel');
|
||||
var {
|
||||
createUpdateQueue,
|
||||
addToQueue,
|
||||
addCallbackToQueue,
|
||||
mergeUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler : () => Scheduler) {
|
||||
|
||||
function markChildAsProgressed(current, workInProgress, priorityLevel) {
|
||||
// We now have clones. Let's store them as the currently progressed work.
|
||||
@@ -105,25 +116,116 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function scheduleUpdate(fiber: Fiber, updateQueue: UpdateQueue, priorityLevel : PriorityLevel): void {
|
||||
const { scheduleLowPriWork } = getScheduler();
|
||||
fiber.updateQueue = updateQueue;
|
||||
// Schedule update on the alternate as well, since we don't know which tree
|
||||
// is current.
|
||||
if (fiber.alternate) {
|
||||
fiber.alternate.updateQueue = updateQueue;
|
||||
}
|
||||
while (true) {
|
||||
if (fiber.pendingWorkPriority === NoWork ||
|
||||
fiber.pendingWorkPriority >= priorityLevel) {
|
||||
fiber.pendingWorkPriority = priorityLevel;
|
||||
}
|
||||
if (fiber.alternate) {
|
||||
if (fiber.alternate.pendingWorkPriority === NoWork ||
|
||||
fiber.alternate.pendingWorkPriority >= priorityLevel) {
|
||||
fiber.alternate.pendingWorkPriority = priorityLevel;
|
||||
}
|
||||
}
|
||||
// Duck type root
|
||||
if (fiber.stateNode && fiber.stateNode.containerInfo) {
|
||||
const root : FiberRoot = (fiber.stateNode : any);
|
||||
scheduleLowPriWork(root, priorityLevel);
|
||||
return;
|
||||
}
|
||||
if (!fiber.return) {
|
||||
throw new Error('No root!');
|
||||
}
|
||||
fiber = fiber.return;
|
||||
}
|
||||
}
|
||||
|
||||
// Class component state updater
|
||||
const updater = {
|
||||
enqueueSetState(instance, partialState) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const updateQueue = fiber.updateQueue ?
|
||||
addToQueue(fiber.updateQueue, partialState) :
|
||||
createUpdateQueue(partialState);
|
||||
scheduleUpdate(fiber, updateQueue, LowPriority);
|
||||
},
|
||||
enqueueReplaceState(instance, state) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const updateQueue = createUpdateQueue(state);
|
||||
updateQueue.isReplace = true;
|
||||
scheduleUpdate(fiber, updateQueue, LowPriority);
|
||||
},
|
||||
enqueueForceUpdate(instance) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const updateQueue = fiber.updateQueue || createUpdateQueue(null);
|
||||
updateQueue.isForced = true;
|
||||
scheduleUpdate(fiber, updateQueue, LowPriority);
|
||||
},
|
||||
enqueueCallback(instance, callback) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
let updateQueue = fiber.updateQueue ?
|
||||
fiber.updateQueue :
|
||||
createUpdateQueue(null);
|
||||
addCallbackToQueue(updateQueue, callback);
|
||||
fiber.updateQueue = updateQueue;
|
||||
if (fiber.alternate) {
|
||||
fiber.alternate.updateQueue = updateQueue;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function updateClassComponent(current : ?Fiber, workInProgress : Fiber) {
|
||||
// A class component update is the result of either new props or new state.
|
||||
// Account for the possibly of missing pending props by falling back to the
|
||||
// memoized props.
|
||||
var props = workInProgress.pendingProps;
|
||||
if (!props && current) {
|
||||
props = current.memoizedProps;
|
||||
}
|
||||
// Compute the state using the memoized state and the update queue.
|
||||
var updateQueue = workInProgress.updateQueue;
|
||||
var previousState = current ? current.memoizedState : null;
|
||||
var state = updateQueue ?
|
||||
mergeUpdateQueue(updateQueue, previousState, props) :
|
||||
previousState;
|
||||
|
||||
var instance = workInProgress.stateNode;
|
||||
if (!instance) {
|
||||
var ctor = workInProgress.type;
|
||||
workInProgress.stateNode = instance = new ctor(props);
|
||||
} else if (typeof instance.shouldComponentUpdate === 'function') {
|
||||
state = instance.state || null;
|
||||
// The initial state must be added to the update queue in case
|
||||
// setState is called before the initial render.
|
||||
if (state !== null) {
|
||||
workInProgress.updateQueue = createUpdateQueue(state);
|
||||
}
|
||||
// The instance needs access to the fiber so that it can schedule updates
|
||||
ReactInstanceMap.set(instance, workInProgress);
|
||||
instance.updater = updater;
|
||||
} else if (typeof instance.shouldComponentUpdate === 'function' &&
|
||||
!(updateQueue && updateQueue.isForced)) {
|
||||
if (workInProgress.memoizedProps !== null) {
|
||||
// Reset the props, in case this is a ping-pong case rather than a
|
||||
// completed update case. For the completed update case, the instance
|
||||
// props will already be the memoizedProps.
|
||||
instance.props = workInProgress.memoizedProps;
|
||||
if (!instance.shouldComponentUpdate(props)) {
|
||||
instance.state = workInProgress.memoizedState;
|
||||
if (!instance.shouldComponentUpdate(props, state)) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance.props = props;
|
||||
instance.state = state;
|
||||
var nextChildren = instance.render();
|
||||
reconcileChildren(current, workInProgress, nextChildren);
|
||||
|
||||
@@ -251,10 +353,11 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
workInProgress.child = workInProgress.progressedChild;
|
||||
}
|
||||
|
||||
if (workInProgress.pendingProps === null || (
|
||||
if ((workInProgress.pendingProps === null || (
|
||||
workInProgress.memoizedProps !== null &&
|
||||
workInProgress.pendingProps === workInProgress.memoizedProps
|
||||
)) {
|
||||
)) &&
|
||||
workInProgress.updateQueue === null) {
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ var {
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
} = ReactTypeOfWork;
|
||||
var { callCallbacks } = require('ReactFiberUpdateQueue');
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
|
||||
@@ -31,6 +32,18 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
function commitWork(current : ?Fiber, finishedWork : Fiber) : void {
|
||||
switch (finishedWork.tag) {
|
||||
case ClassComponent: {
|
||||
// Clear updates from current fiber. This must go before the callbacks
|
||||
// are reset, in case an update is triggered from inside a callback. Is
|
||||
// this safe? Relies on the assumption that work is only committed if
|
||||
// the update queue is empty.
|
||||
if (finishedWork.alternate) {
|
||||
finishedWork.alternate.updateQueue = null;
|
||||
}
|
||||
if (finishedWork.callbackList) {
|
||||
const { callbackList } = finishedWork;
|
||||
finishedWork.callbackList = null;
|
||||
callCallbacks(callbackList, finishedWork.stateNode);
|
||||
}
|
||||
// TODO: Fire componentDidMount/componentDidUpdate, update refs
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: It's possible this will create layout thrash issues because mutations
|
||||
// of the DOM and life-cycles are interleaved. E.g. if a componentDidMount
|
||||
// of a sibling reads, then the next sibling updates and reads etc.
|
||||
@@ -59,7 +58,6 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
}
|
||||
workInProgress.lastEffect = workInProgress;
|
||||
}
|
||||
*/
|
||||
|
||||
function transferOutput(child : ?Fiber, returnFiber : Fiber) {
|
||||
// If we have a single result, we just pass that through as the output to
|
||||
@@ -132,6 +130,17 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
return null;
|
||||
case ClassComponent:
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
// Don't use the state queue to compute the memoized state. We already
|
||||
// merged it and assigned it to the instance. Transfer it from there.
|
||||
// Also need to transfer the props, because pendingProps will be null
|
||||
// in the case of an update
|
||||
const { state, props } = workInProgress.stateNode;
|
||||
workInProgress.memoizedState = state;
|
||||
workInProgress.memoizedProps = props;
|
||||
// Transfer update queue to callbackList field so callbacks can be
|
||||
// called during commit phase.
|
||||
workInProgress.callbackList = workInProgress.updateQueue;
|
||||
markForPostEffect(workInProgress);
|
||||
return null;
|
||||
case HostContainer:
|
||||
transferOutput(workInProgress.child, workInProgress);
|
||||
|
||||
@@ -29,9 +29,19 @@ var {
|
||||
|
||||
var timeHeuristicForUnitOfWork = 1;
|
||||
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
export type Scheduler = {
|
||||
scheduleLowPriWork: (root : FiberRoot, priority : PriorityLevel) => void
|
||||
};
|
||||
|
||||
const { beginWork } = ReactFiberBeginWork(config);
|
||||
module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
// Use a closure to circumvent the circular dependency between the scheduler
|
||||
// and ReactFiberBeginWork. Don't know if there's a better way to do this.
|
||||
let scheduler;
|
||||
function getScheduler(): Scheduler {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
const { beginWork } = ReactFiberBeginWork(config, getScheduler);
|
||||
const { completeWork } = ReactFiberCompleteWork(config);
|
||||
const { commitWork } = ReactFiberCommitWork(config);
|
||||
|
||||
@@ -133,6 +143,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
// The work is now done. We don't need this anymore. This flags
|
||||
// to the system not to redo any work here.
|
||||
workInProgress.pendingProps = null;
|
||||
workInProgress.updateQueue = null;
|
||||
|
||||
const returnFiber = workInProgress.return;
|
||||
|
||||
@@ -259,7 +270,8 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
}
|
||||
*/
|
||||
|
||||
return {
|
||||
scheduler = {
|
||||
scheduleLowPriWork: scheduleLowPriWork,
|
||||
};
|
||||
return scheduler;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberUpdateQueue
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
type UpdateQueueNode = {
|
||||
partialState: any,
|
||||
callback: ?Function,
|
||||
callbackWasCalled: boolean,
|
||||
next: ?UpdateQueueNode,
|
||||
};
|
||||
|
||||
export type UpdateQueue = UpdateQueueNode & {
|
||||
isReplace: boolean,
|
||||
isForced: boolean,
|
||||
tail: UpdateQueueNode
|
||||
};
|
||||
|
||||
exports.createUpdateQueue = function(partialState : mixed) : UpdateQueue {
|
||||
const queue = {
|
||||
partialState,
|
||||
callback: null,
|
||||
callbackWasCalled: false,
|
||||
next: null,
|
||||
isReplace: false,
|
||||
isForced: false,
|
||||
tail: (null : any),
|
||||
};
|
||||
queue.tail = queue;
|
||||
return queue;
|
||||
};
|
||||
|
||||
exports.addToQueue = function(queue : UpdateQueue, partialState : mixed) : UpdateQueue {
|
||||
const node = {
|
||||
partialState,
|
||||
callback: null,
|
||||
callbackWasCalled: false,
|
||||
next: null,
|
||||
};
|
||||
queue.tail.next = node;
|
||||
queue.tail = node;
|
||||
return queue;
|
||||
};
|
||||
|
||||
exports.addCallbackToQueue = function(queue : UpdateQueue, callback: Function) : UpdateQueue {
|
||||
if (queue.tail.callback) {
|
||||
// If the tail already as a callback, add an empty node to queue
|
||||
exports.addToQueue(queue, null);
|
||||
}
|
||||
queue.tail.callback = callback;
|
||||
return queue;
|
||||
};
|
||||
|
||||
exports.callCallbacks = function(queue : UpdateQueue, context : any) {
|
||||
let node : ?UpdateQueueNode = queue;
|
||||
while (node) {
|
||||
if (node.callback && !node.callbackWasCalled) {
|
||||
node.callbackWasCalled = true;
|
||||
node.callback.call(context);
|
||||
}
|
||||
node = node.next;
|
||||
}
|
||||
};
|
||||
|
||||
exports.mergeUpdateQueue = function(queue : UpdateQueue, prevState : any, props : any) : any {
|
||||
let node : ?UpdateQueueNode = queue;
|
||||
let state = queue.isReplace ? null : Object.assign({}, prevState);
|
||||
while (node) {
|
||||
let partialState;
|
||||
if (typeof node.partialState === 'function') {
|
||||
const updateFn = node.partialState;
|
||||
partialState = updateFn(state, props);
|
||||
} else {
|
||||
partialState = node.partialState;
|
||||
}
|
||||
state = Object.assign(state || {}, partialState);
|
||||
node = node.next;
|
||||
}
|
||||
return state;
|
||||
};
|
||||
@@ -556,4 +556,208 @@ describe('ReactIncremental', () => {
|
||||
expect(ops).toEqual(['Content', 'Bar', 'Middle']);
|
||||
|
||||
});
|
||||
|
||||
it('can update in the middle of a tree using setState', () => {
|
||||
let instance;
|
||||
class Bar extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { a: 'a' };
|
||||
instance = this;
|
||||
}
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Foo() {
|
||||
return (
|
||||
<div>
|
||||
<Bar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
expect(instance.state).toEqual({ a: 'a' });
|
||||
instance.setState({ b: 'b' });
|
||||
ReactNoop.flush();
|
||||
expect(instance.state).toEqual({ a: 'a', b: 'b' });
|
||||
});
|
||||
|
||||
it('can queue multiple state updates', () => {
|
||||
let instance;
|
||||
class Bar extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { a: 'a' };
|
||||
instance = this;
|
||||
}
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Foo() {
|
||||
return (
|
||||
<div>
|
||||
<Bar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
// Call setState multiple times before flushing
|
||||
instance.setState({ b: 'b' });
|
||||
instance.setState({ c: 'c' });
|
||||
instance.setState({ d: 'd' });
|
||||
ReactNoop.flush();
|
||||
expect(instance.state).toEqual({ a: 'a', b: 'b', c: 'c', d: 'd' });
|
||||
});
|
||||
|
||||
it('can use updater form of setState', () => {
|
||||
let instance;
|
||||
class Bar extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { num: 1 };
|
||||
instance = this;
|
||||
}
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Foo({ multiplier }) {
|
||||
return (
|
||||
<div>
|
||||
<Bar multiplier={multiplier} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function updater(state, props) {
|
||||
return { num: state.num * props.multiplier };
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo multiplier={2} />);
|
||||
ReactNoop.flush();
|
||||
expect(instance.state.num).toEqual(1);
|
||||
instance.setState(updater);
|
||||
ReactNoop.flush();
|
||||
expect(instance.state.num).toEqual(2);
|
||||
instance.setState(updater);
|
||||
ReactNoop.render(<Foo multiplier={3} />);
|
||||
ReactNoop.flush();
|
||||
expect(instance.state.num).toEqual(6);
|
||||
});
|
||||
|
||||
it('can call setState inside update callback', () => {
|
||||
let instance;
|
||||
class Bar extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { num: 1 };
|
||||
instance = this;
|
||||
}
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Foo({ multiplier }) {
|
||||
return (
|
||||
<div>
|
||||
<Bar multiplier={multiplier} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function updater(state, props) {
|
||||
return { num: state.num * props.multiplier };
|
||||
}
|
||||
|
||||
function callback() {
|
||||
this.setState({ called: true });
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo multiplier={2} />);
|
||||
ReactNoop.flush();
|
||||
instance.setState(updater);
|
||||
instance.setState(updater, callback);
|
||||
ReactNoop.flush();
|
||||
expect(instance.state.num).toEqual(4);
|
||||
expect(instance.state.called).toEqual(true);
|
||||
});
|
||||
|
||||
it('can replaceState', () => {
|
||||
let instance;
|
||||
const Bar = React.createClass({
|
||||
getInitialState() {
|
||||
instance = this;
|
||||
return { a: 'a' };
|
||||
},
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
function Foo() {
|
||||
return (
|
||||
<div>
|
||||
<Bar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
instance.setState({ b: 'b' });
|
||||
instance.setState({ c: 'c' });
|
||||
instance.replaceState({ d: 'd' });
|
||||
ReactNoop.flush();
|
||||
expect(instance.state).toEqual({ d: 'd' });
|
||||
});
|
||||
|
||||
it('can forceUpdate', () => {
|
||||
const ops = [];
|
||||
|
||||
function Baz() {
|
||||
ops.push('Baz');
|
||||
return <div />;
|
||||
}
|
||||
|
||||
let instance;
|
||||
class Bar extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
instance = this;
|
||||
}
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
ops.push('Bar');
|
||||
return <Baz />;
|
||||
}
|
||||
}
|
||||
|
||||
function Foo() {
|
||||
ops.push('Foo');
|
||||
return (
|
||||
<div>
|
||||
<Bar />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
|
||||
instance.forceUpdate();
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual(['Foo', 'Bar', 'Baz', 'Bar', 'Baz']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -354,4 +354,34 @@ describe('ReactIncrementalSideEffects', () => {
|
||||
// moves to "current" without flushing due to having lower priority. Does this
|
||||
// even happen? Maybe a child doesn't get processed because it is lower prio?
|
||||
|
||||
it('calls callback after update is flushed', () => {
|
||||
let instance;
|
||||
class Foo extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
instance = this;
|
||||
this.state = { text: 'foo' };
|
||||
}
|
||||
render() {
|
||||
return <span prop={this.state.text} />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.root.children).toEqual([
|
||||
span('foo'),
|
||||
]);
|
||||
let called = false;
|
||||
instance.setState({ text: 'bar' }, () => {
|
||||
expect(ReactNoop.root.children).toEqual([
|
||||
span('bar'),
|
||||
]);
|
||||
called = true;
|
||||
});
|
||||
ReactNoop.flush();
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
// TODO: Test that callbacks are not lost if an update is preempted.
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user