Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions examples/fiber/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,40 @@ <h1>Fiber Example</h1>
<script src="../../build/react.js"></script>
<script src="../../build/react-dom-fiber.js"></script>
<script>
class Child extends React.Component {
render() {
return React.DOM.p(null, this.context.foo);
}
}

Child.contextTypes = {
foo: React.PropTypes.string
}

class Parent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = { message: 'Hello' };
}
componentDidMount() {
this.timeout = setTimeout(() => {
this.setState({ message: 'Another message' });
}, 1000);
}
componentWillUnmount() {
clearTimeout(this.interval);
}
getChildContext() {
console.log('getChildContext', this.state);
return { foo: this.state.message };
}
render() {
return React.createElement(Child);
}
}
Parent.childContextTypes = {
foo: React.PropTypes.string,
};
function ExampleApplication(props) {
var elapsed = Math.round(props.elapsed / 100);
var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
Expand All @@ -32,13 +66,11 @@ <h1>Fiber Example</h1>
// Call React.createFactory instead of directly call ExampleApplication({...}) in React.render
var ExampleApplicationFactory = React.createFactory(ExampleApplication);

var start = new Date().getTime();
setInterval(function() {
ReactDOMFiber.render(
ExampleApplicationFactory({elapsed: new Date().getTime() - start}),
document.getElementById('container')
);
}, 50);
ReactDOMFiber.render(
React.createElement(Parent),
//ExampleApplicationFactory({elapsed: new Date().getTime() - start}),
document.getElementById('container')
);
</script>
</body>
</html>
6 changes: 6 additions & 0 deletions src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export type Fiber = {
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.
// Context
childContext: any,
// A queue of local state updates.
updateQueue: ?UpdateQueue,
// The state used to create the output. This is a full state object.
Expand Down Expand Up @@ -183,6 +185,8 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
memoizedProps: null,
updateQueue: null,
memoizedState: null,
memoizedContext: null,
memoizedChildContext: null,
callbackList: null,
output: null,

Expand Down Expand Up @@ -257,6 +261,8 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi

alt.memoizedProps = fiber.memoizedProps;
alt.memoizedState = fiber.memoizedState;
alt.memoizedContext = fiber.memoizedContext;
alt.memoizedChildContext = fiber.memoizedChildContext;
alt.output = fiber.output;

return alt;
Expand Down
151 changes: 131 additions & 20 deletions src/renderers/shared/fiber/ReactFiberClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ var shallowEqual = require('shallowEqual');
var warning = require('warning');
var invariant = require('invariant');

let checkReactTypeSpec;

if (__DEV__) {
checkReactTypeSpec = require('checkReactTypeSpec');
}

const isArray = Array.isArray;

Expand Down Expand Up @@ -74,22 +79,23 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
},
};

function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState) {
function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState, newContext) {
const updateQueue = workInProgress.updateQueue;
if (oldProps === null || (updateQueue && updateQueue.isForced)) {
return true;
}

const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === 'function') {
return instance.shouldComponentUpdate(newProps, newState);
return instance.shouldComponentUpdate(newProps, newState, newContext);
}

const type = workInProgress.type;
if (type.prototype && type.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) ||
!shallowEqual(instance.state, newState)
!shallowEqual(instance.state, newState) ||
!shallowEqual(instance.context, newContext)
);
}

Expand Down Expand Up @@ -195,10 +201,100 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
ReactInstanceMap.set(instance, workInProgress);
}

function checkContextTypes(workInProgress : Fiber, typeSpecs: {}, values: {}, location: string) {
checkReactTypeSpec(
typeSpecs,
values,
location,
getName(workInProgress, workInProgress.stateNode),
null,
null
);
}

function maskContext(context: {}, contextTypes: {}): any {
if (!contextTypes) {
return {};
}

const maskedContext = {};
for (let contextName in contextTypes) {
maskedContext[contextName] = context[contextName];
}

return maskedContext;
}

function processChildContext(workInProgress : Fiber, instance : any) {
const Component = workInProgress.type;
const currentContext = (
workInProgress.return &&
workInProgress.return.stateNode &&
workInProgress.return.stateNode._childContext
);

let childContext;
if (instance.getChildContext) {
if (__DEV__) {
// ReactFiberInstrumentation.debugTool.onBeginProcessingChildContext();
try {
childContext = instance.getChildContext();
} finally {
// ReactFiberInstrumentation.debugTool.onEndProcessingChildContext();
}
} else {
childContext = instance.getChildContext();
}
}

if (childContext) {
invariant(
typeof Component.childContextTypes === 'object',
'%s.getChildContext(): childContextTypes must be defined in order to ' +
'use getChildContext().',
getName(workInProgress, instance)
);

if (__DEV__) {
checkContextTypes(workInProgress, Component.childContextTypes, childContext, 'childContext');
}

for (let name in childContext) {
invariant(
name in Component.childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getName(workInProgress, instance),
name
);
}
return Object.assign({}, currentContext, childContext);
}
return currentContext;
}

