NextJs Redux 服务器端渲染应用程序,使用 Next.js、React 和 Redux

2025-05-28

NextJs Redux服务器端渲染应用程序,使用 Next.js、React 和 Redux

预渲染 Web 应用程序或在服务器端渲染 Web 应用程序有许多已知好处,其中包括更好的 SEO、更快的加载时间、为连接较差的用户提供更好的用户体验等等。

这篇文章将指导您快速开始使用 Next 并使用它来开发 React-Redux Web 应用程序。

注意:本文已过时,且适用于 Next.js 9.2 及更低版本。请参阅next-redux-wrapper了解如何创建更新版本的 Next.js-redux Web 应用。

此职位的先决条件:

  • 理解 React 基本概念
  • 理解 Redux 基本概念

为了本文的目的,在了解了所有概念之后,我们将使用服务器端渲染的应用程序创建一个简单的计数器应用程序。

Next.JS 入门

Next.js 是一个 React 框架,它使开发 React 服务器端渲染应用程序变得非常容易。它还提供了一些其他功能,但在本文中,我们将仅介绍如何使用 Next.js 进行服务器端渲染。

我强烈建议您先阅读文档。本部分介绍了Next的基本原理,并且与文档有很多重叠之处。我建议您先阅读文档,然后再继续阅读本文的下一部分。不过,如果文档还不够,您可以继续阅读!

如果你已经安装了 Next.js 并且了解基础知识,则可以跳到以下内容

首先,我们创建一个项目目录:

mkdir hello-next
Enter fullscreen mode Exit fullscreen mode

然后我们使用 npm 初始化项目:

cd hello-next
npm init -y
Enter fullscreen mode Exit fullscreen mode

然后我们需要安装nextreactreact-dom,这些是必要的依赖项next

npm install --save react react-dom next
Enter fullscreen mode Exit fullscreen mode

然后,我们需要在项目目录中创建一个“pages”目录。默认情况下,此目录中的所有 React 文件都会根据文件名映射到服务器端渲染应用的 url 路由:

mkdir pages
Enter fullscreen mode Exit fullscreen mode

名为 的文件index.jsx将被映射到根 URL,即localhost:3000/
类似地,名为 的文件login.jsx将被映射到 。localhost:3000/login
此功能默认启用,对于我们的用例来说,这已经足够了。

首先next,我们需要编辑项目目录中的 package.json 并将脚本替换为以下内容:

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}
Enter fullscreen mode Exit fullscreen mode

完成后,一切就绪。现在可以在项目目录中运行以下命令:

npm run dev
Enter fullscreen mode Exit fullscreen mode

几秒钟后,开发服务器应该启动并运行,访问时localhost:3000会返回“404 | 页面未找到”错误。这是因为我们的 pages 目录没有“index.jsx”文件。我们可以继续创建它:

touch pages/index.jsx
Enter fullscreen mode Exit fullscreen mode

然后我们可以创建一个简单的 Hello-World 索引页:

import React from 'react';

class App extends React.Component {
    render() {
        return (
            <h1> Hello World! </h1>
        );
    }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

这里我们创建一个渲染“Hello World”的 React 组件,访问根路径将显示结果。

接下来仅识别 pages 目录中 React 文件中的默认导入,并且仅在浏览到 URL 路径时才会呈现默认组件。

创建一个简单的计数器应用程序(不使用 Redux)

要创建一个简单的计数器应用程序,我们可以执行以下操作:

import React from 'react';

class App extends React.Component {

    constructor(props) {
        super(props);

        //Initialise state
        this.state = {
            counter: 0
        };
    }

    //Updates the counter in state by +1
    incrementCounter = () => {
        this.setState(prevState => {
            this.setState({
                counter: prevState.counter + 1.
            });
        });
    };

    //Updates the counter in state by  -1
    decrementCounter = () => {
        this.setState(prevState => {
            this.setState({
                counter: prevState.counter - 1.
            });
        });
    };

    render() {
        return (
            <div>
                <button onClick={this.incrementCounter}>Increment</button>
                <button onClick={this.decrementCounter}>Decrement</button>
                <h1>{this.state.counter}</h1>
            </div>
        );
    }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

这样做将显示以下结果:

结果图像

单击相应的按钮可增加或减少。

正如您所见,next它利用了 React,因此使用 Next 很简单,只需使用 React 即可,唯一的区别是next自动(在后台)呈现应用程序服务器端。

理解使用 Redux 进行服务器端渲染

使用 Redux 的方式Next与使用 React 基本相同。Redux Web 应用的行为方式也相同。所有操作都与客户端渲染的应用类似。使用 Redux 的唯一挑战在于服务器端渲染的初始 Redux 设置,这正是下一节将要介绍的内容。

Redux官方文档很好地解释了服务器端渲染如何与 Redux 协同工作。解释如下:

当使用 Redux 进行服务器渲染时,我们还必须在响应中发送应用的状态,以便客户端可以将其用作初始状态。这一点很重要,因为如果我们在生成 HTML 之前预加载任何数据,我们希望客户端也能访问这些数据。否则,客户端生成的标记将与服务器标记不匹配,客户端将不得不重新加载数据。

要将数据发送到客户端,我们需要:

  • 每次请求时创建一个新的 Redux 存储实例;
  • 选择性地调度一些动作;
  • 将状态从存储中拉出;
  • 然后将状态传递给客户端。

在客户端,将创建一个新的 Redux 存储,并使用服务器提供的状态进行初始化。Redux 在服务器端的唯一工作是提供我们应用的初始状态。

这看起来可能令人困惑,但重要的是:

  1. 为新用户请求初始化并创建一个新的 redux 存储
  2. (可选)向商店填充信息,例如,您可以利用请求中的用户 cookie 来识别用户并使用用户信息填充商店。
  3. 将 redux 状态发送到客户端
  4. 然后,客户端使用接收到的状态来初始化客户端 redux 存储。

下一部分将介绍如何实现这一目标。

设置 Redux

为了开始使用 Redux,我们将创建一个基本的 Redux 应用程序,在其中跟踪我们状态中的计数器。

我们需要首先安装redux和react-redux:

npm install --save redux react-redux
Enter fullscreen mode Exit fullscreen mode

我们的项目结构如下:

hello-next
    +- .next
    +- node_modules
    +- pages
    +- redux
        +- actions
        +- reducers
Enter fullscreen mode Exit fullscreen mode

为此,我们可以执行以下操作:

mkdir redux redux/actions redux/reducers
Enter fullscreen mode Exit fullscreen mode

现在我们将创建一个 counterReducer,它将跟踪我们的计数器状态。我们可以将它放在 reducers 文件夹中:

touch redux/reducers/counterReducer.js
Enter fullscreen mode Exit fullscreen mode

counterReducer 文件如下所示:

const counterReducer = (state = {value: 0}, action) => {
    return {...state};
};

export default counterReducer;
Enter fullscreen mode Exit fullscreen mode

这将创建一个初始状态,计数器值设置为 0

现在我们的 counterReducer 什么也没做。我们可以继续创建动作:

touch redux/actions/counterActions.js
Enter fullscreen mode Exit fullscreen mode

我们只需指定两个动作——增加和减少:

//Action Types
export const INCREMENT_COUNTER = "INCREMENT_COUNTER";
export const DECREMENT_COUNTER = "DECREMENT_COUNTER";


//Action Creator
export const incrementCounter = () => ({
   type: INCREMENT_COUNTER
});

export const decrementCounter = () => ({
    type: DECREMENT_COUNTER
});

Enter fullscreen mode Exit fullscreen mode

我们现在可以修改我们的 reducer 来包含这些操作:

import {DECREMENT_COUNTER, INCREMENT_COUNTER} from '../actions/counterActions';

const counterReducer = (state = {value: 0}, action) => {
    switch (action.type) {
        case INCREMENT_COUNTER:
            return {...state, value: state.value + 1};
        case DECREMENT_COUNTER:
            return {...state, value: state.value - 1};
        default:
            return {...state};
    }
};

export default counterReducer;
Enter fullscreen mode Exit fullscreen mode

INCREMENT_COUNTER当或DECREMENT_COUNTER动作被分派时,这将增加或减少我们的计数器。

现在我们可以创建根 Reducer,它将负责组合所有 Reducer。在我们的例子中,只有一个 Reducer “counterReducer”,但通常情况下,我们会组合所有 Reducer。

创建 rootReducer 文件:

touch redux/reducers/rootReducer.js
Enter fullscreen mode Exit fullscreen mode

我们的 rootReducer 文件如下所示:

import counterReducer from './counterReducer';
import {combineReducers} from 'redux';

const rootReducer = combineReducers({
    counter: counterReducer
});

export default rootReducer;
Enter fullscreen mode Exit fullscreen mode

这将我们所有的 reducer 组合成一个 rootReducer,我们可以使用它来初始化我们的 redux 存储。

现在我们可以继续创建我们的 redux 存储:

touch redux/store.js
Enter fullscreen mode Exit fullscreen mode
import {createStore} from 'redux';
import rootReducer from './reducers/rootReducer';

const store = createStore(rootReducer);

export default store;
Enter fullscreen mode Exit fullscreen mode

现在我们已经设置好了 redux 逻辑,可以使用 react-redux 将我们的应用与 redux 连接起来。不过,要做到这一点,我们需要在 pages 目录中创建一个名为“_app.jsx”的特殊文件:

touch pages/_app.jsx
Enter fullscreen mode Exit fullscreen mode

next使用 App 组件初始化页面。我们创建了“_app.jsx”文件来覆盖默认的 App 组件。首先,我们的新 App 组件需要扩展默认的 App 组件,以便next仍然可以使用它来初始化页面。

我们可以从“next/app”导入默认的App组件并创建我们自己的App组件:

import App from 'next/app';

class MyApp extends App {


}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

不过,目前我们什么都没做。类似于 Redux 连接到客户端 React 应用的方式,我们可以在这里连接服务器端渲染的应用程序。

我们使用 react-redux 提供的“Provider”并连接我们的商店:

import App from 'next/app';
import {Provider} from 'react-redux';
import React from 'react';

class MyApp extends App {

