一个简单的解决方案是使用该context
功能:
const MainView = React.createClass({
childContextTypes: {
triggerAlert: PropTypes.func.isRequired,
},
getChildContext() {
return {
triggerAlert: alert => this.setState({ alert }),
};
},
getInitialState() {
return {
alert: null,
};
},
render() {
return (
<div>
{this.props.children}
<Alert alert={this.state.alert} />
</div>
};
},
});
const AnyChild = React.createClass({
contextTypes: {
triggerAlert: PropTypes.func.isRequired,
},
handleClick() {
this.context.triggerAlert('Warning: you clicked!');
},
render() {
return <button onClick={this.handleClick} />
},
});
但是,这与命令式 API 相关联。这也意味着产生警报的所有子组件必须重新实现一些样板。
您还可以将与警报渲染相关的所有内容移动到它们自己的组件中,将您的实现隐藏在更简单的 API 下:
const AlertRenderer = React.createClass({
childContextTypes: {
addAlert: PropTypes.func.isRequired,
},
getChildContext() {
return {
addAlert: this.addAlert,
};
},
getInitialState() {
return {
alerts: [],
};
},
addAlert(alert) {
this.setState(({ alerts }) =>
({ alerts: alerts.concat([alert]) })
);
const remove = () => {
this.setState(({ alerts }) =>
({ alerts: alerts.splice(alerts.indexOf(alert), 1) })
);
};
const update = () => {
this.setState(({ alerts }) =>
({ alerts: alerts.splice(alerts.indexOf(alert), 1, alert) })
);
};
return { remove, update };
},
render() {
return (
<div>
{this.props.children}
{this.state.alerts.map(alert =>
<this.props.component
key={alert} // Or whatever uniquely describes an alert
alert={alert}
/>
)}
</div>
);
},
});
const Alert = React.createClass({
contextTypes: {
addAlert: PropTypes.func.isRequired,
},
propTypes: {
alert: PropTypes.any.isRequired,
},
componentWillMount() {
const { update, remove } = this.context.addAlert(this.props.alert);
this.updateAlert = update;
this.removeAlert = remove;
},
componentWillUnmount() {
this.removeAlert();
},
componentWillReceiveProps(nextProps) {
this.updateAlert(nextProps.alert);
},
render() {
return null;
},
});
然后您的应用程序代码变为:
const AnyChild = React.createClass({
getInitialState() {
return {
alert: null,
};
},
handleClick() {
this.setState({
alert: 'Warning: you clicked the wrong button!',
});
},
render() {
return (
<div>
<button onClick={this.handleClick}>
</button>
{this.state.alert &&
<Alert alert={this.state.alert} />
}
</div>
);
},
});
const MainView = React.createClass({
render() {
return (
<AlertRenderer component={MyAlertComponent}>
{this.props.children}
</AlertRenderer>
);
},
});
这种特定方法的一个问题是,每当更新警报时,AlertRenderer 都会重新渲染其所有子项。当 AlertSub 的 componentWillReceiveProps 生命周期再次触发时,这可能会导致无限递归。请注意,仅当您的组件具有可变道具且未实现时才会出现此问题shouldComponentUpdate
。
解决此问题的一种简单方法是创建一个条件子渲染器,它仅在检测到其子渲染器已更改时才更新。
下面的示例代码为单个 child创建了这样一个条件渲染器。为了有条件地渲染多个孩子,您需要将其包装到一个额外的 DOM 组件中。
const ShouldSingleChildUpdate = React.createClass({
shouldComponentUpdate(nextProps) {
return nextProps.children !== this.props.children;
},
render() {
return this.props.children;
},
});
const AlertRenderer = React.createClass({
/* ... */
render() {
return (
<div>
<ShouldSingleChildUpdate>
{this.props.children}
</ShouldSingleChildUpdate>
{this.state.alerts.map(alert =>
<this.props.component
key={alert} // Or whatever uniquely describes an alert
alert={alert}
/>
)}
</div>
);
},
});
请注意,这种方法适用于任何类型的模态,其中可以随时在屏幕上显示多个模态。在您的情况下,由于您在任何时候都应该只有一个Alert
组件在屏幕上,您可以AlertRenderer
通过仅在状态中存储一个警报来简化代码。