Skip updating effect tag when skipping effect

For example, if you have `useEffect(..., [])`, there's no need to set .effectTag to `Update | Passive` on updates.
This commit is contained in:
Sophie Alpert
2018-10-29 11:26:54 -07:00
committed by Andrew Clark
parent 9f34eb79a3
commit 5fc84efacc
7 changed files with 64 additions and 39 deletions
@@ -24,7 +24,7 @@ let useContext;
let useCallback;
let useMemo;
let useRef;
let useAPI;
let useImperativeMethods;
let useMutationEffect;
let useLayoutEffect;
let forwardRef;
@@ -49,7 +49,7 @@ function initModules() {
useCallback = React.useCallback;
useMemo = React.useMemo;
useRef = React.useRef;
useAPI = React.useAPI;
useImperativeMethods = React.useImperativeMethods;
useMutationEffect = React.useMutationEffect;
useLayoutEffect = React.useLayoutEffect;
forwardRef = React.forwardRef;
@@ -518,10 +518,10 @@ describe('ReactDOMServerHooks', () => {
});
});
describe('useAPI', () => {
describe('useImperativeMethods', () => {
it('should not be invoked on the server', async () => {
function Counter(props, ref) {
useAPI(ref, () => {
useImperativeMethods(ref, () => {
throw new Error('should not be invoked');
});
return <Text text={props.label + ': ' + ref.current} />;
+2 -2
View File
@@ -365,8 +365,8 @@ export const Dispatcher = {
useState,
useMutationEffect,
useLayoutEffect,
// useAPI is not run in the server environment
useAPI: noop,
// useImperativeMethods is not run in the server environment
useImperativeMethods: noop,
// Callbacks are not run in the server environment.
useCallback: noop,
// Effects are not run in the server environment.
+2 -2
View File
@@ -9,10 +9,10 @@
import {readContext} from './ReactFiberNewContext';
import {
useAPI,
useCallback,
useContext,
useEffect,
useImperativeMethods,
useLayoutEffect,
useMemo,
useMutationEffect,
@@ -23,10 +23,10 @@ import {
export const Dispatcher = {
readContext,
useAPI,
useCallback,
useContext,
useEffect,
useImperativeMethods,
useLayoutEffect,
useMemo,
useMutationEffect,
+14 -15
View File
@@ -179,9 +179,7 @@ export function finishHooks(
renderedWork.memoizedState = firstWorkInProgressHook;
renderedWork.expirationTime = remainingExpirationTime;
if (componentUpdateQueue !== null) {
renderedWork.updateQueue = (componentUpdateQueue: any);
}
renderedWork.updateQueue = (componentUpdateQueue: any);
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
@@ -576,26 +574,27 @@ function useEffectImpl(fiberEffectTag, hookEffectTag, create, inputs): void {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
let nextEffect;
let nextInputs = inputs !== undefined && inputs !== null ? inputs : [create];
let destroy = null;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
const prevInputs = prevEffect.inputs;
nextEffect = pushEffect(
inputsAreEqual(nextInputs, prevInputs) ? NoHookEffect : hookEffectTag,
create,
prevEffect.destroy,
nextInputs,
);
} else {
nextEffect = pushEffect(hookEffectTag, create, null, nextInputs);
destroy = prevEffect.destroy;
if (inputsAreEqual(nextInputs, prevEffect.inputs)) {
pushEffect(NoHookEffect, create, destroy, nextInputs);
return;
}
}
workInProgressHook.memoizedState = nextEffect;
currentlyRenderingFiber.effectTag |= fiberEffectTag;
workInProgressHook.memoizedState = pushEffect(
hookEffectTag,
create,
destroy,
nextInputs,
);
}
export function useAPI<T>(
export function useImperativeMethods<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
inputs: Array<mixed> | void | null,
@@ -24,7 +24,7 @@ let useLayoutEffect;
let useCallback;
let useMemo;
let useRef;
let useAPI;
let useImperativeMethods;
let forwardRef;
let flushPassiveEffects;
let memo;
@@ -70,7 +70,7 @@ describe('ReactHooks', () => {
useCallback = React.useCallback;
useMemo = React.useMemo;
useRef = React.useRef;
useAPI = React.useAPI;
useImperativeMethods = React.useImperativeMethods;
forwardRef = React.forwardRef;
memo = React.memo;
});
@@ -87,7 +87,7 @@ describe('ReactHooks', () => {
it('resumes after an interruption', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
useAPI(ref, () => ({updateCount}));
useImperativeMethods(ref, () => ({updateCount}));
return <Text text={props.label + ': ' + count} />;
}
Counter = forwardRef(Counter);
@@ -171,7 +171,7 @@ describe('ReactHooks', () => {
it('simple mount and update', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
useAPI(ref, () => ({updateCount}));
useImperativeMethods(ref, () => ({updateCount}));
return <Text text={'Count: ' + count} />;
}
Counter = forwardRef(Counter);
@@ -195,7 +195,7 @@ describe('ReactHooks', () => {
ReactNoop.yield('getInitialState');
return props.initialState;
});
useAPI(ref, () => ({updateCount}));
useImperativeMethods(ref, () => ({updateCount}));
return <Text text={'Count: ' + count} />;
}
Counter = forwardRef(Counter);
@@ -213,7 +213,7 @@ describe('ReactHooks', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
const [label, updateLabel] = useState('Count');
useAPI(ref, () => ({updateCount, updateLabel}));
useImperativeMethods(ref, () => ({updateCount, updateLabel}));
return <Text text={label + ': ' + count} />;
}
Counter = forwardRef(Counter);
@@ -232,7 +232,7 @@ describe('ReactHooks', () => {
it('callbacks', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
useAPI(ref, () => ({updateCount}));
useImperativeMethods(ref, () => ({updateCount}));
return <Text text={'Count: ' + count} />;
}
Counter = forwardRef(Counter);
@@ -264,7 +264,7 @@ describe('ReactHooks', () => {
it('does not fire callbacks more than once when rebasing', () => {
function Counter(props, ref) {
const [count, updateCount] = useState(0);
useAPI(ref, () => ({updateCount}));
useImperativeMethods(ref, () => ({updateCount}));
return <Text text={'Count: ' + count} />;
}
Counter = forwardRef(Counter);
@@ -507,7 +507,7 @@ describe('ReactHooks', () => {
function Counter({row: newRow}, ref) {
let [reducer, setReducer] = useState(() => reducerA);
let [count, dispatch] = useReducer(reducer, 0);
useAPI(ref, () => ({dispatch}));
useImperativeMethods(ref, () => ({dispatch}));
if (count < 20) {
dispatch('increment');
// Swap reducers each time we increment
@@ -568,7 +568,7 @@ describe('ReactHooks', () => {
function Counter(props, ref) {
const [count, dispatch] = useReducer(reducer, 0);
useAPI(ref, () => ({dispatch}));
useImperativeMethods(ref, () => ({dispatch}));
return <Text text={'Count: ' + count} />;
}
Counter = forwardRef(Counter);
@@ -609,7 +609,7 @@ describe('ReactHooks', () => {
function Counter(props, ref) {
const [count, dispatch] = useReducer(reducer, 0, initialAction);
useAPI(ref, () => ({dispatch}));
useImperativeMethods(ref, () => ({dispatch}));
return <Text text={'Count: ' + count} />;
}
Counter = forwardRef(Counter);
@@ -910,7 +910,6 @@ describe('ReactHooks', () => {
expect(ReactNoop.flush()).toEqual(['Will set count to 1', 'Count: 2']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]);
flushPassiveEffects();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).toHaveBeenCalledTimes(0);
});
@@ -1019,6 +1018,33 @@ describe('ReactHooks', () => {
expect(ReactNoop.getChildren()).toEqual([]);
});
it('unmounts on deletion after skipped effect', () => {
function Counter(props) {
useEffect(() => {
ReactNoop.yield(`Did create [${props.count}]`);
return () => {
ReactNoop.yield(`Did destroy [${props.count}]`);
};
}, []);
return <Text text={'Count: ' + props.count} />;
}
ReactNoop.render(<Counter count={0} />);
expect(ReactNoop.flush()).toEqual(['Count: 0']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
flushPassiveEffects();
expect(ReactNoop.clearYields()).toEqual(['Did create [0]']);
ReactNoop.render(<Counter count={1} />);
expect(ReactNoop.flush()).toEqual(['Count: 1']);
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
flushPassiveEffects();
expect(ReactNoop.clearYields()).toEqual(null);
ReactNoop.render(null);
expect(ReactNoop.flush()).toEqual(['Did destroy [0]']);
expect(ReactNoop.getChildren()).toEqual([]);
});
it('skips effect if constructor has not changed', () => {
function effect() {
ReactNoop.yield(`Did mount`);
+2 -2
View File
@@ -29,10 +29,10 @@ import {lazy} from './ReactLazy';
import forwardRef from './forwardRef';
import memo from './memo';
import {
useAPI,
useCallback,
useContext,
useEffect,
useImperativeMethods,
useLayoutEffect,
useMemo,
useMutationEffect,
@@ -89,10 +89,10 @@ if (enableStableConcurrentModeAPIs) {
}
if (enableHooks) {
React.useAPI = useAPI;
React.useCallback = useCallback;
React.useContext = useContext;
React.useEffect = useEffect;
React.useImperativeMethods = useImperativeMethods;
React.useLayoutEffect = useLayoutEffect;
React.useMemo = useMemo;
React.useMutationEffect = useMutationEffect;
+2 -2
View File
@@ -110,11 +110,11 @@ export function useMemo(
return dispatcher.useMemo(create, inputs);
}
export function useAPI<T>(
export function useImperativeMethods<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
inputs: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useAPI(ref, create, inputs);
return dispatcher.useImperativeMethods(ref, create, inputs);
}