写一个类似Vue的Transition的React组件

由于React的简陋 【什么都没有】的API的设计,在React组件使用过渡动画十分困难,当然可以用 react-transition-group ,不过不好用啊!

相比Vue官方的过渡组件transition 组件就十分强大,很贴心 ,确切的说真香!

鉴于React什么都没有,什么都需要你自己搞 的情况下,在这里就研究下React的过渡组件的实现…

my-React-Transition-Group

classnames 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
(function () {
var hasOwn = {}.hasOwnProperty;

function classNames () {
var classes = [];

for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;

var argType = typeof arg;

if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg)) {
classes.push(classNames.apply(null, arg));
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}

return classes.join(' ');
}

if (typeof module !== 'undefined' && module.exports) {
module.exports = classNames;
} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
// register as 'classnames', consistent with npm package name
define('classnames', [], function () {
return classNames;
});
} else {
window.classNames = classNames;
}
}());

React 顶层 API

React 相关方法(API)介绍-元素与组件操作

React 新特性讲解及实例Context,ContextType,lazy,Suspense,错误边界(Error boundaries),memo


react中实用的一些API

React.createClass:

创建一个ReactClass(组件类),参数是一个对象且必须带有 render 属性方法,该方法必须返回一个封闭的容器(容器内可以有其它不限结构的容器)或 null/false(表示啥都不渲染)

React.createElement

React.createElement:第一个参数是DOM,第二个是属性,第三个是值
创建一个指定类型的React元素,注意第三个参数children可以是任意个React元素:

1
2
3
4
5
6
React.createElement( 'p', null,
React.createElement('span', null, 'Hello,'),
React.createElement('span', null, 'world,'),
React.createElement( Component, {a : 1})
)
React.createElement(type, props, children)如:React.createElement('span', null, 'Hello,')

React.cloneElement:

