LoginSignup
19
9

More than 5 years have passed since last update.

続・JSフレームワークの末端がWebComponentsになるのか、なれるのか、検証してみた

Posted at

やりたかったこと: Reactから子に関数参照を渡すのを綺麗に実装したい

前回からの変化

  • lit-extended を使えば、テンプレートにコールバックを渡せることを知った
  • 子に対して関数参照を渡すのではなく、 CustomEvent を生成するようにして dispatchEvent するようにした
  • 一旦変換用のイベント定義辞書を渡すようにした

WebComponent定義

/* @flow */
import { html, render } from 'lit-html/lib/lit-extended'

type Props = {
  onClick?: Function,
  text: string
}

const template = (props: Props) => {
  return html`
    <button on-click=${props.onClick}>${props.text}</button>
  `
}

export default class MyButton extends HTMLElement {
  static get observedAttributes(): string[] {
    return ['text']
  }

  connectedCallback() {
    this.render()
  }

  attributeChangedCallback(_attrName: string, _old: any, _new: any) {
    this.render()
  }

  render() {
    const text = this.getAttribute('text') || 'button'
    const onClick = () =>
      this.dispatchEvent(
        new CustomEvent('my-button-clicked', {
          detail: {
            text
          }
        })
      )
    render(template({ text, onClick }), this)
  }
}

window.customElements.define('my-button', MyButton)

静的な値だけを一旦渡すようにした。

React側

reactify という関数で、特定のエレメントをReactで表示する際にどう値を渡すか定義する

/* @flow */
import ReactDOM from 'react-dom'
import * as React from 'react'
import '~/elements/MyButton'

const reactify = (name: string, opts = {}) =>
  class extends React.PureComponent {
    constructor() {
      super()
      this._listeners = []
    }
    componentDidMount() {
      const el = ReactDOM.findDOMNode(this)
      for (const eventName of Object.keys(opts.eventMap || {})) {
        const translated = opts.eventMap[eventName]
        const callback = this.props[translated]
        console.log('register', eventName, translated)
        el.addEventListener(eventName, callback)
        this._listeners.push({ eventName, callback })
      }
    }
    componentWillUnmount() {
      this._listeners.forEach(listener => {
        el.removeEventListener(listener.eventName, listener.callback)
      })
      this._listeners = []
    }
    render() {
      return React.createElement(name, this.props)
    }
  }

const MyButtonReact = reactify('my-button', {
  eventMap: {
    'my-button-clicked': 'onMyButtonClicked'
  }
})

export default function Home() {
  return (
    <div>
      <MyButtonReact
        text="my button on react"
        onMyButtonClicked={_ev => console.log('clicked!')}
      />
      {/* <my-button text="my-button" /> */}
    </div>
  )
}

「このイベントが来たらこのコールバックに渡す」という中間層を用意したことで、とりあえず動くようになった。

残ってる点

  • 本当は define した名前ではなく オブジェクト参照を使いたいが React.createElement が対応していないので、一旦こうなった。
  • やっぱ eventMap ダサい…
19
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
9