React同构起步

React同构从0到1

前言

如果你想快速做react同构的新项目建议你去了解next.js等成熟框架,本教程仅限于想了解如何从0开始实现一个同构环境过程的同学,对于想改造现有spa项目的同学也很有帮助,具有一定参考价值。

需要实现的目标

  • 服务端渲染
  • 带路由的服务端渲染
  • 同构
  • 热更新
  • 前后端按需加载
  • 服务端预加载数据
  • css模块化(选修)
  • seo(选修)

以上任务中热更新和代码按需加载比较难实现,实现按需加载时会带来同构不一致的问题,随着你可能想做的越来越完美,就要添加功能,我们最起码的需求是要保证同构,否则这一切除了浪费服务器资源之外将毫无意义。

可能用到的技术

我们拥抱最新技术,所用框架全是最新版本,可能你看到这个教程时已经有更新的版本产生了。

  • webpack
  • express
  • react
  • react-dom
  • react-router-dom
  • react-router-config
  • react-loadable
  • redux
  • babel

看到这些框架是不是很凌乱,我做这个教程的时候仅仅对react和redux了解的还算熟悉,react-router-dom了解一般,仅仅会使用路由,其他的基本上是小白,用到啥就去github上看api即可,所以大家不必担心,而且这些不是必须的,比如express完全可以用koa取代,除了react技术栈其他的并不是核心,对于webpack建议大家多接触些,非常有用,它的生态已经很全面了,我是在做这个同构时对webpack有不少需求然后边做边学,现在想想它确实是个好帮手。

服务端渲染

该目标简单到爆,寥寥几行代码即可实现。

import React from 'react';
import { renderToString } from 'react-dom/server';
import express from 'express';

var app = express();
var ReactApp = (props) => <h1>Hello SSR from {props.path}</h1>;

app.get('*', (req, res, next) => {
    var ssrDomStr = renderToString(
        <ReactApp path={req.url} />
    );

    res.send(ssrDomStr);
    return;
});

app.listen(3000);

注意,我用的是ES6语法,需要自行配置babel,用babel-node替代node运行程序即可。

带路由的服务端渲染

该实现目标也不难,原理很简单,就是客户端怎么搞服务端也怎么搞就是了,代码如下

import React from 'react';
import { StaticRouter, Switch, Route } from 'react-router-dom';
import { renderToString } from 'react-dom/server';
import express from 'express';

var app = express();
var Home = ({match}) => <h1>Hello Home, {match.url}</h1>;
var Todo = ({match}) => <h1>Hello Todo, {match.url}</h1>;
var ReactApp = (props) => (
    <div>
        <div>这里可以放些公共头之类的</div>
        <Switch>
            <Route path="/" exact component={Home}></Route>
            <Route path="/todo" exact component={Todo}></Route>
        </Switch>
    </div>
);

app.get('*', function (req, res, next) {
    var context = {};
    var ssrDomStr = renderToString(
        <StaticRouter
            location={req.url}
            context={context}
        >
            <ReactApp />
        </StaticRouter>
    );

    res.send(ssrDomStr);
    return;
});

app.listen(3000);

做到这里我们已经能让服务器根据输入的地址不同用react框架渲染出不同的内容了,为自己鼓个掌吧,简单分析下代码,上半部分就是很普通的react路由配置,下面处理渲染时使用了StaticRouter,这是专门在没有浏览器对象环境中使用的,比如服务端或者无头浏览器,我们传入当前路径给让StaticRouter知道匹配哪个路由,context属性能携带一些信息出来,比如有没有匹配到等,现在用不到这些信息,所以没做什么处理,后面随着深入可以慢慢了解。

同构

我们目前说的都是服务端渲染,既然同构至少要两端吧,不然谁和谁同,这里要说的就是客户端渲染和服务端渲染相同,你可能发现我们上面的代码返回的只是一些html片段,你里面如果绑定事件肯定不会在浏览器里实现,因为查看源码你会发现里面并没有脚本文件,因为客户端没有参与渲染(这里指客户端脚本没有参与渲染),而做这些需要同构。简单的说就是服务端和客户端协同渲染,准确来讲客户端接着服务渲染的结果继续渲染,所以这里一定要保证前后端对相同组件渲染出一致的结果,顾名思义叫同构,这只是我个人理解,也就是服务端分担了一部分客户端的工作,服务端分担要有条件的,就是它的渲染要和客户端一致,打个比方,你生产宝马汽车,从头到尾都是你生产,后来有个合作伙伴,他能生产宝马轮子,你可以让他生产轮子你接着轮子继续制造汽车,他能分担你工作唯一的条件就是他生产的轮子和你的轮子一模一样,否则他的轮子不管是大还是小都无法一次性组装一辆车,这种情况下你可能要对他的轮子修改,而此时正是浏览器会都抖动的原因,它在修改与服务端不一致的结果,我们在做的时候会踩到这些坑,庆幸的是react框架给做好了警告,我们很容易发现问题。说到这里,同构带的优势就很明显了,首屏无白屏现象,展示更快,至于为什么,想必大家明白了吧。废话少说,我们在服务端渲染的基础上加客户端渲染,所谓客户端渲染就是最熟悉不过的spa项目。这里会有一些坑或者叫一些难点(大神直接无视)。

// 核心代码同上, 在服务端返回给浏览器时需要带上webpack前端打包后的js文件,这里有整个前端的代码逻辑,包括事件绑定,样式加载(未抽离css文件时)等
res.send(html.repalce('<div id="root"></div>', `<div id="root">${ssrDomStr}</div>`));

上述中的html指的是html-webpack-plugin生成后的html,里面已经有客户端所需的入口代码,在它会接替服务渲染之后的事情,比如事件绑定,路由跳转等。整个代码篇幅较大,需要有相应的webpack配置,server等,这里不一一贴出代码,感谢的同学可以下载项目源码进行研究。

更完整版本请参考完整例子仓库地址。
(以后有时间再补全详细说明,现在感兴趣的同学可参考代码进行理解。)

原文地址:https://www.cnblogs.com/idiv/p/10013464.html