克隆并返回一个新的 ReactElement (内部子元素也会跟着克隆),新返回的元素会保留有旧元素的 props、ref、key,也会集成新的 props(只要在第二个参数中有定义)
var newSpan = React.cloneElement(span, {b:’2’}
要注意的是,createElement 的第一个参数必须是字符串或 ReactClass,而在 cloneElement 里第一个参数应该是 ReactElement

React.createFactory:

返回一个某种类型的ReactElement工厂函数,可以利用返回的函数来创建一个ReactElement(配置 props 和 children)

1
2
3
4
var p = React.createFactory(Component),
ReactElementP = p({a:1}),
div = React.createFactory('div'),
ReactElementDiv = div(null, ReactElementP);

React.Children:

  1. React.Children.map(object children, function fn [, object context])
    遍历子元素,映射为一个新的子元素集合(跟 ES5 的 Array.map 差不多)

  2. React.Children.forEach(object children, function fn [, object context])
    遍历子元素,对每一个子元素执行回调,但不像上述的 map 那样最终返回一个新的集合(跟 ES5 的 Array.forEach 差不多)

  3. React.Children.count(object children)
    返回子元素的总数

  4. React.Children.only(object children)
    返回仅有的一个子元素,否则(没有子元素或超过一个子元素)报错且不渲染任何东西:

5.React.initializeTouchEvents:
开启或关闭 React 的触摸事件机制,传入参数 true 使 React 能处理移动设备的触摸( touch )事件

React.DOM.tag:

常规是用于在非 JSX 下来创建 ReactElement,tag 表示相应的DOM类型(比如“div”、“p”)。另外首个参数可以定制相关的 DOM 属性(比如“name”),第二个参数表示 DOM 内的内容

1
2
3
4
5
6
7
var div = React.DOM.div({name : 'div1'}, 
'HELLO ',
React.DOM.span(null, <em>WORLD</em>)
);
React.render(
div, document.body
)

生成结果:

1
2
3
4
5
6
<div name="div1" data-reactid=".0">
<span data-reactid=".0.0">HELLO</span>
<span data-reactid=".0.1">
<em data-reactid=".0.1.0">WORLD</em>
</span>
</div>

React.isValidElement:

判断参数是否一个合法的 ReactElement,并返回 Boolean 值

1
2
3
4
5
6
7
8
9
10
var Component = React.createClass({
render: function() {
return this.props.a==1 ? <p>123</p> : null
}
});
var com = <Component/>,
com2 = '<Component/>';

console.log(React.isValidElement(com)); //true
console.log(React.isValidElement(com2)); //false

React.renderToStaticMarkup:

类似 React.renderToString ,但只生成纯粹的HTML标记字符串,不会包含类似 data-reactid 之类的React属性,从而节省字节数

1
2
3
4
5
6
7
8
9
var Component = React.createClass({
render: function() {
return this.props.a==1 ? <p>123</p> : null
}
});

var com = <Component a="1" />,
comHTML = React.renderToStaticMarkup(com);
console.log(comHTML); //输出“<p>123</p>

React.renderToString:

React为服务端提供的一个方法,可以直接输出 ReactElement 为 HTML 字符串,将这些标记发送(比如 res.write(HTMLString))给客户端,可以获得更快的页面加载速度,并且有利于搜索引擎抓取页面,方便做 SEO(主要是百度不争气,谷歌早可以从内存中去抓最终生成的HTML内容了)

1
2
3
4
5
6
7
8
var Component = React.createClass({
render: function() {
return this.props.a==1 ? <p>123</p> : null
}
});
var com = <Component a="1" />,
comHTML = React.renderToString(com);
console.log(comHTML); // 输出“<p data-reactid=".0" data-react-checksum="-2122315716">123</p>


Transition.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import React from 'react'
import PropTypes from "prop-types"
import classNames from './classnames'

import './transition.css'

function noop() {}

export default class Transition extends React.Component {
static propTypes = {
className: PropTypes.string,
onEnter: PropTypes.func,
onLeave: PropTypes.func,
show: PropTypes.bool
}

static defaultProps = {
className: '',
onEnter: noop,
onLeave: noop,
show: false
}

constructor(props) {
super(props)
this.state = {
entering: props.show,
leaving: false,
visible: props.show
}
}

componentWillReceiveProps(nextProps) {
if(!this.props.show && nextProps.show) {
this.setState({
entering: true,
leaving: false,
visible: true
})
} else if(this.props.show && !nextProps.show) {
this.setState({
entering: false,
leaving: true,
visible: true
})
}
}

componentWillUnmount() {
console.log('>>>>> componentWillUnmount')
this.setState({
entering: false,
leaving: false,
visible: false
})
}

animationEnd = (e) => {
const el = e.target
const {
onEnter,
onLeave
} = this.props;

const {
entering,
leaving
} = this.state;

this.setState({
entering: false,
leaving: false,
visible: entering
})

if(entering) {
onEnter && onEnter(el)
} else if(leaving) {
onLeave && onLeave(el)
}
}

getClasses() {
const {
className
} = this.props;

const {
entering,
leaving
} = this.state;

return classNames({
[`rodal-${className}-enter`]: entering,
[`rodal-${className}-leave`]: leaving
})
}

render() {
const {
visible
} = this.state;

const {
children,
...inProps
} = this.props;

var childrenPropsList=children.props.className||'';

const style = {
display: visible ? '' : 'none'
}

const child = React.Children.only(children);



const childProps = { ...inProps,
className: childrenPropsList+' '+this.getClasses(),
style: style,
onAnimationEnd: this.animationEnd,
ref: (el) => {
this.refs = el
}
}

delete childProps.onEnter;
delete childProps.onLeave;
delete childProps.show;


console.log(React.cloneElement(child, childProps))

return React.cloneElement(child, childProps)
}
}

TransitionGroup.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import React, { Children, cloneElement, isValidElement } from "react"
import PropTypes from "prop-types"

export default class TransitionGroup extends React.Component {
static propTypes = {
component: PropTypes.any
}

static defaultProps = {
component: 'div',
}

constructor(props) {
super(props);

this.state = {
children: Children.map(props.children, (child) => {

console.log(child);


const { show, ...inProps } = child.props
return cloneElement(child, { show: true, ...inProps })
})
}
}

componentWillReceiveProps(nextProps) {
const props = this.props
const newChildren = nextProps.children
const oldChildren = this.state.children
const oldCount = Children.count(oldChildren)
const newCount = Children.count(newChildren)
console.log('oldCount:', oldCount)
console.log('newCount:', newCount)

if (newCount > oldCount) {
Children.forEach(newChildren, (child, key) => {
if (oldChildren[key]) {
newChildren[key] = oldChildren[key]
} else {
const { show, ...inProps } = child.props
newChildren[key] = cloneElement(child, {show: true, ...inProps})
}
})
const children = newChildren
this.setState({ children })
} else {
Children.forEach(oldChildren, (child, key) => {
if (!newChildren[key]) {
const { show, onLeave, ...inProps } = child.props
newChildren[key] = cloneElement(child, {
show: false,
onLeave: this.handleLeave.bind(this, child),
...inProps})
} else {
newChildren[key] = child
}
})
const children = newChildren
this.setState({ children })
}
}

handleLeave = (child) => {
console.log('handleLeave:key:', child.key)
const children = this.state.children
children.splice(child.key, 1)
this.setState({ children })
}

render() {
const { children } = this.state
const { component: Component ,...propsList} = this.props
return (
<Component {...propsList}>{children}</Component>
)
}
}

transition.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416


/* -- fade -- */
@-webkit-keyframes rodal-fade-enter {
from {
opacity: 0;
}
}

@keyframes rodal-fade-enter {
from {
opacity: 0;
}
}

.rodal-fade-enter {
-webkit-animation: rodal-fade-enter 0.4s both ease-in;
animation: rodal-fade-enter 0.4s both ease-in;
}

@-webkit-keyframes rodal-fade-leave {
to {
opacity: 0
}
}

@keyframes rodal-fade-leave {
to {
opacity: 0
}
}

.rodal-fade-leave {
-webkit-animation: rodal-fade-leave 0.4s both ease-out;
animation: rodal-fade-leave 0.4s both ease-out;
}

/* -- zoom -- */
@-webkit-keyframes rodal-zoom-enter {
from {
opacity: 0;
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
}

@keyframes rodal-zoom-enter {
from {
opacity: 0;
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
}

.rodal-zoom-enter {
-webkit-animation: rodal-zoom-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
animation: rodal-zoom-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
}

@-webkit-keyframes rodal-zoom-leave {
to {
opacity: 0;
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
}

@keyframes rodal-zoom-leave {
to {
opacity: 0;
-webkit-transform: scale3d(.3, .3, .3);
transform: scale3d(.3, .3, .3);
}
}

.rodal-zoom-leave {
-webkit-animation: rodal-zoom-leave 0.4s both;
animation: rodal-zoom-leave 0.4s both;
}

/* -- slideDown -- */
@-webkit-keyframes rodal-slideDown-enter {
from {
opacity: 0;
-webkit-transform: translate3d(0, -100px, 0);
transform: translate3d(0, -100px, 0);
}
}

@keyframes rodal-slideDown-enter {
from {
opacity: 0;
-webkit-transform: translate3d(0, -100px, 0);
transform: translate3d(0, -100px, 0);
}
}

.rodal-slideDown-enter {
-webkit-animation: rodal-slideDown-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
animation: rodal-slideDown-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
}

@-webkit-keyframes rodal-slideDown-leave {
to {
opacity: 0;
-webkit-transform: translate3d(0, -100px, 0);
transform: translate3d(0, -100px, 0);
}
}

@keyframes rodal-slideDown-leave {
to {
opacity: 0;
-webkit-transform: translate3d(0, -100px, 0);
transform: translate3d(0, -100px, 0);
}
}

.rodal-slideDown-leave {
-webkit-animation: rodal-slideDown-leave 0.4s both;
animation: rodal-slideDown-leave 0.4s both;
}

/* -- slideLeft -- */
@-webkit-keyframes rodal-slideLeft-enter {
from {
opacity: 0;
-webkit-transform: translate3d(-150px, 0, 0);
transform: translate3d(-150px, 0, 0);
}
}

@keyframes rodal-slideLeft-enter {
from {
opacity: 0;
-webkit-transform: translate3d(-150px, 0, 0);
transform: translate3d(-150px, 0, 0);
}
}

.rodal-slideLeft-enter {
-webkit-animation: rodal-slideLeft-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
animation: rodal-slideLeft-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
}

@-webkit-keyframes rodal-slideLeft-leave {
to {
opacity: 0;
-webkit-transform: translate3d(-150px, 0, 0);
transform: translate3d(-150px, 0, 0);
}
}

@keyframes rodal-slideLeft-leave {
to {
opacity: 0;
-webkit-transform: translate3d(-150px, 0, 0);
transform: translate3d(-150px, 0, 0);
}
}

.rodal-slideLeft-leave {
-webkit-animation: rodal-slideLeft-leave 0.4s both;
animation: rodal-slideLeft-leave 0.4s both;
}

/* -- slideRight -- */
@-webkit-keyframes rodal-slideRight-enter {
from {
opacity: 0;
-webkit-transform: translate3d(150px, 0, 0);
transform: translate3d(150px, 0, 0);
}
}

@keyframes rodal-slideRight-enter {
from {
opacity: 0;
-webkit-transform: translate3d(150px, 0, 0);
transform: translate3d(150px, 0, 0);
}
}

.rodal-slideRight-enter {
-webkit-animation: rodal-slideRight-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
animation: rodal-slideRight-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
}

@-webkit-keyframes rodal-slideRight-leave {
to {
opacity: 0;
-webkit-transform: translate3d(150px, 0, 0);
transform: translate3d(150px, 0, 0);
}
}

@keyframes rodal-slideRight-leave {
to {
opacity: 0;
-webkit-transform: translate3d(150px, 0, 0);
transform: translate3d(150px, 0, 0);
}
}

.rodal-slideRight-leave {
-webkit-animation: rodal-slideRight-leave 0.4s both;
animation: rodal-slideRight-leave 0.4s both;
}

/* -- slideUp -- */
@-webkit-keyframes rodal-slideUp-enter {
from {
opacity: 0;
-webkit-transform: translate3d(0, 100px, 0);
transform: translate3d(0, 100px, 0);
}
}

@keyframes rodal-slideUp-enter {
from {
opacity: 0;
-webkit-transform: translate3d(0, 100px, 0);
transform: translate3d(0, 100px, 0);
}
}

.rodal-slideUp-enter {
-webkit-animation: rodal-slideUp-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
animation: rodal-slideUp-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
}

@-webkit-keyframes rodal-slideUp-leave {
to {
opacity: 0;
-webkit-transform: translate3d(0, 100px, 0);
transform: translate3d(0, 100px, 0);
}
}

@keyframes rodal-slideUp-leave {
to {
opacity: 0;
-webkit-transform: translate3d(0, 100px, 0);
transform: translate3d(0, 100px, 0);
}
}

.rodal-slideUp-leave {
-webkit-animation: rodal-slideUp-leave 0.4s both;
animation: rodal-slideUp-leave 0.4s both;
}

/* -- flip -- */
@-webkit-keyframes rodal-flip-enter {
from {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 60deg);
transform: perspective(400px) rotate3d(1, 0, 0, 60deg);
}
70% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -15deg);
transform: perspective(400px) rotate3d(1, 0, 0, -15deg);
}
to {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}

@keyframes rodal-flip-enter {
from {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 60deg);
transform: perspective(400px) rotate3d(1, 0, 0, 60deg);
}
70% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -15deg);
transform: perspective(400px) rotate3d(1, 0, 0, -15deg);
}
to {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
}

