forked from joname1/joname1.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
react-component-communicate.html
498 lines (434 loc) · 19.7 KB
/
react-component-communicate.html
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
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Cache-Control" content="no-siteapp">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=1, minimum-scale=1, maximum-scale=1">
<meta name="renderer" content="webkit">
<meta name="google" value="notranslate">
<meta name="robots" content="index,follow">
<link rel="shortcut icon" href="/favicon.png?v=198964">
<link rel="apple-itouch-icon" href="/favicon.png?v=198964">
<link href="https://joe-10005639.cossh.myqcloud.com/index.min.css" rel="stylesheet">
<link href="https://fonts.loli.net/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400" rel="stylesheet">
<link href="https://cdn.bootcss.com/prism/1.13.0/themes/prism.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
var timeSinceLang = {year: ' years ago',month: ' months ago',day: ' days ago',hour: ' hours ago',minute: ' minutes ago',second: ' seconds ago'};var root = '';
</script>
<meta name="keywords" content="React,">
<meta name="description" content="React 组件之间交流">
<meta name="author" content="江矿叔叔.">
<title>React 组件之间交流</title>
<link href="/bundle/iconfont.css" rel="stylesheet">
<script src="https://joe-10005639.cossh.myqcloud.com/av.min.js"></script>
<script src='https://joe-10005639.cossh.myqcloud.com/valine.min.js'></script>
</head>
<body>
<article class="container">
<header class="header-wrap asset" style="background-image: url(https://joe-10005639.cossh.myqcloud.com/bg.jpg);">
<nav class="main-nav">
<ul class="menu vertical">
<li class="menu-item"><a href="/">Home</a></li>
<li class="menu-item"><a href="/archive.html">Archive</a></li>
<li class="menu-item"><a href="/tag.html">Tag</a></li>
<li class="menu-item"><a href="/atom.xml">RSS</a></li>
</ul>
</nav>
<div class="vertical">
<div class="header-wrap-content inner">
<h3 class="title">Stay before every beautiful thoughts.</h3>
<h3 class="subtitle">Just be nice, always think twice!</h3>
</div>
</div>
<a class="scroll-down icon-arrow-left" href="#content" data-offset="-45"><span class="hidden">Scroll Down</span></a>
</header>
<article class="main article">
<h1 class="title">React 组件之间交流</h1>
<section class="info">
<span class="avatar" style="background-image: url(https://joe-10005639.cossh.myqcloud.com/jkbb.jpg);"></span> <a class="name" href="javascript:;">江矿叔叔.</a>
<span class="date" data-time="1516453200"><span class="from"></span></span>
<span class="tags"><a class="tages" href="/tag/React/index.html">React</a></span>
</section>
<article class="content"><p>处理 React 组件之间的交流方式,主要取决于组件之间的关系,然而这些关系的约定人就是你。
我不会讲太多关于 data-stores、data-adapters 或者 data-helpers 之类的话题。</p>
<p>React 组件之间交流的方式,可以分为以下 3 种:</p>
<ul>
<li>【父组件】向【子组件】传值;</li>
<li>【子组件】向【父组件】传值;</li>
<li>没有任何嵌套关系的组件之间传值(如:兄弟组件之间传值)</li>
</ul>
<h2>一、【父组件】向【子组件】传值</h2>
<p>这个是相当容易的,在使用 React 开发的过程中经常会使用到,主要是利用 props 来进行交流。例子如下:</p>
<pre><code class="language-js">// 父组件
var MyContainer = React.createClass({
getInitialState: function () {
return {
checked: true
};
},
render: function() {
return (
<ToggleButton text="Toggle me" checked={this.state.checked} />
);
}
});
// 子组件
var ToggleButton = React.createClass({
render: function () {
// 从【父组件】获取的值
var checked = this.props.checked,
text = this.props.text;
return (
<label>{text}: <input type="checkbox" checked={checked} /></label>
);
}
});
</code></pre>
<p>如果组件嵌套层次太深,那么从外到内组件的交流成本就变得很高,通过 props 传递值的优势就不那么明显了。(PS:所以我建议尽可能的减少组件的层次,就像写 HTML 一样,简单清晰的结构更惹人爱)</p>
<pre><code class="language-js">// 父组件
var MyContainer = React.createClass({
render: function() {
return (
<Intermediate text="where is my son?" />
);
}
});
// 子组件1:中间嵌套的组件
var Intermediate = React.createClass({
render: function () {
return (
<Child text={this.props.text} />
);
}
});
// 子组件2:子组件1的子组件
var Child = React.createClass({
render: function () {
return (
<span>{this.props.text}</span>
);
}
});
</code></pre>
<h3>二、【子组件】向【父组件】传值</h3>
<p>接下来,【子组件】控制自己的 state 然后告诉【父组件】的点击状态,然后在【父组件】中展示出来。因此,我们添加一个 change 事件来做交互。</p>
<pre><code class="language-js">// 父组件
var MyContainer = React.createClass({
getInitialState: function () {
return {
checked: false
};
},
onChildChanged: function (newState) {
this.setState({
checked: newState
});
},
render: function() {
var isChecked = this.state.checked ? "yes" : "no";
return (
<div>
<div>Are you checked: {isChecked}</div>
<ToggleButton text="Toggle me"
initialChecked={this.state.checked}
callbackParent={this.onChildChanged}
/>
</div>
);
}
});
// 子组件
var ToggleButton = React.createClass({
getInitialState: function () {
return {
checked: this.props.initialChecked
};
},
onTextChange: function () {
var newState = !this.state.checked;
this.setState({
checked: newState
});
// 这里要注意:setState 是一个异步方法,所以需要操作缓存的当前值
this.props.callbackParent(newState);
},
render: function () {
// 从【父组件】获取的值
var text = this.props.text;
// 组件自身的状态数据
var checked = this.state.checked;
return (
<label>{text}: <input type="checkbox" checked={checked} onChange={this.onTextChange} /></label>
);
}
});
</code></pre>
<p><code>这样做其实是依赖 props 来传递事件的引用,并通过回调的方式来实现的,这样实现不是特别好,但是在没有任何工具的情况下也是一种简单的实现方式</code></p>
<p>这里会出现一个我们在之前讨论的问题,就是组件有多层嵌套的情况下,你必须要一次传入回调函数给 props 来实现子组件向父组件传值或者操作。</p>
<h4>Tiny-Tip: React Event System</h4>
<p>在 onChange 事件或者其他 React 事件中,你能够获取以下东西:</p>
<ul>
<li>【this】:指向你的组件</li>
<li>【一个参数】:这个参数是一个 React 合成事件,SyntheticEvent。</li>
</ul>
<p>React 对所有事件的管理都是自己实现的,与我们之前使用的 onclick、onchange 事件不一样。从根本上来说,他们都是绑定到 body 上。
<code>document.on("change", "input[data-reactid=".0.2"]", function () {...});</code>
上面这份代码不是来自于 React,只是打一个比方而已。</p>
<p>如果我没有猜错的话,React 真正处理一个事件的代码如下:</p>
<pre><code class="language-js">var listenTo = ReactBrowserEventEmitter.listenTo;
...
function putListener(id, registrationName, listener, transaction) {
...
var container = ReactMount.findReactContainerForID(id);
if (container) {
var doc = container.nodeType === ELEMENT_NODE_TYPE ? container.ownerDocument : container;
listenTo(registrationName, doc);
}
...
}
// 在监听事件的内部,我们能发现如下:
target.addEventListener(eventType, callback, false);
</code></pre>
<p><code>多个子组件使用同一个回调的情况</code></p>
<pre><code class="language-js">// 父组件
var MyContainer = React.createClass({
getInitialState: function () {
return {
totalChecked: 0
};
},
onChildChanged: function (newState) {
var newToral = this.state.totalChecked
+ (newState ? 1 : -1);
this.setState({
totalChecked: newToral
});
},
render: function() {
var totalChecked = this.state.totalChecked;
return (
<div>
<div>How many are checked: {totalChecked}</div>
<ToggleButton text="Toggle me"
initialChecked={this.state.checked}
callbackParent={this.onChildChanged}
/>
<ToggleButton text="Toggle me too"
initialChecked={this.state.checked}
callbackParent={this.onChildChanged}
/>
<ToggleButton text="And me"
initialChecked={this.state.checked}
callbackParent={this.onChildChanged}
/>
</div>
);
}
});
// 子组件
var ToggleButton = React.createClass({
getInitialState: function () {
return {
checked: this.props.initialChecked
};
},
onTextChange: function () {
var newState = !this.state.checked;
this.setState({
checked: newState
});
// 这里要注意:setState 是一个异步方法,所以需要操作缓存的当前值
this.props.callbackParent(newState);
},
render: function () {
// 从【父组件】获取的值
var text = this.props.text;
// 组件自身的状态数据
var checked = this.state.checked;
return (
<label>{text}: <input type="checkbox" checked={checked} onChange={this.onTextChange} /></label>
);
}
});
</code></pre>
<p>这是非常容易理解的,在父组件中我们增加了一个【totalChecked】来替代之前例子中的【checked】,当子组件改变的时候,使用同一个子组件的回调函数给父组件返回值。</p>
<h3>三、没有任何嵌套关系的组件之间传值</h3>
<p>如果组件之间没有任何关系,组件嵌套层次比较深(个人认为 2 层以上已经算深了),或者你为了一些组件能够订阅、写入一些信号,不想让组件之间插入一个组件,让两个组件处于独立的关系。对于事件系统,这里有 2 个基本操作步骤:订阅(subscribe)/监听(listen)一个事件通知,并发送(send)/触发(trigger)/发布(publish)/发送(dispatch)一个事件通知那些想要的组件。</p>
<p>下面讲介绍 3 种模式来处理事件,你能点击这里来比较一下它们。</p>
<p>简单总结一下:</p>
<ul>
<li>Event Emitter/Target/Dispatcher
特点:需要一个指定的订阅源</li>
</ul>
<pre><code class="language-js">// to subscribe
otherObject.addEventListener(‘click’, function() { alert(‘click!’); });
// to dispatch
this.dispatchEvent(‘click’);
</code></pre>
<ul>
<li>Publish / Subscribe
特点:触发事件的时候,你不需要指定一个特定的源,因为它是使用一个全局对象来处理事件(其实就是一个全局 广播的方式来处理事件)</li>
</ul>
<pre><code class="language-js">// to subscribe
globalBroadcaster.subscribe(‘click’, function() { alert(‘click!’); });
// to dispatch
globalBroadcaster.publish(‘click’);
</code></pre>
<ul>
<li>Signals
特点:与Event Emitter/Target/Dispatcher相似,但是你不要使用随机的字符串作为事件触发的引用。触发事件的每一个对象都需要一个确切的名字(就是类似硬编码类的去写事件名字),并且在触发的时候,也必须要指定确切的事件。(看例子吧,很好理解)</li>
</ul>
<pre><code class="language-js">// to subscribe
otherObject.clicked.add(function() { alert(‘click’); });
// to dispatch
this.clicked.dispatch();
</code></pre>
<p>如果你只想简单的使用一下,并不需要其他操作,可以用简单的方式来实现:</p>
<pre><code class="language-js">// 简单实现了一下 subscribe 和 dispatch
var EventEmitter = {
_events: {},
dispatch: function (event, data) {
if (!this._events[event]) { // 没有监听事件
return;
}
for (var i = 0; i < this._events[event].length; i++) {
this._events[event][i](data);
}
},
subscribe: function (event, callback) {
// 创建一个新事件数组
if (!this._events[event]) {
this._events[event] = [];
}
this._events[event].push(callback);
}
};
otherObject.subscribe("namechanged", function(data) { alert(data.name); });
this.dispatch("namechanged", { name: "John" });
</code></pre>
<p>如果你想使用 Publish/Subscribe 模型,可以使用:PubSubJS
React 团队使用的是:js-signals 它基于 Signals 模式,用起来相当不错。</p>
<h3>Events in React</h3>
<p>使用 React 事件的时候,必须关注下面两个方法:
* componentDidMount
* componentWillUnmount
在处理事件的时候,需要注意:</p>
<p>在 componentDidMount 事件中,如果组件挂载(mounted)完成,再订阅事件;当组件卸载(unmounted)的时候,在 componentWillUnmount 事件中取消事件的订阅。
(如果不是很清楚可以查阅 React 对生命周期介绍的文档,里面也有描述。原文中介绍的是 componentWillMount 个人认为应该是挂载完成后订阅事件,比如Animation这个就必须挂载,并且不能动态的添加,谨慎点更好)
因为组件的渲染和销毁是由 React 来控制的,我们不知道怎么引用他们,所以EventEmitter 模式在处理组件的时候用处不大。</p>
<p>pub/sub 模式可以使用,你不需要知道引用。</p>
<p>下面来一个例子:实现有多个 product 组件,点击他们的时候,展示 product 的名字。
(我在例子中引入了之前推荐的 PubSubJS 库,如果你觉得引入代价太大,也可以手写一个简版,还是比较容易的,很好用哈,大家也可以体验,但是我还是不推荐全局广播的方式)</p>
<pre><code class="language-js">// 定义一个容器
var ProductList = React.createClass({
render: function () {
return (
<div>
<ProductSelection />
<Product name="product 1" />
<Product name="product 2" />
<Product name="product 3" />
</div>
);
}
});
// 用于展示点击的产品信息容器
var ProductSelection = React.createClass({
getInitialState: function() {
return {
selection: "none"
};
},
componentDidMount: function () {
this.pubsub_token = PubSub.subscribe("products", function (topic, product) {
this.setState({
selection: product
});
}.bind(this));
},
componentWillUnmount: function () {
PubSub.unsubscribe(this.pubsub_token);
},
render: function () {
return (
<p>You have selected the product : {this.state.selection}</p>
);
}
});
var Product = React.createClass({
onclick: function () {
PubSub.publish("products", this.props.name);
},
render: function() {
return <div onClick={this.onclick}>{this.props.name}</div>;
}
});
</code></pre>
<h3>ES6: yield and js-csp</h3>
<p>ES6 中有一种传递信息的方式,使用生成函数(generators)和 yield 关键字。</p>
<pre><code class="language-js">function* list() {
for(var i = 0; i < arguments.length; i++) {
yield arguments[i];
}
return "done.";
}
var o = list(1, 2, 3);
var cur = o.next;
while(!cur.done) {
cur = o.next();
console.log(cur);
}
</code></pre>
<p>通常来说,你有一个队列,对象在里面都能找到一个引用,在定义的时候锁住,当发生的时候,立即打开锁执行。js-csp 是一种解决办法,也许以后还会有其他解决办法。</p>
<h3>结尾</h3>
<p>在实际应用中,按照实际要解决的需求选择解决办法。对于小应用程序,你可以使用 props 和回调的方法进行组件之间的数据交换。你可以通过 pub/sub 模式,以避免污染你的组件。在这里,我们不是在谈论数据,只是组件。对于数据的请求、数据的变化等场景,可以使用 Facebook 的 Flux、Relay、GraphQL 来处理,都非常的好用。</p>
</article>
<section class="author">
<div class="avatar" style="background-image: url(https://joe-10005639.cossh.myqcloud.com/jkbb.jpg);"></div>
<a class="name" href="javascript:;">江矿叔叔.</a>
<div class="intro">前(台)端(菜), 喜欢钻研新技术.</div>
</section>
<section class="social">
<a href="https://github.com/joname1" target="_blank">
<i class="iconfont i-github"></i>
</a>
<a href="https://twitter.com/im_joname" target="_blank">
<i class="iconfont i-twitter"></i>
</a>
<a href="https://www.zhihu.com/people/joname-liangtan" target="_blank">
<i class="iconfont i-zhihu"></i>
</a>
<a href="javascript:alert('对方不想跟你讲话, 并向你扔来一段乱码 atob(decodeURI(“am9uYW1lLmxpYW5ndGFuQGdtYWlsLmNvbQ”))')" target="_blank">
<i class="iconfont i-email"></i>
</a>
</section>
<div id="comment"></div>
</article>
</article>
<footer class="footer clearfix">
<span class="copyright">
<script>
document.write(new Date().getFullYear());
</script> © Made with <i class="fa fa-heart"></i> using <Joname/>
</span>
</footer>
<script src="https://joe-10005639.cossh.myqcloud.com/index.min.js"></script>
<script src="https://cdn.bootcss.com/prism/1.13.0/prism.min.js"></script>
<script src="https://tajs.qq.com/stats?sId=59279948" charset="UTF-8"></script>
<script>
new Valine({
el: '#comment',
appId: 'jnCxgrLfxzf5aeWnhldmpset-gzGzoHsz',
appKey: 'gEclatgmn0rmGbgoFi1OuA00',
placeholder: 'ヾノ≧∀≦)o来啊, 快活啊!',
path: window.location.pathname,
avatar: 'retro',
pageSize: 10,
guest_info: ['nick','mail'],
lang: 'en'
})
</script>
</body>
</html>