玩转 React(七)- 组件之间的数据共享

上一篇文章 玩转 React(六)- 处理事件 介绍了在 React 中如何处理用户事件,以及 React 事件机制与原生 DOM 事件的差别和注意的问题,同时也介绍了事件处理函数中 this 的指向问题以及处理的几种方式及其优缺点。html

你们在阅读的过程当中有任何为题能够给我留言,同时欢迎你们加入玩转 React 微信群,个人微信号是 leobaba88,先加我好友,验证信息:玩转 React,而后我会拉你进群。前端


今天这篇文章要讲的内容是关于多个组件之间如何共享数据,或者说是如何通讯的。只有掌握了正确的组件之间通讯的方式,才能在开发交互复杂的前端应用时作到游刃有余,所谓正确的方式也就是符合 React 设计理念的方式。使用一个框架时,必定要听从框架的最佳实践,人家框架是这样设计的,你偏要那样来用,用得不爽还要喷其很差用,那就不该该了。react

内容摘要

  • React 中的数据是单向自顶向下传递的。
  • 单向数据流与双向绑定的差别。
  • 最符合 React 理念的组件之间共享数据的方式。
  • 数据惟一来源原则。
  • 一些很差的方式。
  • 先跟 Redux 打个招呼。
  • 其余一些关于组件间通讯的内容(context、ref)。

组件之间通讯的最佳方式

如今咱们就来探讨下,什么样的方式才是 React 中组件之间通讯的正确方式。redux

在前面的文章中,咱们有说过,React 之因此能胜任大型复杂前端项目的开发,是由于其 单向数据流 这一重要特性,单向数据流能让视图更新逻辑变得简单,从原始的对 DOM 操做变为对数据操做,简单了就容易维护。segmentfault

React 组件中数据的流动方向是自顶向下的,也就是说在组件树中,数据只能从父组件以属性的方式传递到子组件,父组件的数据多是其接收到的属性,也多是自身的内部状态。bash

有些同窗这里可能会比较困惑,说子组件明明能够经过一个函数属性将数据传递给父组件呀。好多同窗甚至所以搞不明白单向数据流和双向绑定的差别。其实换个角度考虑一下就清楚不少了,既然“数据传递”这个词区分度不够大,那就换个区分度比较大的说法。咱们能够这样理解,函数属性是子组件用来通知父组件发生了什么,它更像是子组件触发的一个事件,父组件能够依据业务逻辑来选择如何处理这个事件,它能够更新数据后从新传递给子组件,也能够置之不理。微信

函数属性(或者说事件)在组件之间通讯过程当中是必不可少的,可是切莫让它影响了你们对单向数据流这一律念的理解。框架

数据双向绑定不同,在双向绑定中父组件将数据传递给子组件,子组件修改数据后会将数据回传同步给父组件,父组件是无条件接受的。这里就不过多去说哪一个好哪一个差了,有兴趣的同窗能够本身去体会,懒一点的就坚持学习 React 吧。函数

状态提高(Lifting State Up)

既然 React 中的数据是单向自顶向下传递的,那么符合 React 这一特性的组件通讯方式就显而易见了。工具

状态提高的意思是,当组件 A 须要依赖另一个组件 B 的内部状态,而他们又不是父子关系时,须要将组件 B 的内部状态提高到他们公共的祖先组件中管理。这样他们就均可以经过属性接收到这份数据了。

当组件 B 须要对数据进行变动时,能够经过函数属性来通知祖先组件对数据更新,而后从新传递给子组件。

惟一数据来源(Single source of truth)

有些同窗可能又会迷惑,为何多个组件之间必需要共用同一份数据,我可不能够引入一个事件库,一个组件分发事件,另外一个组件注册相应的事件来接受数据本身维护。

相似的方案五花八门,会有不少,我认为这样作固然是很差的,会有以下问题:

  1. 破坏了组件的封装性,易于复用的组件都是相对独立的,它只须要定义本身须要的数据和行为(函数属性)便可,我不须要谁帮我分发事件。

  2. 数据传递是不连续的,这样作会增长项目的复杂性,当项目到必定阶段后,对这份数据的依赖就变得千丝万缕、难以维护了。

  3. 相同的数据会有多个副本,须要保证数据同步,在增长项目复杂性的同时也提升了出现BUG的概率。

这是我我的的见解,我也确实有遇到过这种用法,有不一样意见你们能够进群交流。

数据惟一来源是官方推荐的数据共享的原则,也是最符合 React 设计理念,与单向数据流特性相辅相成的,但愿你们务必遵照。

Redux

Redux 是一个状态管理库,它不是专属于 React 技术栈的,可是跟 React 配合起来至关不错。

当咱们的前端应用规模较小的时候,咱们能够不引入任何的状态管理工具,只须要依据上面说的状态提高的方式来管理应用状态便可。为了让应用的状态更直观,你能够将跟组件做为状态总线,来管理整个应用全部的状态。并且对于小规模的项目是推荐这样来作的,没有必要高射炮打蚊子,过渡设计。

可是当前端应用规模变得比较复杂时,咱们就须要有相似 Redux 这样一个来专门进行状态管理的东西了。它的职责以下:

  1. 维护一个数据仓库(store)管理整个应用的状态(state),确保数据的惟一来源。
  2. 能够经过 dispatch 方法分发一个 action,来通知 Redux 须要对数据进行变动。
  3. Redux 接收到 action 后能够依据 action 的类型对 state 进行相应的修改。
  4. 数据跟新后 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 style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: PropTypes.string
};

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

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 (
      <div>
        <input
          type="text"
          name="name"
          placeholder="Name"
          onChange={e => this.handleFieldChange(e)}
        />
        <input
          type="text"
          name="age"
          placeholder="Age"
          onChange={e => this.handleFieldChange(e)}
        />
      </div>
    )
  }
}
class App extends React.Component {
  handleSubmit() {
    const formData = this.form.formData()
    alert(`formData: ${JSON.stringify(formData)}`)
  }
  render() {
    return (
      <div>
        <UserForm ref={form => {this.form = form}} />
        <button onClick={() => this.handleSubmit()}>Submit</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.querySelector('#root'))
复制代码

演示地址:https://codepen.io/Sarike/pen/OOKYXJ

既然经过 ref 可以获取子组件的实例,那么咱们天然能够调用其成员方法,从而获取数据。

固然,目前这确实能工做,但绝对不是一种好的方式。由于做为一个组件,是须要有必定的封装性的,它应该对外只会承诺我接受什么样的属性,而不会承诺有什么样的成员方法。换句话说,若是 JavaScript 的类支持私有成员方法,那么 React 组件类中的成员方法都应该定义成私有的。

这应该属于一种 Hack 的使用方式,并且这样作有悖单向数据流原则。

ref 有它本身的使用场景,这里只是说明这种方式不适用于组件之间通讯。

总结

虽然啰嗦了这么多,实际上只但愿你们知道一件事情,请使用状态提高的方式在多个组件之间共享数据,切记维持应用单向数据流和数据惟一来源原则。

文章中有些观点仁者见仁,有什么疑惑欢迎留言讨论。

很久没更新了,可是没有放弃,感谢你们支持。欢迎加我微信好友:leobaba88,进群交流。验证信息:玩转 React


PS:本系列的全部文章将在 segmentfault 和 掘金 同步发布。

本做品保留全部权利。未得到许可人许可前,不容许他人复制、发行、展览和表演做品。不容许他人基于该做品创做演绎做品 。

相关文章
相关标签/搜索