Spreado - 基于redux与swr的next.js SSR 集成

422 阅读4分钟

本文代码地址:github.com/ishanyang/m…

spreado地址:github.com/react-easie…

关于服务端渲染Server-side rendering(SSR)简述

通过前面的文章希望你对Spreado 基本用法有了足够了解( Spreado - 轻松地在 React 组件间传播状态和数据)。

本文采用next.js框架来实现react 服务端渲染 ssr (因为next.js基本可以做到0配置,即可完成ssr功能开发), swr库用于数据请求。

与常规redux的preloaded state生成过程类似,通过spreado ssr相关方法来简化preloaded state的生成(详细用法请参考 spreado#sever-side-rendering-ssr 文档地址),接下来通过简单的示例希望你能对Spreado有进一步了解。

项目初始化

npx create-next-app my-first-next-spreado-app --example with-redux-thunk --use-npm

可以从这里选择示例redux模版 github.com/vercel/next…

--example 选择with-redux-thunk模版

--use-npm 使用npm

安装swr、spreado

npm install swr spreado

接来下的目标

  1. next.js集成spreado
  2. 通过Repo例子演示:传播 swr 数据请求
  3. 通过Repo例子演示:传播 swr 数据请求如何实现ssr

1.集成spreado

reducer.js添加reducer spreadoReduxReducerPack

// reducer.js
import { combineReducers } from 'redux'
+ import {
+   spreadoReduxReducerPack,
+ } from 'spreado/for-redux-swr';
import * as types from './types'
...
// COMBINED REDUCERS
const reducers = {
  counter: counterReducer,
  timer: timerReducer,
+  ...spreadoReduxReducerPack
}

pages/_app.js集成spreado

+ import {useMemo} from 'react';
+ import {SpreadoSetupProvider} from 'spreado';
+ import {
+   SpreadoSetupForReduxSwr,
+ } from 'spreado/for-redux-swr';
import { Provider } from 'react-redux'
import { useStore } from '../store'

export default function App({ Component, pageProps }) {
  const store = useStore(pageProps.initialReduxState)
+ const spreadoSetup = useMemo(() => new SpreadoSetupForReduxSwr({store}), [store]);

  return (
    <Provider store={store}>
+      <SpreadoSetupProvider setup={spreadoSetup}>
        <Component {...pageProps} />
+      </SpreadoSetupProvider>
    </Provider>
  )
}

至此集成spreado已完成。

2.通过Repo例子演示:传播 swr 数据请求。

components目录新增文件repo.js、repo-share.js

repo.js

import { useState } from 'react'
import { useSpreadIn, useSpreadOut } from 'spreado'
import { useSwrFallbackData } from 'spreado/for-redux-swr'
import useSWR from 'swr'

export const INDEX_OF_REPO_SWR = 'INDEX_OF_REPO_SWR'

export function useRepoSwrSpreadOut(repoName) {
  return useSpreadOut(
    INDEX_OF_REPO_SWR,
    useSWR([INDEX_OF_REPO_SWR, repoName], () => fetchRepoInfo(repoName), {
      fallbackData: useSwrFallbackData(INDEX_OF_REPO_SWR),
    })
  )
}

export function useRepoSwrSpreadIn() {
  return useSpreadIn(INDEX_OF_REPO_SWR, {})
}

export async function fetchRepoInfo(repoName) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: repoName,
        description: `Mocked description ${Math.random()}`,
        subscribers_count: Math.round(1000 + 9000 * Math.random()),
        stargazers_count: Math.round(1000 + 9000 * Math.random()),
        forks_count: Math.round(1000 + 9000 * Math.random()),
      })
    }, 1000 + Math.random() * 1000)
  })
}

export const DEFAULT_REPO_NAME = 'reduxjs/redux'

function Repo() {
  const [repoName, setRepoName] = useState(DEFAULT_REPO_NAME)
  const { data } = useRepoSwrSpreadOut(repoName)
  const isLoading = !data

  return (
    <div>
      <div>
        <label>Please select a repo: </label>
        <select value={repoName} onChange={(e) => setRepoName(e.target.value)}>
          <option>reduxjs/redux</option>
          <option>vercel/swr</option>
          <option>react-easier/spreado</option>
        </select>
      </div>
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        <div>
          <p>Name: {data.name}</p>
          <p>Description: {data.description}</p>
          <p>
            Numbers:
            <span>👀 {data.subscribers_count}</span>
            <span>✨ {data.stargazers_count}</span>
            <span>🍴 {data.forks_count}</span>
          </p>
        </div>
      )}
    </div>
  )
}

