github地址:github.com/bbwlfx/ts-b…css
最近参与了不少迁库的工做,感受不少老代码已经不太实用,而且存在一些漏洞,加上这点时间听了不少同事的分享,所以决定尝试一下构建React的最佳实践。html
为了更好地了解react项目结构,锻炼本身的能力,这里并无使用create-react-app
。前端
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的定义就是redux的actions、reducer以及state,@rematch将三者合为一个model文件,每个modal有三个属性:state、reducers、effects。web
而且在effects中,咱们能够经过dispatch去调用其余model的方法,去修改其余模块的数据。chrome
// effects的两种写法
dispatch({ type: 'count/incrementAsync', payload: 1 }) // state = { count: 3 } after delay
dispatch.count.incrementAsync(1)
复制代码
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)
}
})
}
复制代码
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
}
}
};
复制代码
TypeScript
选择ts的最根本的愿意实际上是由于js已经用烂了,打算尝试一下ts的使用,由于在网上也看到了不少关于ts优点的介绍。本着追求极致的原则选择使用了ts。
Koa@2
Koa自己是一个十分轻量的node框架,而且拥有丰富的第三方插件库以及生态环境,而且Koa自己的易扩展性让咱们能够灵活开发,koa2支持的async/await语法也让异步请求写起来十分舒服。
Webpack@4
react-router@4
自己在前端路由方面选择了@reach/router,可是使用了一段时间以后发现常常会出现页面刷新以后忽然滚动到另外的位置上,后来查资料发现@reach/router源码中使用了大量的光标操做,听说是为了对残疾人友好。这些光标操做不知道何时就会产生一些奇怪的bug,所以最终仍是放弃了@reach/router选择了react-router。
@reach/router和react-router的区别在于:@reach/router是分型路由,支持咱们以碎片化的方式定义局部路由,没必要像react-router同样须要有一个大的路由配置文件,全部的路由都写在一块儿。这种分型路由在大型应用里面开发起来比较方便,可是一样也会产生不易维护的反作用。
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 }
复制代码
react-loadable
目录总体分为前端目录:public 、 后端目录:src
public的js目录中存放文件以下:
src的目录以下:
webpack配置这里配合webpack-merge,作到webpack配置文件的拆分。
base负责基本的配置
client负责前端打包的配置
ssr负责服务端渲染的打包的配置
dev负责开发模式的配置
prod负责生产模式的配置
具体的配置能够到项目源码中查看
public和src目录都须要一个单独的.babelrc文件,因为babel7支持经过js的写法书写配置文件了,因此这里直接用两个.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,所以这里使用插件来解决这个问题。
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。
{
"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
文件中定义好对应的代码:
"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"
]
}
复制代码
基本的配置到这里就结束了,下一章开始正式开发的介绍。
系列文章: