Skip to content

Latest commit

 

History

History
253 lines (211 loc) · 10.8 KB

README_CN.md

File metadata and controls

253 lines (211 loc) · 10.8 KB

基于 React 同构直出的 Sonic 使用示例。React 同构直出使用 Redux/Next.js/Koa2 实现。

license PRs Welcome wiki

目录

推荐升级到 node 8.x + npm 5.x 环境。

git clone https://github.com/Tencent/VasSonic.git <my-project-name>
cd <my-project-name>/sonic-react
npm install  # 安装项目依赖
npm run build
npm start

手机端安装 Android 或 iOS 测试用应用程序。(下载

然后将手机与服务器连接在同一局域网下,查看服务器 ip 配置手机代理,并设置测试链接地址为 http://服务器ip:3000/demo

1:设置手机代理 2:设置测试链接
设置手机代理 设置测试链接
3:访问 demo 4:效果演示
访问demo demo
npm run <script> 描述
start 启动服务(生产环境,需先执行 npm run build 命令)
dev 启动服务(开发环境,无需执行 npm run build 命令)
build 打包构建到目录 .next

.
├── components               # demo 页面视图组件
├── containers               # demo 页面容器组件
├── pages                    # Next.js 用于存放每个页面入口组件的目录
│   └── demo.js              # demo 页面入口 js
├── redux                    # Redux 相关模块
│   └── duck.js              # ducks 模式组织 redux 模块
├── static                   # Next.js 用于存放静态资源的目录
└── server.js                # 服务入口 js

我们不去深究 React 直出以及示例中拼图游戏逻辑的实现,主要来说明下示例中是如何在 React 项目中使用 Sonic 的,流程图如下所示:

  • 服务端拦截 React 渲染出来的HTML字符串,添加 HTML 注释标签来帮助 Sonic 区分模板和数据块。数据块需要通过 <!-- sonicdiff-moduleName --> <!-- sonicdiff-moduleName-end --> 来标记,剩下的部分称为模版。示例中代码实现如下:
/**
 * 添加 Sonic 所需的 HTML 注释标签
 *
 * 举例:
 * <!DOCTYPE html>                                                 <!DOCTYPE html>
 * <html>                                                          <html>
 * <head></head>                                                   <head></head>
 * <body>                                                          <body>
 * … …                                                             … …
 * <div id="root" data-sonicdiff="firstScreenHtml">      =>        <!-- sonicdiff-firstScreenHtml -->
 *     … …                                                         <div id="root" data-sonicdiff="firstScreenHtml">
 * </div>                                                              … …
 * … …                                                             </div>
 * <script>                                                        <!-- sonicdiff-firstScreenHtml-end -->
 *     __NEXT_DATA__=xxx                                           … …
 * </script>                                                       <!-- sonicdiff-initState -->
 * </body>                                                         <script>
 *                                                                     __NEXT_DATA__=xxx
 *                                                                 </script>
 *                                                                 <!-- sonicdiff-initState-end -->
 *                                                                 </body>
 *
 * @param html {string} 原始 HTML 字符串
 * @returns {string} 添加注释标签后的 HTML 字符串
 */
function formatHtml(html) {
    const $ = cheerio.load(html);
    $('*[data-sonicdiff]').each(function(index, element) {
        let tagName = $(this).data('sonicdiff');
        return $(this).replaceWith('<!--sonicdiff-' + tagName + '-->' + $(this).clone() + '<!--sonicdiff-' + tagName + '-end-->');
    });
    html = $.html();
    html = html.replace(/<script\s*>\s*__NEXT_DATA__\s*=([\s\S]+?)<\/script>/ig, function(data1) {
        return '<!--sonicdiff-initState-->' + data1 + '<!--sonicdiff-initState-end-->';
    });
    return html;
}
  • 服务端使用 sonic_differ 模块对数据进行处理后输出给浏览器。
server.use(async (ctx, next) => {
    await next();

    // 只拦截 html 请求
    if (!ctx.response.is('html')) {
        return;
    }

    // 非 sonic 模式不做特殊处理
    if (!ctx.request.header['accept-diff']) {
        ctx.body = ctx.state.resHtml;
        return;
    }

    // 使用 sonic_differ 模块对数据进行处理
    let sonicData = sonicDiff(ctx, formatHtml(ctx.state.resHtml));

    if (sonicData.cache) {
        // sonic 模式:完全缓存
        ctx.body = '';
    } else {
        // 其它 sonic 状态
        ctx.body = sonicData.data;
    }
});
  • 前端在执行到 componentDidMount() 阶段时,通过 js 调用终端接口来获取 sonic 状态和数据,根据终端返回的不同状态,来决定如何渲染页面。
componentDidMount() {
    // 获取客户端返回的 sonic 状态和数据,根据终端返回数据做出相应的处理
    this.getSonicData((status, sonicUpdateData) => {
        switch (status) {
            // sonic 状态:数据更新
            case 3:
                // 使用客户端返回的数据更新页面 Store
                let initState = sonicUpdateData['{initState}'] || '';
                initState.replace(/<!--sonicdiff-initState-->\s*<script>\s*__NEXT_DATA__\s*=([\s\S]+?)module=/ig, function(matched, $1) {
                    window.__NEXT_DATA__ = JSON.parse($1);
                });
                this.props.initImgArr(window.__NEXT_DATA__.props.initialState.gameArea);
                break;
            default:
                break
        }
        // 展示 sonic 状态
        this.props.setSonicStatus(status);
    });
}

getSonicData(callback) {
    let sonicHadExecute = 0;   // 判断回调是否触发过的标识
    const timeout = 3000;      // 终端接口 3s 内没有响应,触发超时逻辑

    // 调用终端接口通知客户端进行 sonic 处理逻辑
    window.sonic && window.sonic.getDiffData();

    function sonicCallback(data) {
        if (sonicHadExecute === 0) {
            sonicHadExecute = 1;
            callback(data['sonicStatus'], data['sonicUpdateData']);
        }
    }

    setTimeout(function() {
        if (sonicHadExecute === 0) {
            sonicHadExecute = 1;
            callback(0, {});
        }
    }, timeout);

    // 终端调用 getDiffDataCallback 方法将数据传递给页面
    window['getDiffDataCallback'] = function (sonicData) {
        /**
         * Sonic 状态:
         * 0: 异常
         * 1: 首次加载(首次和正常页面逻辑一样,前端无需特殊处理)
         * 2: 模板更新(当模版发生变化时,终端会自动刷新当前页面,前端也无需特殊处理)
         * 3: 数据更新(sonic页面模版没有变化,只有数据块发生变化,终端会返回变化的数据块名称和内容,前端只需要把变化的内容替换到页面即可)
         * 4: 完全缓存(sonic页面模版和数据都没有变化,页面无需任何处理)
         */
        let sonicStatus = 0;
        let sonicUpdateData = {};  // 数据更新时终端返回的数据
        sonicData = JSON.parse(sonicData);
        switch (parseInt(sonicData['srcCode'], 10)) {
            case 1000:
                sonicStatus = 1;
                break;
            case 2000:
                sonicStatus = 2;
                break;
            case 200:
                sonicStatus = 3;
                sonicUpdateData = JSON.parse(sonicData['result'] || '{}');
                break;
            case 304:
                sonicStatus = 4;
                break;
        }
        sonicCallback({ sonicStatus: sonicStatus, sonicUpdateData: sonicUpdateData });
    };
}

遇到其他问题,可以:

  1. 通过demo来理解 sample
  2. 联系我们。

VasSonic is under the BSD license. See the LICENSE file for details.