下面是高德地图覆盖物中的点标记(Marker类)的文档。
可以看到使用 content 属性,就可以自定义 DOM 节点。但传入的是字符串形式,那如何传入 React 组件呢?(下面是思考过程)
- 一
首先,肯定不可能以字符串的形式写入组件。毕竟有使用 JSX ,所以组件需要编译,字符串就没法编译了。
- 二
那用模板字符串,把组件以变量的形式传入吗?
这样子组件虽然可以被编译了,但每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。
从下面 react 源码中,可以看到最后返回的是一个 ReactElement 。
export function createElement(type, config, children) {
//...省略
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
接着往下看,可以看到 ReactElement 返回的 element 就是一个对象。
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
if (__DEV__) {
// The validation flag is currently mutative. We put it on
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
element._store = {};
// To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
// self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
// Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
所以,如果把组件以变量的形式传入的话,出现的恐怕就是一个 object 。
- 三
我想要拿到这个组件被 react 渲染完后的 DOM,要怎么做呢?如何渲染成 DOM 呢?
回想起来了!在 webpage 搭建的项目中的入口文件里,一定会出现这行代码。
ReactDOM.render(element, container[, callback])
如果使用 umi 的话,调用这个方法的代码,不知道被放哪了。所以 umi 用久后,这行代码就有点忘记了~
下面看 render 方法的源码。
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
const isModernRoot =
isContainerMarkedAsRoot(container) &&
container._reactRootContainer === undefined;
if (isModernRoot) {
console.error(
'You are calling ReactDOM.render() on a container that was previously ' +
'passed to ReactDOM.createRoot(). This is not supported. ' +
'Did you mean to call root.render(element)?',
);
}
}
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
从代码的传参就已经可以知道 container 是必传的了。(里面写到的 invariant 是一种在开发中提供描述性错误,但在生产中提供通用错误的方法)
export function isValidContainer(node: mixed): boolean {
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(node.nodeType === COMMENT_NODE &&
(node: any).nodeValue === ' react-mount-point-unstable '))
);
}
代码里也对这里做了判断,是否是有效的容器。
- 解决方案
所以最后的解决方案是,先渲染一个带 id 的div。
var marker = new AMap.Marker({
position: new AMap.LngLat(116.40061686197998, 39.845549045139),
content: '<div id="test" ></div>',
});
map.add([marker]);
然后通过 ReactDOM.render 方法,把组件渲染到这个 div 上。
let fun = setInterval(() => {
if (document.getElementById('test')) {
ReactDOM.render(<Chestnut />, document.getElementById('test'));
clearInterval(fun);
}
}, 500);
因为 map.add() 方法不会使 react 重新 render,所以这边用到了一个定时循环,确保地图上增加了这个 div 后,把组件渲染上去。
- 最后
想到这个解决方案,其实没有花费太多时间,就很顺利的一步一步分析尝试下来。
但在解决的那一瞬间,其实内心有小激动一下。因为很明显的发现自己比以前肯定是成长了~
网友评论