zoukankan      html  css  js  c++  java
  • 自制的React同构脚手架

    代码地址如下:
    http://www.demodashi.com/demo/12575.html

    Web前端世界日新月异变化太快,为了让自己跟上节奏不掉队,总结出了自己的一套React脚手架,方便日后新项目可以基于此快速上手开发。

    源码: https://github.com/54sword/react-starter

    特点

    • 服务端渲染,完美解决SEO问题
    • 按页面将代码分片,然后按需加载
    • 支持 CSS Modules,避免CSS全局污染
    • 支持流行UI框架 Bootstrap 4
    • 开发环境支持热更新
    • 内置登录、退出、页面权限控制、帖子列表获取、帖子详情获取等功能
    • 内置用户访问页面时,301、404 状态相应的处理逻辑

    需求配置

    node ^8.6.0
    npm ^5.7.1
    

    没有在windows机器上测试过,可能会报错

    开始

    $ git clone git@github.com:54sword/react-starter.git
    $ cd react-starter
    $ npm install
    $ npm run dev
    

    浏览器打开 http://localhost:4000

    相关命令说明

    开发环境

    注意:开发环境下,代码不分片,生产环境下才会分片

    npm run dev
    

    生产环境测试

    npm run dist
    npm run server
    

    部署到服务器

    1、修改 config/index.js 中的 public_path 配置
    2、打包文件,除了index.ejs是服务端渲染的模版文件,其他都是客户端使用的文件

    npm run dist
    

    3、将项目上传至你的服务器
    4、启动服务

    Node 启动服务

    NODE_ENV=production __NODE__=true BABEL_ENV=server node src/server
    

    或使用 pm2 启动服务

    NODE_ENV=production __NODE__=true BABEL_ENV=server pm2 start src/server --name "react-starter" --max-memory-restart 400M
    

    目录结构

    .
    ├── config                              # 项目配置文件
    ├── dist                                # 所有打包文件储存在这里
    ├── src                                 # 程序源文件
    │   ├── actions                         # redux actions
    │   ├── client                          # 客户端入口
    │   ├── common                          # 全局可复用的容器组件
    │   ├── components                      # 全局可复用的容器组件
    │   ├── pages                           # 页面组件
    │   ├── reducers                        # redux reducers
    │   ├── router                          # 路由配置
    │   ├── server                          # 服务端入口
    │   ├── store                           # redux store
    │   └── view                            # html模版文件
    ├── .babelrc                            # 程序源文件
    ├── webpack.development.config.js       # 开发环境的webpack配置项
    └── webpack.profuction.config.js        # 生产环境的wbepakc配置项
    

    运行效果图

    20180306-1.png
    20180306-2.png
    20180306-3.png

    部分功能实现思路详解

    配置路由

    src/router/index.js 为路由配置文件,如下代码是一个路由项的配置说明

    {
      // 路径
      path: '/',
      // 如果为true,则只有在路径完全匹配location.pathname时才匹配
      exact: true,
      // 页面头部组件
      head: Head,
      /**
       * 内容组件(页面主要内容)
       * generateAsyncRouteComponent 为生成一个异步加载组件,
       * 客户端打包的时候 ../pages/home,会将该组件单独打包成一个js文件,用于在客户端按需加载。
       */
      component: generateAsyncRouteComponent({
        loader: () => import('../pages/home')
      }),
      /**
       * 进入该页面的触发事件
       * requireAuth 为需要登录才能访问
       * requireTourists 只有游客可以访问
       * triggerEnter 进入事件,可以用作任何人都可以访问
       */
      enter: requireAuth
    }
    
    

    页面组件详细

    src/pages/ 为页面组件,实现具体的页面内容,以首页为例的说明 ./src/pages/home/index.js

    import React from 'react';
    import PropTypes from 'prop-types';
    // 加载帖子列表的方法
    import { loadPostsList } from '../../actions/posts';
    
    // http://blog.csdn.net/ISaiSai/article/details/78094556
    import { withRouter } from 'react-router-dom';
    
    // 壳组件,给页面组件套一个壳组件,方便给所有页面增加额外功能和属性
    import Shell from '../../components/shell';
    // 生成页面Meta,如标题、描述、关键词
    import Meta from '../../components/meta';
    // 帖子列表组件
    import PostsList from '../../components/posts/list';
    
    export class Home extends React.Component {
    
      // 服务端渲染
      // 加载需要在服务端渲染的数据
      static loadData({ store, match }) {
        return new Promise(async function (resolve, reject) {
    
          /**
           * 这里的 loadPostsList 方法,是在服务端加载 posts 数据,储存到 redux 中。
           * 这里对应的组件是 PostsList,PostsList组件里面也有 loadPostsList 方法,但它是在客户端执行。
           * 然后,服务端在渲染 PostsList 组件的时候,我们会先判断如果redux中,是否存在该条数据,如果存在,直接拿该数据渲染
           */
    
          await loadPostsList({
            id: 'home',
            filter: {
              sort_by: "create_at",
              deleted: false,
              weaken: false
            }
          })(store.dispatch, store.getState);
    
          resolve({ code:200 });
        })
      }
    
      constructor(props) {
        super(props);
      }
    
      render() {
        return(<div>
    
          <Meta title="首页" />
    
          <PostsList
            id={'home'}
            filter={{
              sort_by: "create_at",
              deleted: false,
              weaken: false
            }}
            />
        </div>)
      }
    
    }
    
    
    Home = withRouter(Home);
    export default Shell(Home);
    
    

    服务端渲染

    
    import path from 'path';
    import express from 'express';
    import bodyParser from 'body-parser';
    import cookieParser from 'cookie-parser';
    import compress from 'compression';
    
    // 服务端渲染依赖
    import React from 'react';
    import ReactDOMServer from 'react-dom/server';
    import { StaticRouter, matchPath } from 'react-router';
    import { Provider } from 'react-redux';
    import DocumentMeta from 'react-document-meta';
    
    // 路由配置
    import configureStore from '../store';
    // 路由组件
    import createRouter from '../router';
    // 路由初始化的redux内容
    import { initialStateJSON } from '../reducers';
    import { saveAccessToken, saveUserInfo } from '../actions/user';
    
    // 配置
    import { port, auth_cookie_name } from '../../config';
    import sign from './sign';
    import webpackHotMiddleware from './webpack-hot-middleware';
    
    const app = express();
    
    
    // ***** 注意 *****
    // 不要改变如下代码执行位置,否则热更新会失效
    // 开发环境开启修改代码后热更新
    if (process.env.NODE_ENV === 'development') webpackHotMiddleware(app);
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(cookieParser());
    app.use(compress());
    app.use(express.static(__dirname + '/../../dist'));
    
    // 登录、退出
    app.use('/sign', sign());
    
    
    app.get('*', async (req, res) => {
    
      // 创建 store
      const store = configureStore(JSON.parse(initialStateJSON));
    
      let user = null;
      let accessToken = req.cookies[auth_cookie_name] || '';
    
      // 验证 token 是否有效
      if (accessToken) {
        // 这里可以去查询 accessToken 是否有效
        // your code
        // 这里假设如果有 accessToken ,那么就是登录用户,将他保存到redux中
        user = { id: '001', nickname: accessToken };
        // 储存用户信息
        store.dispatch(saveUserInfo({ userinfo: user }));
        // 储存access token
        store.dispatch(saveAccessToken({ accessToken }));
      }
    
      // 创建路由,返回 list 、dom
      // list 是路由的配置列表,dom render的dom
      const router = createRouter(user);
      const _Router = router.dom;
    
      let _route = null,
          _match = null;
    
      // 从路由配置列表中,找到对应的路由
      router.list.some(route => {
        let match = matchPath(req.url.split('?')[0], route);
        if (match && match.path) {
          _route = route;
          _match = match;
          return true;
        }
      })
    
      /**
       * 加载异步组件,并在异步组件中执行 loadData,loadData 加载的数据,储存到redux store中
       */
      const context = await _route.component.load({ store, match: _match });
    
      // 渲染页面
      let html = ReactDOMServer.renderToString(
        <Provider store={store}>
          <StaticRouter location={req.url} context={context}>
            <_Router />
          </StaticRouter>
        </Provider>
      );
    
      // 将redux state 转换成 json 储存到页面中
      let reduxState = JSON.stringify(store.getState()).replace(/</g, '\x3c');
    
      // 获取页面的meta,嵌套到模版中
      // 给客户端 initState
      let meta = DocumentMeta.renderAsHTML();
    
      if (context.code == 301) {
        res.writeHead(301, {
          Location: context.url
        });
      } else {
        res.status(context.code);
        res.render('../dist/index.ejs', { html, reduxState, meta });
      }
    
      res.end();
    
    });
    
    app.listen(port);
    console.log('server started on port ' + port);
    
    

    自制的React同构脚手架

    代码地址如下:
    http://www.demodashi.com/demo/12575.html

    注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

  • 相关阅读:
    subprocess
    bytes(str_, encoding="utf8")
    按文件生成时间 排序 批量与生成同步上传文件
    async
    http trigger 事件源是事件的生产者,函数是事件的处理者
    分片上传
    使用 FFmpeg 处理高质量 GIF 图片
    兴趣 主题 字段 二值化 多值并列属性 拆分 二值化
    打开 回收站
    shell如何查看单个或多个文件的行数或总行数
  • 原文地址:https://www.cnblogs.com/demodashi/p/9171610.html
Copyright © 2011-2022 走看看