ReduxとはUIの状態(state)を管理するための状態管理ライブラリ。
FaceBook社が提唱しているFluxというデータフロー管理のためのアーキテクチャパターンを採用しています。
Webアプリは、アプリケーション全体にまたがってデータやオブジェクトを保存したり、共有したりする必要があります。
状態管理ライブラリがない場合、親コンポーネントから子コンポーネントにPropsの形で受け渡すが、規模が大きくなったり、階層の違うコンポーネントに対して渡すときには慎重に行わないと行けず、不具合の原因になりうる。
そんな不安定で管理が複雑な状態を改善することができるのが、Reduxとなります。
Reduxにはこれから紹介する、「3つのコンセプト」と「4つの要素」によって実現しています。
Reduxではアプリケーションの状態をStateと呼ばれるオブジェクトツリーで管理しており、Storeがそれを保持しています。
後ほど具体例を記述するが、Stateはオブジェクトを宣言するのと同じように宣言的で可視化されているので、どのようなデータを管理しているのかが簡単に把握できます。
Stateに直接アクセスをして値を書き換えることは基本的には許可されていなく、値を書き換えるためにはActionと呼ばれるオブジェクトを発行しなくてはいけません。そのような一元化した仕組みにすることで意図しないタイミングでの値の書き換わりなどを防ぎ、不具合が起きる可能性を低めています。
Reducerという純粋関数でStateの変更を行っています。
先ほどちらっと出てきたActionというオブジェクトとStoreで保持されているStateを用いて、新しいStateを生成します。
警告ウィンドウなどユーザーに操作を促すために使われるModalウィンドウですが、今回はこのModalウィンドウの実装をコンポーネントで用意し、それを必要なページにimportし、データをPropsの形で渡して実現するのではなく、状態管理ライブラリReduxを用いて実現してみます。 Reduxを使うことのメリットとしては、いちいちimportする必要がなくなるくらいかもしれませんが、(あとは初期設計ではいらないと思ったが、後付けで必要になったときにPropsを渡すような親子関係ではなく、完全に分離されているので気軽に導入しやすいとか?)Reduxで調べると出てくる解説の多くはTODOリスト作ってみようだと思うので、こういったこともできるのか、ふむふむと思っていただけたら幸いです。
src―redux |―store.ts |―modal |―modal.reducer.ts |―modal.types.ts
import { configureStore } from '@reduxjs/toolkit' import modalReducer from './modal/modal.reducer' export const store = configureStore({ reducer: { modal: modalReducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [ 'modal/openModal', ], ignoredPaths: [ 'modal.modalInfo.buttonItems' ] }, }), }) export type RootState = ReturnType<typeof store.getState> export default store
import { createSlice } from '@reduxjs/toolkit' import type { PayloadAction } from '@reduxjs/toolkit' import * as ModalType from './modal.types' const initialState = { isModalOpen: false, modalInfo: { title: '', style: { width: 390, height: 260, fSize: 25, }, buttonItems: [{ btnTitle: '', callback: Function.prototype, }], component: '', } } as ModalType.ModalState const modalSlice = createSlice({ name: 'modal', initialState, reducers: { openModal(state, action: PayloadAction<ModalType.ModalInfo>) { state.isModalOpen = true const payload = {...action.payload} state.modalInfo.title = payload.title state.modalInfo.style = payload.style! ? payload.style : initialState.modalInfo.style state.modalInfo.buttonItems = payload.buttonItems! ? payload.buttonItems.slice() : initialState.modalInfo.buttonItems.slice() state.modalInfo.component = payload.component }, closeModal(state) { state.isModalOpen = false }, } }) export const modalReducer = modalSlice.actions export default modalSlice.reducer
export interface ModalStyle { width: number, height: number, fSize: number, } export interface ButtonItems { btnTitle: string, callback: () => void, } export type ModalInfo = { title: string, style?: ModalStyle, buttonItems?: Array<ButtonItems>, component: string, } export interface ModalState { isModalOpen: boolean, modalInfo: { title: string, style: ModalStyle, buttonItems: Array<ButtonItems>, component: string, }, }
以下、タイトルとサイズを状態管理して可変にしているモーダルウィンドウ
useSelectorでStateの値を取得、Vuexのgettersみたいなもの
useDispatchでStateの値を変更する、VuexのActions,Mutationsみたいなもの
const dispatch = useDispatch() const openModal = () => { const modalInfo= { title: 'Notice', style: { width: 900, height: 600, fSize: 50, }, component: 'commingsoon' } dispatch(modalReducer.openModal(modalInfo)) }
import { useSelector, useDispatch } from 'react-redux' import { RootState } from '@/redux/store' import { modalReducer } from '@/redux/modal/modal.reducer' import { CSSTransition } from 'react-transition-group' import styles from '@/styles/components/Modal.module.sass' import { ButtonItems } from '@/redux/modal/modal.types' // ViewComponent import ComingSoon from './comingsoon' // *** How to use Modal Window ************************** // // // const openModal = () => { // const modalInfo = { // title: 'This is sample modal', // style: { //default // other recommended styles // width: 390, // 600, 900 // height: 260, // 400, 600 // fSize: 25, // 35, 50 // }, // buttonItems:[{ // btnTitle: 'OK', // callback: () => clickOk(), // }], // component: 'commingsoon', // } // dispatch(modalReducer.openModal(modalInfo)) // } // // ****************************************************** // interface CompProps { childComp: string } function ViewComponent(props: CompProps) { switch(props.childComp) { case 'commingsoon': return <ComingSoon/> default: console.log('Error, not found component') return <></> } } interface BtnProps { btnItems: Array<ButtonItems>, btnFont: number, } function ExecButton(props: BtnProps) { const execButton = props.btnItems.map((el) => <li key={el.btnTitle} className="inline ml-3 list-none" onClick={el.callback} > <span style={{fontSize: props.btnFont / 2 + 'px'}}> {el.btnTitle} </span> </li> ) return ( <>{ execButton }</> ); } const Modal = () => { const isModalOpen = useSelector((state: RootState) => state.modal.isModalOpen) const modalInfo = useSelector((state: RootState) => state.modal.modalInfo) const dispatch = useDispatch() const closeModal = () => { dispatch(modalReducer.closeModal()) } return ( <CSSTransition in={isModalOpen} timeout={390} unmountOnExit styles={{zIndex: 20}} classNames={{ enter: styles['modal-enter'], enterActive: styles['modal-enter-active'], enterDone: styles['modal-enter-done'], exit: styles['modal-exit'], exitActive: styles['modal-exit-active'] }}> <div> <div className={styles['modal-overlay']} onClick={closeModal}> <div style={{width: modalInfo.style.width + 'px', height: modalInfo.style.height + 'px',}} onClick={(event) => event.stopPropagation()}> <div className={styles['modal-content']}> <header className="flex h-1/6 justify-center"> <span className="font-bold" style={{fontSize: modalInfo.style.fSize + 'px'}}> {modalInfo.title} </span> </header> <div className="h-2/3"> <ViewComponent childComp={modalInfo.component} /> </div> <footer className="flex h-1/6 items-center justify-end pr-8"> <ExecButton btnItems={modalInfo.buttonItems} btnFont={modalInfo.style.fSize} /> <li className="inline ml-3 list-none" onClick={closeModal}> <span className="font-bold hover:underline" style={{fontSize: modalInfo.style.fSize / 2 + 'px'}}>Close</span> </li> </footer> </div> </div> </div> </div> </CSSTransition> ) } export default Modal
参考ページ : https://reffect.co.jp/react/redux-toolkit#Redux_Toolkit