本篇主要讲述 React 中的 props state 以及 父子组件之间的数据传递。

props & state

对于一个组件来说,props 属性是只读的,像一个透明包裹一样,子组件可以看到包裹里的东西,却不能直接改变 props 的值。props 来自他的父组件。而 state 属性则好像是自己的背包一样,可以随意通过 setState 修改,而且是永远跟随着自己的,不用担心丢失。

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
class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
item: ''
...
}
}
sendToFather(data){
this.props.setData(data);
}
render() {
return (
<div className="main">
<div onClick={()=>{this.sendToFather({item1: value2})}}>{this.props.item}</div>
<div>{this.state.item}</div>
</div>
)
}
}
class FatherComponent extends Component {
constructor(props) {
super(props);
this.state = {
item: 'value1',
item1: ''
...
}
}
setDateMethod(value){
this.setState(value)
}
render() {
return (
<ChildComponent setData={(this.setDateMethod).bind(this))} item={this.state.item}></ChildComponent>
)
}
}

数据传递

父组件需要向自组件传递数据的时候,比如需要传递一个名字为 item ,值为 value1 的数据,
只需要引用子组件的时候为其添加一个名字为 item 的属性,然后将值赋予它,那么在子组件中就可以通过 props.item 获取到传递进来的值。

子组件要把数据返回给父组件,比如需要传递一个名字为 item1 ,值为 value2 的数据,
就需要父组件传递一个函数 setData 给子组件,子组件通过传入参数 { item1: value2 } 调用函数 setData 将数据返回给父组件的函数,父组件的函数 setDateMethod 接受实参来改变父组件中的 state 等值。

简单来说:

  • 父组件将需要传递的数据,更改 state 的方法(setData方法)或组件本身通过 props 传递给子组件
  • 子组件通过调用 props 传来的回调函数(setData方法)向父组件传递数据或更改状态

全局传递数据

EventEmitter

因为数据只能在父子之间传递,当父子关系嵌套很深的时候,不同组件之间传递数据十分不便。这时可以使用 EventEmitter 事件传递来实现组件之间的数据传递。使用事件模型,组件之间无论是父子关系还是非父子关系都可以直接沟通,从而解决了组件间层层回调传递的问题,但是频繁地使用事件实现组件间沟通会使整个程序的数据流向越来越乱,因此,组件间的沟通还是要尽量遵循单向数据流机制。

一个简单的发布订阅 Eventbus

1
2
3
4
5
6
7
8
9
10
11
12
13
export default class Eventbus {
constructor () {
this.fns = []
}
emit (type, ...args) {
const fns = this.fns[type]
if (fns) fns.forEach(fn => fn.apply(this, args))
}
on (type, fn) {
const fns = this.fns[type]
fns ? fns.push(fn) : (this.fns[type] = [fn])
}
}

Context

全局传递数据可以通过 Context 来实现
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,比如UI主题,地区偏好。

什么是 Context

  • Context 提供了一种无需为每层组件手动添加 props,能在组件树间进行数据传递的方法
  • Context 设计目的是共享或缓存组件树的全局数据,如用户认证,地区偏好,主题和语言
  • Context 应用场景是不同层级组件访问同样数据,副作用是降低组件的复用性
  • Context API
    • React.createContext 创建 Context 对象,订阅该对象的组件从组件树中离自身最近的匹配的 Provider 中读取当前的 context 值
    • Context.Provider 接收 value 属性,当其发生变化时,内部所有消费组件都会重新渲染,忽略 shouldComponentUpdate 函数
    • Class.contextType 订阅单一 context,在任何生命周期中,使用 this.context 访问
    • Context.Consumer
      • 在函数式组件中订阅 context 的变更
      • 需要一个函数作为子元素。函数接收当前 context 值,并返回一个 React 节点
        • context 值由最近的 Provider 提供
        • 没有 Provider 时等同于 createContext() 的 defaultValue
    • Context.displayName 指定组件在 DevToools 中显示的名称

Context 的适用场景

  • 适合在组件之间共享如地区偏好,主题等数据,避免逐层传递 props
  • 存在 context 的 value 更新,内部所有消费组件都重新渲染问题
  • 过度使用组件的状态与上下文相关,复用度降低

refs

什么是 Ref 转发

  • Ref 转发可以将 ref 传递到子组件
    • 由 React.forwardRef 实现
    • 向 ref 传入回调函数,函数第一参数是 React 组件实例或 HTML DOM 元素
  • Ref 转发适合应用场景
    • 转发表单组件的 ref 到 DOM 节点,便于访问 DOM 节点,来管理焦点、选中或动画
    • 在高阶组件内,转发外层组件的 ref 到 被包裹的组件
  • Ref 转发更改了组件默认的 ref 指向,对组件使用者不可见,不建议使用
    • 不兼容之前同时使用组件和 ref 的应用
    • 对组件使用者,ref 结果可能不符合直观预期
  • 由 React.forwardRef 实现 Ref 转发,可以使用函数决定 ref 转发组件显示的内容

    • 设置传入 React.forwardRef 函数名称,例如

      1
      2
      3
      4
      const newComponent = React.forwardRef(function myName(props, ref) {
      return <innerComponent {...props} forwardedRef={ref} />
      })
      // DevTools DisplayName: ForwardRef(myName)

      设置函数的 displayName 来包含被包裹组件的名称

      1
      2
      3
      4
      5
      6
      function myName(props, ref) {
      return <innerComponent {...props} forwardedRef={ref}
      }
      myName.displayName = 'myDisplayName'
      const newComponent = React.forwardRef(myName)
      // DevTools DisplayName: ForwardRef(myDisplayName)

refs 适用场景

  • 适合在典型数据流之外强制修改子组件的场景,例如
    • 管理焦点,文本选择或媒体播放
    • 触发强制动画
    • 集成第三方 DOM 库
  • DOM refs 打破组件封装,ref 转发更改开发者预期
    • 应避免过度使用,用声明式实现和状态提升代替
    • 一定要使用时,建议添加注释和说明
  • 被修改的子组件可以是 React 组件实例或 DOM 元素
  • 包含向子组件添加 ref、回调 refs 和 ref 转发三种方式

使用状态管理库

状态管理库例如 Redux、Mobx 通过自己的 API 实现了对数据状态的管理。

为什么 React 是单向数据流

在 React 中,父组件可以把它的 state 作为 props 向下传递它的子组件中,子组件通过 props 接收父组件的数据,不关心数据来源于父组件的 state 或 props。 状态 state 总是所属于特定的组件,而能够使用 state 的组件只限定于当前组件或者他的孩子组件。React 中这种自上而下的数据传递被称为单向数据流。

单向数据流的好处在于,数据只限定于特定的流向,在寻找数据流向和查找问题时,链路更加清晰明了。但是在某些情况下,相比于双向绑定,单向数据流需要通过通过传入回调来实现,不够简洁。