组件通信无外乎以下几种情况:
- 父子通信
- 子向父通信
- 同级通信
- 跨层级通信
- 任意组件
1. 父子通信
父子通信是最常见的通信方式,父组件通过props
将数据传递到子组件。
1 2 3 4
| export default function Child({ name }) { return <h1>Hi, {name}</h1>; }
|
1 2 3 4 5 6 7 8
| 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
| 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 component 和 Consumer 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"));
|