在典型的React应用中,数据是通过props属性自上而下(由父及子)进行传递的。

 

但是,在实际开发中,我们发现有某些属性是许多组件都需要的,那么通过组件树逐层传递props就会特别繁琐,且不容易维护。

 

所以总结以下三种不用逐层传递props,也可以获得共享数据的方法:

 

1、Context

 

版本: 从React 16.3.0开始,加入了Context API。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
 
API: 具体可查阅官方文档https://zh-hans.reactjs.org/docs/context.html#reactcreatecontext
 
React.createContext
 
Context.Provider
 
Context.contextType
 
Context.Consumer
 
Context.displayName

  

 

应用场景: 很多不同层级的组件需要访问同样一些数据。 例如当前主题、locale等。

 

应用:Context共享当前主题

 

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
ThemeContext.js
 
  
 
import { createContext } from "react";
 
const ThemeContext = createContext();
 
ThemeContext.displayName = "themeContext";
 
export default ThemeContext;
 
App.js
 
  
 
import React, { Component } from "react";
 
import ThemeContext from "./ThemeContext";
 
import ClearButton from "./ClearButton";
 
class App extends Component {
 
  render() {
 
    return (
 
      <ThemeContext.Provider value="dark">
 
        <ClearButton />
 
      </ThemeContext.Provider>
 
    );
 
  }
 
}
 
ClearButton.js
 
  
 
import React, { Component } from 'react';
 
import ThemeContext from "./ThemeContext";
 
class ClearButton extends Component{
 
    componentDidMount(){
 
        console.log(this.context)
 
    }
 
    handleClear=()=>{
 
  
 
    }
 
    render() {
 
        return (
 
              <Tooltip placement="bottom" title="清空画布">
 
                <span className="handlerbar-icon-box">
 
                  <a
 
                    className={`${this.context} top-icon-button clear-icon`}
 
                    onClick={this.handleClear}
 
                  ></a>
 
                </span>
 
              </Tooltip>
 
            )
 
          }
 
    );
 
  }
 
}
 
ClearButton.contextType = ThemeContext;
 
export default ClearButton;

  

挂载在class上的contextType属性会被重新赋值为一个由React.createContext()创建的Context对象,这可以使我们使用this.context来拿到最近Context上的那个值,并且可以在任何生命周期中访问到它,包括在render函数中。

 

如果你正在使用public class fields语法,你可以使用static这个类属性来初始化你的contextType.如下:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
class ClearButton extends Component{
 
  static contextType = ThemeContext;
 
  render(){
 
    console.log(this.context)
 
    ....
 
  }
 
}

  

如果是函数式组件,你该怎么订阅context呢?

可以使用Context.Consumer,他需要函数作为子元素, 这个函数接收当前的context值,返回一个React节点

 

1
2
3
4
5
6
7
8
9
10
11
<ThemeContext.Consumer>
 
       {value => (
 
           <span>{value}</span>
 
       )}
 
</ThemeContext.Consumer>

  

 

在React DevTools中可看到如上图, themeContext就是我们为了好区分其他的Context而起的名字

ThemeContext.displayName = "themeContext"

React DevTools使用该字符串来确定context要显示的内容。

 

应用:react-redux中的context

 

从React 16.3.0开始,Context API包含了2个特殊的组件<Provider>和<Consumer>。如果用过react-redux,会觉得其中的一个很熟悉。那就是<Provider>。因为react-redux也提供了一个叫<Provider>的组件。实际上,react-redux和react提供了两个大致上一样的东西。

 

react-redux是使用了React的Context特性使Redux的Store共享给嵌入的组件。react-redux中,唯一默认的Context对象实例被React.createContext()创建,被叫做ReactReduxContext. 

react-redux中Provider.js中部分代码:

 

 

react-redux的<Provider>组件用<ReactReduxContext.Provider>把Redux的store数据放入Context 中,connect用<ReactReduxContext.Consumer>拿到这些值进行一些更新处理。

react-redux中ConnectAdvanced.js中部分代码:

 

 

 

在使用Context 之前请先考虑下面2点:

 

Context如上所说主要的应用于很多不同层级的组件需要访问同样一些数据。 所以请谨慎使用,因为这会使组件的复用性变差。

 

如果你只是想避免层层传递一些属性,组件组合(Component composition)有时候是一个比context 更好的解决方案。所以请继续看第二种方法吧!

 

2、component composition

 