.rodal-flip-enter {
-webkit-animation: rodal-flip-enter 0.4s both ease-in;
animation: rodal-flip-enter 0.4s both ease-in;
-webkit-backface-visibility: visible !important;
backface-visibility: visible !important;
}

@-webkit-keyframes rodal-flip-leave {
from {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -15deg);
transform: perspective(400px) rotate3d(1, 0, 0, -15deg);
}
to {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 45deg);
transform: perspective(400px) rotate3d(1, 0, 0, 45deg);
}
}

@keyframes rodal-flip-leave {
from {
-webkit-transform: perspective(400px);
transform: perspective(400px);
}
30% {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -15deg);
transform: perspective(400px) rotate3d(1, 0, 0, -15deg);
}
to {
-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 45deg);
transform: perspective(400px) rotate3d(1, 0, 0, 45deg);
}
}

.rodal-flip-leave {
-webkit-animation: rodal-flip-leave 0.4s both;
animation: rodal-flip-leave 0.4s both;
-webkit-backface-visibility: visible !important;
backface-visibility: visible !important;
}

/* -- rotate -- */
@-webkit-keyframes rodal-rotate-enter {
from {
-webkit-transform: rotate3d(0, 0, 1, -180deg) scale3d(.3, .3, .3);
transform: rotate3d(0, 0, 1, -180deg) scale3d(.3, .3, .3);
}
}

