Skip to content

React 组件间的通信 #35

Open
Open
@myLightLin

Description

@myLightLin

在 React 里,UI = render(state),页面是由数据驱动更新的。平时我们写 React 的时候,本质上就是在跟数据打交道,了解清楚数据的流向,对厘清业务是有帮助的。而 React 在数据这方面,推崇的是 单向数据流,也就是数据只能由高层级的组件流向低层级的组件。下面介绍几种 React 组件间的数据通信。

父子组件

对于父子组件来说,通常是由父组件定义数据,然后通过 props 传递给子组件。在这种模式下,子组件就是个无状态组件,它的数据全部由父组件定义和控制。当需要更改自身状态时,通过 事件驱动 的方式来更改,即子组件 emit 发送一个事件出去,并把当前的数据状态传递给父组件,父组件接收到后,调用 setState 更新数据,然后页面 UI 随之刷新。

兄弟组件

对于兄弟组件,数据的传递方式是通过 父组件 做中转。假设有兄弟组件 B 和 C,以及它们的父组件 A。此时 B 要向 C 传递数据,怎么做呢?首先,父组件 A 在子组件 B 里绑定一个事件监听函数 fn,子组件 B 通过 fn 函数入参把想传递的数据交给父组件 A,A 拿到数据后,通过 setState 更改 A 组件当前的 state 数据,然后再把这个数据通过 props 的方式传递给子组件 C。这样就实现了兄弟组件通信。

跨组件通信 —— 发布订阅模式

React 单向数据流的好处是数据来源清晰,方便管理,但坏处是假设组件很多的话,通过一层层 props 传递下去,嵌套太多不是好办法。所以还有一种传递数据的方式: 发布——订阅模式。它通过一方发布事件,另一方订阅事件来实现数据的传递。对于发布订阅模式来说,有以下几个重要方法:

  • on
  • off
  • emit
  • once

一个简易的发布——订阅模式实现代码如下:

class EventEmitter {
  constructor() {
    this.events = {}
  }
  
  emit(type, ...args) {
    this.events[type].forEach(fn => {
      fn(...args)
    })
    return true
  }
  
  on(type, handler) {
    this.events[type] = this.events[type] || []
    this.events[type].push(handler)
    return this
  }
  
  off(type, handler) {
    const lis = this.events[type]
    if (!lis) return this
    for (let i = lis.length; i > 0; i--) {
      if (lis[i] === handler) {
        lis.splice(i, 1)
        break
      }
    }
    return this
  }
  
  once(type, handler) {
    this.events[type] = this.events[type] || []
    const onceWrapper = () => {
      handler()
      this.off(type, onceWrapper)
    }
    this.events[type].push(onceWrapper)
    return this
  }
}

Context API

React 本身提供了一个组件间全局通信的方式,那就是 Context API。这个 API 有三个重要概念:

  • React.createContext: 创建一个上下文对象用来保存数据
  • Provider:把组件包裹在 Provider 里,通过 Provider 的 value 属性获取需要传递的数据
  • Consumer: 消费数据的组件,接收定义在 Provider 的 value 数据

Context API 驾驭起来难度大,因此很少被推荐使用,如果是大型复杂项目,数据管理通常会采用第三方状态库 redux

redux

引用官方的描述:

Redux 是 JavaScript 状态容器,它提供可预测的状态管理。

Redux 并不属于 React,而是提出了一套数据管理的思想和规范,React 根据这个思想实现了自己的状态管理库 react-redux
对于 Redux 架构,有三个重要概念需要理解:

  • store: 全局唯一的数据源,它是只读的
  • reducer:是一个纯函数,它根据 action 的动作,来对数据进行分发处理,最后返回新的数据状态,更新 store。store 一旦更新,就驱动视图层刷新,UI 也就改变了,这是个 严格的单一数据流
  • action: 因为 store 是只读的,我们如果想改变数据,就需要定义一个改变「动作」,这个动作描述了新的数据状态信息,它会被 reducer 接收处理。

redux 的工作流是这样:首先,我们需要有一个类似 createStore 的方法来创建我们的唯一数据源 store,有了 store 后,我们想改变数据,该怎么做?首先,我们需要定义一个 action 动作,描述你这个数据是新增还是修改,比如 action = {type: 'ADD', payload: '张三'},定义 action 就是解决 你想要什么。然后,我们还要编写 reducer 纯函数,在里面指定如何来响应 action,比如上面的 action 是新增一个叫 张三 的名字,那我们就要在函数里根据这个动作,来创建新的数据,这个数据最终会被处理更新到 store 上。现在我们 store 有了,action 也有了,reducer 处理函数也有了,那怎么实现更新到 store 这个操作呢?这时 dispatch 函数就登场了,当你通过 createStore 方法创建完 store 数据源后,就可以通过 store.dispatch 来触发数据的更新。
总结下 redux 的整个工作流程:

  • 创建唯一数据源 store
  • 定义更改数据的动作 action
  • 定义处理 action 的函数,称作 reducer
  • 通过 store.dispatch 来让 reducer 处理 action

总结

React 遵循单向数据流原则,有以下几种传递数据的方式:

  • props
  • 发布——订阅模式
  • Context API
  • redux

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions