はじめに
宣伝から始まり恐縮ですが、Potate という React (ほぼ)互換エンジンを開発中です。React師範代の方々のご協力をお待ちしております。
参加条件は「シンプルが好き」ただそれだけです。「複雑なのが好き」な方は React を使い続ければよいだけですので問題はないかなと。
「コードは書けないが応援はしたい」という方はサブスク型による寄付も受け付けております。
Potate の README をお読みになられて、もしかしたら Astro または Vite または esbuild がないと使えないのか・・・とがっかりする方もいるかもしれませんが、そんなことはありません。
esm.sh/potatejs のような CDN を使えば ペライチ React ならぬ ペライチ Potate も可能です(まだ試してませんが💦)。
私は React は jQuery の進化系くらいにしか思ってませんので、そこは忘れないようにしたいと常々思っております、ハイ。
さて、 Potate を作成していて閃いたのですが(と無理やり関連付ける)、みなさん、実務で React を利用していて、次のような現象に悩まされませんか。
export default props => {
const [state1, setState1] = useState(0)
const [state2, setState2] = useState(0)
const ref1 = useRef(0)
const ref2 = useRef(0)
:
:
useEffect(() => {
初期化1
return 後始末1
}, [])
useEffect(() => {
初期化2
return 後始末2
}, [])
:
:
const handleClick1 = e => {
何か処理1
}
const handleClick2 = e => {
何か処理2
}
:
:
// 💦💦💦やっと描画宣言
return (
<div
onClick={e => {
なんならここでも処理💦
}}
>
描きたいだけなのにナニコレ
</div>
)
}
これくらいならまだ見やすいですが、なんだろう、ロジックはさほど複雑ではないのに、関数の宣言部分から、描画の宣言部分までがやたら離れてしまって見にくいみたいな・・・・
そんな方へのお薦めが liine!(エルいいね!)です。
liine!
liine! はライブラリでも何でもない、ただの思想/宗教の類です。ある特定のライブラリやフレームワークに依存するものでもありません。
覚えるべきは2つの基本形です。
- 状態がロカールのみでよい場合の基本形
- 状態を共有したい場合の基本形
これだけです。さっき考え付いたばかりなので本当はもっとあるのかもしれませんが、たぶんこれだけでいいはずです。
状態がロカールのみでよい場合の基本形
export default props => {
const l = useMemo(logic, [])
useEffect(() => l.cleanup, [])
return (
<div onClick={l.onclick}>
描きたいものが近い!
</div>
)
}
const logic = () => {
let count = 0 // クロージャ―なので値は保持される
let count2, count3・・・・とにかくぜんぶここでやれ
const f = () => count // getter もちろん .get() とかしてもいい
f.set = v => { count = v } // setter もちろん不要ならなくてもいいです
f.onclick = {
・・・
}
f.cleanup = () => {
count = 0 // 今回は不要ですがクリーンアップが必要ならここで
}
return f
}
-
const l = useMemo(logic, [])はconst l = useMemo(() => logic(), [])と書いてもいいです。() =>を何個書くかは好みの問題です。 - とはいっても、
const l = useMemo(() => logic(props), [])これは教義に反するとして、即刻除名処分とさせていただきます。propsを描画部分で単純に使うのは全く問題ないですが、「propsを logic に渡そう」と考えた時点で地獄が始まります。「そんなの信じられん!」 と思う方は是非自分でお試しください。「const l = useMemo(() => logic(props), [props])みたいに[props]を指定すればいけるんじゃね?俺天才」とdepsのことを考え始めたらもう沼る直前まできていますので正気に返り一目散に逃げることをお勧めいたします。
「props 使えないならコンポーネント間の情報共有どうすんだよ!」とう方が進むべき途こそが次の「状態を共有したい場合」です。
状態を共有したい場合の基本形
export default props => {
const l = useMemo(logic, [])
useEffect(() => {
liine.setonclick(l.onClick)
return l.cleanup
}, [])
return (
<div onClick={l.onclick}>
<Inner />
</div>
)
}
const Inner = props => {
const l = useMemo(logic, [])
useEffect(() => l.cleanup, [])
return (
<div onClick={l.onclick}>
<div onClick={liine.onclick}>
情報共有!
</div>
</div>
)
}
const sharedLogic = () => {
let onclick
const f = () => {}
f.onclick = () => onclick
f.setonclick = v => { onclick = v }
return f
}
const liine = sharedLogic()
const logic = () => {
:
return f
}
-
propsに関する補足ですが、例えば<Inner user={user} />みたいな、単純に表示で使うような類のものを渡すのは問題ないです(props バケツリーレー問題は別の次元の問題です)。しかし、<Inner handleInsert={handleInsert} />みたいなのは撲滅しませんかという話であって、そうすれば JSX 部分もかなりすっきりするんではないかと。
その他
-
useEffect,useTransitionのようなライフサイクルや描画に係る Hooks は使わざるを得ないと思います。その辺はカスタムフックにしてすっきりするのがいいかも。
ちなみに Potate ならwatch()API と相性がよいです。 -
描画に係る値は
Jotaiなど(最悪useSyncExternalStore)を経由する必要があります。逆にいえば、描画に係る値は素直に Jotai などで管理すればいいだけで、let countなどのキャプチャされるローカル変数で状態保持みたいな裏技に頼る必要がない、といいますか。 -
"状態を共有したい場合の基本形" で
cleanupが必要な場合はどうすべきか。そこは個々人でお考え下さい。情報を共有しているわけですから少なくとも「誰も使わなくなったらクリア!」みたいな処理になるはずです。 -
logic 内で Async Generator を使いこなせると さらに夢が広がるかもしれません。
-
ロジック複数はあり?
const l1 = useMemo(logic1, [])
const l2 = useMemo(logic2, [])
別にやっても構いませんが、もっといい分け方がある気がしないでもありません。あなたがこれが見やすいと思えるのであれば懲戒事例ではありませんので安心してください。
最後に
現場からは以上です。それでは liine! で楽しい React or Potate Life を!
なお、最後も宣伝で恐縮ですが、Potate などというどこの馬の骨ともわからないようなものは使いたくないが、SSR Emotion はいいね!、いや liine! という方のために SSR Emotion React なるものもご用意しております。奮ってご利用ください。