From c47231909fd89578678b739bbe3c4c3961f401a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A1=E9=A2=82?= <313439271@qq.com> Date: Thu, 7 Jan 2021 10:49:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20#11=20useEffect=20destroy=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=89=A7=E8=A1=8C=E6=97=B6=E6=9C=BA=E4=B8=8D=E5=AF=B9?= =?UTF-8?q?=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/__tests__/hook-test.js | 261 +++++++++++++++++++++++++++ packages/fiber/commitWork.js | 42 +++-- packages/fiber/dispatcher.js | 15 +- 3 files changed, 291 insertions(+), 27 deletions(-) create mode 100644 packages/core/__tests__/hook-test.js diff --git a/packages/core/__tests__/hook-test.js b/packages/core/__tests__/hook-test.js new file mode 100644 index 000000000..efee17e26 --- /dev/null +++ b/packages/core/__tests__/hook-test.js @@ -0,0 +1,261 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactDOM; +let ReactTestUtils; + +describe('ReactElement', () => { + let ComponentClass; + let originalSymbol; + + beforeEach(() => { + jest.resetModules(); + + // Delete the native Symbol if we have one to ensure we test the + // unpolyfilled environment. + originalSymbol = global.Symbol; + global.Symbol = undefined; + + React = require('react'); + ReactDOM = require('react-dom'); + ReactTestUtils = require('test-utils'); + // NOTE: We're explicitly not using JSX here. This is intended to test + // classic JS without JSX. + ComponentClass = class extends React.Component { + render() { + return React.createElement('div'); + } + }; + }); + + afterEach(() => { + global.Symbol = originalSymbol; + }); + + + it('useEffect执行顺序', () => { + const container = document.createElement('div'); + const {useEffect, useState} = React; + + const testList = []; + + function App() { + const [num, updateNum] = useState(0); + const [num2, updateNum2] = useState(0); + const [showChild, updateChildShow] = useState(true); + + useEffect(() => { + testList.push(1); + + return () => testList.push(999); + }, []) + + // useLayoutEffect(() => { + // testList.push(100); + + // return () => testList.push(99900); + // }, []) + + + useEffect(() => { + testList.push(8); + }) + + // useLayoutEffect(() => { + // testList.push(80); + // }) + + useEffect(() => { + testList.push(13); + + return () => testList.push(14); + }) + + // useLayoutEffect(() => { + // testList.push(130); + + // return () => testList.push(140); + // }) + + useEffect(() => { + testList.push(9); + + return () => { + testList.push(10); + } + }, [num]) + + // useLayoutEffect(() => { + // testList.push(90); + + // return () => { + // testList.push(100); + // } + // }, [num]) + + useEffect(() => { + testList.push(11); + + return () => { + testList.push(12); + } + }, [num2]) + + // useLayoutEffect(() => { + // testList.push(110); + + // return () => { + // testList.push(120); + // } + // }, [num2]) + + + return ( +
+ + + + {showChild && } +
+ ); + } + + function Child({num, num2}) { + useEffect(() => { + testList.push(2); + + return () => { + testList.push(3); + } + }, [num]) + + // useLayoutEffect(() => { + // testList.push(20); + + // return () => { + // testList.push(30); + // } + // }, [num]) + + useEffect(() => { + testList.push(4); + return () => { + testList.push(5); + } + }, []) + + // useLayoutEffect(() => { + // testList.push(40); + // return () => { + // testList.push(50); + // } + // }, []) + + useEffect(() => { + testList.push(6); + return () => { + testList.push(6); + } + }, [num2]) + + // useLayoutEffect(() => { + // testList.push(60); + // return () => { + // testList.push(60); + // } + // }, [num2]) + + useEffect(() => { + testList.push(15); + return () => { + testList.push(16); + } + }) + + // useLayoutEffect(() => { + // testList.push(150); + // return () => { + // testList.push(160); + // } + // }) + + useEffect(() => { + testList.push(17); + return () => { + testList.push(18); + } + }, [true]) + + // useLayoutEffect(() => { + // testList.push(170); + // return () => { + // testList.push(180); + // } + // }, [true]) + + return

num:{num} num2:{num2}

; + } + + function GrandChild() { + useEffect(() => { + testList.push(21); + + return () => { + testList.push(22); + } + }) + + // useLayoutEffect(() => { + // testList.push(210); + + // return () => { + // testList.push(220); + // } + // }) + + useEffect(() => { + testList.push(23); + + return () => { + testList.push(24); + } + }, []) + + // useLayoutEffect(() => { + // testList.push(230); + + // return () => { + // testList.push(240); + // } + // }, []) + + return 'grand child'; + } + + const s = ReactDOM.render(, container); + + ReactTestUtils.Simulate.click(s.refs.a); + ReactTestUtils.Simulate.click(s.refs.a); + ReactTestUtils.Simulate.click(s.refs.b); + ReactTestUtils.Simulate.click(s.refs.a); + ReactTestUtils.Simulate.click(s.refs.b); + ReactTestUtils.Simulate.click(s.refs.b); + ReactTestUtils.Simulate.click(s.refs.c); + ReactTestUtils.Simulate.click(s.refs.a); + ReactTestUtils.Simulate.click(s.refs.b); + + const rightOrder = '21,23,2,4,6,15,17,1,8,13,9,11,22,21,3,16,2,15,14,10,8,13,9,22,21,3,16,2,15,14,10,8,13,9,22,21,6,16,6,15,14,12,8,13,11,22,21,3,16,2,15,14,10,8,13,9,22,21,6,16,6,15,14,12,8,13,11,22,21,6,16,6,15,14,12,8,13,11,3,5,6,16,18,22,24,14,8,13,14,10,8,13,9,14,12,8,13,11'; + + expect(testList.join()).toBe(rightOrder); + }); + +}); \ No newline at end of file diff --git a/packages/fiber/commitWork.js b/packages/fiber/commitWork.js index e4f5142c8..a298854c0 100755 --- a/packages/fiber/commitWork.js +++ b/packages/fiber/commitWork.js @@ -212,25 +212,27 @@ export function disposeFibers(fiber) { delete fiber.oldChildren; fiber.children = {}; } -function safeInvokeHooks(upateQueue, create, destory) { - var uneffects = upateQueue[destory], - effects = upateQueue[create], fn; - if (!uneffects){ - return; - } - while ((fn = uneffects.shift())) { - try { - fn(); - } catch (e) { /** */ } - } - while ((fn = effects.shift())) { - try { - var f = fn(); - if (typeof f === 'function') { - uneffects.push(f); +function safeInvokeHooks(upateQueue, create, destory, isUnmount) { + const prevDestroyList = upateQueue[destory]; + const curCreateList = upateQueue[create]; + + curCreateList && curCreateList.forEach((createFn, i) => { + const depsChange = typeof createFn === 'function'; + + if (depsChange || isUnmount) { + const prevDestroyFn = prevDestroyList[i]; + if (typeof prevDestroyFn === 'function') { + prevDestroyFn(); } - } catch (e) { /** */ } - } + } + }); + + !isUnmount && curCreateList && curCreateList.forEach((createFn, i) => { + if (typeof createFn === 'function') { + const destroyFn = createFn(); + prevDestroyList[i] = destroyFn; + } + }) } function disposeFiber(fiber, force) { let { stateNode, effectTag } = fiber; @@ -248,8 +250,8 @@ function disposeFiber(fiber, force) { Renderer.onDispose(fiber); if (fiber.hasMounted) { if (isStateless) { - safeInvokeHooks(fiber.updateQueue, 'layout', 'unlayout'); - safeInvokeHooks(fiber.updateQueue, 'passive', 'unpassive'); + safeInvokeHooks(fiber.updateQueue, 'layout', 'unlayout', true); + safeInvokeHooks(fiber.updateQueue, 'passive', 'unpassive', true); } stateNode.updater.enqueueSetState = returnFalse; guardCallback(stateNode, 'componentWillUnmount', []); diff --git a/packages/fiber/dispatcher.js b/packages/fiber/dispatcher.js index c2f0abd1a..2afcd076c 100755 --- a/packages/fiber/dispatcher.js +++ b/packages/fiber/dispatcher.js @@ -70,15 +70,16 @@ export function useCallbackImpl(create, deps, isMemo, isEffect) {//ok } export function useEffectImpl(create, deps, EffectTag, createList, destroyList) {//ok let fiber = getCurrentFiber(); + const hookIndex = hookCursor; let updateQueue = fiber.updateQueue; - if (useCallbackImpl(create, deps, false, true)) {//防止重复添加 - if (fiber.effectTag % EffectTag) { - fiber.effectTag *= EffectTag; - } - let list = updateQueue[createList] || (updateQueue[createList] = []); - updateQueue[destroyList] || (updateQueue[destroyList] = []); - list.push(create); + const depsChange = !!useCallbackImpl(create, deps, false, true); + + if (depsChange && fiber.effectTag % EffectTag) { + fiber.effectTag *= EffectTag; } + updateQueue[createList] || (updateQueue[createList] = []); + updateQueue[destroyList] || (updateQueue[destroyList] = []); + updateQueue[createList][hookIndex] = depsChange && create; } export function useRef(initValue) {//ok let fiber = getCurrentFiber();