踩坑场景
在做业务的时候,有些模块是可以拖动的,恰好这些模块需要从根组件App的context上拿属性,同时App也是作为拖动上下文,被@DragDropContext(HTML5Backend)装饰,当时年少无知,无脑写下了以下代码
const BoxSource = { canDrag(props,monitor) { ... },beginDrag(props) { ... },endDrag(props,monitor) { ... },}; @DragSource('Box',BoxSource,(connect,monitor) => ({ connectDragSource: connect.dragSource(),isDragging: monitor.isDragging(),})) export default class Box extends Component { static contextTypes = { value: PropTypes.number }; static propTypes = { ... } render() { const { isDragging,connectDragSource,src } = this.props; const { value } = this.context; return ( connectDragSource( ... ) ); } }
美滋滋啊,美滋滋啊,so ez,会用react-dnd了,赶紧将代码跑起来,结果傻眼了,居然报这个错误
Invariant Violation: Could not find the drag and drop manager in the context of Box. Make sure to wrap the top-level component of your app with DragDropContext. Read more: http://react-dnd.github.io/react-dnd/docs-troubleshooting.html#could-not-find-the-drag-and-drop-manager-in-the-context
提示我们在拖拽组件Box的context上找不到react-dnd需要的drag and drop manager,懵了,让我想想是咋回事,是不是最后给
static contextTypes = { value: PropTypes.number }
给覆盖了原来的Box.contextTypes
呀?
不过这也简单,不让他覆盖就好了嘛,于是我写下了如下的代码
Box.contextTypes = Object.assign(Box.contextTypes,{ value: PropTypes.number });
真好,报错消失了,大功告成!等等,this.context.value怎么是undefined
,拿不到了?我明明在contextTypes里声明了呀,不行,还是得去看一看源码。
React-dnd源码
查看DragSource的源码,可以看到DragSource就是一个普通装饰器包装函数
function DragSource(type,spec,collect,options = {}) { ... return function decorateSource(DecoratedComponent) { return decorateHandler({ connectBackend: (backend,sourceId) => backend.connectDragSource(sourceId),containerDisplayName: 'DragSource',createHandler: createSource,registerHandler: registerSource,createMonitor: createSourceMonitor,createConnector: createSourceConnector,DecoratedComponent,getType,options,}); }; }
那我们继续去看一看 decorateHandler
这个函数呗
export default function decorateHandler({ DecoratedComponent,createHandler,createMonitor,createConnector,registerHandler,containerDisplayName,}) { ... class DragDropContainer extends Component { ... static contextTypes = { dragDropManager: PropTypes.object.isrequired,} ... render() { return ( <DecoratedComponent {...this.props} {...this.state} ref={this.handleChildRef} /> ); } } return hoistStatics(DragDropContainer,DecoratedComponent); }
嗯, decorateHandler
就是一个HOC生成函数嘛,hoistStatics
就是hoist-non-react-statics
这个库,做过HOC的童鞋一定不陌生,他就是将WrappedComponent的静态方法和静态属性提到HOC上,面,避免WrappedComponent的静态属性和静态方法丢失了,看似挺合理,嗯嗯。等等!这不就用WrappedComponent的contextTypes将HOC的contextTypes给覆盖了么?这也很合理的解释了为啥会报错了。
解决步骤
知道了其中的原来,那我们就让HOC和WrappedComponent各自保留一份contextTypes好了,首先我们需要用另一个变量来保留对WrappedComponent的引用,因为被@DragSource
装饰后,WrappedComponent的变量名就会被HOC覆盖了,然后我们再对WrappedComponent加上contextTypes就好了,代码如下:
class Box extends Component { static propTypes = { connectDragSource: PropTypes.func.isrequired,... } render() { const { isDragging,src } = this.props; const { value } = this.context; ... return ( connectDragSource( ... ) ); } } const Temp = Box; const Box1 = DragSource('Box',}))(Box); Temp.contextTypes = { value: PropTypes.number,} export default Box1;
大功告成,我们再来跑一跑。
哇,又报错了,囧,说
Invariant Violation: App.getChildContext(): childContextTypes must be defined in order to use getChildContext().
好,那我们来看看根组件咋回事,我写的根组件如下
@DragDropContext(HTML5Backend) class App extends React.Component { constructor(props) { super(props); } static childContextTypes = { value:PropTypes.number,} getChildContext(){ return { value:1 } } render() { return ( <Box /> ) } }
让我们看看DragDropContext
源码
export default function DragDropContext(backendOrModule) { ... return function decorateContext(DecoratedComponent) { ... class DragDropContextContainer extends Component { getChildContext() { return childContext; } render() { return ( <DecoratedComponent {...this.props} ref={(child) => { this.child = child; }} /> ); } } return hoistStatics(DragDropContextContainer,DecoratedComponent); }; }
得,又是HOC的问题,但是有点不同,就是contextTypes一定要准确设置在需要的组件上,但是childContextTypes只要放在上层组件就可以了,所以我做了如下修改:
- 删去
class App
中的
static childContextType = { value: PropTypes.number }
App.childContextTypes = Object.assign(App.childContextTypes,{ value: PropTypes.number });
这次总该行了吧,心累啊。嗯?还是拿不到this.context.value
,想起来了!,虽然hoist-non-react-statics
将静态属性拿了出来,但是原型方法不会拿出来啊,所以WrappedComponent的getChildContext
就没用了,所以我们需要也将他拿出来,于是,加上一下代码
const temp = {...App.prototype.getChildContext()}; App.prototype.getChildContext = () => ({...temp,value:1})
这次总算拿到正确的结果了,开心