お疲れ様です。
今回は、kintoneプラグインの開発時に問題になった「kintone-ui-component v1 が React 非対応」という点について対処した方法について書いてみます。
背景
どういった背景かというと
kintone-ui-component v0 を使ってプラグインを開発していた
↓
プラグインの内部では、react をつかっていた
↓
kintone-ui-component v0 が2023/12/31 でサポート終了
↓
じゃあkintone-ui-component v1 に置き換えよう
↓
『kintone-ui-component v1 は react 非対応です』
『kintone-ui-component v1 は react 非対応です』
https://cybozu.dev/ja/kintone/sdk/library/kintone-ui-component-v1/
Oh...
困りごと
kintoneプラグインは react で実装されている
kintone-ui-component v1 を使いたい
kintone-ui-component v1 は react 非対応
↑こんな感じで「Its instance type [component名] is not a valid JSX component.」というエラーになる
分析
原因も何もJSX component ではないものをJSX component 内に含めることはできないという理由だ
無理やりkintone-ui-component v1 を JSX component に変換してみようと React.createElementを使って書いてみてもビルドエラーか実行時エラーになる
これはもうreactを諦めて素のTypeScriptに戻すしかないのか
もしくはkintone-ui-component のようなものを自作するか
諦めかけていたがふとした拍子に光明が見えた
解決方法 → @lit/react
kintone-ui-component v1 のソースコードを見ていたところ、
各種Component は KucBase というクラスを拡張していた
KucBase は、LitElement というクラスを拡張していた
LitElement というやつは、ReactiveComponent を拡張していた
ReactiveComponent というのを見て
「Reactと似たような名前なのにReactに対応してないんだな」
「props とかあるのにReactComponentじゃないのか」
と思っていた
LitElement ってなんだろうと思い調べてみると
LitというのはGoogle社製のOSS
https://lit.dev/
シンプルで早くWebComponentを開発できるとのこと
LitとReactの親和性を調べてみると、
「@lit/react」というOSSがあることが判明
@lit/react
https://www.npmjs.com/package/@lit/react
こちらのOSSを使ってラップしてみたところ、Reactで書かれたソースコードからkintone-ui-component v1 を呼び出すことに成功
各種イベントハンドリングもつなげることが可能
Button.tsx
// KUC V1とReactをつなぐために@lit/reactを使ってLitElementをReactComponentに変換するラッパーを用意した
import React from 'react';
import { createComponent } from '@lit/react';
import { Button as KucButton } from 'kintone-ui-component/lib';
export const Button = createComponent({
tagName: 'kuc-button-1-15-0',
elementClass: KucButton,
react: React,
events: {},
});
kintone-ui-component v1 のボタンをラップしたButton.tsxの実装をおいておきます
tagName のところにkintone-ui-component のバージョンまで入れないとElementをうまく認識してくれずでした
kintone-ui-component のバージョンを上げるとラッパー側のコードも直さなきゃいけないのは微妙...
ここはうまいことバージョンを動的に入れられればいいけどひとまず決め打ち
events は空ですが、デフォルトでonClick などは備えているので呼び出し元からこんな風に書ける
MinusButton.tsx
import { Button } from '../atoms/kintone/Button';
import React, { memo, VFC } from 'react';
type Props = {
onClick: () => void;
isDisabled?: boolean;
};
const MinusButton: VFC<Props> = memo(({ onClick, isDisabled = false }) => {
return (
<div className="icon-button">
<Button text="-" onClick={onClick} disabled={isDisabled} />
</div>
);
});
export default MinusButton;
他にもラジオボタンのラッパーを実装していたところ
onchange はデフォルトで生えてないようだったので、カスタムイベントとして登録した
RadioButton.tsx
// KUC V1とReactをつなぐために@lit/reactを使ってLitElementをReactComponentに変換するラッパーを用意した
import React from 'react';
import { createComponent } from '@lit/react';
import { RadioButton as KucRadioButton } from 'kintone-ui-component/lib';
export const RadioButton = createComponent({
tagName: 'kuc-radio-button-1-15-0',
elementClass: KucRadioButton,
react: React,
events: {
onchange: 'change',
},
});
events に onchange:'change' と記載する
ラジオボタンの呼び出し元ではちょっと工夫して書く必要あり
RadioButtonField.tsx
import React, { FormEvent, memo, useCallback, useEffect, useReducer, useState, VFC } from 'react';
import { RadioButton } from '../atoms/kintone/RadioButton';
type RadioOption = {
value: string;
label: string;
};
type Props = {
id?: string;
name: string;
items: RadioOption[];
onChange: (name: string, value: string) => void;
value: string;
label: string;
isRequired?: boolean;
isDisabled?: boolean;
};
export const RadioButtonField: VFC<Props> = memo(
({ name, items, onChange, label, value, id, isRequired = false, isDisabled = false }) => {
const [radioValue, setRadioValue] = useState<string>('');
useEffect(() => {
setRadioValue(value);
}, [value]);
const handleChange = (event: Event) => {
const customEvent = event as CustomEvent;
if (!customEvent.detail) {
return;
}
change(customEvent.detail.value);
};
const change = useCallback(
async (newValue: string) => {
if (newValue !== value) {
setRadioValue(newValue);
onChange(name, newValue);
}
},
[name, onChange]
);
return (
<div id={id} className="row-item">
<div className="label">
<div>{label}<span className='required'>{isRequired ? "*" : " "}</span></div>
</div>
<RadioButton
items={items}
value={radioValue}
borderVisible={false}
itemLayout='vertical'
onchange={(event) => {
handleChange(event);
}}
disabled={isDisabled}></RadioButton>
</div>
);
}
);
export default RadioButtonField;
だいぶ雑な感じですが、ひとまずこれでonchange も動きました
こんな風にラッパーを用意することで一通りのComponentをReact上で動作させることに成功
注意点としては、
@lit/react が今後kintone-ui-component v1 で動作するかは保証されていないのでなんらかの拍子に動かなくなる可能性はある
締め
なかば諦めかけていたReact上でのkintone-ui-component v1 の動作ですが、なんとかなりました
ネット上での情報はなかったので同じようなお困りごとの方の助けになればと思いこちらに残しておきます
こちらの記事が役に立った/参考になった/面白かったというかたは👍を押して言っていただけると励みになります!
ここまで読んでいただきありがとうございました