大概的讲了功能背景,组件化设计过程及一些基本原则,生命周期,组件的两种实现方式等。
这篇就把剩下的完成了。
6. 组件间通信
就通信模式来说,对组件进行分类一下,大概可以分成三类:
- 父组件
- 子组件
- 其他组件
它们之间可以相互进行通信,如下图:
父子组件之间的通信比较常规,没什么特别的,只需要注意的是,无论是props还是回调函数,要想将变化展示到界面上,就得更新组件的状态。
比较需要注意的是,和其他组件的通信要怎么实现,理论上,只要父子组件能通信,则任何组件都能通信,因为整个页面的组件结构本质就是一棵树。但是如果两个组件之间相距比较远,这时如果依赖父子组件之间的通信来实现就很罗嗦了,出了问题也很难调试和定位问题。因此,需要一种更加便捷的通信方式。
我这里选择的是发布订阅者模式,这个模式的特点是,消息一次发布(相当于广播),可以被多个订阅者消费,这个特点也非常适合前端界面的事件。一个事件发生之后,只需要进行发布(广播),事件源并不需要知道谁会消费(关心)这个事件。而消费者也只需要监听这个事件,并不需要知道事件源在哪。这样,这样生产者和消费者就完全是松耦合的关系。
对于内聚的组件(内部比较紧耦合),其他组件通常不应该直接和该组件的子组件进行通信。不过话又说回来,整个页面都是一棵树,所谓父组件本质上也是某个组件的子组件。
发布订阅者模式的实现也很简单:
代码语言:javascript复制import PubSub from 'pubsub-js';
// 发布者
// 实际项目中,事件名应该定义到常量文件里,或者是父组件传进来的值
PubSub.publish("event-name", data);
// 其他组件的订阅者
componentDidMount() {
// 订阅相应的事件
this.pubsub_token = PubSub.subscribe(
"event-name",
function(topic, message) {
console.log('===> subscribe event:', message);
// dosomethings...
}.bind(this)
);
}
componentWillUnmount() {
// 组件卸载的时候记得注销该订阅者
PubSub.unsubscribe(this.pubsub_token);
}
需要注意的一点是,如果订阅者在函数式组件里的话,可能每次render的时候可能都会订阅一次,这样你的代码就可能会被进行很多次。
还有一点需要注意的是,组件尽量不要依赖于全局的配置文件来获取数据,可以从上层组件导入,然后通过props传到子组件。
7. 文件名与目录规范
上一篇已经简单提到一些了,主要规范有:
- 一个组件文件只输出一个组件,组件名和文件名保持一致;
- 比较通用的组件应该放到一个公共的目录下,目录名可以命名为components。
下面是一个演示项目的目录结构:
对于实际项目,则不会是这么简单的,应该先进行模块划分,基于模块再进行组件设计。
8. 具体实现
完整的实现代码见:https://github.com/cyy0523xc/react-puqu3m/
9. 总结
首先,这两篇文章所说的并不一定是最佳实践,只是我在给同事解决问题的时候,恰好把代码写成了这个样子(前端代码其实已经好多年没有写了)。
稍微总结一下:
(1)前端开发的时候,首先应该模块设计,然后组件设计,把这两块想清楚之后,再动手写代码;
(2)无论是模块设计,还是组件设计,或者是其他的设计,其关键点都是怎么做到组件间的松耦合和组件内的紧耦合,将复杂性控制在组件内;
(3)所谓组件,关键要厘清状态,属性及生命周期,对于组件一定要非常熟悉其渲染的过程及条件;
(4)组件间的交互尽量都应该使用消息队列的方式来实现;
(5)数据应该保持单向流动;
(6)状态的设计有两个比较简单的原则,最小化原则和正交性原则。
20210828