react的SSR(3)

上回我们完成了开发环境的热更新,这次我们要完成的是针对SSR的路由。

服务端渲染的路由和客户端渲染的路由并没有很大差距,主要差距在服务端接受到url之后解析路由拿到的数据,然后返回特定页面,之后的的渲染又全部交由客户端完成。

也就是说,服务端渲染其实只在用户第一次访问的时候,服务端路由是起效的,之后全部是靠客户端完成的。但是这里我们要考虑路由匹配失效的情况。

首先我们下载react-router-dom

npm install react-router-dom --save-dev

我们先从客户端路由写起,这段应该都特熟悉了,就不多说了。
在app文件夹内建立router文件夹,然后在router文件夹内建立enter-client.js文件

import {
    BrowserRouter as Router,
    Route,
    Switch,
    Redirect,
    NavLink
} from "react-router-dom";
import hello from "../pages/hello";
import hello2 from "../pages/hello2";
import React from 'react'
import Nofound from './nofound'

export default class Root extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <Router>
                <div>
                    <div className="title">This is a react ssr demo</div>
                    <ul className="nav">
                        <li><NavLink to="/">hello</NavLink></li>
                        <li><NavLink to="/two">hello2</NavLink></li>
                    </ul>
                    <div className="view">
                        <Switch>
                            <Route path="/" exact component={hello} />
                            <Route path="/two" component={hello2} />
                            <Nofound code={404}>
                                <div>
                                    <h1>Not Found</h1>
                                </div>
                            </Nofound>
                        </Switch>
                    </div>
                </div>
            </Router>
        );
    }
}

这个是最基本的路由,基本没什么可说的,硬要说有什么需要注意的,就是添加了几个新的组件,hello和hello2,还有应对404的Nofound组件,helllo组件就是之前的那个组件,只不过还了个地方,hello2组件就是复制的hello组件,改了几个字,方便识别。

Nofound组件为

import React from "react";
import { Route } from "react-router-dom";

const Nofound = (props) => (
    <Route render={({nofoundContext}) => {
        return props.children;
    }} />
);

export default Nofound;

这个enter-client.js是客户端路由,对应的引入地址应该是客户端打包的index.js。而与之相对的还有服务端路由,服务端路由和客户端路由基本一致,不同的是,他没有Router组件包裹,而是使用StaticRouter组件包裹。

新建enter-server.js

import {
    BrowserRouter as Router,
    Route,
    Switch,
    Redirect,
    NavLink,
    StaticRouter
} from "react-router-dom";
import hello from "../pages/hello";
import hello2 from "../pages/hello2";
import React from 'react'
import Nofound from './nofound'

class Root extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <div>
                <div className="title">This is a react ssr demo</div>
                <ul className="nav">
                    <li><NavLink to="/">hello</NavLink></li>
                    <li><NavLink to="/two">hello2</NavLink></li>
                </ul>
                <div className="view">
                    <Switch>
                        <Route path="/" exact component={hello} />
                        <Route path="/two" component={hello2} />
                        <Nofound code={404}>
                            <div>
                                <h1>Not Found</h1>
                            </div>
                        </Nofound>
                    </Switch>
                </div>
            </div>
        );
    }
}

const ServerRouter = (context, url)=>{
    const CreateRouter = () => {
        return (
            <StaticRouter context={context} location={url}>
                <Root/>
            </StaticRouter>
        )
    }
    return <CreateRouter/>;
}

module.exports = {
    ServerRouter
};

enter-server的引入地方应该是运行服务的server.js

在sever/server.js中,我们之前引入的模块是hello模块,而现在我们应该引入的是

import ServerRouter from '../app/router/enter-server'

之前我们的express配置的路由是遇到 ‘/’ 返回hello模块,现在更改为

server.get('*', (req, res) => {
    let context = {};
    let component = ServerRouter.ServerRouter(context, req.url);
    const appString = renderToString(component);

    res.send(template({
        body: appString,
        title: 'Hello World from the server',
    }));
});

使用*来代替/是为了让他匹配所有路径,把url传递进路由组件里面,让路由自己返回正确的模块。

好了这样我们的路由组件就写好了,下面我们来写他的数据部分。

下一步我们就要让数据在前后端统一化,不然的话就是我们这边发送的东西和用户实际看到的页面有差别,如果我们的js比较大,加载的时候比较长,对用户的体验会不太好。

说道数据这里就需要使用redux了。但是reudx的具体用法就不多讲了,代码会上传到github,可以对照着看一下,这里仅仅说一下redux在服务端的的写法。

通过阅读redux的官方文档,我们可以直接服务端的redux和客户端的redux主要差别有两点

一个是需要在每次请求的时候,创建一个新的store。

另一个是在每次请求都需要执行该页面的初始化的action。

为什么需要这样的东西呢?其实都是为了页面展示正确的渲染效果,因为页面本身是没有数据的,在客户端我们在componentDidMount中进行初始化,但是在服务端,输出字符串的时候并不会触发componentDidMount,虽然他会触发componentWillMount,但是也有其他的问题。

所以我们需要再请求的时候触发一个初始化的action,既然有action,那么我们也就需要一个store,所以我们需要再请求来临之后创建一个全新的store。

所以全部的核心思想就是上面这两点。下面我们来改造一下我们的server.js

server.get('*', (req, res) => {
    const store = createStore({});
    let context = {},promises;
    let component = ServerRouter.ServerRouter(context, req.url,store);

    getLoadableState(component).then(loadableState => {

        let matchs = matchRoutes(router, req.path);
        promises = matchs.map(({ route, match }) => {
            const asyncData = route.component.Component.asyncData;
            return asyncData ? asyncData(store, Object.assign(match.params, req.query)) : Promise.resolve(null);
        });

        Promise.all(promises).then(() => {
            const appString = renderToString(component);
            res.send(template({
                body: appString,
                title: 'Hello World from the server',

            }));
        }).catch(error => {
            console.log(error);
            res.status(500).send("Internal server error");
        });
    })
});

这里解释一下,matchRoutes的第一个参数是路由,这里更改了一下路由的写法,方便维护。路由更改为

import Loadable from "loadable-components";
import TopList from "../pages/TopList"
import hello from "../pages/hello"
import hello2 from "../pages/hello2"
const router = [
    {
        path: "/",
        component: Loadable(()=>import('../pages/TopList')),
        exact: true
    },
    {
        path: "/one",
        component: hello
    },
    {
        path: "/two",
        component:hello2
    }
];

export default router;

那asyncData又是什么呢?

static asyncData(store) {
    return store.dispatch(something());
}

这个是在每个组件中的一个静态函数,函数中写明你需要执行的action,就可以在请求之后执行这个action了。

以上就完成了一个react项目的服务端加载的骨架了,细微处肯定还有大量的问题等待完善,但是我不打算完善他了,毕竟这只是用来认知react的ssr到底是怎么运行的,而不是用来写项目的,那用什么写呢?

你听说过安利NEXT.js吗?

附录:项目地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值