1
+ import {
2
+ isValidElement ,
3
+ cloneElement ,
4
+ useEffect ,
5
+ useState ,
6
+ Children ,
7
+ } from 'react' ;
1
8
import { type TransitionProps , transitionCBAdapter } from '@pkg/components' ;
2
- import type { ChildMap } from '../transition-group.types' ;
3
- import React , { useEffect , useState } from 'react' ;
4
- import { useIsInitDep } from '@pkg/shared' ;
5
- export function useChildMap ( children : React . ReactNode , name : string ) {
9
+ import type { RefAttributes , ReactElement , ReactNode , Key } from 'react' ;
10
+ import type { ChildMapValue , ChildMap } from '../transition-group.types' ;
11
+ import { useIsInitDep , forwardRefs } from '@pkg/shared' ;
12
+
13
+ export function useChildMap ( children : ReactNode , name : string ) : ChildMap {
6
14
const isInit = useIsInitDep ( children ) ;
7
15
8
16
const [ childMap , setChildMap ] = useState ( ( ) : ChildMap => {
@@ -16,7 +24,7 @@ export function useChildMap(children: React.ReactNode, name: string) {
16
24
} ) ;
17
25
} ) ;
18
26
19
- const onLeaved = ( key : React . Key ) => {
27
+ const onLeaved = ( key : Key ) => {
20
28
// leave 可能会丢失
21
29
setChildMap ( ( prevChildren ) => {
22
30
const map = new Map ( prevChildren ) ;
@@ -34,66 +42,28 @@ export function useChildMap(children: React.ReactNode, name: string) {
34
42
}
35
43
36
44
const nextChildMap = (
37
- children : React . ReactNode ,
45
+ children : ReactNode ,
38
46
prevChildMap : ChildMap ,
39
47
name : string ,
40
- onLeaved : ( key : React . Key ) => void ,
48
+ onLeaved : ( key : Key ) => void ,
41
49
) : ChildMap => {
42
50
const childMap = createMap ( children ) ;
43
- return mergeMaps ( prevChildMap , childMap , ( child , key ) : React . ReactNode => {
44
- if ( ! React . isValidElement ( child ) ) return child ;
45
-
46
- const inNext = childMap . get ( key ) !== undefined ;
47
- const inPrev = prevChildMap . get ( key ) !== undefined ;
48
- const inBoth = inNext && inPrev ;
49
-
50
- const isAdd = inNext && ! inPrev ;
51
- const isRemove = ! inNext && inPrev ;
52
-
53
- if ( isRemove ) {
54
- if ( child . props . show === false ) return child ;
55
- // 因为 remove 了的 child 是不存在于 next 的,所以这个 child 是旧的,是 clone 过的
56
- // tips: 加了 on 就不会等待多个 remove 完才 move,而是 remove 一个 move 一个
57
- return cloneTransition ( child , { appear : false , show : false } ) ;
58
- }
59
-
60
- const on = transitionCBAdapter ( {
61
- onAfterLeave : ( ) => onLeaved ( child . key || '' ) ,
62
- } ) ;
63
-
64
- if ( isAdd ) {
65
- // 旧的不存在,所以 child 是新创建的,是未 clone 过的
66
- return cloneTransition ( child , {
67
- appear : true ,
68
- name : name ,
69
- show : true ,
70
- on,
71
- } ) ;
72
- }
73
-
74
- if ( inBoth ) {
75
- // 两者皆有取最新,所以 child 是新创建的,是未 clone 过的
76
- return cloneTransition ( child , {
77
- appear : false ,
78
- show : true ,
79
- name : name ,
80
- on,
81
- } ) ;
82
- }
83
- return child ;
84
- } ) ;
51
+ return mergeMaps ( prevChildMap , childMap , name , onLeaved ) ;
85
52
} ;
86
53
87
54
function createMap (
88
- children : React . ReactNode ,
89
- callback : ( child : React . ReactElement ) => React . ReactNode = ( v ) => v ,
55
+ children : ReactNode ,
56
+ callback : ( child : ReactElement ) => ChildMapValue = ( v ) => ( {
57
+ reactEl : v ,
58
+ ref : null ,
59
+ } ) ,
90
60
) : ChildMap {
91
61
const map : ChildMap = new Map ( ) ;
92
62
if ( ! children ) return map ;
93
63
94
- // 如果没有手动添加key, React. Children.map会自动添加key
95
- React . Children . map ( children , ( c ) => c ) ?. forEach ( ( child ) => {
96
- if ( ! React . isValidElement ( child ) ) return ;
64
+ // 如果没有手动添加key, Children.map会自动添加key
65
+ Children . map ( children , ( c ) => c ) ?. forEach ( ( child ) => {
66
+ if ( ! isValidElement ( child ) ) return ;
97
67
const key = child . key || '' ;
98
68
if ( ! key ) return ;
99
69
map . set ( key , callback ( child ) ) ;
@@ -105,14 +75,14 @@ function createMap(
105
75
function mergeMaps (
106
76
prevMap : ChildMap ,
107
77
nextMap : ChildMap ,
108
- callback : ( child : React . ReactNode , key : React . Key ) => React . ReactNode ,
78
+ name : string ,
79
+ onLeaved : ( key : Key ) => void ,
109
80
) : ChildMap {
110
- const getValue = ( key : React . Key ) => nextMap . get ( key ) ?? prevMap . get ( key ) ;
111
-
112
- let insertKeys : React . Key [ ] = [ ] ;
113
- const insertKeysMap = new Map < React . Key , typeof insertKeys > ( ) ;
81
+ let insertKeys : Key [ ] = [ ] ;
82
+ const insertKeysMap = new Map < Key , typeof insertKeys > ( ) ;
83
+ const result : ChildMap = new Map ( ) ;
114
84
115
- prevMap . forEach ( ( _ , key ) => {
85
+ prevMap . forEach ( ( _ , key ) : void => {
116
86
if ( nextMap . has ( key ) ) {
117
87
if ( ! insertKeys . length ) return ;
118
88
insertKeysMap . set ( key , insertKeys ) ;
@@ -121,23 +91,95 @@ function mergeMaps(
121
91
}
122
92
insertKeys . push ( key ) ;
123
93
} ) ;
124
-
125
- const result : ChildMap = new Map ( ) ;
126
- const push = ( k : React . Key ) => result . set ( k , callback ( getValue ( k ) , k ) ) ;
127
- nextMap . forEach ( ( _ , key ) => {
94
+ nextMap . forEach ( ( _ , key ) : void => {
128
95
const keys = insertKeysMap . get ( key ) ;
129
96
if ( keys ) keys . forEach ( push ) ;
130
97
push ( key ) ;
131
98
} ) ;
132
99
insertKeys . forEach ( push ) ;
133
100
134
101
return result ;
102
+
103
+ function push ( k : Key ) : void {
104
+ const value = mergeMapValue (
105
+ prevMap . get ( k ) ,
106
+ nextMap . get ( k ) ,
107
+ k ,
108
+ name ,
109
+ onLeaved ,
110
+ ) ;
111
+ value && result . set ( k , value ) ;
112
+ }
113
+ }
114
+
115
+ function mergeMapValue (
116
+ prevValue : ChildMapValue | undefined ,
117
+ nextValue : ChildMapValue | undefined ,
118
+ key : Key ,
119
+ name : string ,
120
+ onLeaved : ( key : Key ) => void ,
121
+ ) : ChildMapValue | void {
122
+ const inNext = nextValue ?. reactEl !== undefined ;
123
+ const inPrev = prevValue ?. reactEl !== undefined ;
124
+ const inBoth = inNext && inPrev ;
125
+
126
+ const isAdd = inNext && ! inPrev ;
127
+ const isRemove = ! inNext && inPrev ;
128
+
129
+ if ( isRemove ) {
130
+ // noinspection PointlessBooleanExpressionJS
131
+ if (
132
+ ( prevValue . reactEl as ReactElement < TransitionProps > ) . props . show === false
133
+ )
134
+ return prevValue ;
135
+ // 因为 remove 了的 child 是不存在于 next 的,所以这个 child 是旧的,是 clone 过的
136
+ // tips: 加了 on 就不会等待多个 remove 完才 move,而是 remove 一个 move 一个
137
+ return cloneTransition (
138
+ prevValue . reactEl ,
139
+ { appear : false , show : false } ,
140
+ prevValue . ref ,
141
+ ) ;
142
+ }
143
+
144
+ const on = transitionCBAdapter ( {
145
+ onAfterLeave : ( ) => onLeaved ( key || '' ) ,
146
+ } ) ;
147
+
148
+ if ( isAdd ) {
149
+ // 旧的不存在,所以 child 是新创建的,是未 clone 过的
150
+ return cloneTransition (
151
+ nextValue . reactEl ,
152
+ {
153
+ appear : true ,
154
+ name : name ,
155
+ show : true ,
156
+ on,
157
+ } ,
158
+ prevValue ?. ref ,
159
+ ) ;
160
+ }
161
+
162
+ if ( inBoth ) {
163
+ // 两者皆有取最新,所以 child 是新创建的,是未 clone 过的
164
+ return cloneTransition (
165
+ nextValue . reactEl ,
166
+ {
167
+ appear : false ,
168
+ show : true ,
169
+ name : name ,
170
+ on,
171
+ } ,
172
+ prevValue . ref ,
173
+ ) ;
174
+ }
135
175
}
136
176
137
177
function cloneTransition (
138
- transition : React . ReactElement ,
178
+ transition : ReactElement ,
139
179
props : Partial < TransitionProps > ,
140
- ) {
180
+ ref : HTMLElement | null = null ,
181
+ ) : ChildMapValue {
182
+ const result : ChildMapValue = { reactEl : transition , ref } ;
141
183
const _props = { ...props } as TransitionProps ;
142
184
const nextOn = props . on ;
143
185
if ( nextOn ) {
@@ -147,5 +189,23 @@ function cloneTransition(
147
189
nextOn ( el , status , lifeCircle ) ;
148
190
} ;
149
191
}
150
- return React . cloneElement < TransitionProps > ( transition , _props ) ;
192
+
193
+ result . reactEl = cloneElement < TransitionProps > (
194
+ transition ,
195
+ _props ,
196
+ cloneChildren ( ) ,
197
+ ) ;
198
+ return result ;
199
+
200
+ function cloneChildren ( ) {
201
+ const children = transition . props . children ;
202
+ return isValidElement ( children )
203
+ ? cloneElement ( children as ReactElement , {
204
+ ref : ( el : HTMLElement ) => {
205
+ forwardRefs ( el , ( children as RefAttributes < any > ) . ref ) ;
206
+ el && ( result . ref = el ) ;
207
+ } ,
208
+ } )
209
+ : undefined ;
210
+ }
151
211
}
0 commit comments