function processContext(workInProgress : Fiber) {
const Component = workInProgress.type;
const contextTypes = Component.contextTypes;
const currentContext = (
workInProgress.return &&
workInProgress.return.stateNode &&
workInProgress.return.stateNode._childContext
);
const maskedContext = maskContext(currentContext, contextTypes);
if (__DEV__) {
if (contextTypes) {
checkContextTypes(workInProgress, contextTypes, maskedContext, 'context');
}
}
return maskedContext;
}

function constructClassInstance(workInProgress : Fiber) : any {
const ctor = workInProgress.type;
const props = workInProgress.pendingProps;
const instance = new ctor(props);
const context = processContext(workInProgress);
const instance = new ctor(props, context);

checkClassInstance(workInProgress, instance);
adoptClassInstance(workInProgress, instance);
return instance;
Expand All @@ -217,6 +313,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {

instance.props = props;
instance.state = state;
instance.context = instance.context || processContext(workInProgress);
instance._childContext = instance.childContext || processChildContext(workInProgress, instance);

if (typeof instance.componentWillMount === 'function') {
instance.componentWillMount();
Expand All @@ -225,13 +323,17 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
const updateQueue = workInProgress.updateQueue;
if (updateQueue) {
instance.state = mergeUpdateQueue(updateQueue, instance, state, props);
// instance.context = processContext(workInProgress);
instance._childContext = processChildContext(workInProgress, instance);
}
}
}

// Called on a preexisting class instance. Returns false if a resumed render
// could be reused.
function resumeMountClassInstance(workInProgress : Fiber) : boolean {
const newContext = workInProgress.memoizedContext;
const newChildContext = workInProgress.memoizedChildContext;
let newState = workInProgress.memoizedState;
let newProps = workInProgress.pendingProps;
if (!newProps) {
Expand All @@ -242,7 +344,6 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
throw new Error('There should always be pending or memoized props.');
}
}

// TODO: Should we deal with a setState that happened after the last
// componentWillMount and before this componentWillMount? Probably
// unsupported anyway.
Expand All @@ -251,7 +352,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
workInProgress,
workInProgress.memoizedProps,
newProps,
newState
newState,
newContext
)) {
return false;
}
Expand All @@ -261,7 +363,8 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
const newInstance = constructClassInstance(workInProgress);
newInstance.props = newProps;
newInstance.state = newState = newInstance.state || null;

newInstance.context = newContext;
newInstance._childContext = newChildContext;
if (typeof newInstance.componentWillMount === 'function') {
newInstance.componentWillMount();
}
Expand All @@ -271,15 +374,18 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
const newUpdateQueue = workInProgress.updateQueue;
if (newUpdateQueue) {
newInstance.state = mergeUpdateQueue(newUpdateQueue, newInstance, newState, newProps);
// newInstance.context = processContext(workInProgress);
newInstance._childContext = processChildContext(workInProgress, newInstance);
}
return true;
}

// Invokes the update life-cycles and returns false if it shouldn't rerender.
function updateClassInstance(current : Fiber, workInProgress : Fiber) : boolean {
const instance = workInProgress.stateNode;

const oldProps = workInProgress.memoizedProps || current.memoizedProps;
const oldContext = workInProgress.memoizedContext || current.memoizedContext;

let newProps = workInProgress.pendingProps;
if (!newProps) {
// If there aren't any new props, then we'll reuse the memoized props.
Expand All @@ -290,16 +396,6 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
}
}

// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.

if (oldProps !== newProps) {
if (typeof instance.componentWillReceiveProps === 'function') {
instance.componentWillReceiveProps(newProps);
}
}

// Compute the next state using the memoized state and the update queue.
const updateQueue = workInProgress.updateQueue;
const previousState = workInProgress.memoizedState;
Expand All @@ -315,8 +411,20 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
newState = previousState;
}

let newContext = processContext(workInProgress);
// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.

if (oldProps !== newProps || oldContext !== newContext) {
if (typeof instance.componentWillReceiveProps === 'function') {
instance.componentWillReceiveProps(newProps, newContext);
}
}

if (oldProps === newProps &&
previousState === newState &&
oldContext === newContext &&
updateQueue && !updateQueue.isForced) {
return false;
}
Expand All @@ -325,18 +433,21 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
workInProgress,
oldProps,
newProps,
newState
newState,
newContext
)) {
// TODO: Should this get the new props/state updated regardless?
return false;
}

if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState);
instance.componentWillUpdate(newProps, newState, newContext);
}

instance.props = newProps;
instance.state = newState;
instance.context = newContext;
instance._childContext = processChildContext(workInProgress, instance);
return true;
}

Expand Down
7 changes: 6 additions & 1 deletion src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,18 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
// 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;
const { state, props, context, _childContext } = workInProgress.stateNode;
const updateQueue = workInProgress.updateQueue;
workInProgress.memoizedState = state;
workInProgress.memoizedProps = props;
workInProgress.memoizedContext = context;
workInProgress.memoizedChildContext = _childContext;

if (current) {
if (current.memoizedProps !== workInProgress.memoizedProps ||
current.memoizedState !== workInProgress.memoizedState ||
current.memoizedContext !== workInProgress.memoizedContext ||
current.memoizedChildContext !== workInProgress.memoizedChildContext ||
updateQueue && updateQueue.isForced) {
markUpdate(workInProgress);
}
Expand Down