下面是高德地图覆盖物中的点标记(Marker类)的文档。
可以看到使用 content 属性,就可以自定义 DOM 节点。传入 HTML 字符串,或 HTML DOM 对象形式,那如何传入 React 组件呢?
- 一
首先,肯定不可能以字符串的形式写入组件。毕竟是使用 JSX ,所以组件需要编译,字符串就没法编译了。
- 二
那用模板字符串,把组件以变量的形式传入吗?
这样子组件虽然可以被编译了,但每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。(createElement 几乎都是在格式化数据)
从下面 react 源码中,可以看到最后返回的是一个 ReactElement 。
export function createElement(type, config, children) {
//......
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
接着往下看,可以看到 ReactElement 返回的 element 就是一个对象。(简短的代码里可以看到,就是把传入的参数按照一定的规范,“组装”进了 element 对象里)
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// 是一个常量,用来标识该对象是一个ReactElement
$$typeof: REACT_ELEMENT_TYPE,
// 内置属性赋值
type: type,
key: key,
ref: ref,
props: props,
// 记录创造该元素的组件
_owner: owner,
};
if (__DEV__) {
// 针对 __DEV__ 环境下的处理
}
return element;
};
所以,如果把组件以变量的形式传入的话,出现的恐怕就是一个 ReactElement 对象实例(本质上是以 JavaScript 对象形式存在的对 DOM 的描述,也就是虚拟 DOM)。
- 三
既然是“虚拟 DOM”,那如何得到渲染到页面上的真实 DOM 呢?react 负责在浏览器环境渲染的 Renderer 是?
—— ReactDOM!(在每一个 React 项目的入口文件里,一定会出现下面的这行代码)
ReactDOM.render(element, container[, callback])
下面看 render 方法的源码。
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
// 针对 __DEV__ 环境下的处理
}
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 后,把组件渲染上去。
- 代码优化
const domRender = async reactElement => {
const div = document.createElement('div');
await new Promise(resolve => {
ReactDOM.render(reactElement, div, resolve);
});
return div.firstChild;
};
async () => {
const element = await domRender(<Chestnut />);
const text = new AMap.Marker({
position: new AMap.LngLat(116.40061686197998, 39.845549045139),
content: element,
});
map.add([text]);
};
- 最后
想到解决方案,其实没有花费太多时间,就很顺利的一步一步分析尝试下来。
但在解决的那一瞬间,内心有兴奋一下。所以决定稍微记录一下~