踩坑場景
在做業務的時候,有些模組是可以拖動的,恰好這些模組需要從根元件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,
collect,
options,
});
};
}
那我們繼續去看一看 decorateHandler
這個函式唄
export default function decorateHandler({
DecoratedComponent,
createHandler,
createMonitor,
createConnector,
registerHandler,
containerDisplayName,
getType,
collect,
options,
}) {
...
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, connectDragSource, src } = this.props;
const { value } = this.context;
...
return (
connectDragSource(
...
)
);
}
}
const Temp = Box;
const Box1 = DragSource(`box`, boxSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
}))(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})
這次總算拿到正確的結果了,開心