export default Repo

repo-share.js

import { useRepoSwrSpreadIn } from './repo'

function RepoShare() {
  const { data } = useRepoSwrSpreadIn()
  const isLoading = !data
  return (
    <div>
      {isLoading ? (
        <div>Loading in another format...</div>
      ) : (
        <p>
          Numbers in another format:
          <span>👁 {data.subscribers_count}</span>
          <span>⭐️ {data.stargazers_count}</span>
          <span>⑂ {data.forks_count}</span>
        </p>
      )}
    </div>
  )
}

export default RepoShare

在首页pages/index.js引入组件RepoRepoShare

import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import Link from 'next/link'
import { startClock } from '../actions'
import Examples from '../components/examples'
+ import Repo from '../components/repo';
+ import RepoShare from '../components/repo-share';

const Index = () => {
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(startClock())
  }, [dispatch])

  return (
    <>
      <Examples />
      <Link href="/show-redux-state">
        <a>Click to see current Redux State</a>
      </Link>
+       <section>
+         <h2>repo info</h2>
+         <Repo />
+         <RepoShare />
+       </section>
    </>
  )
}

export default Index

访问http://localhost:3000/

首次渲染RepoShare RepoShare两个组件均为loading状态(此时并没有实现ssr)

first-render.png

在短暂的数据拉取延迟后RepoShare RepoShare两个组件均正常显示

image-20220830-110845.png

3.通过Repo例子演示:传播 swr 数据请求如何实现ssr

关键点:

  1. 通过next.js 服务端方法 getServerSideProps() 动态获取相关初始值
  2. 通过spreado/for-redux-swr renderSwrResponse() 方法加工数据
  3. 通过spreado/for-redux-swr createSpreadoReduxPreloadedState方法生成spread redux preloaded state数据

在首页pages/index.js新增服务端方法getServerSideProps() 用于repoInfo数据的获取

export async function getServerSideProps() {
  const repoInfo = await fetchRepoInfo(DEFAULT_REPO_NAME)

  return {
    props: {
      initialReduxState: createSpreadoReduxPreloadedState({
        [INDEX_OF_REPO_SWR]: renderSwrResponse(repoInfo)
      }),
    }
  }
}

首页pages/index.js最终代码如下:

import { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import Link from 'next/link'
+ import {
+   createSpreadoReduxPreloadedState,
+   renderSwrResponse,
+ } from 'spreado/for-redux-swr'
import { startClock } from '../actions'
import Examples from '../components/examples'
+ import Repo, {
+   DEFAULT_REPO_NAME,
+   fetchRepoInfo,
+   INDEX_OF_REPO_SWR
+ } from '../components/repo';
import RepoShare from '../components/repo-share';

+ export async function getServerSideProps() {
+   const repoInfo = await fetchRepoInfo(DEFAULT_REPO_NAME)
+ 
+   return {
+     props: {
+       initialReduxState: createSpreadoReduxPreloadedState({
+         [INDEX_OF_REPO_SWR]: renderSwrResponse(repoInfo)
+       }),
+     }
+   }
+ }

const Index = () => {
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(startClock())
  }, [dispatch])

  return (
    <>
      <Examples />
      <Link href="/show-redux-state">
        <a>Click to see current Redux State</a>
      </Link>
      <section>
        <h2>repo info</h2>
        <Repo />
        <RepoShare />
      </section>
    </>
  )
}

export default Index

访问http://localhost:3000/

此时可以看到ssr内容已正确渲染

image-20220830-111610.png 至此传播 swr 数据请求ssr已实现。

参考:Next.js SSR – SWR

写在最后

在组合使用 状态管理库数据拉取库 时会藏有许多这样那样的小问题,而 Spreado 旨在将 React 组件间状态和数据的传播问题一网打尽,让我们可以更加专注于 React 应用逻辑。如果有任何的疑问或想法,欢迎在 spreado/issues 留言,中英文都可以。如果有时间和兴趣,欢迎直接提交代码,具体可以参考开发引导。如果觉得小工具有帮助,请给 GitHub repo react-easier/spreado 点个 ⭐️,这也是我们不断前行的动力 😃。