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最佳实践尝试(三)
相关文章
相关标签/搜索