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"
]
}
複製代碼
基本的配置到這裏就結束了,下一章開始正式開發的介紹。
系列文章: