React最佳實踐嘗試(一)技術選型

github地址:github.com/bbwlfx/ts-b…css

最近參與了不少遷庫的工做,感受不少老代碼已經不太實用,而且存在一些漏洞,加上這點時間聽了不少同事的分享,所以決定嘗試一下構建React的最佳實踐。html

爲了更好地瞭解react項目結構,鍛鍊本身的能力,這裏並無使用create-react-app前端

目標

  1. SPA + SSR
  2. 首屏數據加載
  3. 優雅的先後端同構
  4. 使用最新的技術棧
  5. ...

技術選型

  1. React + @rematchnode

    @rematch前段時間新出的redux框架,擁有比redux更簡潔的語法,無需複雜的action creators和thunk middleware。react

    github地址:github.com/rematch/rem…webpack

    中文文檔地址:rematch.gitbook.io/handbook/ap…git

    而且rematch自己支持immer插件,能夠經過mutable的寫法去管理狀態的變化。github

    Model定義

    model的定義就是redux的actions、reducer以及state,@rematch將三者合爲一個model文件,每個modal有三個屬性:state、reducers、effects。web

    • state存放當前model的全部狀態
    • reducers存放各類同步修改state的函數,和redux的定義同樣
    • effects存放各類異步函數,而且不須要任何middleware,@rematch自己就支持async/await寫法。

    而且在effects中,咱們能夠經過dispatch去調用其餘model的方法,去修改其餘模塊的數據。chrome

    // effects的兩種寫法
    dispatch({ type: 'count/incrementAsync', payload: 1 }) // state = { count: 3 } after delay
    dispatch.count.incrementAsync(1)
    複製代碼
    Modal.js
    export const count = {
        state: 0, // initial state
        reducers: {
            // handle state changes with pure functions
            increment(state, payload) {
            return state + payload
        }
    },
     effects: (dispatch) => ({
        // handle state changes with impure functions.
        // use async/await for async actions
        async incrementAsync(payload, rootState) {
            await new Promise(resolve => setTimeout(resolve, 1000))
                dispatch.count.increment(payload)
            }
        })
    }
    複製代碼
    Immer插件
    const todo = {
        state: [{
            todo: "Learn typescript",
            done: true
        }, {
            todo: "Try immer",
            done: false
        }],
        reducers: {
            done(state) {
                state.push({todo: "Tweet about it"})
                state[1].done = true
                return state
            }
        }
    };
    複製代碼
  2. TypeScript

    選擇ts的最根本的願意實際上是由於js已經用爛了,打算嘗試一下ts的使用,由於在網上也看到了不少關於ts優點的介紹。本着追求極致的原則選擇使用了ts。

  3. Koa@2

    Koa自己是一個十分輕量的node框架,而且擁有豐富的第三方插件庫以及生態環境,而且Koa自己的易擴展性讓咱們能夠靈活開發,koa2支持的async/await語法也讓異步請求寫起來十分舒服。

  4. Webpack@4

  5. react-router@4

    自己在前端路由方面選擇了@reach/router,可是使用了一段時間以後發現常常會出現頁面刷新以後忽然滾動到另外的位置上,後來查資料發現@reach/router源碼中使用了大量的光標操做,聽說是爲了對殘疾人友好。這些光標操做不知道何時就會產生一些奇怪的bug,所以最終仍是放棄了@reach/router選擇了react-router。

    @reach/router和react-router的區別在於:@reach/router是分型路由,支持咱們以碎片化的方式定義局部路由,沒必要像react-router同樣須要有一個大的路由配置文件,全部的路由都寫在一塊兒。這種分型路由在大型應用裏面開發起來比較方便,可是一樣也會產生不易維護的反作用。

  6. pug

    模板引擎選擇了pug(jade),pug模板自己使用的是js語法,對前端開發人員十分友好,而且pug自己也支持很是多的功能。koa-pug中間件也支持pug引擎。

    doctype html
    
    html
        head
            meta(http-equiv="X-UA-Compatible" content="IE=edge,chrome=1")
            meta(charset="utf-8")
            include ./common_state.pug
            block links
            | !{ styles }
            block common_title
                title TodoList 
            include counter.pug
            block custom_state
        body
            block doms
            #root !{ html }
            | !{ scripts }
    複製代碼
  7. react-loadable

目錄結構

目錄總體分爲前端目錄:public 、 後端目錄:src

public的js目錄中存放文件以下:

  • components 組件代碼
  • constants 常量代碼
  • containers 頁面代碼
  • decorators 各類裝飾器的代碼
  • entry 頁面入口代碼
  • lib 工具庫代碼
  • models @rematch的Modal代碼
  • scripts 輔助腳本代碼
  • typings ts類型聲明代碼

