父组件 => 子组件
Props
Instance Methods
子组件 => 父组件
Callback Functions
Event Bubbling
兄弟组件之间
Parent Component
不太相关的组件之间
Context
Portals
Global Variables
Observer Pattern
Redux等
父组件通过props把数据传给子组件,子组件通过this.props去使用相应的数据
const Child = ({ name }) => {
<div>{name}</div>
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'zach'
}
}
render() {
return (
<Child name={this.state.name} />
)
}
}
父组件可以通过使用refs来直接调用子组件实例的方法,看下面的例子:
class Child extends React.Component {
myFunc() {
return "hello"
}
}
class Parent extends React.Component {
componentDidMount() {
var x = this.foo.myFunc() // x is now 'hello'
}
render() {
return (
<Child
ref={foo => {
this.foo = foo
}}
/>
)
}
}
大致的过程:
首先子组件有一个方法myFunc
父组件给子组件传递一个ref属性,并且采用callback-refs的形式。这个callback函数接收react组件实例/原生dom元素作为它的参数。当父组件挂载时,react会去执行这个ref回调函数,并将子组件实例作为参数传给回调函数,然后我们把子组件实例赋值给this.foo。
最后我们在父组件当中就可以使用this.foo来调用子组件的方法咯
class Modal extends React.Component {
show = () => {// do something to show the modal}
hide = () => {// do something to hide the modal}
render() {
return <div>I'm a modal</div>
}
}
class Parent extends React.Component {
componentDidMount() {
if(// some condition) {
this.modal.show()
}
}
render() {
return (
<Modal
ref={el => {
this.modal = el
}}
/>
)
}
}
子组件通过调用父组件传来的回调函数,从而将数据传给父组件。
const Child = ({ onClick }) => {
<div onClick={() => onClick('zach')}>Click Me</div>
}
class Parent extends React.Component {
handleClick = (data) => {
console.log("Parent received value from child: " + data)
}
render() {
return (
<Child onClick={this.handleClick} />
)
}
}
利用的是原生dom元素的事件冒泡机制。
class Parent extends React.Component {
render() {
return (
<div onClick={this.handleClick}>
<Child />
</div>
);
}
handleClick = () => {
console.log('clicked')
}
}
function Child {
return (
<button>Click</button>
);
}
通过父组件作为中间层来实现数据互通了。
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {count: 0}
}
setCount = () => {
this.setState({count: this.state.count + 1})
}
render() {
return (
<div>
<SiblingA
count={this.state.count}
/>
<SiblingB
onClick={this.setCount}
/>
</div>
);
}
}
React.createContext创建了一个Context对象,某个组件订阅了这个对象,当react去渲染这个组件时,会从离这个组件最近的一个Provider组件中读取当前的context值
Context.Provider: 每一个Context对象都有一个Provider属性,这个属性是一个react组件。
在Provider组件以内的所有组件都可以通过它订阅context值的变动。具体来说,Provider组件有一个叫value的prop传递给所有内部组件,每当value的值发生变化时,Provider内部的组件都会根据新value值重新渲染
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
const value = this.context
return <Button theme={value} />;
}
}
function ThemedButton {
return (
<ThemeContext.Consumer>
{value => <Button theme={value} />}
</ThemeContext.Consumer>
)
}
类似Vue的teleport ,使用 react-dom/createPortal
<html>
<body>
<div id="react-root"></div>
<div id="portal-root"></div>
</body>
</html>
然后我们创建一个可复用的portal容器,这里使用了react hooks的语法,看不懂的先过去看下我另外一篇讲解react hooks的文章:30分钟精通React今年最劲爆的新特性——React Hooks
import { useEffect } from "react";
import { createPortal } from "react-dom";
const Portal = ({children}) => {
const mount = document.getElementById("portal-root");
const el = document.createElement("div");
useEffect(() => {
mount.appendChild(el);
return () => mount.removeChild(el);
}, [el, mount]);
return createPortal(children, el)
};
export default Portal;
最后在父组件中使用我们的portal容器组件,并将Tooltip作为children传给portal容器组件
const Parent = () => {
const [coords, setCoords] = useState({});
return <div style={{overflow: "hidden"}}>
<Button>
Hover me
</Button>
<Portal>
<Tooltip coords={coords}>
Awesome content that is never cut off by its parent container!
</Tooltip>
</Portal>
</div>
}
将对象挂载到window对象下
class ComponentA extends React.Component {
handleClick = () => window.a = 'test'
...
}
class ComponentB extends React.Component {
render() {
return <div>{window.a}</div>
}
}
观察者模式,发布监听 当我们有两个完全不相关的组件想要通信时,就可以利用这种模式,其中一个组件负责订阅某个消息,而另一个元素则负责发送这个消息。javascript提供了现成的api来发送自定义事件: CustomEvent,我们可以直接利用起来。
首先,在ComponentA中,我们负责接受这个自定义事件:
class ComponentA extends React.Component {
componentDidMount() {
document.addEventListener('myEvent', this.handleEvent)
}
componentWillUnmount() {
document.removeEventListener('myEvent', this.handleEvent)
}
handleEvent = (e) => {
console.log(e.detail.log) //i'm zach
}
}
然后,ComponentB中,负责在合适的时候发送该自定义事件:
class ComponentB extends React.Component {
sendEvent = () => {
document.dispatchEvent(new CustomEvent('myEvent', {
detail: {
log: "i'm zach"
}
}))
}
render() {
return <button onClick={this.sendEvent}>Send</button>
}
}
这样我们就用观察者模式实现了两个不相关组件之间的通信。当然现在的实现有个小问题,我们的事件都绑定在了document上,这样实现起来方便,但很容易导致一些冲突的出现,所以我们可以小小的改良下,独立一个小模块EventBus专门这件事:
class EventBus {
constructor() {
this.bus = document.createElement('fakeelement');
}
addEventListener(event, callback) {
this.bus.addEventListener(event, callback);
}
removeEventListener(event, callback) {
this.bus.removeEventListener(event, callback);
}
dispatchEvent(event, detail = {}){
this.bus.dispatchEvent(new CustomEvent(event, { detail }));
}
}
export default new EventBus
然后我们就可以愉快的使用它了,这样就避免了把所有事件都绑定在document上的问题:
import EventBus from './EventBus'
class ComponentA extends React.Component {
componentDidMount() {
EventBus.addEventListener('myEvent', this.handleEvent)
}
componentWillUnmount() {
EventBus.removeEventListener('myEvent', this.handleEvent)
}
handleEvent = (e) => {
console.log(e.detail.log) //i'm zach
}
}
class ComponentB extends React.Component {
sendEvent = () => {
EventBus.dispatchEvent('myEvent', {log: "i'm zach"}))
}
render() {
return <button onClick={this.sendEvent}>Send</button>
}
}
最后我们也可以不依赖浏览器提供的api,手动实现一个观察者模式,或者叫pub/sub,或者就叫EventBus。
function EventBus() {
const subscriptions = {};
this.subscribe = (eventType, callback) => {
const id = Symbol('id');
if (!subscriptions[eventType]) subscriptions[eventType] = {};
subscriptions[eventType][id] = callback;
return {
unsubscribe: function unsubscribe() {
delete subscriptions[eventType][id];
if (Object.getOwnPropertySymbols(subscriptions[eventType]).length === 0) {
delete subscriptions[eventType];
}
},
};
};
this.publish = (eventType, arg) => {
if (!subscriptions[eventType]) return;
Object.getOwnPropertySymbols(subscriptions[eventType])
.forEach(key => subscriptions[eventType][key](arg));
};
}
export default EventBus;
最后终于来到了大家喜闻乐见的Redux等状态管理库,当大家的项目比较大,前面讲的9种方法已经不能很好满足项目需求时,才考虑下使用redux这种状态管理库。这里就先不展开讲解redux了...否则我花这么大力气讲解前面9种方法的意义是什么???