[React Three Fiber]r3fとrapierを使ってVueのslot的なことをやりたい
やったこと
物理演算ライブラリであるrapierを使っていて、PhysicsコンポーネントとRigidBodyコンポーネントを別のコンポーネントとして書きたくなった。(ファイルを分けたい)
しかし、以下のようにPhysicsとは別のところにRigidBodyを記載してしまうとRigidBodyはPhysicsコンポーネント内部に書けと怒られる。
<Physics gravity={[0, -30, 0]} colliders="cuboid" debug={debug}>
</Physics>
<RigidBody position={[0, -1, 0]} type="fixed" colliders="false">
<CuboidCollider restitution={0.01} args={[1000, 1, 1000]} />
</RigidBody>
そのためコンポーネント内部に別のコンポーネントを入れ子にしたい。
この時vueだとslotでそれができたが、同じものがReactではわからなかったのでslotのように機能するコンポーネントを作成した。
やり方
まずはslotのように機能するクラスを用意する。
// vueでいうslotのようなものをreactで実現するためのクラス
// 実装参考
// https://blog.capilano-fw.com/?p=11118
import { Children } from "react";
export class ComponentSlot {
constructor(children) {
this.children = Children.toArray(children);
}
get(key, defaultValue = "") {
const contents = this.children.find((child) => {
return (
child.type === "template" && child.props.slot === key // スロット名が一致する要素を返す
);
});
return contents ? contents.props.children : defaultValue; // コンテンツがない場合はデフォルト値を返す
}
}
定義したコンポーネントを使用してPhysicsコンポーネントを使用したWorldコンポーネントを新規で定義する。
/** 物理演算の世界を作る関数
* 実装参考:
* 当たり判定固定(静的)回り:
* https://codesandbox.io/p/sandbox/bruno-simons-20k-challenge-857z1i?file=%2Fsrc%2FApp.js
* https://codesandbox.io/p/sandbox/pensive-drake-66cd7?
*
* vueのslot的なことをやる:
* https://blog.capilano-fw.com/?p=11118
*/
import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier";
import { useControls } from "leva";
import { ComponentSlot } from "@components/common/helper/Componentslot";
export default function World(props) {
const { debug } = useControls({ debug: false });
const slot = new ComponentSlot(props.children);
const obj = slot.get("object", "-");
return (
<>
{/* 以下物理演算 */}
<Physics gravity={[0, -30, 0]} colliders="cuboid" debug={debug}>
<RigidBody position={[0, -1, 0]} type="fixed" colliders="false">
<CuboidCollider restitution={0.01} args={[1000, 1, 1000]} />
</RigidBody>
{/* ここにslot的なことやる */}
{obj}
</Physics>
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0, 0]} receiveShadow>
<planeGeometry args={[100, 100]} />
<shadowMaterial transparent opacity={0.4} />
</mesh>
</>
);
}
最後にApp.tsxに記載したWorldコンポーネント内部でtemplateタグを使用して内部に別のコンポーネントを定義できるようにする。
<World>
<template slot="object">
<Boxes2 position={[0, 10, 0]} colors={opsions} />
</template>
</World>
これで別ファイルにコンポーネントを定義できるようになった。
実際に作ったコード
参考資料
-
vueのslot回り:
https://zenn.dev/tsukiyama3/articles/3f423e8d9d2ac4
https://ja.vuejs.org/guide/components/slots -
rapier関連:
https://github.com/pmndrs/react-three-rapier?tab=readme-ov-file#the-physics-component
https://codesandbox.io/p/sandbox/bruno-simons-20k-challenge-857z1i?file=%2Fsrc%2FApp.js
https://codesandbox.io/p/sandbox/pensive-drake-66cd7 -
slotのように機能するコンポーネントの作り方:
https://blog.capilano-fw.com/?p=11118
Discussion