@keyframes rodal-rotate-enter {
from {
-webkit-transform: rotate3d(0, 0, 1, -180deg) scale3d(.3, .3, .3);
transform: rotate3d(0, 0, 1, -180deg) scale3d(.3, .3, .3);
}
}

.rodal-rotate-enter {
-webkit-animation: rodal-rotate-enter 0.4s both;
animation: rodal-rotate-enter 0.4s both;
-webkit-transform-origin: center;
transform-origin: center;
}

@-webkit-keyframes rodal-rotate-leave {
to {
-webkit-transform: rotate3d(0, 0, 1, 180deg) scale3d(.3, .3, .3);
transform: rotate3d(0, 0, 1, 180deg) scale3d(.3, .3, .3);
}
}

@keyframes rodal-rotate-leave {
to {
-webkit-transform: rotate3d(0, 0, 1, 180deg) scale3d(.3, .3, .3);
transform: rotate3d(0, 0, 1, 180deg) scale3d(.3, .3, .3);
}
}

.rodal-rotate-leave {
-webkit-animation: rodal-rotate-leave 0.4s both;
animation: rodal-rotate-leave 0.4s both;
-webkit-transform-origin: center;
transform-origin: center;
}

/* -- door -- */
@-webkit-keyframes rodal-door-enter {
from {
-webkit-transform: scale3d(0, 1, 1);
transform: scale3d(0, 1, 1);
}
}

