高德地圖如何傳入 React 組件(附React 源碼簡單分析過程)

下面是高德地圖覆蓋物中的點(diǎn)標(biāo)記(Marker類)的文檔。

可以看到使用 content 屬性,就可以自定義 DOM 節(jié)點(diǎn)。傳入 HTML 字符串,或 HTML DOM 對(duì)象形式,那如何傳入 React 組件呢?

首先,肯定不可能以字符串的形式寫入組件。畢竟是使用 JSX ,所以組件需要編譯,字符串就沒法編譯了。

那用模板字符串,把組件以變量的形式傳入嗎?
這樣子組件雖然可以被編譯了,但每個(gè) JSX 元素只是調(diào)用 React.createElement(component, props, ...children) 的語法糖。(createElement 幾乎都是在格式化數(shù)據(jù))
從下面 react 源碼中,可以看到最后返回的是一個(gè) ReactElement 。

export function createElement(type, config, children) {
    //......
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

接著往下看,可以看到 ReactElement 返回的 element 就是一個(gè)對(duì)象。(簡短的代碼里可以看到,就是把傳入的參數(shù)按照一定的規(guī)范,“組裝”進(jìn)了 element 對(duì)象里)

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 是一個(gè)常量,用來標(biāo)識(shí)該對(duì)象是一個(gè)ReactElement
    $$typeof: REACT_ELEMENT_TYPE,

    // 內(nèi)置屬性賦值
    type: type,
    key: key,
    ref: ref,
    props: props,

    // 記錄創(chuàng)造該元素的組件
    _owner: owner,
  };

  if (__DEV__) {
   // 針對(duì) __DEV__ 環(huán)境下的處理
  }

  return element;
};

所以,如果把組件以變量的形式傳入的話,出現(xiàn)的恐怕就是一個(gè) ReactElement 對(duì)象實(shí)例(本質(zhì)上是以 JavaScript 對(duì)象形式存在的對(duì) DOM 的描述,也就是虛擬 DOM)。

既然是“虛擬 DOM”,那如何得到渲染到頁面上的真實(shí) DOM 呢?react 負(fù)責(zé)在瀏覽器環(huán)境渲染的 Renderer 是?
—— ReactDOM!(在每一個(gè) React 項(xiàng)目的入口文件里,一定會(huì)出現(xiàn)下面的這行代碼)

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__) {
   // 針對(duì) __DEV__ 環(huán)境下的處理
  }
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

從代碼的傳參就已經(jīng)可以知道 container 是必傳的了。(里面寫到的 invariant 是一種在開發(fā)中提供描述性錯(cuò)誤,但在生產(chǎn)中提供通用錯(cuò)誤的方法)

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 '))
  );
}

代碼里也對(duì)這里做了判斷,是否是有效的容器。

  • 解決方案

所以最后的解決方案是,先渲染一個(gè)帶 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 方法,把組件渲染到這個(gè) div 上。

    let fun = setInterval(() => {
      if (document.getElementById('test')) {
        ReactDOM.render(<Chestnut />, document.getElementById('test'));
        clearInterval(fun);
      }
    }, 500);

因?yàn)?map.add() 方法不會(huì)使 react 重新 render,所以這邊用到了一個(gè)定時(shí)循環(huán),確保地圖上增加了這個(gè) div 后,把組件渲染上去。

  • 代碼優(yōu)化
  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]);
  };
  • 最后

想到解決方案,其實(shí)沒有花費(fèi)太多時(shí)間,就很順利的一步一步分析嘗試下來。
但在解決的那一瞬間,內(nèi)心有興奮一下。所以決定稍微記錄一下~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容