Reactã®useActionStateãã¹ããŒããã·ã³ãšçµã¿åãããŠãå ç¢ã§äºæž¬å¯èœãªUIãæ§ç¯ããŸããè€éãªã¢ããªã±ãŒã·ã§ã³ã®ããã®ã¢ã¯ã·ã§ã³ç¶æ é·ç§»ããžãã¯ãåŠã³ãŸãããã
React useActionStateã¹ããŒããã·ã³ïŒã¢ã¯ã·ã§ã³ã®ç¶æ é·ç§»ããžãã¯ããã¹ã¿ãŒãã
Reactã®useActionState
ã¯ãReact 19ïŒçŸåšcanaryçïŒã§å°å
¥ããã匷åãªããã¯ã§ãç¹ã«ãµãŒããŒã¢ã¯ã·ã§ã³ãæ±ãéã®éåæãªç¶æ
æŽæ°ãç°¡çŽ åããããã«èšèšãããŠããŸããã¹ããŒããã·ã³ãšçµã¿åãããããšã§ãè€éãªUIã€ã³ã¿ã©ã¯ã·ã§ã³ãšç¶æ
é·ç§»ã管çããããã®ãšã¬ã¬ã³ãã§å
ç¢ãªæ¹æ³ãæäŸããŸãããã®ããã°èšäºã§ã¯ãuseActionState
ãã¹ããŒããã·ã³ãšãšãã«å¹æçã«æŽ»çšããäºæž¬å¯èœã§ä¿å®æ§ã®é«ãReactã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããæ¹æ³ã詳ãã解説ããŸãã
ã¹ããŒããã·ã³ãšã¯ïŒ
ã¹ããŒããã·ã³ã¯ãã·ã¹ãã ã®æ¯ãèããæéåã®ç¶æ ãšãããã®ç¶æ éã®é·ç§»ãšããŠèšè¿°ããèšç®ã®æ°åŠçã¢ãã«ã§ããåç¶æ ã¯ã·ã¹ãã ã®æç¢ºãªæ¡ä»¶ã衚ããé·ç§»ã¯ã·ã¹ãã ãããç¶æ ããå¥ã®ç¶æ ãžç§»åãããã€ãã³ãã衚ããŸãããããŒãã£ãŒãã«äŒŒãŠããŸãããã¹ãããéãã©ã®ããã«ç§»åã§ãããã«ã€ããŠãã峿 Œãªã«ãŒã«ããããŸãã
Reactã¢ããªã±ãŒã·ã§ã³ã§ã¹ããŒããã·ã³ã䜿çšããããšã«ã¯ãããã€ãã®å©ç¹ããããŸãïŒ
- äºæž¬å¯èœæ§ïŒã¹ããŒããã·ã³ã¯æç¢ºã§äºæž¬å¯èœãªå¶åŸ¡ãããŒã匷å¶ãããããã¢ããªã±ãŒã·ã§ã³ã®æ¯ãèãã«ã€ããŠæšè«ãããããªããŸãã
- ä¿å®æ§ïŒç¶æ ããžãã¯ãUIã¬ã³ããªã³ã°ããåé¢ããããšã§ãã¹ããŒããã·ã³ã¯ã³ãŒãã®æ§æãæ¹åããã¢ããªã±ãŒã·ã§ã³ã®ä¿å®ãšæŽæ°ã容æã«ããŸãã
- ãã¹ãå®¹ææ§ïŒåç¶æ ãšé·ç§»ã«å¯ŸããŠæåŸ ãããæ¯ãèããç°¡åã«å®çŸ©ã§ãããããã¹ããŒããã·ã³ã¯æ¬è³ªçã«ãã¹ãã容æã§ãã
- èŠèŠç衚çŸïŒã¹ããŒããã·ã³ã¯èŠèŠçã«è¡šçŸã§ãããããä»ã®éçºè ãã¹ããŒã¯ãã«ããŒã«ã¢ããªã±ãŒã·ã§ã³ã®æ¯ãèããäŒããã®ã«åœ¹ç«ã¡ãŸãã
useActionState
ã®ç޹ä»
useActionState
ããã¯ã¯ãã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ãæœåšçã«å€æŽããã¢ã¯ã·ã§ã³ã®çµæãåŠçããããšãå¯èœã«ããŸãããµãŒããŒã¢ã¯ã·ã§ã³ãšã·ãŒã ã¬ã¹ã«é£æºããããã«èšèšãããŠããŸãããã¯ã©ã€ã¢ã³ãåŽã®ã¢ã¯ã·ã§ã³ã«ãé©å¿ã§ããŸããããŒãã£ã³ã°ç¶æ
ããšã©ãŒãã¢ã¯ã·ã§ã³ã®æçµçµæã管çããã¯ãªãŒã³ãªæ¹æ³ãæäŸããã¬ã¹ãã³ã·ãã§ãŠãŒã¶ãŒãã¬ã³ããªãŒãªUIã®æ§ç¯ã容æã«ããŸãã
以äžã¯ãuseActionState
ã®åºæ¬çãªäœ¿çšäŸã§ãïŒ
const [state, dispatch] = useActionState(async (prevState, formData) => {
// ããã«ã¢ã¯ã·ã§ã³ããžãã¯ãèšè¿°
try {
const result = await someAsyncFunction(formData);
return { ...prevState, data: result };
} catch (error) {
return { ...prevState, error: error.message };
}
}, { data: null, error: null });
ãã®äŸã§ã¯ïŒ
- æåã®åŒæ°ã¯ãã¢ã¯ã·ã§ã³ãå®è¡ããéåæé¢æ°ã§ããããã¯åã®ç¶æ ãšãã©ãŒã ããŒã¿ïŒè©²åœããå ŽåïŒãåãåããŸãã
- 2çªç®ã®åŒæ°ã¯ãåæç¶æ ã§ãã
- ãã®ããã¯ã¯ãçŸåšã®ç¶æ ãšdispatch颿°ãå«ãé åãè¿ããŸãã
useActionState
ãšã¹ããŒããã·ã³ã®çµã¿åãã
çã®åã¯ãuseActionState
ãšã¹ããŒããã·ã³ãçµã¿åãããããšã§çºæ®ãããŸããããã«ãããéåæã¢ã¯ã·ã§ã³ã«ãã£ãŠããªã¬ãŒãããè€éãªç¶æ
é·ç§»ãå®çŸ©ã§ããŸããäŸãšããŠãååã®è©³çްãååŸããã·ã³ãã«ãªeã³ããŒã¹ã³ã³ããŒãã³ããèããŠã¿ãŸãããã
äŸïŒåå詳现ã®ååŸ
åå詳现ã³ã³ããŒãã³ãã®ããã«ã以äžã®ç¶æ ãå®çŸ©ããŸãïŒ
- IdleïŒåŸ æ©ïŒïŒåæç¶æ ããŸã ååã®è©³çްã¯ååŸãããŠããŸããã
- LoadingïŒèªã¿èŸŒã¿äžïŒïŒååã®è©³çްãååŸããŠããéã®ç¶æ ã
- SuccessïŒæåïŒïŒååã®è©³çŽ°ãæ£åžžã«ååŸãããåŸã®ç¶æ ã
- ErrorïŒãšã©ãŒïŒïŒååã®è©³çްååŸäžã«ãšã©ãŒãçºçããå Žåã®ç¶æ ã
ãã®ã¹ããŒããã·ã³ã¯ããªããžã§ã¯ãã䜿çšããŠè¡šçŸã§ããŸãïŒ
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
ããã¯ç°¡ç¥åããã衚çŸã§ããXStateã®ãããªã©ã€ãã©ãªã¯ãéå±€ç¶æ ã䞊åç¶æ ãã¬ãŒããªã©ã®æ©èœãåãããããæŽç·Žãããã¹ããŒããã·ã³ã®å®è£ ãæäŸããŸãã
Reactã§ã®å®è£
ããã§ã¯ããã®ã¹ããŒããã·ã³ãReactã³ã³ããŒãã³ãã«çµ±åããŠã¿ãŸãããã
import React from 'react';
// å®å
šãªã¹ããŒããã·ã³äœéšããããå Žåã¯XStateãã€ã³ã¹ããŒã«ããŠãã ããããã®åºæ¬çãªäŸã§ã¯ãã·ã³ãã«ãªãªããžã§ã¯ãã䜿çšããŸãã
// import { createMachine, useMachine } from 'xstate';
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
function ProductDetails({ productId }) {
const [state, dispatch] = React.useReducer(
(state, event) => {
const nextState = productDetailsMachine.states[state].on[event];
return nextState || state; // 次ã®ç¶æ
ãè¿ãããé·ç§»ãå®çŸ©ãããŠããªãå Žåã¯çŸåšã®ç¶æ
ãè¿ã
},
productDetailsMachine.initial
);
const [productData, setProductData] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
if (state === 'loading') {
const fetchData = async () => {
try {
const response = await fetch(`https://api.example.com/products/${productId}`); // ããªãã®APIãšã³ããã€ã³ãã«çœ®ãæããŠãã ãã
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setProductData(data);
setError(null);
dispatch('SUCCESS');
} catch (e) {
setError(e.message);
setProductData(null);
dispatch('ERROR');
}
};
fetchData();
}
}, [state, productId, dispatch]);
const handleFetch = () => {
dispatch('FETCH');
};
return (
Product Details
{state === 'idle' && }
{state === 'loading' && Loading...
}
{state === 'success' && (
{productData.name}
{productData.description}
Price: ${productData.price}
)}
{state === 'error' && Error: {error}
}
);
}
export default ProductDetails;
説æïŒ
productDetailsMachine
ããã¹ããŒããã·ã³ã衚ãã·ã³ãã«ãªJavaScriptãªããžã§ã¯ããšããŠå®çŸ©ããŸããReact.useReducer
ã䜿çšããŠããã·ã³ã«åºã¥ããç¶æ é·ç§»ã管çããŸãã- Reactã®
useEffect
ããã¯ã䜿çšããŠãç¶æ ã'loading'ã®ãšãã«ããŒã¿ååŸãããªã¬ãŒããŸãã handleFetch
颿°ã¯'FETCH'ã€ãã³ãããã£ã¹ãããããããŒãã£ã³ã°ç¶æ ãéå§ããŸãã- ã³ã³ããŒãã³ãã¯ãçŸåšã®ç¶æ ã«åºã¥ããŠç°ãªãã³ã³ãã³ããã¬ã³ããªã³ã°ããŸãã
useActionState
ã®äœ¿çšïŒä»®èª¬ - React 19ã®æ©èœïŒ
useActionState
ã¯ãŸã å®å
šã«ã¯å©çšã§ããŸããããå©çšå¯èœã«ãªã£ãå Žåã«ãããã¯ãªãŒã³ãªã¢ãããŒããæäŸããå®è£
ãã©ã®ããã«ãªããã以äžã«ç€ºããŸãïŒ
import React from 'react';
//import { useActionState } from 'react'; // å©çšå¯èœã«ãªã£ããã³ã¡ã³ããå€ããŠãã ãã
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
function ProductDetails({ productId }) {
const initialState = { state: productDetailsMachine.initial, data: null, error: null };
// useActionStateã®ä»®èª¬çãªå®è£
const [newState, dispatch] = React.useReducer(
(state, event) => {
const nextState = productDetailsMachine.states[state.state].on[event];
return nextState ? { ...state, state: nextState } : state; // 次ã®ç¶æ
ãè¿ãããé·ç§»ãå®çŸ©ãããŠããªãå Žåã¯çŸåšã®ç¶æ
ãè¿ã
},
initialState
);
const handleFetchProduct = async () => {
dispatch('FETCH');
try {
const response = await fetch(`https://api.example.com/products/${productId}`); // ããªãã®APIãšã³ããã€ã³ãã«çœ®ãæããŠãã ãã
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// ååŸæå - SUCCESSãããŒã¿ãšå
±ã«ãã£ã¹ãããïŒ
dispatch('SUCCESS');
// ååŸããããŒã¿ãããŒã«ã«ã¹ããŒãã«ä¿åããªãã¥ãŒãµãŒå
ã§ãã£ã¹ãããã¯ã§ããªãã
newState.data = data; // ãã£ã¹ãããã£ã®å€ã§æŽæ°
} catch (error) {
// ãšã©ãŒçºç - ãšã©ãŒã¡ãã»ãŒãžãšå
±ã«ERRORããã£ã¹ãããïŒ
dispatch('ERROR');
// render()ã§è¡šç€ºããããã«ãšã©ãŒãæ°ãã倿°ã«æ ŒçŽ
newState.error = error.message;
}
//}, initialState);
};
return (
Product Details
{newState.state === 'idle' && }
{newState.state === 'loading' && Loading...
}
{newState.state === 'success' && newState.data && (
{newState.data.name}
{newState.data.description}
Price: ${newState.data.price}
)}
{newState.state === 'error' && newState.error && Error: {newState.error}
}
);
}
export default ProductDetails;
éèŠäºé
ïŒãã®äŸã¯ãuseActionState
ããŸã å®å
šã«ã¯å©çšã§ããããã®æ£ç¢ºãªAPIã倿Žãããå¯èœæ§ãããããã仮説çãªãã®ã§ããããã§ã¯ã³ã¢ããžãã¯ãå®è¡ããããã«ãæšæºã®useReducerã§çœ®ãæããŠããŸãããããããã®æå³ã¯ãå©çšå¯èœã«ãªã£ãéã«ããã*ã©ã®ããã«*䜿çšãããã瀺ãããšã§ãããuseReducerãuseActionStateã«çœ®ãæããå¿
èŠããããŸããå°æ¥çã«ã¯ãuseActionState
ã«ãã£ãŠãã®ã³ãŒãã¯æå°éã®å€æŽã§èª¬æã©ããã«åäœããéåæããŒã¿åŠçã倧å¹
ã«ç°¡çŽ åããã¯ãã§ãã
useActionState
ãã¹ããŒããã·ã³ãšå
±ã«äœ¿çšããå©ç¹
- é¢å¿ã®æç¢ºãªåé¢ïŒç¶æ ããžãã¯ã¯ã¹ããŒããã·ã³å ã«ã«ãã»ã«åãããUIã¬ã³ããªã³ã°ã¯Reactã³ã³ããŒãã³ãã«ãã£ãŠåŠçãããŸãã
- ã³ãŒãã®å¯èªæ§ã®åäžïŒã¹ããŒããã·ã³ã¯ã¢ããªã±ãŒã·ã§ã³ã®æ¯ãèããèŠèŠçã«è¡šçŸãããããçè§£ãšä¿å®ã容æã«ãªããŸãã
- éåæåŠçã®ç°¡çŽ åïŒ
useActionState
ã¯éåæã¢ã¯ã·ã§ã³ã®åŠçãå¹çåãããã€ã©ãŒãã¬ãŒãã³ãŒããåæžããŸãã - ãã¹ãå®¹ææ§ã®åäžïŒã¹ããŒããã·ã³ã¯æ¬è³ªçã«ãã¹ãã容æã§ãããã¢ããªã±ãŒã·ã§ã³ã®æ¯ãèãã®æ£ãããç°¡åã«æ€èšŒã§ããŸãã
é«åºŠãªæŠå¿µãšèæ ®äºé
XStateã®çµ±å
ããè€éãªç¶æ 管çã®ããŒãºã«ã¯ãXStateã®ãããªå°çšã®ã¹ããŒããã·ã³ã©ã€ãã©ãªã®äœ¿çšãæ€èšããŠãã ãããXStateã¯ãéå±€ç¶æ ã䞊åç¶æ ãã¬ãŒããã¢ã¯ã·ã§ã³ãªã©ã®æ©èœãåãããã¹ããŒããã·ã³ãå®çŸ©ããã³ç®¡çããããã®åŒ·åã§æè»ãªãã¬ãŒã ã¯ãŒã¯ãæäŸããŸãã
// Example using XState
import { createMachine, useMachine } from 'xstate';
const productDetailsMachine = createMachine({
id: 'productDetails',
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
invoke: {
id: 'fetchProduct',
src: (context, event) => fetch(`https://api.example.com/products/${context.productId}`).then(res => res.json()),
onDone: {
target: 'success',
actions: assign({ product: (context, event) => event.data })
},
onError: {
target: 'error',
actions: assign({ error: (context, event) => event.data })
}
}
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
}, {
services: {
fetchProduct: (context, event) => fetch(`https://api.example.com/products/${context.productId}`).then(res => res.json())
}
});
ããã«ãããç¶æ
ããã宣èšçãã€å
ç¢ã«ç®¡çããæ¹æ³ãæäŸãããŸããnpm install xstate
ã䜿çšããŠå¿
ãã€ã³ã¹ããŒã«ããŠãã ããã
ã°ããŒãã«ãªç¶æ 管ç
è€æ°ã®ã³ã³ããŒãã³ãã«ãŸãããè€éãªç¶æ 管çèŠä»¶ãæã€ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãReduxãZustandã®ãããªã°ããŒãã«ãªç¶æ 管çãœãªã¥ãŒã·ã§ã³ãã¹ããŒããã·ã³ãšçµã¿åãããŠäœ¿çšããããšãæ€èšããŠãã ãããããã«ãããã¢ããªã±ãŒã·ã§ã³ã®ç¶æ ãäžå åããã³ã³ããŒãã³ãéã§ç°¡åã«å ±æã§ããŸãã
ã¹ããŒããã·ã³ã®ãã¹ã
ã¹ããŒããã·ã³ã®ãã¹ãã¯ãã¢ããªã±ãŒã·ã§ã³ã®æ£ãããšä¿¡é Œæ§ã確ä¿ããããã«äžå¯æ¬ ã§ããJestãMochaã®ãããªãã¹ããã¬ãŒã ã¯ãŒã¯ã䜿çšããŠãã¹ããŒããã·ã³ã®åäœãã¹ããäœæããæåŸ ã©ããã«ç¶æ éãé·ç§»ããããŸããŸãªã€ãã³ããæ£ããåŠçããããšã確èªã§ããŸãã
以äžã¯ç°¡åãªäŸã§ãïŒ
// Example Jest test
import { interpret } from 'xstate';
import { productDetailsMachine } from './productDetailsMachine';
describe('productDetailsMachine', () => {
it('should transition from idle to loading on FETCH event', (done) => {
const service = interpret(productDetailsMachine).onTransition((state) => {
if (state.value === 'loading') {
expect(state.value).toBe('loading');
done();
}
});
service.start();
service.send('FETCH');
});
});
åœéåïŒi18nïŒ
ã°ããŒãã«ãªãªãŒãã£ãšã³ã¹åãã®ã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããå ŽåãåœéåïŒi18nïŒã¯äžå¯æ¬ ã§ããã¹ããŒããã·ã³ã®ããžãã¯ãšUIã¬ã³ããªã³ã°ããè€æ°ã®èšèªãšæåçèæ¯ããµããŒãããããã«é©åã«åœéåãããŠããããšã確èªããŠãã ããã以äžã®ç¹ãèæ ®ããŠãã ããïŒ
- ããã¹ãã³ã³ãã³ãïŒi18nã©ã€ãã©ãªã䜿çšããŠããŠãŒã¶ãŒã®ãã±ãŒã«ã«åºã¥ããŠããã¹ãã³ã³ãã³ãã翻蚳ããŸãã
- æ¥æãã©ãŒãããïŒãã±ãŒã«å¯Ÿå¿ã®æ¥æãã©ãŒãããã©ã€ãã©ãªã䜿çšããŠããŠãŒã¶ãŒã®å°åã«é©ãã圢åŒã§æ¥æã衚瀺ããŸãã
- é貚ãã©ãŒãããïŒãã±ãŒã«å¯Ÿå¿ã®é貚ãã©ãŒãããã©ã€ãã©ãªã䜿çšããŠããŠãŒã¶ãŒã®å°åã«é©ãã圢åŒã§é貚å€ã衚瀺ããŸãã
- æ°å€ãã©ãŒãããïŒãã±ãŒã«å¯Ÿå¿ã®æ°å€ãã©ãŒãããã©ã€ãã©ãªã䜿çšããŠããŠãŒã¶ãŒã®å°åã«é©ãã圢åŒïŒäŸïŒå°æ°ç¹åºåããååäœåºåãïŒã§æ°å€ã衚瀺ããŸãã
- å³ããå·ŠïŒRTLïŒã¬ã€ã¢ãŠãïŒã¢ã©ãã¢èªãããã©ã€èªãªã©ã®èšèªã®ããã«RTLã¬ã€ã¢ãŠãããµããŒãããŸãã
ãããã®i18nã®åŽé¢ãèæ ®ããããšã§ãã¢ããªã±ãŒã·ã§ã³ãã°ããŒãã«ãªãªãŒãã£ãšã³ã¹ã«ãšã£ãŠã¢ã¯ã»ã¹ããããããŠãŒã¶ãŒãã¬ã³ããªãŒã§ããããšãä¿èšŒã§ããŸãã
çµè«
Reactã®useActionState
ãšã¹ããŒããã·ã³ãçµã¿åãããããšã¯ãå
ç¢ã§äºæž¬å¯èœãªãŠãŒã¶ãŒã€ã³ã¿ãŒãã§ãŒã¹ãæ§ç¯ããããã®åŒ·åãªã¢ãããŒããæäŸããŸããç¶æ
ããžãã¯ãUIã¬ã³ããªã³ã°ããåé¢ããæç¢ºãªå¶åŸ¡ãããŒã匷å¶ããããšã§ãã¹ããŒããã·ã³ã¯ã³ãŒãã®æ§æãä¿å®æ§ããã¹ãå®¹ææ§ãåäžãããŸããuseActionState
ã¯ãŸã ä»åŸã®æ©èœã§ãããä»ããã¹ããŒããã·ã³ãçµ±åããæ¹æ³ãçè§£ããŠããããšã§ãå©çšå¯èœã«ãªã£ãéã«ãã®å©ç¹ã掻çšããæºåãã§ããŸããXStateã®ãããªã©ã€ãã©ãªã¯ãããã«é«åºŠãªç¶æ
ç®¡çæ©èœãæäŸããè€éãªã¢ããªã±ãŒã·ã§ã³ããžãã¯ã®åŠçã容æã«ããŸãã
ã¹ããŒããã·ã³ãšuseActionState
ãåãå
¥ããããšã§ãReactéçºã¹ãã«ãåäžãããäžçäžã®ãŠãŒã¶ãŒã«ãšã£ãŠããä¿¡é Œæ§ãé«ããä¿å®ããããããŠãŒã¶ãŒãã¬ã³ããªãŒãªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ã§ããŸãã