Posted at

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

More than 1 year has passed since last update.

https://qiita.com/mizchi/items/053f5b42a6d0902e9412

やりたかったこと: 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 ダサい…