概述
在第一章我们曾经提过,数据流(Data flow)是React的一个很大的特点,在React的思想中页面完全由数据驱动,也就是UI=f(data)。在React中数据包括props和state,其区别在于state主要用来管理组件自身的UI状态,而props主要用于组件之间的通信。同时React的数据流向最重要的特点在于它是单向的,也就是单向数据流。在这样的的数据流转方式下,当用户修改了一个组件的状态时,React会自动的更新其子孙组件的UI。
组件之间的通信
父子组件的通信
父子组件的通信是React最基础的通信,父组件只需将需要传递的内容通过组件属性传递给子组件就行:
class Father extends React.Component {
render() {
return (
<Child {...props} />
);
}
}
在React的单向数据流中并不允许子组件直接修改或向父组件传递props,子组件向父组件通信需要通过回调函数的方式实现。
class Father extends React.Component {
handleClick(name) {
console.log(`click ${name}`);
} render() {
return (
<Child {...props} handleClick={this.handleClick} />
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'child'
}
}
click() {
this.props.handleClick && this.props.handleClick(this.state.name);
}
render() {
return (
<div onClick={this.click}>click</div>
);
}
}
兄弟组件通信
兄弟组件在React中同样无法直接通信,但是可以通过其共同的父组件进行中转来进行通信。其方法也很简单,其中一个子组件按照上述回调函数的方式向父组件传递信息,父组件再通过props传递给另一个组件即可。
跨层级组件通信
上述组件通信有个很明显的问题,那便是多层级的组件通信,当然通过父子关系和props一层层的传递当然可行,但这样的做法明显不太合理,需要一层层传递props或一层层的处理回调,这样会使得代码的可读性和可维护性变得很差。同时有的属性在项目的多个组件中都会用到,类似于这种情况,React提供了context来使多个组件可以共享一些属性。
需要注意的是,如果只是单纯的多层传递组件导致的代码的可读性的降低,React官方并不推荐使用context,而是推荐通过传递组件而非传递属性的方式来解决这个问题:
我们需要将头像的属性传递给Avatar组件。
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
只有Avatar组件会用到这些参数,这样的传递并不合理,React推荐将Avatar组件传递的方式
function Page(props) {
const user = props.user;
const content = <Feed user={user} />;
const topBar = (
<NavigationBar>
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
</NavigationBar>
);
return (
<PageLayout
topBar={topBar}
content={content}
/>
);
}
Context 能让你将这些数据向组件树下所有的组件进行“广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。
import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// 整个 state 都被传递进 provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}
ReactDOM.render(<App />, document.root);
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
// Theme Toggler 按钮不仅仅只获取 theme 值,它也从 context 中获取到一个 toggleTheme 函数
return (
ThemeContext.Consumer>
{({theme, toggleTheme}) => (
<button onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;
export const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
在这个demo中我们通过createContext创建一个context,然后通过context.Provider来共享给所有的子组件,而子组件可以通过context.Consumer来获取到到共享出的属性。需要注意的是,我们这里向子组件中共享了一个函数,子组件可以通过调用该函数来更新上级组件的状态,实现数据的双向通信。
通过发布订阅模式来进行组件通信
在没有任何关系的组件通信中,比如单页应用中的两个路由页面之间的通信,我们可以使用发布订阅模式来进行通信,我们模拟实现一个简单EventEmitter。
class EventEmitter {
constructor() {
this.eventMap = {};
}
on(name, fn) {
this.eventMap[name] ? this.eventMap[name].push(fn) : this.eventMap[name] = [fn];
}
emit(name, ...args) {
if(!this.eventMap.hasOwnProperty(name)) return;
this.eventMap[name].map((fn) => {
fn.apply(this, args);
})
}
off(name) {
if(!this.eventMap[name]) {
return;
} else {
delete this.eventMap[name];
}
}
}
通过Redux、Mobx等状态管理工具实现组件通信
相信大家在平时的开发过程当中或多或少都接触过Redux活着Mobx等状态管理工具,状态管理工具主要是用于辅助处理复杂大型应用的状态管理,但同时它们结合redux-react等库同样也能实现各个组件的通信,其原理在于redux-react本质上是使用了React中的context API来实现了储存在store中的状态的共享。由于context API我们已在前文中介绍,而Redux和Mobx我们也会在下一章状态管理中详细介绍,所以在这里我们就不再介绍了。
总结
-
React通过props和state来处理数据流,其中props主要用来处理组件之间的通信,而state主要用来管理组件自身状态。
-
组件之间的通信主要分为:
-
父子组件通信,主要通过直接传递props和回调函数来实现
-
兄弟组件通信通过共同的父组件进行中转
-
跨层级组件可以将组件进行传递,或者使用context来处理
-
也可以通过发布订阅模式和状态管理库来实现不同组件的通信