    render() {
        return (
            <Provider store={}>

            </Provider>
        );
    }

}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

但是,我们应该在 Provider 组件中将什么作为 store 的参数呢?为了完成设置,我们必须使用一个静态函数getInitialProps。根据next文档,该函数负责:

Next.js 附带了getInitialProps,这是一个async可以作为添加到任何页面的函数static method

获取初始属性allows the page to wait for data before rendering starts.

使用getInitialProps将使页面选择按需服务器端渲染

每个页面都会getInitialProps在服务器端渲染。如果不包含此方法,则文件将立即渲染为静态 HTML next build。包含此函数将允许此页面在服务器上渲染,并且该函数内的所有内容将在将页面发送到客户端之前执行。这在页面需要获取数据的情况下非常有用。从此函数返回任何内容都将允许将该信息发送到客户端。客户端可以使用 React 组件的 props 访问从此函数返回的信息。

我们还可以选择在将 redux 状态发送到客户端之前选择性地填充它,将此函数添加到我们的“_app.jsx”中,如下所示:

import App from 'next/app';
import {Provider} from 'react-redux';
import React from 'react';
import {INCREMENT_COUNTER} from '../redux/actions/counterActions';

class MyApp extends App {

    static async getInitialProps({Component, ctx}) {
        const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};

        //Anything returned here can be access by the client
        return {pageProps: pageProps};
    }

