摘要:函数属性或者说事件在组件之间通信过程中是必不可少的,但是切莫让它影响了大家对单向数据流这一概念的理解。这应该属于一种的使用方式,而且这样做有悖单向数据流原则。
上一篇文章 玩转 React(六)- 处理事件 介绍了在 React 中如何处理用户事件,以及 React 事件机制与原生 DOM 事件的差异和注意的问题,同时也介绍了事件处理函数中 this 的指向问题以及处理的几种方式及其优缺点。
大家在阅读的过程中有任何为题可以给我留言,同时欢迎大家加入玩转 React 微信群,我的微信号是 leobaba88,先加我好友,验证信息:玩转 React,然后我会拉你进群。
今天这篇文章要讲的内容是关于多个组件之间如何共享数据,或者说是如何通信的。只有掌握了正确的组件之间通信的方式,才能在开发交互复杂的前端应用时做到游刃有余,所谓正确的方式也就是符合 React 设计理念的方式。使用一个框架时,一定要遵从框架的最佳实践,人家框架是这样设计的,你偏要那样来用,用得不爽还要喷其不好用,那就不应该了。
内容摘要React 中的数据是单向自顶向下传递的。
单向数据流与双向绑定的差异。
最符合 React 理念的组件之间共享数据的方式。
数据唯一来源原则。
一些不好的方式。
先跟 Redux 打个招呼。
其他一些关于组件间通信的内容(context、ref)。
组件之间通信的最佳方式现在我们就来探讨下,什么样的方式才是 React 中组件之间通信的正确方式。
在前面的文章中,我们有说过,React 之所以能胜任大型复杂前端项目的开发,是因为其 单向数据流 这一重要特性,单向数据流能让视图更新逻辑变得简单,从原始的对 DOM 操作变为对数据操作,简单了就容易维护。
React 组件中数据的流动方向是自顶向下的,也就是说在组件树中,数据只能从父组件以属性的方式传递到子组件,父组件的数据可能是其接收到的属性,也可能是自身的内部状态。
有些同学这里可能会比较困惑,说子组件明明可以通过一个函数属性将数据传递给父组件呀。好多同学甚至因此搞不明白单向数据流和双向绑定的差异。其实换个角度考虑一下就清楚很多了,既然“数据传递”这个词区分度不够大,那就换个区分度比较大的说法。我们可以这样理解,函数属性是子组件用来通知父组件发生了什么,它更像是子组件触发的一个事件,父组件可以依据业务逻辑来选择如何处理这个事件,它可以更新数据后重新传递给子组件,也可以置之不理。
函数属性(或者说事件)在组件之间通信过程中是必不可少的,但是切莫让它影响了大家对单向数据流这一概念的理解。
数据双向绑定不一样,在双向绑定中父组件将数据传递给子组件,子组件修改数据后会将数据回传同步给父组件,父组件是无条件接受的。这里就不过多去说哪个好哪个差了,有兴趣的同学可以自己去体会,懒一点的就坚持学习 React 吧。
状态提升(Lifting State Up)既然 React 中的数据是单向自顶向下传递的,那么符合 React 这一特性的组件通信方式就显而易见了。
状态提升的意思是,当组件 A 需要依赖另外一个组件 B 的内部状态,而他们又不是父子关系时,需要将组件 B 的内部状态提升到他们公共的祖先组件中管理。这样他们就都可以通过属性接收到这份数据了。
当组件 B 需要对数据进行变更时,可以通过函数属性来通知祖先组件对数据更新,然后重新传递给子组件。
唯一数据来源(Single source of truth)有些同学可能又会迷惑,为什么多个组件之间必须要共用同一份数据,我可不可以引入一个事件库,一个组件分发事件,另一个组件注册相应的事件来接受数据自己维护。
类似的方案五花八门,会有很多,我认为这样做当然是不好的,会有如下问题:
破坏了组件的封装性,易于复用的组件都是相对独立的,它只需要定义自己需要的数据和行为(函数属性)即可,我不需要谁帮我分发事件。
数据传递是不连续的,这样做会增加项目的复杂性,当项目到一定阶段后,对这份数据的依赖就变得千丝万缕、难以维护了。
相同的数据会有多个副本,需要保证数据同步,在增加项目复杂性的同时也提高了出现BUG的几率。
这是我个人的看法,我也确实有遇到过这种用法,有不同意见大家可以进群交流。
数据唯一来源是官方推荐的数据共享的原则,也是最符合 React 设计理念,与单向数据流特性相辅相成的,希望大家务必遵守。
ReduxRedux 是一个状态管理库,它不是专属于 React 技术栈的,但是跟 React 配合起来相当不错。
当我们的前端应用规模较小的时候,我们可以不引入任何的状态管理工具,只需要依据上面说的状态提升的方式来管理应用状态即可。为了让应用的状态更直观,你可以将跟组件作为状态总线,来管理整个应用所有的状态。而且对于小规模的项目是推荐这样来做的,没有必要高射炮打蚊子,过渡设计。
但是当前端应用规模变得比较复杂时,我们就需要有类似 Redux 这样一个来专门进行状态管理的东西了。它的职责如下:
维护一个数据仓库(store)管理整个应用的状态(state),确保数据的唯一来源。
可以通过 dispatch 方法分发一个 action,来通知 Redux 需要对数据进行变更。
Redux 接收到 action 后可以依据 action 的类型对 state 进行相应的修改。
数据跟新后 Redux 会触发注册的监听器(如:更新组件属性),完成视图更新。
Redux 跟 React 一起来用,更详细的介绍可以参考:官方文档,这里大家可以先简单了解下,在后面关于 React 实战的文章中也会详细介绍 Redux 的使用。
类似的状态管理工具还有:MobxJS,感兴趣的同学也可以了解下。
关于组件通信的其他内容在 React 中还有一些其他的与组件间通信相关的知识,这里也顺便跟大家介绍下。
context首先说一下,这是一个不推荐使用的特性,React 官方有明确说明,这是一个实验性的API,可能会在后面的版本中去掉这个东西。所以我是从来不用的,呵呵!
context 的作用是啥呢,当大家有过 React 实战经验时,很容易遇到这种场景,如果组件的层级组织得不合适,可能会嵌套的非常深,当底层的一个组件需要使用顶层一个组件的数据时,需要通过属性一层层传递下去,非常繁琐。
context 就是解决这个问题的,只需要在顶层组件中声明 context,那它的所有子组件可以通过 this.context 直接获取得到。如下实例所示:
import React from "react"; import PropTypes from "prop-types"; class Button extends React.Component { render() { return ( ); } } Button.contextTypes = { color: PropTypes.string }; class Message extends React.Component { render() { return ({this.props.text}); } } class MessageList extends React.Component { getChildContext() { return {color: "purple"}; } render() { const children = this.props.messages.map((message) =>); return {children}; } } MessageList.childContextTypes = { color: PropTypes.string };
实例中,组件层级关系是:MessageList -> Message -> Button。
MessageList 组件中维护一个 color 值用于 Button 组件的背景色,一般情况下我们需要将 color 以属性的方式传给 Message 组件,再通过属性传给 Button 组件。然后在实例中,通过 React 的 context 功能,MessageList 可以将 color 的值越过 Message 直接传给 Button。
是不是很方便?确实很方便,但是这会导致数据传递不连续,过度使用会使得项目逻辑变得不直观,增加项目维护的复杂性。
ref每一个 React 组件有一个特殊的属性 ref,该属性的值可以是一个字符串,也可以是一个函数。由于字符串形式的 ref 在内部实现和实际使用中存在诸多问题,官方不推荐使用,而且可能在未来的版本中会移除,所以我们也没必要聊它了,只要大家在看到字符串形式的 ref 属性时知道也有这种用法就可以了。
当 ref 属性值是一个函数时,如果组件是一个 HTML 元素兼容的 React 内部组件时(如:div、img 等),函数接收其对应的原生 DOM 节点作为参数。如果组件是一个我们以类的方式定义的组件时,函数接收该组件类的实例作为参数。需要注意的是,如果组件是一个以函数的方式定义的组件,那么设置为 ref 值得函数永远都会接收到一个 null。
那么 ref 与组件之间的通信有什么关系呢?请看上段文字加粗内容和下面这个实例:
class UserForm extends React.Component { constructor(props) { super(props) this.state = { name: null, age: null } } formData() { return this.state } handleFieldChange(e) { const { name, value } = e.target this.setState({ [name]: value }) } render() { return (this.handleFieldChange(e)} /> this.handleFieldChange(e)} />) } } class App extends React.Component { handleSubmit() { const formData = this.form.formData() alert(`formData: ${JSON.stringify(formData)}`) } render() { return () } } ReactDOM.render({this.form = form}} /> , document.querySelector("#root"))
演示地址:https://codepen.io/Sarike/pen...
既然通过 ref 能够获取子组件的实例,那么我们自然可以调用其成员方法,从而获取数据。
当然,目前这确实能工作,但绝对不是一种好的方式。因为作为一个组件,是需要有一定的封装性的,它应该对外只会承诺我接受什么样的属性,而不会承诺有什么样的成员方法。换句话说,如果 JavaScript 的类支持私有成员方法,那么 React 组件类中的成员方法都应该定义成私有的。
这应该属于一种 Hack 的使用方式,而且这样做有悖单向数据流原则。
ref 有它自己的使用场景,这里只是说明这种方式不适用于组件之间通信。
总结虽然啰嗦了这么多,实际上只希望大家知道一件事情,请使用状态提升的方式在多个组件之间共享数据,切记维持应用单向数据流和数据唯一来源原则。
文章中有些观点仁者见仁,有什么疑惑欢迎留言讨论。
好久没更新了,但是没有放弃,感谢大家支持。欢迎加我微信好友:leobaba88,进群交流。验证信息:玩转 React。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/90330.html
摘要:本人计划编写一个针对中初级前端开发者学习的系列教程玩转。使用的原因是新的语言规范开发效率更高代码更优雅,尤其是基于开发的项目。其次也是目前特别流行的一个前端框架,截止目前,上有将近万,国内一二线互联网公司都有深度依赖开发的项目。 本人计划编写一个针对中初级前端开发者学习 React 的系列教程 - 《玩转 React》。 文章更新频率:每周 1 ~ 2 篇。 目录 玩转 React(...
摘要:类组件中的增加学习成本,类组件在基于现有工具的优化上存在些许问题。由于业务变动,函数组件不得不改为类组件等等。那么可爱的各位看官,还不赶紧使用起来在线示例点我版本基础入门项目录像教程 视图与业务,好一对冤家 业务型model model是需要精心的设计和合理的划分的,这是我们之前开发大型的redux+react单页面应用,大家都认同的真理,同样的,在react-control-cent...
摘要:这也就是所谓的单向数据流,在这种开发方式下,会让你更新视图的逻辑非常清晰简单,哪怕你的前端交互很复杂,也不至于让你的代码那么容易变成一坨。就是在前端开发过程中,要善于观察和抽象。 这是《玩转 React》系列的第二篇。在该篇中,我们来了解下,React 的出现到底给我们的开发方式带来了什么样的变化。 我的感触可以用一个字来形容,爽!主要爽在以下两个方面。 视图是数据的映射(单向数据流)...
摘要:属性是一个组件的外部输入。只会在开发模式下进行属性类型检查,当代码进行生产发布后,为了减少额外的性能开销,类型检查将会被略过。某个类的实例枚举,属性值必须为其中的某一个值。属性为一个数组,且数组中的元素必须符合指定类型。 在第二篇文章 《新型前端开发方式》 中有说到 React 有很爽的一点就是给我们一种创造 HTML 标签的能力,那么今天这篇文章就详细讲解下 React 是如何提供这...
摘要:属性是一个组件的外部输入。只会在开发模式下进行属性类型检查,当代码进行生产发布后,为了减少额外的性能开销,类型检查将会被略过。某个类的实例枚举,属性值必须为其中的某一个值。属性为一个数组,且数组中的元素必须符合指定类型。 在第二篇文章 《新型前端开发方式》 中有说到 React 有很爽的一点就是给我们一种创造 HTML 标签的能力,那么今天这篇文章就详细讲解下 React 是如何提供这...
阅读 2045·2021-11-23 09:51
阅读 3676·2021-10-20 13:49
阅读 1684·2021-09-06 15:13
阅读 1797·2021-09-06 15:02
阅读 3054·2021-09-02 15:11
阅读 872·2019-08-29 15:37
阅读 1717·2019-08-29 13:24
阅读 2257·2019-08-29 11:28