Next.jsのSSRでredux(next-redux-wrapper)を使う

20775 ワード

背景


Next.jsでSSRサーバ側レンダリングで特定のページを正しくレンダリングしてクライアントに送信したいと思います.
そのため、以下の2点を実現しなければならない.
  • サーバ側で動作ディスパッチディスパッチを実行するデータを取得する操作
  • .
  • の新しいステータス(store)を使用して、サーバ側で意味のあるコンテンツを表示します.
  • ソリューション


    next-redux-wrapperを使用してgetServerSidePropsのパッケージを作成する

  • references
  • https://github.com/kirill-konshin/next-redux-wrapper#usage-with-redux-saga
  • https://github.com/vercel/next.js/tree/canary/examples/with-redux-saga
  • my code
  • ファイル
  • https://github.com/Tinkerbell-Green/need-compliments/blob/develop/stores/index.ts
  • プロジェクトリポジトリ
    - https://github.com/Tinkerbell-Green/need-compliments/tree/develop
  • getServerSidePropsストレージの使用

  • references
  • https://github.com/kirill-konshin/next-redux-wrapper#using-getserversideprops-or-getstaticprops
  • my code
  • https://github.com/Tinkerbell-Green/need-compliments/blob/develop/pages/test/ssr/index.tsx
  • 私的に関数を追加して、データのキャプチャが完了するのを待っています.
    type Options = {
      actionType: DataSagaActionType
      key: string
    }
    
    export const waitDuringLoading = async (store: Store<RootState, AnyAction>, {actionType, key}: Options) => {
      while (true){
        await (async () => new Promise(resolve => setTimeout(resolve, 100)))()
    
        const isLoading = store.getState().data[actionType][key].status === DataSagaStatus.LOADING
        if (!isLoading) break;
      }
    } 
    import {waitDuringLoading} from // ...
    
    // ...
    export const getServerSideProps = wrapper.getServerSideProps(store => async ({req, res, ...etc}) => {
      const GET_PUBLIC_TASKS_KEY = ""
      const GET_GOALS_BY_IDS_KEY = ""
    
      // 액션 디스패치 하기
      store.dispatch(dataActionCreators[DataActionType.GET_PUBLIC_TASKS]({
        author: undefined,
        key: GET_PUBLIC_TASKS_KEY,
        startTime: new Date("1999-11-11"),
        endTime: new Date("2222-11-11"),
      }))
    
      // 데이터 fetch 완료될때까지 기다리기
      await waitDuringLoading(store, {actionType: DataActionType.GET_PUBLIC_TASKS, key: GET_PUBLIC_TASKS_KEY})
    
      // state 에서 값 읽기
      const tasksGoal = store.getState().data[DataActionType.GET_PUBLIC_TASKS][GET_PUBLIC_TASKS_KEY].data?.map(item => item.goal)
    
    // ...
      return ({
        props: {}
      })
    });

    クライアント・ステータスとサーバ・ステータスのマージ

  • references
  • https://github.com/kirill-konshin/next-redux-wrapper#state-reconciliation-during-hydration
  • my code
  • https://github.com/Tinkerbell-Green/need-compliments/blob/develop/stores/reducers.ts
  • HYDRATEというアクションを使用してnext-redux-wrapperで動作します.
  • 2getStaticPropsまたはgetServerSidePropsを含むページに初期接続または移動する場合、
  • .
  • の前のページのクライアント・ステータスとサーバ側が新しく作成したステータスを使用して、最終ステータスを作成します.
  • このときの合併の仕方は想像以上に重要である
  • .
  • 私は複数の状態を組み合わせて、組合せReducerでルート状態を作成して使用します.ここでは、クライアントでのみ処理(すなわち、サーバ側では使用しません)する状態もあれば、deepmergeを毎回マージする方式
  • を採用しています.
    また、
  • deepmergeで配列をマージする方法も決定される.オブジェクトのid値を同一オブジェクトとして扱うことにした
  • import merge, {Options as MergeOptions} from "deepmerge"
    import {HYDRATE} from "next-redux-wrapper";
    import {combineReducers} from "redux";
    import {dataReducer, State as DataState, initialState as dataInitialState} from "./data";
    import {navigationReducer, State as NavigationState, initialState as navigationInitialState} from "./navigation";
    
    const combinedReducer = combineReducers({
      data: dataReducer,
      navigation: navigationReducer,
    });
    
    export type RootState = {
      data: DataState;
      navigation: NavigationState;
    }
    
    const initialRootState: RootState = {
      data: dataInitialState,
      navigation: navigationInitialState
    }
    
    const arrayMerge: MergeOptions["arrayMerge"] = (previousArray, incomingArray, options) => {
      const resultArray: typeof previousArray = [...previousArray]
    
      incomingArray.forEach((incomingItem) => {
        const prevItemIndex = previousArray.findIndex(previousItem => previousItem.id === incomingItem.id)
        if (prevItemIndex !== -1){
          resultArray[prevItemIndex] = merge(resultArray[prevItemIndex], incomingItem, options)
        }
        else {
          resultArray.push(incomingItem)
        }
      })
      return resultArray
    }
    
    const rootReducer = (previousClientState = initialRootState, action: any) => {
      if (action.type === HYDRATE) {
        const incomingServerState = action.payload as RootState
    
        const nextState: RootState = {
          navigation: previousClientState.navigation,
          data: merge(previousClientState.data, incomingServerState.data, {arrayMerge})
        }
        return nextState;
      } else {
        return combinedReducer(previousClientState, action);
      }
    };
    
    export default rootReducer;

    レンダリング効果が良好で、クライアントに転送されていることを確認します。

  • Chrome Networkタブからコピー要求(ページパス名)応答

  • フォント
  • でコピーされたhmtlコードを美化して、ボディのレンダリング部分を表示します.
  • htmlコードのウェブサイトを表示します
  • https://www.freeformatter.com/html-formatter.html