階層の異なるコンポーネント間でボタンから変数や更新伝達の基本めも
App
├── SubA
│ └── ButtonX ← チェックボックス的なボタン
├── SubB
│ └── Content ← 状態に応じて内容を変える
└── SubC
ということをしたいときどうするのかめも
方法1 - 状態のリフトアップ方式
簡単に言うと useState の変数とセッター関数の両方を props を通じてバケツリレーする方式
App.tsx:
// App.tsx
import React, { useState } from 'react';
import SubA from './SubA';
import SubB from './SubB';
export default function App()
{
const [isButtonXOn, setIsButtonXOn] = useState(false);
return (
<div>
<SubA isOn={ isButtonXOn } setIsOn={ setIsButtonXOn } />
<SubB isOn={ isButtonXOn } />
</div>
);
}
SubA.tsx:
// SubA.tsx
import ButtonX from './ButtonX';
export default function SubA({ isOn ,setIsOn }:{
isOn:boolean;
setIsOn:React.Dispatch<React.SetStateAction<boolean>>;
})
{
return <ButtonX isOn={ isOn } setIsOn={ setIsOn } />;
}
ButtonX.tsx:
// ButtonX.tsx
export default function ButtonX({ isOn ,setIsOn }:{
isOn:boolean;
setIsOn:React.Dispatch<React.SetStateAction<boolean>>;
})
{
return (
<button onClick={ ()=>setIsOn( !isOn ) }>
{ isOn ? 'ON' : 'OFF' }
</button>
);
}
SubB.tsx:
// SubB.tsx
import Content from './Content';
export default function SubB({ isOn }:{ isOn:boolean })
{
return <Content highlight={ isOn } />;
}
Content.tsx:
// Content.tsx
export default function Content({ highlight }:{ highlight:boolean })
{
return (
<div>
Content Component – Highlight: { highlight ? 'ON' : 'OFF' }
</div>
);
}
方法2 - Context API(グローバルステート)
App.tsx:
// App.tsx
import React, { useState, createContext } from 'react';
import SubA from './SubA';
import SubB from './SubB';
export const UIContext = createContext(null);
export default function App()
{
const [isButtonXOn, setIsButtonXOn] = useState(false);
return (
<UIContext.Provider value={{ isButtonXOn, setIsButtonXOn }}>
<SubA />
<SubB />
</UIContext.Provider>
);
}
ButtonX.tsx:
// ButtonX.tsx
import { useContext } from 'react';
import { UIContext } from './App';
export default function ButtonX()
{
const { isButtonXOn ,setIsButtonXOn } = useContext(UIContext);
return (
<button onClick={ ()=>setIsButtonXOn(!isButtonXOn) }>
{ isButtonXOn ? 'ON' : 'OFF' }
</button>
);
}
Content.tsx:
// Content.tsx
import { useContext } from 'react';
import { UIContext } from './App';
export default function Content()
{
const { isButtonXOn } = useContext(UIContext);
return <div>Content – Highlight: { isButtonXOn ? 'ON' : 'OFF' }</div>;
}
案3 - Zustand 等のサードパーティモジュールを導入する
Zustand の特徴:
- フィールド単位で購読でき、Content 側は必要な切片だけ読む
- ストアをスライス分割して整理(UI/ContentView/Layers/Filters…)
- subscribeWithSelector を使えば副作用もフィールド単位で発火
サンプル:
store.ts:
// store.ts
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
type UISlice =
{
buttonX:boolean;
toggleButtonX: ()=>void;
setButtonX: (v:boolean)=>void;
};
type ContentViewSlice =
{
center:[number,number];
zoom:number;
setView: (center:[number,number], zoom:number)=>void;
highlight:boolean;
setHighlight: (v:boolean)=>void;
};
type LayersSlice =
{
visibleLayerIds: Set<string>;
showLayer: (id:string)=>void;
hideLayer: (id:string)=>void;
};
type StoreT = UISlice & ContentViewSlice & LayersSlice;
export const useStore = create<StoreT>()( subscribeWithSelector( ( set ,get ) =>
({
// UI
buttonX: false,
toggleButtonX: () => set({ buttonX: !get().buttonX }),
setButtonX: ( v ) => set({ buttonX: v }),
// ContentView
center: [139.767, 35.681], // Tokyo
zoom: 12,
setView: ( center ,zoom ) => set({ center ,zoom }),
highlight: false,
setHighlight: ( v ) => set({ highlight: v }),
// Layers
visibleLayerIds: new Set<string>(),
showLayer: ( id ) =>
{
const next = new Set( get().visibleLayerIds ); next.add( id );
set({ visibleLayerIds: next });
},
hideLayer: ( id ) =>
{
const next = new Set( get().visibleLayerIds ); next.delete( id );
set({ visibleLayerIds: next });
},
}) ) );
ButtonX.tsx:
// ButtonX.tsx
import React from 'react';
import { useStore } from './store';
export default React.memo( function ButtonX()
{
const isOn = useStore( s => s.buttonX );
const toggle = useStore( s => s.toggleButtonX );
return <button onClick={ toggle }>{ isOn ? 'ON' : 'OFF' }</button>;
});
Content.tsx:
// Content.tsx
import React, { useEffect, useRef } from 'react';
import { useStore } from './store';
// 例: 実ライブラリに置換
function createContent( el:HTMLElement )
{
return {
setView: ( center:[number,number] ,zoom:number ) => {},
setHighlight: ( on:boolean ) => {},
setVisibleLayers: ( ids:Set<string> ) => {},
destroy: () => {},
};
}
export default React.memo( function Content()
{
const ref = useRef<HTMLDivElement | null>( null );
const contentRef = useRef<ReturnType<typeof createContent> | null>( null );
// 初期化
useEffect( () =>
{
if( !ref.current ) return;
contentRef.current = createContent( ref.current );
return () => { contentRef.current?.destroy(); contentRef.current = null; };
} ,[]);
// フィールドごとに購読(必要な時だけ命令的更新)
const center = useStore( s => s.center );
const zoom = useStore( s => s.zoom );
useEffect( () => { contentRef.current?.setView( center ,zoom ); } ,[center ,zoom]);
const highlight = useStore( s => s.highlight );
useEffect( () => { contentRef.current?.setHighlight( highlight ); } ,[highlight]);
const visibleLayerIds = useStore( s => s.visibleLayerIds );
useEffect( () => { contentRef.current?.setVisibleLayers( visibleLayerIds ); } ,[visibleLayerIds]);
return <div ref={ ref } style={{ width:'100%' ,height:'420px' }} />;
});