应用场景: 子组件与父组件解耦。

 

组件可以接收任意props,包括基本的数据类型(Number、String、Boolean、Array、Object)、React元素以及函数。

 

这句话很重要,尤其是props可以是React元素。要先有这个意识才能自己写出来这样的组件。

 

下面这张草图是商城类的网站,可以先考虑一下要怎样才能写出比较好的组件。

 

 

 

这三个List,普通(例如首页、分类等)的商品列表、订单中的商品列表、购物车里的商品列表,每一个Item很像却又有不一样的地方。用组合组件的思路,我会封装成3个组件,如下:

 

一个通用的商品Card组件,但是它的header、leftContent、rightContent、footer是可以作为React元素通过Props传进来,正如我们在众多的UI框架中看到的slot.

Stepper组件:用户选择、增减商品数量

 

Button组件:cancel、退货按钮等风格统一的按钮。

Stepper、Button、checkBox作为React元素,通过rightContent、footer、leftContent的props属性传给Card组件。自从有了这个思路,我很鄙视我之前的做法,因为不是真正的可复用封装。瞅一下以前的做法:那就是List里面嵌套Card,Card组件里面又硬编码嵌套了Stepper组件,比如说现在要根据list组件里面的某个值,判断Stepper组件的UI状态,那么你可能就需要List通过props传给Card,Card又通过props传给Stepper组件。而通过把Stepper组件作为prop(react元素)动态的传给Card,那么Stepper组件就会和Card处于同一级别的组件,List=>Card+Stepper这样就不会出现props的多层传递。

 

3、render props

 

render prop是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,更具体地说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。

 

重要的是要记住,render prop 是因为模式才被称为render prop ,你不一定要用名为render的 prop 来使用这种模式。事实上,任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”。

 

1
2
3
4
5
6
7
8
9
10
11
<DataProvider render={data => (
 
<h1>Hello {data.target}</h1>
 
)}/>
 
<Mouse children={mouse => (
 
  <p>鼠标的位置是 {mouse.x},{mouse.y}</p>
 
)}/>

  

它和组件组合component composition不同的是:子组件需要在渲染前和父组件进行一些交流,例如子组件要分享父组件中的state。

 

使用 render prop 的库有React Router

 

下面是官网的一个猫追逐鼠标的例子,猫追逐鼠标,自然要监听鼠标的位置x,y。虽然Cat作为子组件硬编码到Mouse中,但是它不是一个真正的可复用的封装。我们可以提供一个带有prop函数的<Mouse>组件,它能够动态决定什么需要渲染的,并把state作为参数传给prop函数。

 

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
class Cat extends React.Component {
 
render() {
 
  const mouse = this.props.mouse;
 
  return (
 
    <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
 
  );
 
}
 
}
 
  
 
class Mouse extends React.Component {
 
constructor(props) {
 
  super(props);
 
  this.handleMouseMove = this.handleMouseMove.bind(this);
 
  this.state = { x: 0, y: 0 };
 
}
 
  
 
handleMouseMove(event) {
 
  this.setState({
 
    x: event.clientX,
 
    y: event.clientY
 
  });
 
}
 
  
 
render() {
 
  return (
 
    <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
 
  
 
      {/*
 
        Instead of providing a static representation of what <Mouse> renders,
 
        use the `render` prop to dynamically determine what to render.
 
      */}
 
      {this.props.render(this.state)}
 
    </div>
 
  );
 
}
 
}
 
  
 
class MouseTracker extends React.Component {
 
render() {
 
  return (
 
    <div>
 
      <h1>移动鼠标!</h1>
 
      <Mouse render={mouse => (
 
        <Cat mouse={mouse} />
 
      )}/>
 
    </div>
 
  );
 
}
 
}

  

可以使用带有 render prop 的常规组件来实现大多数高阶组件(HOC)

 

高阶组件是一种基于React的组合特性而形成的设计模式,是一个参数是组件,返回值为新组件的函数。例如react-redux中的connectHOC就是一个高阶组件,详细的可以看react-redux源码。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function withMouse(Component) {
 
return class extends React.Component {
 
  render() {
 
    return (
 
      <Mouse render={mouse => (
 
        <Component {...this.props} mouse={mouse} />
 
      )}/>
 
    );
 
  }
 
}
 
}

  

掌握了以上三种方法,会让你开发React应用如鱼得水。

posted on   ygunoil  阅读(2270)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5
点击右上角即可分享
微信分享提示