React组件之间的通信

React 的组件相对于 Vue 更加的灵活和多样,按照不同的方式可以分成很多类组件:

  • 根据组件的定义方式,可以分为:函数组件(Functional Component )类组件(Class Component)
  • 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )有状态组件(Stateful Component)
  • 根据组件的不同职责,可以分成:展示型组件(Presentational Component)容器型组件(Container Component)

React 中的组件之间的通信方法。

父子组件通信

下面这个案例演示了子传父父传子通过 props 传递。

父组件

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
import React, { Component } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Main from "./Main";

class App extends Component {
constructor() {
super();
this.state = {
title: "Header的标题",
};
}

headerClick() {
console.log("----");
}

render() {
const { title } = this.state;

return (
<div>
<Header title={title} headerClick={() => this.headerClick()} />
<Main />
<Footer />
</div>
);
}
}

export default App;

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from "react";

export class Header extends Component {
// constructor(props) {
// super(props)

// this.state = {}
// }

render() {
const { title, headerClick } = this.props;
return <div onClick={headerClick}>Header: {title}</div>;
}
}

export default Header;

非父子组件通信

非父子组件数据的共享
在开发中,比较常见的数据传递方式是通过 props 属性自上而下(由父到子)进行传递。
但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI 主题、用户登录状态、用户信息等)。
如果我们在顶层的 App 中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。

React 提供了一个 API:Context
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;

可以使用Context或者eventBus

React.createContext

创建一个需要共享的 Context 对象:

如果一个组件订阅了 Context,那么这个组件会从离自身最近的那个匹配的  Provider  中读取到当前的 context 值;
defaultValue 是组件在顶层查找过程中没有找到对应的 Provider,那么就使用默认值

1
const MyContext = React.createContext(defaultValue);

Context.Provider

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:

Provider 接收一个  value  属性,传递给消费组件;
一个 Provider 可以和多个消费组件有对应关系;
多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
当 Provider 的  value  值发生变化时,它内部的所有消费组件都会重新渲染;

1
<MyContext.Provider value={/* 某个值 */} />

案例

以下案例同时演示ContexteventBus

context.jsx

1
2
3
import React from "react";

export const UserContext = React.createContext({ name: "kobe", age: 30 });

app.jsx

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
import React, { Component } from "react";
import Home from "./Home";
import Recommend from "./Recommend";
import { UserContext } from "./context";
import eventBus from "./event/event_bus";

class App extends Component {
constructor() {
super();

this.state = {
name: "anzhiyu",
};
}

componentDidMount() {
eventBus.on("changeName", this.productClick, this);
}

componentWillUnmount() {
eventBus.off("changeName", this.productClick);
}

productClick(name, age) {
console.log("productClick", name, age, this);
this.setState({ name: name });
}

render() {
return (
<div className="app">
<UserContext.Provider value={{ name: "安知鱼", level: 100 }}>
<Home />
<Recommend />
</UserContext.Provider>
<h2>{this.state.name}</h2>
</div>
);
}
}

export default App;

Home.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from "react";
import HomeProduct from "./HomeProduct";

class Home extends Component {
render() {
return (
<div>
<HomeProduct />
</div>
);
}
}

export default Home;

HomeProduct.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component } from "react";
import { UserContext } from "./context";
import eventBus from "./event/event_bus";

class HomeProduct extends Component {
productClick() {
eventBus.emit("changeName", "安知鱼变帅", 18);
}

render() {
console.log(this.context);

return (
<div>
<div>HomeProduct</div>
<button onClick={e => this.productClick()}>product</button>
</div>
);
}
}

HomeProduct.contextType = UserContext;

export default HomeProduct;

Recommend.jsx 演示 函数式组件 Consumer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { UserContext } from "./context";

function Recommend(props) {
return (
<div>
<span>Recommend</span>
{/* 函数式组件中使用Context共享的数据 */}
<UserContext.Consumer>
{value => {
return <span>{value.name}</span>;
}}
</UserContext.Consumer>
</div>
);
}

export default Recommend;

如果需要使用多个 context 可以使用Consumer共享

theme-context.js

1
2
3
4
5
6
import React from "react";

// 1.创建一个Context
const ThemeContext = React.createContext({ color: "blue", size: 10 });

export default ThemeContext;

user-context.js

1
2
3
4
5
6
import React from "react";

// 1.创建一个Context
const UserContext = React.createContext();

export default UserContext;

HomeInfo.jsx

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
import React, { Component } from "react";
import ThemeContext from "./context/theme-context";
import UserContext from "./context/user-context";

export class HomeInfo extends Component {
render() {
// 4.第四步操作: 获取数据, 并且使用数据
console.log(this.context);

return (
<div>
<h2>HomeInfo: {this.context.color}</h2>
<UserContext.Consumer>
{value => {
return <h2>Info User: {value.nickname}</h2>;
}}
</UserContext.Consumer>
</div>
);
}
}

// 3.第三步操作: 设置组件的contextType为某一个Context
HomeInfo.contextType = ThemeContext;

export default HomeInfo;

context 默认数据

当没有被 context 组件包裹时就会使用到默认数据

theme-context.js

1
2
3
4
5
6
import React from "react";

// 1.创建一个Context
const ThemeContext = React.createContext({ color: "blue", size: 10 });

export default ThemeContext;

App.jsx

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
import React, { Component } from "react";
import Home from "./Home";

import ThemeContext from "./context/theme-context";
import UserContext from "./context/user-context";
import Profile from "./Profile";

export class App extends Component {
constructor() {
super();

this.state = {
info: { name: "kobe", age: 30 },
};
}

render() {
const { info } = this.state;

return (
<div>
<h2>App</h2>
<UserContext.Provider value={{ nickname: "kobe", age: 30 }}>
<ThemeContext.Provider value={{ color: "red", size: "30" }}>
<Home {...info} />
</ThemeContext.Provider>
</UserContext.Provider>
<Profile />
</div>
);
}
}

export default App;

Profile.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from "react";
import ThemeContext from "./context/theme-context";

export class Profile extends Component {
render() {
console.log(this.context);

return <div>Profile</div>;
}
}

Profile.contextType = ThemeContext;

export default Profile;