高德地图如何传入 React 组件(附思考过程)

下面是高德地图覆盖物中的点标记(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,要怎么做呢?react 负责在浏览器环境渲染的 Renderer 是? ReactDom!在 webpage 搭建的项目中的入口文件里,一定会出现的这行代码。
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__) {
    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 后,把组件渲染上去。
  • 最后
想到这个解决方案,其实没有花费太多时间,就很顺利的一步一步分析尝试下来。 但在解决的那一瞬间,其实内心有小激动一下。因为很明显的发现自己比以前肯定是成长了~

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):