@keyframes rodal-door-enter {
from {
-webkit-transform: scale3d(0, 1, 1);
transform: scale3d(0, 1, 1);
}
}

.rodal-door-enter {
-webkit-animation: rodal-door-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
animation: rodal-door-enter 0.4s both cubic-bezier(0.4, 0, 0, 1.5);
}

@-webkit-keyframes rodal-door-leave {
60% {
-webkit-transform: scale3d(.01, 1, 1);
transform: scale3d(.01, 1, 1);
}
to {
-webkit-transform: scale3d(0, 1, .1);
transform: scale3d(0, 1, .1);
}
}

@keyframes rodal-door-leave {
60% {
-webkit-transform: scale3d(.01, 1, 1);
transform: scale3d(.01, 1, 1);
}
to {
-webkit-transform: scale3d(0, 1, .1);
transform: scale3d(0, 1, .1);
}
}

.rodal-door-leave {
-webkit-animation: rodal-door-leave 0.4s both;
animation: rodal-door-leave 0.4s both;
}

组件中的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import React, { Component } from 'react';

import Loading from './Loading/index'
import Transition from './transition/Transition'
import TransitionGroup from './transition/TransitionGroup'
import { CSSTransition } from 'react-transition-group'
import './App.css'
import logo from './logo.svg';


let idx = 1
class App extends Component {
state = {
list: ['Transition-Group-Demo-0'],
show: true
}

addClick = () => {
let list = this.state.list.slice()
this.setState({
show: true,
list: list.concat(['Transition-Group-Demo-' + idx++])
})
}

deleteClick = () => {
let list = this.state.list.slice()
if (list.length === 0) return
list.splice(0, 1)
console.log('list=>', list);

this.setState({
show: false,
list: list
})
}

loading=()=>{
Loading.loading("哈哈")
}

handleEnter = (el) => {
//console.log('111', el.scrollHeight)
}

handleLeave = (el) => {
//console.log('222', el.scrollHeight)
}

render() {


return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
<div className="btn-con">
<button onClick={this.addClick}>增加一条数据</button>
<button onClick={this.deleteClick}>删除一条数据</button>
<button onClick={this.loading}>loading</button>
</div>
</header>
{/*
<Transition show={this.state.show} className="zoom" onEnter={this.handleEnter} onLeave={this.handleLeave}>
<div><h1>see you next time 1</h1></div>
</Transition>

<CSSTransition in={this.state.show} timeout={400} classNames="fade">
<div><h1>see you next time 2</h1></div>
</CSSTransition>
*/}

<TransitionGroup component="ul" className="grop">
{this.state.list.map((item, key) => (
<Transition
key={key}
className="rotate"
onEnter={this.handleEnter}
onLeave={this.handleLeave}>
<li>
<img src={logo} className="App-logo" alt="logo" />
<span>{item}</span>
</li>
</Transition>
))}
</TransitionGroup>

</div>
);
}
}

export default App;

插件中实用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import React from "react";
import './loading.css'

import Transition from '../transition/Transition'
import TransitionGroup from '../transition/TransitionGroup'

export default class Toast extends React.Component {
checkToast(n) {
switch(n) {
case 0:
return(

<div className="loading-icon">
<div className="wrapper">
<div className="spinner"></div>
</div>
</div>

)

case 1:
return(
<div className="loading-icon">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
)
default:
return null
}
}

render() {

let {
type,
content,
opacity = 0
} = this.props.setting;

let style = {
"background": `rgba(0,0,0,${opacity})`
}

return(
<div className="mask" style={style}>
<TransitionGroup component="div">
<Transition className="slideDown">
<div className="loading">
{this.checkToast(type)}
<div className="msg">{content}</div>
</div>
</Transition>
</TransitionGroup>
</div>
);
}
}