src的目錄以下:

  • config 配置文件
  • controllers 路由處理文件
  • routes 路由聲明文件
  • template 模板文件
  • utils 工具代碼
  • app.js 後端啓動入口,主要存放運維代碼
  • server.js server啓動代碼,主要存放業務代碼

webpack配置文件

webpack配置這裏配合webpack-merge,作到webpack配置文件的拆分。

  • webpack.base.config.js
  • webpack.client.config.js
  • webpack.dev.config.js
  • webpack.prod.config.js
  • webpack.ssr.config.js

base負責基本的配置

client負責前端打包的配置

ssr負責服務端渲染的打包的配置

dev負責開發模式的配置

prod負責生產模式的配置

具體的配置能夠到項目源碼中查看

其餘配置文件

public和src目錄都須要一個單獨的.babelrc文件,因爲babel7支持經過js的寫法書寫配置文件了,因此這裏直接用兩個.babelrc.js文件便可。

public/.babelrc.js
module.exports = api => {
  const env = api.env();
  // 服務端渲染時不加載css
  const importConfig =
    env === "client"
      ? {
          libraryName: "antd",
          libraryDirectory: "es",
          style: true
        }
      : {
          libraryName: "antd"
        };

  return {
    presets: [
      [
        "@babel/env",
        {
          modules: env === "ssr" ? false : "commonjs",
          targets: {
            browsers: ["last 2 versions"]
          }
        }
      ],
      "@babel/react",
      "@babel/typescript"
    ],
    plugins: [
      ["import", importConfig],
      "dynamic-import-node",
      "@babel/plugin-proposal-class-properties",
      [
        "babel-plugin-module-resolver",
        {
          cwd: "babelrc",
          extensions: [".ts", ".tsx"],
          root: ["./"],
          alias: {
            components: "./js/components",
            containers: "./js/containers",
            models: "./js/models",
            decorators: "./js/decorators",
            constants: "./js/constants",
            lib: "./js/lib",
            typings: "./js/typings"
          }
        }
      ],
      "react-loadable/babel"
    ]
  };
};

複製代碼

babel-plugin-import插件負責處理對antd的按需加載問題,而且處理ssr不加載css的邏輯。

dynamic-import-node插件負責處理服務端渲染時候對前端組件動態加載的處理。

module-resolver插件負責處理alias問題,因爲webpack的alias只能在前端使用,服務端渲染的時候沒法處理webpack中定義的alias,所以這裏使用插件來解決這個問題。

src/.babelrc.js
module.exports = {
  presets: [
    [
      "@babel/env",
      {
        targets: {
          node: "current"
        }
      }
    ],
    "@babel/react",
    "@babel/typescript"
  ],
  plugins: [
    "@babel/plugin-proposal-class-properties",
    "dynamic-import-node",
    [
      "babel-plugin-module-resolver",
      {
        cwd: "babelrc",
        alias: {
          components: "../public/js/components",
          containers: "../public/js/containers",
          models: "../public/js/models",
          controllers: "./controllers",
          decorators: "../public/js/decorators",
          server: "./public/buildServer",
          lib: "../public/js/lib",
          typings: "./js/typings"
        },
        extensions: [".ts", ".tsx", ".js", ".jsx"]
      }
    ]
  ]
};
複製代碼

爲了配合SSR,node層的.babelrc文件也須要一樣一套alias。

tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "./dist/",
    "moduleResolution": "node",
    "jsx": "preserve",
    "module": "esNext",
    "target": "es2015",
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "lib": ["es2017", "dom"],
    "baseUrl": ".",
    "noEmit": true,
    "pretty": true,
    "skipLibCheck": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "resolveJsonModule": true,
    "noImplicitReturns": true
  },
  "include": ["**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules", "**/*.spec.ts", "**/*.d.ts"]
}
複製代碼

其餘的還有一切開發的配置文件,好比.eslintrc,.stylelintrc等看我的喜愛配置便可。

爲了更好的格式化代碼,以及在commit以前作一些校驗工做,項目裏添加了husky、lint-staged、prettier-eslint等npm包。 而且在package.json文件中定義好對應的代碼:

package.json
"scripts": {
    "precommit": "lint-staged",
    "format": "prettier-eslint --write public/**/*.{js,ts}"
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "npm run format --",
      "git add"
    ],
    "*.{js,jsx}": [
      "npm run format --",
      "git add"
    ],
    "*.{css,less,scss}": [
      "npm run format --",
      "stylelint --syntax=less",
      "git add"
    ]
  }
複製代碼

基本的配置到這裏就結束了,下一章開始正式開發的介紹。

系列文章:

  1. React最佳實踐嘗試(二)
  2. React最佳實踐嘗試(三)
相關文章
相關標籤/搜索