UNPKG

react-native

Version:

A framework for building native apps using React

495 lines (446 loc) • 17.1 kB
/** * 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 ReactFiberClassComponent * @flow */ 'use strict'; import type { Fiber } from 'ReactFiber'; import type { PriorityLevel } from 'ReactPriorityLevel'; var { Update, } = require('ReactTypeOfSideEffect'); var { cacheContext, getMaskedContext, getUnmaskedContext, isContextConsumer, } = require('ReactFiberContext'); var { addUpdate, addReplaceUpdate, addForceUpdate, beginUpdateQueue, } = require('ReactFiberUpdateQueue'); var { hasContextChanged } = require('ReactFiberContext'); var { getComponentName, isMounted } = require('ReactFiberTreeReflection'); var ReactInstanceMap = require('ReactInstanceMap'); var emptyObject = require('fbjs/lib/emptyObject'); var shallowEqual = require('fbjs/lib/shallowEqual'); var invariant = require('fbjs/lib/invariant'); const isArray = Array.isArray; if (__DEV__) { var warning = require('fbjs/lib/warning'); var warnOnInvalidCallback = function(callback : mixed, callerName : string) { warning( callback === null || typeof callback === 'function', '%s(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callerName, String(callback) ); }; } module.exports = function( scheduleUpdate : (fiber : Fiber, priorityLevel : PriorityLevel) => void, getPriorityContext : () => PriorityLevel, memoizeProps: (workInProgress : Fiber, props : any) => void, memoizeState: (workInProgress : Fiber, state : any) => void, ) { // Class component state updater const updater = { isMounted, enqueueSetState(instance, partialState, callback) { const fiber = ReactInstanceMap.get(instance); const priorityLevel = getPriorityContext(); callback = callback === undefined ? null : callback; if (__DEV__) { warnOnInvalidCallback(callback, 'setState'); } addUpdate(fiber, partialState, callback, priorityLevel); scheduleUpdate(fiber, priorityLevel); }, enqueueReplaceState(instance, state, callback) { const fiber = ReactInstanceMap.get(instance); const priorityLevel = getPriorityContext(); callback = callback === undefined ? null : callback; if (__DEV__) { warnOnInvalidCallback(callback, 'replaceState'); } addReplaceUpdate(fiber, state, callback, priorityLevel); scheduleUpdate(fiber, priorityLevel); }, enqueueForceUpdate(instance, callback) { const fiber = ReactInstanceMap.get(instance); const priorityLevel = getPriorityContext(); callback = callback === undefined ? null : callback; if (__DEV__) { warnOnInvalidCallback(callback, 'forceUpdate'); } addForceUpdate(fiber, callback, priorityLevel); scheduleUpdate(fiber, priorityLevel); }, }; function checkShouldComponentUpdate(workInProgress, oldProps, newProps, oldState, newState, newContext) { if (oldProps === null || (workInProgress.updateQueue !== null && workInProgress.updateQueue.hasForceUpdate)) { // If the workInProgress already has an Update effect, return true return true; } const instance = workInProgress.stateNode; if (typeof instance.shouldComponentUpdate === 'function') { const shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext); if (__DEV__) { warning( shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', getComponentName(workInProgress) ); } return shouldUpdate; } const type = workInProgress.type; if (type.prototype && type.prototype.isPureReactComponent) { return ( !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState) ); } return true; } function checkClassInstance(workInProgress: Fiber) { const instance = workInProgress.stateNode; if (__DEV__) { const name = getComponentName(workInProgress); const renderPresent = instance.render; warning( renderPresent, '%s(...): No `render` method found on the returned component ' + 'instance: you may have forgotten to define `render`.', name ); const noGetInitialStateOnES6 = ( !instance.getInitialState || instance.getInitialState.isReactClassApproved || instance.state ); warning( noGetInitialStateOnES6, 'getInitialState was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Did you mean to define a state property instead?', name ); const noGetDefaultPropsOnES6 = ( !instance.getDefaultProps || instance.getDefaultProps.isReactClassApproved ); warning( noGetDefaultPropsOnES6, 'getDefaultProps was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Use a static property to define defaultProps instead.', name ); const noInstancePropTypes = !instance.propTypes; warning( noInstancePropTypes, 'propTypes was defined as an instance property on %s. Use a static ' + 'property to define propTypes instead.', name, ); const noInstanceContextTypes = !instance.contextTypes; warning( noInstanceContextTypes, 'contextTypes was defined as an instance property on %s. Use a static ' + 'property to define contextTypes instead.', name, ); const noComponentShouldUpdate = typeof instance.componentShouldUpdate !== 'function'; warning( noComponentShouldUpdate, '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', name ); const noComponentDidUnmount = typeof instance.componentDidUnmount !== 'function'; warning( noComponentDidUnmount, '%s has a method called ' + 'componentDidUnmount(). But there is no such lifecycle method. ' + 'Did you mean componentWillUnmount()?', name ); const noComponentWillRecieveProps = typeof instance.componentWillRecieveProps !== 'function'; warning( noComponentWillRecieveProps, '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', name ); const hasMutatedProps = instance.props !== workInProgress.pendingProps; warning( instance.props === undefined || !hasMutatedProps, '%s(...): When calling super() in `%s`, make sure to pass ' + 'up the same props that your component\'s constructor was passed.', name, name ); } const state = instance.state; if (state && (typeof state !== 'object' || isArray(state))) { invariant( false, '%s.state: must be set to an object or null', getComponentName(workInProgress) ); } if (typeof instance.getChildContext === 'function') { invariant( typeof workInProgress.type.childContextTypes === 'object', '%s.getChildContext(): childContextTypes must be defined in order to ' + 'use getChildContext().', getComponentName(workInProgress) ); } } function markUpdate(workInProgress) { workInProgress.effectTag |= Update; } function markUpdateIfAlreadyInProgress(current: Fiber | null, workInProgress : Fiber) { // If an update was already in progress, we should schedule an Update // effect even though we're bailing out, so that cWU/cDU are called. if (current !== null) { if (workInProgress.memoizedProps !== current.memoizedProps || workInProgress.memoizedState !== current.memoizedState) { markUpdate(workInProgress); } } } function resetInputPointers(workInProgress : Fiber, instance : any) { instance.props = workInProgress.memoizedProps; instance.state = workInProgress.memoizedState; } function adoptClassInstance(workInProgress : Fiber, instance : any) : void { instance.updater = updater; workInProgress.stateNode = instance; // The instance needs access to the fiber so that it can schedule updates ReactInstanceMap.set(instance, workInProgress); } function constructClassInstance(workInProgress : Fiber) : any { const ctor = workInProgress.type; const props = workInProgress.pendingProps; const unmaskedContext = getUnmaskedContext(workInProgress); const needsContext = isContextConsumer(workInProgress); const context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyObject; const instance = new ctor(props, context); adoptClassInstance(workInProgress, instance); checkClassInstance(workInProgress); // Cache unmasked context so we can avoid recreating masked context unless necessary. // ReactFiberContext usually updates this cache but can't for newly-created instances. if (needsContext) { cacheContext(workInProgress, unmaskedContext, context); } return instance; } // Invokes the mount life-cycles on a previously never rendered instance. function mountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : void { markUpdate(workInProgress); const instance = workInProgress.stateNode; const state = instance.state || null; let props = workInProgress.pendingProps; invariant( props, 'There must be pending props for an initial mount. This error is ' + 'likely caused by a bug in React. Please file an issue.' ); const unmaskedContext = getUnmaskedContext(workInProgress); instance.props = props; instance.state = state; instance.refs = emptyObject; instance.context = getMaskedContext(workInProgress, unmaskedContext); if (typeof instance.componentWillMount === 'function') { instance.componentWillMount(); // If we had additional state updates during this life-cycle, let's // process them now. const updateQueue = workInProgress.updateQueue; if (updateQueue !== null) { instance.state = beginUpdateQueue( workInProgress, updateQueue, instance, state, props, priorityLevel ); } } } // Called on a preexisting class instance. Returns false if a resumed render // could be reused. function resumeMountClassInstance(workInProgress : Fiber, priorityLevel : PriorityLevel) : boolean { markUpdate(workInProgress); const instance = workInProgress.stateNode; resetInputPointers(workInProgress, instance); let newState = workInProgress.memoizedState; let newProps = workInProgress.pendingProps; if (!newProps) { // If there isn't any new props, then we'll reuse the memoized props. // This could be from already completed work. newProps = workInProgress.memoizedProps; invariant( newProps != null, 'There should always be pending or memoized props. This error is ' + 'likely caused by a bug in React. Please file an issue.' ); } const newUnmaskedContext = getUnmaskedContext(workInProgress); const newContext = getMaskedContext(workInProgress, newUnmaskedContext); // TODO: Should we deal with a setState that happened after the last // componentWillMount and before this componentWillMount? Probably // unsupported anyway. if (!checkShouldComponentUpdate( workInProgress, workInProgress.memoizedProps, newProps, workInProgress.memoizedState, newState, newContext )) { // Update the existing instance's state, props, and context pointers even // though we're bailing out. instance.props = newProps; instance.state = newState; instance.context = newContext; return false; } // If we didn't bail out we need to construct a new instance. We don't // want to reuse one that failed to fully mount. const newInstance = constructClassInstance(workInProgress); newInstance.props = newProps; newInstance.state = newState = newInstance.state || null; newInstance.context = newContext; if (typeof newInstance.componentWillMount === 'function') { newInstance.componentWillMount(); } // If we had additional state updates, process them now. // They may be from componentWillMount() or from error boundary's setState() // during initial mounting. const newUpdateQueue = workInProgress.updateQueue; if (newUpdateQueue !== null) { newInstance.state = beginUpdateQueue( workInProgress, newUpdateQueue, newInstance, newState, newProps, priorityLevel ); } return true; } // Invokes the update life-cycles and returns false if it shouldn't rerender. function updateClassInstance(current : Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : boolean { const instance = workInProgress.stateNode; resetInputPointers(workInProgress, instance); const oldProps = workInProgress.memoizedProps; let newProps = workInProgress.pendingProps; if (!newProps) { // If there aren't any new props, then we'll reuse the memoized props. // This could be from already completed work. newProps = oldProps; invariant( newProps != null, 'There should always be pending or memoized props. This error is ' + 'likely caused by a bug in React. Please file an issue.' ); } const oldContext = instance.context; const newUnmaskedContext = getUnmaskedContext(workInProgress); const newContext = getMaskedContext(workInProgress, newUnmaskedContext); // 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 (instance.state !== workInProgress.memoizedState) { if (__DEV__) { warning( false, '%s.componentWillReceiveProps(): Assigning directly to ' + 'this.state is deprecated (except inside a component\'s ' + 'constructor). Use setState instead.', getComponentName(workInProgress) ); } updater.enqueueReplaceState(instance, instance.state, null); } } } // Compute the next state using the memoized state and the update queue. const updateQueue = workInProgress.updateQueue; const oldState = workInProgress.memoizedState; // TODO: Previous state can be null. let newState; if (updateQueue !== null) { newState = beginUpdateQueue( workInProgress, updateQueue, instance, oldState, newProps, priorityLevel ); } else { newState = oldState; } if (oldProps === newProps && oldState === newState && !hasContextChanged() && !(updateQueue !== null && updateQueue.hasForceUpdate)) { markUpdateIfAlreadyInProgress(current, workInProgress); return false; } const shouldUpdate = checkShouldComponentUpdate( workInProgress, oldProps, newProps, oldState, newState, newContext ); if (shouldUpdate) { markUpdate(workInProgress); if (typeof instance.componentWillUpdate === 'function') { instance.componentWillUpdate(newProps, newState, newContext); } } else { markUpdateIfAlreadyInProgress(current, workInProgress); // If shouldComponentUpdate returned false, we should still update the // memoized props/state to indicate that this work can be reused. memoizeProps(workInProgress, newProps); memoizeState(workInProgress, newState); } // Update the existing instance's state, props, and context pointers even // if shouldComponentUpdate returns false. instance.props = newProps; instance.state = newState; instance.context = newContext; return shouldUpdate; } return { adoptClassInstance, constructClassInstance, mountClassInstance, resumeMountClassInstance, updateClassInstance, }; };