0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactで変数更新と伝播のあれこれめも

Posted at

階層の異なるコンポーネント間でボタンから変数や更新伝達の基本めも

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' }} />;
});

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?