React中的Component 和 PureComponent

React.ComponentReact.PureComponent很相似,两则的区别在于,PureComponent类帮我们以浅比较的方式对比propsstate,实现了shouldComponentUpdate()函数,在某些情况下,使用PureComponent可以减少render函数的执行,提升性能。

PureComponent能够提升性能

当Index组件继承Component

  1. 初次渲染时控制台会依次打印”constructor“、”render“;

  2. 当第一次点击按钮更新state时,控制台会依次打印”render“、”componentDidUpdate“;

  3. 后续每次触发点击事件,尽管flag的值没有变化,控制台还是会依次打印”render“、”componentDidUpdate“,说明组件依然调用render()componentDidUpdate()函数,显然这是多余的,通常我们会手动重新实现shouldComponentUpdate(nextProps, nextState)函数判断stateprops的状态再来决定是否需要重新渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from 'react';

class Index extends React.PureComponent{
constructor(props) {
super(props);
this.state = {
flag:false
};
console.log('constructor');
}
changeState = () => {
this.setState({
flag: true
})
};
render() {
console.log('render');
return (
<div>
<button onClick={this.changeState}>Click me</button>
<div>
{this.state.flag.toString()}
</div>
</div>
);
}
componentDidUpdate() {
console.log("componentDidUpdate")
}
}

export default Index;

当Index组件继承PureComponent

  • 初次渲染和第一次点击按钮更新state时控制台输出同上面继承Component一样没有变化

  • 后续每次触发点击事件,控制台无输出, 省去执行render函数生成虚拟DOM,进行DIFF算法比较等后续操作

1
2
3
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
}

PureComponent默认实现的shouldComponentUpdate()方法使用的是浅比较: 即值的比较或引用的比较, 不会进行深层次的对比,所以当propsstate的值是引用类型时,即使对象的值改变了,但是对象的引用没变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React from 'react';

class IndexPage extends React.PureComponent{
constructor(props) {
super(props);
this.state = {
arr: [1,2,3,4,5]
};
}
changeArr = () => {
let {arr} = this.state
arr.pop()
this.setState({
arr
})
console.log("changeArr", arr)
};
render() {
const { arr } = this.state
console.log('render', arr);
return (
<div>
<button onClick={this.changeArr}>pop</button>
<ul>
{
arr.map(item => <li key={item}>{item}</li>)
}
</ul>
</div>
);
}
componentDidUpdate() {
console.log("componentDidUpdate")
}
}

export default IndexPage;
  1. 初次渲染时控制台会打印 render (5) [1, 2, 3, 4, 5]
  2. 当点击pop按钮时控制台会依次打印 changeArr (4) [1, 2, 3, 4]changeArr (4) [1, 2, 3]`……但是render函数不执行, 因为PureComponent实现的shouldComponentUpdate()认为值的引用没有变,故不执行后续的操作,只有在引用改变的情况下函数才会返回true`

PureComponent也会影响子组件

下面例子中的render函数只会在刚创建的时候执行一次, 后续的点击按钮操作,由于PureComponent中的ShouldComponentUpdate()执行浅比较(对象值的引用没变),不会触发render函数的执行,其子组件也不会更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React from 'react';
import Item from './Item'

class IndexPage extends React.PureComponent{
constructor(props) {
super(props);
this.state = {
arr: [1,2,3,4,5]
};
}
changeArr = () => {
let {arr} = this.state
arr.pop()
this.setState({
arr
})
console.log("changeArr", arr)
};
render() {
const { arr } = this.state
console.log('render', arr);
return (
<div>
<button onClick={this.changeArr}>pop</button>
<ul>
<Item arr={arr} />
</ul>
</div>
);
}
componentDidUpdate() {
console.log("componentDidUpdate")
}
}

export default IndexPage;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// Item.js
import React, { Fragment, Component } from 'react'

class Index extends Component {
render() {
const { arr } = this.props;
console.log("children", arr)
return (
<Fragment>
{
arr.map(item => <li key={item}>{item}</li>)
}
</Fragment>
)
}
}

export default Index;

总结

  1. PureComponent已经用浅层对比propsstate的方式替我们实现了shouldComponentUpdate(), 不仅能影响自身,还会影响其子组件;
  2. PureComponent 某些情况下(propsstate的值不经常变动, 因为浅比较也会耗时)可以提升性能;
  3. 继承自Component中的组件shouldComponentUpdate()默认情况下总是返回true;