    render() {
        //Information that was returned  from 'getInitialProps' are stored in the props i.e. pageProps
        const {Component, pageProps} = this.props;

        return (
            <Provider store={}>
                <Component {...pageProps}/>
            </Provider>
        );
    }

}

export default MyApp;

Enter fullscreen mode Exit fullscreen mode

ctx 是一个getInitialProps指向 Context 的参数。你可以在这里阅读更多相关信息。

使用getInitialPropsin_app.jsx有不同的接口。在普通页面上使用时,getInitialProps只有一个参数ctx。但是在我们的例子中,由于我们覆盖了默认的 App 组件,因此我们可以访问默认的 App 组件。我们需要确保默认的 App 组件使用了该函数,getInitialProps然后我们需要将该函数返回的内容发送给客户端。

接下来,为了将 store 传递给客户端,我们需要用 React-Redux 的 包装原始组件Provider。为了使所有这些工作正常进行,我们需要安装最后一个库:next-redux-wrapper

npm install --save next-redux-wrapper
Enter fullscreen mode Exit fullscreen mode

Next-redux-wrapper 将使我们能够在每个新请求时创建一个存储,并将其MyApp作为道具传递给(我们的应用程序实现)。

我们需要利用 Next-redux-wrapper 的withRedux包装器并用它包装我们的 App 组件。

withRedux函数接受makeStore第一个参数。该makeStore函数将接收初始状态,并在每次调用时返回一个新的 Redux 实例store,这里不需要记忆,它会在包装器内部自动完成。

与 next-redux-wrapper 连接后:

import App from 'next/app';
import {Provider} from 'react-redux';
import React from 'react';
import withRedux from "next-redux-wrapper";
import store from '../redux/store';

class MyApp extends App {

    static async getInitialProps({Component, ctx}) {
        const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};

        //Anything returned here can be accessed by the client
        return {pageProps: pageProps};
    }

    render() {
        //pageProps that were returned  from 'getInitialProps' are stored in the props i.e. pageprops
        const {Component, pageProps, store} = this.props;

        return (
            <Provider store={store}>
                <Component {...pageProps}/>
            </Provider>
        );
    }
}

//makeStore function that returns a new store for every request
const makeStore = () => store;

//withRedux wrapper that passes the store to the App Component
export default withRedux(makeStore)(MyApp);


Enter fullscreen mode Exit fullscreen mode

完成以下更改后,我们的应用就准备好了!现在我们可以像平常一样使用 Redux 了。修改我们的index.jsx代码以包含 Redux。

import React from 'react';
import {connect} from 'react-redux';
import {decrementCounter, incrementCounter} from '../redux/actions/counterActions';

class App extends React.Component {

        static getInitialProps({store}) {}

    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div>
                <button onClick={this.props.incrementCounter}>Increment</button>
                <button onClick={this.props.decrementCounter}>Decrement</button>
                <h1>{this.props.counter}</h1>
            </div>
        );
    }
}

const mapStateToProps = state => ({
    counter: state.counter.value
});

const mapDispatchToProps = {
    incrementCounter: incrementCounter,
    decrementCounter: decrementCounter,
};

export default connect(mapStateToProps, mapDispatchToProps)(App);

Enter fullscreen mode Exit fullscreen mode

我们使用 React-Reduxconnect将 Redux 状态连接到我们的页面,并使用mapStateToPropsmapDispatchToProps将我们的状态和 actionCreators 连接到我们的页面。

运行页面后,我们的 React-Redux 应用按预期运行!点击按钮“增加”和/或“减少”即可!

恭喜,您现在了解了如何使用以下方法创建服务器端渲染的 React-Redux 应用程序的基础知识next.js

需要注意的一点是,目前 Redux 只能作为单页应用程序工作,这意味着客户端路由是 Redux 存储在页面之间传输的唯一方式。

这意味着,如果用户导航到不同的 URL(即服务器端路由),服务器会将其视为新的客户端,并提供一个空的 redux 状态。要了解如何持久化 redux 状态,以使计数器值在每次刷新时保持不变,请参阅next-redux-wrapper 指南。不过,请确保先更新 Next.js 版本和 next-redux-wrapper 版本,并遵循更新后的指南

该项目的代码可以在Github上找到

这篇博文到此结束!这是我的第一篇博文,希望你喜欢!欢迎任何反馈!

如果你想自己阅读更多内容,请参阅next-redux-wrapper 存储库

文章来源:https://dev.to/waqasabbasi/server-side-rendered-app-with-next-js-react-and-redux-38gf
PREV
成为精英开发人员的十个技巧
NEXT
我觉得 Dev 中帖子的质量正在下降 2 个想法......