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