基於create-react-app官方腳手架搭建dva模式的專案(一)
思索良久,決定還是記錄下心得體會:一個基於create-react-app官方腳手架,搭建起來的dva開發模式的react專案。
當然現今的前端市場如此強大,你可以在網路上找到你想要的任何腳手架,並且很多可以開箱即用,不可否認它們很優秀,開發它們的人或團隊更值得我們豎起大拇指,為他們點贊!比較適合國人還是阿里系的一套react開發腳手架,dva-cli,antd,Umi等,當然gitHub上也有諸多優秀的腳手架,有興趣的同學可以自行查閱學習。
這裡,僅僅以react官方腳手架開啟專案,安裝採用dva方式的,構建專案,展示記錄過程中遇到的各種點和Keng,若你也遇到類似問題,也許能幫上你。
1 安裝create-react-app(鑑於國內網路以下使用cnpm,具體配置網上可找大量資料)
cnpm i create-react-app -g
2 建立專案,目錄名project
create-react-app project
等待命令執行完成,腳手架會為你安裝好基礎的元件包,目錄生成完畢如下:
這就是腳手架標準目錄,我們再來看下package.json檔案如下:
{ "name": "project", "version": "0.1.0", "private": true, "dependencies": { "react": "^16.4.0", "react-dom": "^16.4.0", "react-scripts": "1.1.4" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" } }
如上所示,很乾淨的目錄。
此時已可執行npm start執行起來專案了,預設埠3000,效果如圖:
注意scripts執行命令中有一個eject,意為彈射暴露出所有配置,其實腳手架還是為我們封裝了一些東西的,這裡我們就暴露出所有配置吧。
執行npm run eject,一旦選擇eject,那麼所封裝的元件依賴和專案結構會有所變化,如圖:
我們開啟package.json檔案如下所示,是暴露出的依賴配置項,可以看到已經為我們安裝好webpack,babel,eslint等:
{ "name": "project", "version": "0.1.0", "private": true, "dependencies": { "autoprefixer": "7.1.6", "babel-core": "6.26.0", "babel-eslint": "7.2.3", "babel-jest": "20.0.3", "babel-loader": "7.1.2", "babel-preset-react-app": "^3.1.1", "babel-runtime": "6.26.0", "case-sensitive-paths-webpack-plugin": "2.1.1", "chalk": "1.1.3", "css-loader": "0.28.7", "dotenv": "4.0.0", "dotenv-expand": "4.2.0", "eslint": "4.10.0", "eslint-config-react-app": "^2.1.0", "eslint-loader": "1.9.0", "eslint-plugin-flowtype": "2.39.1", "eslint-plugin-import": "2.8.0", "eslint-plugin-jsx-a11y": "5.1.1", "eslint-plugin-react": "7.4.0", "extract-text-webpack-plugin": "3.0.2", "file-loader": "1.1.5", "fs-extra": "3.0.1", "html-webpack-plugin": "2.29.0", "jest": "20.0.4", "object-assign": "4.1.1", "postcss-flexbugs-fixes": "3.2.0", "postcss-loader": "2.0.8", "promise": "8.0.1", "raf": "3.4.0", "react": "^16.4.0", "react-dev-utils": "^5.0.1", "react-dom": "^16.4.0", "resolve": "1.6.0", "style-loader": "0.19.0", "sw-precache-webpack-plugin": "0.11.4", "url-loader": "0.6.2", "webpack": "3.8.1", "webpack-dev-server": "2.9.4", "webpack-manifest-plugin": "1.3.2", "whatwg-fetch": "2.0.3" }, "scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js --env=jsdom" }, "jest": { "collectCoverageFrom": [ "src/**/*.{js,jsx,mjs}" ], "setupFiles": [ "<rootDir>/config/polyfills.js" ], "testMatch": [ "<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}", "<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}" ], "testEnvironment": "node", "testURL": "http://localhost", "transform": { "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest", "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js", "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js" }, "transformIgnorePatterns": [ "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$" ], "moduleNameMapper": { "^react-native$": "react-native-web" }, "moduleFileExtensions": [ "web.js", "js", "json", "web.jsx", "jsx", "node", "mjs" ] }, "babel": { "presets": [ "react-app" ] }, "eslintConfig": { "extends": "react-app" } }
此處我本地更改下3000埠為9999,避免與其他本地專案衝突,安裝cross-env:
cnpm i cross-env
修改start命令為:
- "start": "node scripts/start.js",
+ "start": "cross-env PORT=9999 node scripts/start.js",
然後啟動:npm start 效果如圖:
3 安裝dva庫(dva也有自己的腳手架dva-cli,也可快速構建專案,目前已升至2.x版本,採用[email protected]路由版本)
cnpm i dva --save
安裝history元件,等會後面會用到,主要做BrowserHistory瀏覽器歷史功能,有興趣的同學可網上自行學習。
4 改造專案為dva模式(不瞭解dva的同學可網上自行學習),在src目錄下新建目錄:models,services,routes,utils(utils將來存放配置檔案和工具方法),如圖:
5 路由設計,簡單點暫設計為三個demo地址,分別如下:
(1)http://localhost:9999/aaa
(2)http://localhost:9999/aaa/bbb
(3)http://localhost:9999/ccc
6 元件設計,在routes目錄下新建三個檔案:AAA.js BBB.js CCC.js 如圖:
程式碼分別為:
AAA.js
import React, { Component } from 'react';
class AAA extends Component {
render() {
return (
<div>
<p>
AAA頁
</p>
</div>
);
}
}
export default AAA;
BBB.js
import React, { Component } from 'react';
class BBB extends Component {
render() {
return (
<div>
<p>
BBB頁
</p>
</div>
);
}
}
export default BBB;
CCC.js
import React, { Component } from 'react';
class CCC extends Component {
render() {
return (
<div>
<p>
CCC頁
</p>
</div>
);
}
}
export default CCC;
7 model設計,在model下新建四個檔案:aaa.js bbb.js ccc.js 和app.js(app作為全域性model使用,將來存放全域性變數,如國際化引數,登入使用者資訊等)
程式碼分別為:
aaa.js
export default {
namespace: 'aaa',
state: {
name:'這是aaa的model'
},
subscriptions: {
},
effects: {
},
reducers: {
},
};
bbb.js
export default {
namespace: 'bbb',
state: {
name:'這是bbb的model'
},
subscriptions: {
},
effects: {
},
reducers: {
},
};
ccc.js
export default {
namespace: 'ccc',
state: {
name:'這是ccc的model'
},
subscriptions: {
},
effects: {
},
reducers: {
},
};
app.js
export default {
namespace: 'app',
state: {
name:'這是app的model'
},
subscriptions: {
},
effects: {
},
reducers: {
},
};
8 src目錄下建立router.js路由控制檔案,其中menuGlobal後期會提出到配置檔案中,此處暫先這樣放便於檢視,大家發現其中也定義了id,pid,name,icon等欄位,這些並不是dva路由必須欄位,沒錯這些事自定義的欄位,將來會用到,選單關係,選單是否顯示,和icon圖示等
import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import dynamic from 'dva/dynamic'
const menuGlobal=[
{
id:'aaa',
pid:'0',
name:'aaa頁',
icon:'user',
path: '/aaa',
models: () => [import('./models/aaa')], //models可多個
component: () => import('./routes/AAA'),
},
{
id:'bbb',
pid:'0',
name:'bbb頁',
icon:'user',
path: '/aaa/bbb',
models: () => [import('./models/bbb')], //models可多個
component: () => import('./routes/BBB'),
},
{
id:'ccc',
pid:'0',
name:'ccc頁',
icon:'user',
path: '/ccc',
models: () => [import('./models/ccc')], //models可多個
component: () => import('./routes/CCC'),
},
];
function RouterConfig({ history, app }) {
return (
<Router history={history}>
<Switch>
{
menuGlobal.map(({path,...dynamics},index)=>(
<Route
key={index}
path={path}
exact
component={dynamic({
app,
...dynamics
})}
/>
))
}
</Switch>
</Router>
);
}
export default RouterConfig;
9 修改src目錄下index.js入口檔案:
import dva from 'dva';
import './index.css';
import createHistory from 'history/createBrowserHistory'
// 1. Initialize
const app = dva({
history:createHistory()
});
// 2. Plugins
// app.use({});
// 3. Model
app.model(require('./models/app').default);
// 4. Router
app.router(require('./router').default);
// 5. Start
app.start('#root');
至此,dva的改造基本完畢,後續還有其他的細節補充,執行起來吧:npm start
效果如圖:
試試看,基本的路由已經OK了,接下來,我們將進行對以上主體構造的細節坐下補充。
此處路由只是通過瀏覽器位址列直接訪問的形式,我們還需要增加路由跳轉<Link>
(1)AAA.js中引入import { Link } from 'dva/router'; 並增加跳轉連結如下:
import React, { Component } from 'react';
import { Link } from 'dva/router';
class AAA extends Component {
render() {
return (
<div>
<p>
AAA頁
</p>
<Link to={'/aaa/bbb'}>去BBB頁面</Link>
<br />
<Link to={'/ccc'}>去CCC頁面</Link>
</div>
);
}
}
export default AAA;
(1)BBB.js中引入import { Link } from 'dva/router'; 並增加跳轉連結如下:
import React, { Component } from 'react';
import { Link } from 'dva/router';
class BBB extends Component {
render() {
return (
<div>
<p>
BBB頁
</p>
<Link to={'/aaa'}>去AAA頁面</Link>
<br />
<Link to={'/ccc'}>去CCC頁面</Link>
</div>
);
}
}
export default BBB;
(1)CCC.js中引入import { Link } from 'dva/router'; 並增加跳轉連結如下:
import React, { Component } from 'react';
import { Link } from 'dva/router';
class CCC extends Component {
render() {
return (
<div>
<p>
CCC頁
</p>
<Link to={'/aaa'}>去AAA頁面</Link>
<br />
<Link to={'/aaa/bbb'}>去BBB頁面</Link>
</div>
);
}
}
export default CCC;
此時頁面中已有連結,三個頁面可相互跳轉了。
現在我們將router.js中的menuGlobal提出,放於公用檔案中,首先在utils下新建檔案config.js和index.js 程式碼分別如下:
config.js檔案:
const menuGlobal=[
{
id:'aaa',
pid:'0',
name:'aaa頁',
icon:'user',
path: '/aaa',
models: () => [import('../models/aaa')], //models可多個
component: () => import('../routes/AAA'),
},
{
id:'bbb',
pid:'0',
name:'bbb頁',
icon:'user',
path: '/aaa/bbb',
models: () => [import('../models/bbb')], //models可多個
component: () => import('../routes/BBB'),
},
{
id:'ccc',
pid:'0',
name:'ccc頁',
icon:'user',
path: '/ccc',
models: () => [import('../models/ccc')], //models可多個
component: () => import('../routes/CCC'),
},
];
export default {
menuGlobal
}
index.js檔案(之後utils中的配置檔案,均可在index.js暴露出去):
import config from './config';
export {
config
}
修改src下的router.js檔案,如下:
import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import dynamic from 'dva/dynamic'
import {config} from './utils'
const { menuGlobal } = config
function RouterConfig({ history, app }) {
return (
<Router history={history}>
<Switch>
{
menuGlobal.map(({path,...dynamics},index)=>(
<Route
key={index}
path={path}
exact
component={dynamic({
app,
...dynamics
})}
/>
))
}
</Switch>
</Router>
);
}
export default RouterConfig;
重新整理頁面即可,之所以把menu提出配置檔案中,便於管理和後期拓展。