React VS Vue 之 React 组件通信

组件通信无外乎以下几种情况:

  1. 父子通信
  2. 子向父通信
  3. 同级通信
  4. 跨层级通信
  5. 任意组件

1. 父子通信

父子通信是最常见的通信方式,父组件通过props将数据传递到子组件。

1
2
3
4
// Child.jsx
export default function Child({ name }) {
return <h1>Hi, {name}</h1>;
}
1
2
3
4
5
6
7
8
// Parent.jsx
export default function Parent({ name }) {
return (
<div>
<Child name={name} />
</div>
);
}

2. 子向父通信

父组件可以传递函数给子组件,子组件可以调用父组件传入的函数,将数据返回给父组件。需要注意的是函数的作用域问题,如果需要父组件的作用域则需要提前进行绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Parent.jsx
export default class Parent extends React.Component {
state = { age: 0 };
constructor(props) {
super(props);
}

onClick = age => {
this.setState({
age: age + 1
});
};

render() {
const { age } = this.state;
return <Child age={age} onClick={this.onCLick} />;
}
}

3. 同级通信

同级通信方式,需要将数据传递给父组件,父组件再将数据传递给另一个子组件。

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
class Parent extends React.Component {
state = { age: 0, height: 40 };
constructor(props) {
super(props);
}

onSetAge = age => {
const res = age + 1;
this.setState({
age: res,
height: res * 1.2
});
};

render() {
const { age, height } = this.state;
return (
<div>
<Child2 age={age} setAge={this.onSetAge} />;
<Child1 age={age} height={height} />;
</div>
);
}
}

function Child1({ height, age }) {
return (
<div>
height: {height}, age: {age}
</div>
);
}

function Child2({ age, setAge }) {
return <div onClick={setAge}>age: {age}</div>;
}

4. 跨层级通信

Context API 是 React 提供的一种跨节点数据访问的方式。很多时候组件嵌套多层之后,跨多层组件传递数据变得繁琐复杂。

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
function Parent({ age }) {
const onClick = () => {
console.log("i am parent.");
return (
<div>
<Child1 age={age} onClick={onCLick} />
</div>
);
};
}

function Child1({ age, onClick }) {
return (
<div>
<Child2 age={age} onClick={onClick} />
</div>
);
}

function Child2({ age, onClick }) {
return (
<div>
<Child3 age={age} onClick={onClick} />
</div>
);
}

function Child3({ age, onClick }) {
return (
<div>
<button age={age} onClick={onClick}>
click
</button>
</div>
);
}

可以看到层级变多之后,从父组件传递数据到内部子组件会变得特别繁琐,需要层层显示传递。React 使用了 Context API 解决了这个问题。使用 Context 需要先创建一个 Context 对象,每个 Contet 对象都附带一个 Provider componentConsumer component,Provider 需要包裹 Consumer,Consumer 的 children 为 函数类型,函数参数就是 context 原始数据。

Context 旨在共享一个组件树内可被视为 “全局” 的数据,例如当前经过身份验证的用户,主题或首选语言等。

theme-context.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
export const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
export const ThemeContext = React.createContext({
theme: themes.dark
});
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
import { ThemeContext, themes } from "./theme-context";

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme.bind(this)
};
}

toggleTheme() {
console.log("bb");
this.setState(state => ({
theme: state.theme === themes.dark ? themes.light : themes.dark
}));
}
render() {
return (
<ThemeContext.Provider value={this.state}>
<Toolbar />
</ThemeContext.Provider>
);
}
}

function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}

ThemedButton.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { ThemeContext } from "./theme-context";
function ThemedButton() {
return (
<ThemeContext.Consumer>
{({ theme, toggleTheme }) => (
<button
onClick={toggleTheme}
style={{ backgroundColor: theme.background }}
>
Toggle theme
</button>
)}
</ThemeContext.Consumer>
);
}

5. 任意组件

任意两个组件,这两个组件可能非嵌套关系,他们的通信需要依赖事件来处理。

eventEmitter.js

1
2
import EventEmitter from "events";
export default new EventEmitter();

ComponentA.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 eventEmitter from './eventEmitter'
export default ComponentA extends React.Component {
state = {
time: Date.now()
}
constructor(props){
super(props)
}

componentDidMount(){
this.event = eventEmitter.on('SET_TIME', (time) => {
this.setState({time})
})
}

componentWillUnmount(){
eventEmitter.removeListener(this.event)
}

render(){
const {time} = this.state
return (
<div>{time}</div>
)
}
}

ComponentB.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import eventEmitter from './eventEmitter'
export default ComponentA extends React.Component {

constructor(props){
super(props)
this.event = eventEmitter
}

setTime = () => {
this.event.emit('SET_TIME', Date.now())
}

render(){
const {age} = this.state
return (
<div><button onClick={this.setTime}>click to set time</button></div>
)
}
}

index.jsx

1
2
3
4
5
import ComponentA from "./ComponentA";
import ComponentB from "./ComponentB";

ReactDOM.render(<ComponentA />, document.querySelector("#section1"));
ReactDOM.render(<ComponentB />, document.querySelector("#section2"));