自制的React同構腳手架

代碼地址以下:
http://www.demodashi.com/demo/12575.htmlhtml

Web前端世界突飛猛進變化太快,爲了讓本身跟上節奏不掉隊,總結出了本身的一套React腳手架,方便往後新項目能夠基於此快速上手開發。前端

源碼: https://github.com/54sword/react-starternode

特色

  • 服務端渲染,完美解決SEO問題
  • 按頁面將代碼分片,而後按需加載
  • 支持 CSS Modules,避免CSS全局污染
  • 支持流行UI框架 Bootstrap 4
  • 開發環境支持熱更新
  • 內置登陸、退出、頁面權限控制、帖子列表獲取、帖子詳情獲取等功能
  • 內置用戶訪問頁面時,30一、404 狀態相應的處理邏輯

需求配置

node ^8.6.0
npm ^5.7.1

沒有在windows機器上測試過,可能會報錯react

開始

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

瀏覽器打開 http://localhost:4000webpack

相關命令說明

開發環境

注意:開發環境下,代碼不分片,生產環境下才會分片git

npm run dev

生產環境測試

npm run dist
npm run server

部署到服務器

一、修改 config/index.js 中的 public_path 配置
二、打包文件,除了index.ejs是服務端渲染的模版文件,其餘都是客戶端使用的文件github

npm run dist

三、將項目上傳至你的服務器
四、啓動服務web

Node 啓動服務shell

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

或使用 pm2 啓動服務express

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大師代發,拒絕轉載,轉載須要做者受權

相關文章
相關標籤/搜索