本文來自網易雲社區css
做者:汪洋html
這時候還沒完,又有兩個問題引出來了。 node
按照上面的配置,第三方庫 antd 居然也被編譯了,致使樣式失敗。react
react中,一旦包裹了子組件,子組件沒辦法直接使用 styleName。
webpack
第2個問題,還好解決,查了下 react-css-modules 資料,子組件中經過props獲取ios
const template = ( <div className={this.props.styles['loadingBox']}> <Loading /> </div>);
第1個問題糾結了很久,後來找了個折中的方案,好心酸。 在entry.jsx中引入的antd組件樣式,改爲 nginx
import 'antd/dist/antd.css';
對,直接引入 css文件,跳過less編譯。
而後在webpack中新增配置es6
{ test: /\.(css|less)$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', 'less-loader' ] }), exclude: /node_modules/ }, { test: /\.(css)$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ 'css-loader' ] }), include: /node_modules/ },
到這一步,你們應該明白個人方案了,就是 node_modules 文件夾中的 css文件不啓動 cssmoduls,其它文件夾中 啓動 cssmoduls。web
接下來就是第4個大問題待解決,路由按需加載。
做爲新手,固然首先是搜索一下 react-router 4.x 如何實現按需加載的,果真好多答案。至於如何選擇,固然是哪一個方便哪一個來的原則。 react-loadable 這個插件,固然這個貨得依賴 babel-plugin-syntax-dynamic-import 包。
webpack配置,加入 babel的 syntax-dynamic-import插件ajax
module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: [ { loader: 'babel-loader', query: { presets: ['es2015', 'react', 'stage-0'], plugins: ['syntax-dynamic-import'] } } ] }, ...
react中使用 react-loadable,特別方便
import Loadable from 'react-loadable'; ...const MyLoadingComponent = ({isLoading, error, pastDelay}) => { // Handle the loading state if (pastDelay) { return <div>Loading...</div>; } // Handle the error state else if (error) { return <div>Sorry, there was a problem loading the page.</div>; } else { return null; } }const AsyncTestManager = Loadable({ loader: () => import('./pages/TestManager/Index'), loading: MyLoadingComponent }); ReactDOM.render( <Provider store={Store}> <BrowserRouter basename="/" forceRefresh={!supportsHistory} keyLength={12}> <div> <Route exact path="/testManager" component={AsyncTestManager}/> </div> </BrowserRouter> </Provider>, document.getElementById('root') );
這個插件具體使用你們查看相關文檔,很方便強大。記得上線打包的時候,webpack要啓動hash
output: { filename: '[name][chunkhash].js', path: BUILD_PATH, chunkFilename: '[name][chunkhash].js', publicPath: './' },
至此,腳手架搭建走過的坑結束了。
順便提下
output: { ... publicPath: '../' },
這裏必定要配置爲 ../ ,不要配置爲 ./,由於不當心配錯,致使路由按需加載的時候,js路徑錯誤了。
這裏要介紹下 redux的一箇中間件,redux-thunk。何爲中間件,以及 redux-thunk的做用,你們能夠參考下阮一峯的一篇教程《Redux 入門教程(二):中間件與異步操做》 。 正常狀況下,actions返回的只是一個對象,可是咱們想發送數據前最好能處理下,因此呢,就須要重寫下Store.dispath方法了。中間件就是這樣的做用,改寫 dispatch,在發出 Action 和執行 Reducer 這兩步之間,添加了其餘功能。好比異步操做:發起ajax請求。視圖發起一個action,觸發了一個請求,可是action不能返回函數,這時候redux-thunk就起做用了。
這個過程,就是把 reducer跟Store綁定在一塊兒,同時引入須要的中間件
import { createStore, applyMiddleware } from 'redux'; import thunkMiddleware from 'redux-thunk'; import reducers from '../reducers';const store = applyMiddleware( thunkMiddleware )(createStore)(reducers); export default store;
applyMiddleware 方法它是 Redux 的原生方法,做用是將全部中間件組成一個數組,依次執行。 createStore 方法建立一個 Store。 至於這個參數寫法,其實就是es6的柯里化語法。用es3,es5實現其實原理很簡單,就是利用了閉包保存了上一次的數據,實現過單列模式的同窗應該很清楚。
function add(number1) { return function(number2) { return number1 + number2; }; }var addTwo = add(1)(2);
至於Reducer,其實很好實現,它其實就是單純的函數。
例如:
import * as CONSTANTS from '../../constants/TestControl';const initialState = {};const testControl = (state = initialState, action) => { switch (action.type) { case CONSTANTS.GET_DETAILS_PENDING: return { ...state, isFetching: true, data: action.payload, success: false }; case CONSTANTS.GET_DETAILS_SUCCEEDED: return { ...state, isFetching: false, data: action.data.relatedObject, success: true }; case CONSTANTS.GET_DETAILS_FAILED: return { ...state, isFetching: false, success: false, errorCode: action.data.errorCode }; default: return state; } }; export default testControl;
你們應該注意到,這個實際上是對應action的一個ajax請求,其中,action.type中 ,
_PENDING 結尾的表示 ajax正在發起請求;
_SUCCEEDED 結尾的表示 ajax 請求成功;
_FAILED 結尾的表示 ajax 請求失敗;
這個我是做爲ajax actions的標準命名,你們也能夠用其它方式,原則就是:好理解,統一。 固然其它非ajax的actions(包括ajax的action),個人規則就是,命名要表意,常量要大寫。
因爲個人項目中reduce有n個,因此 reducers/index.js 是這樣的
import { combineReducers } from 'redux'; import testManagerList from './TestManager/list'; import common from './Common'; import system from './System'; import evaluate from './Evaluate'; import ComponentsAddLayer from './Components/addLayer'; import testNew from './TestNew'; import testControl from './TestControl'; export default combineReducers({ testManagerList, system, evaluate, ComponentsAddLayer, testNew, common, testControl });
引入 redux 的combineReducers 方法,這樣就把多個 reducer集合到一塊兒了,調用state的時候,只要如此:
const mapStateToProps = state => ({ type: state.testManagerList.type });
你們看明白了吧,testManagerList 是個人一個 reducer。
Actions 我是做爲存放數據的,好比ajax數據請求,視圖默認數據這些。
const testManager = { testManager_get_list(options) { return (dispatch) => { const fetchData = axios.get('/abtest/getList', options); dispatch({ type: TABLE_GET_LIST_PENDING, payload: fetchData }); fetchData.then((response) => { if (response.data.success) { dispatch({ type: TABLE_GET_LIST_SUCCEEDED, ...response }); } else { dispatch({ type: TABLE_GET_LIST_FAILED, ...response }); } }).catch((error) => { dispatch({ type: TABLE_GET_LIST_FAILED, ...error }); }); }; }, testManager_change_tabs(activeTabs) { return { type: TABS_CHANGE, active: activeTabs }; }, testManager_search(value) { return { type: SEARCH, keyWord: value }; }, testManager_parameters(options) { return { type: TEST_MANAGER, parameters: Object.assign({}, { page: 1, pageSize: 10, sort: '', type: '', keyWord: '' }, options || {}) }; }, testManager_pagination_change(noop) { return { type: PAGINATION_CHANGE, page: noop }; } };
這個模塊觸發的actions:獲取表格列表數據,搜索,分頁操做,獲取默認配置,很好理解,這裏就不說了。 具體如何使用,請看下面的 view 實踐
開始的時候,提出幾個問題:
視圖如何跟Store綁定;
ACTIONS如何在視圖中使用;
引入的第三方組件樣式有什麼好的方式修改;
視圖中的props如何獲取路由信息;
先解決第3個問題,一開始我是想重寫覆蓋第三方的css文件的,後來一看代碼量,果斷放棄了。還好被我發現了 styled-components 這個插件,果真好用。
import styled from 'styled-components'; import Tabs from 'antd/lib/tabs';const TabsStyle = styled(Tabs)` float: left; .ant-tabs-nav-wrap { margin-bottom: 0; } .ant-tabs-tab { text-align: center; transition: background 0.3s; color: #666666; padding: 6px 12px; font-size: 14px; font-weight: 400; cursor: pointer; user-select: none; background-image: none; margin-left: -10px; } `;
這裏面跟寫less同樣就行了。我是這麼以爲。具體你們能夠查看下對應的文檔。開發過react-native的同窗,都很清楚這個插件的給力。
再結晶第4個問題。react-router 官方提供了 withRouter的api,這個api就是專門爲了解決這個問題。
import CSSModules from 'react-css-modules'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; ...... componentDidMount() { // props中就可拿到路由信息了 const { ACTIONS, match } = this.props; ACTIONS.TestControl_get_testing_detail({ id: match.params.id }); }const turnCss = CSSModules(TestManager, styles, { allowMultiple: true }); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));
很是方便。
再來講第一個問題,視圖如何跟Store綁定 Store提供了三個方法
store.getState()
store.dispatch()
store.subscribe()
其中,Store 容許使用store.subscribe方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。因此綁定視圖,調用這個方法就行了。 不過redux做者專門針對react,封裝了一個庫:React-Redux,這裏我就直接引用了,這樣我就不用處理state了。
import { connect } from 'react-redux';const mapStateToProps = state => ({ isFetching: state.testControl.isFetching, success: state.testControl.success, detail: state.testControl.data });const mapDispatchToProps = dispath => ({ ACTIONS: bindActionCreators(actions, dispath) });const turnCss = CSSModules(TestControl, styles, { allowMultiple: true }); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));
這樣 TestControl 視圖就跟 Store綁定到一塊兒了。 具體的API介紹,你們能夠查看下文檔,仍是很好理解的。
解決了第一個問題,再來看第2個問題:ACTIONS如何在視圖中使用
ACTIONS的做用,其實就是消息訂閱/發佈 模式中,發佈那個步驟了。這樣理解,你們應該明白了吧, 好比: 視圖中點擊了一個按鈕後,回調函數中就直接調用對應的ACTIONS方法便可。
還要介紹下redux的bindActionCreators方法:
主要用處:
通常狀況下,咱們能夠經過Provider將store經過React的connext屬性向下傳遞,bindActionCreators的惟一用處就是須要傳遞action creater到子組件,而且該子組件並無接收到父組件上傳遞的store和dispatch。
import { bindActionCreators } from 'redux'; import actions from '../../actions';class TestControl extends Component { componentDidMount() { const { ACTIONS, match } = this.props; ACTIONS.TestControl_get_testing_detail({ id: match.params.id }); } // 開始 start() { const { ACTIONS, match } = this.props; ACTIONS.TestControl_start({ id: match.params.id }); } render() { ... } }const mapStateToProps = state => ({ isFetching: state.testControl.isFetching, success: state.testControl.success, detail: state.testControl.data });const mapDispatchToProps = dispath => ({ ACTIONS: bindActionCreators(actions, dispath) });const turnCss = CSSModules(TestControl, styles, { allowMultiple: true }); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(turnCss));
至此,redux實踐結束。
由於是單頁面模式,且使用了 BrowserRouter,故nginx配置以下:
location / { root E:/program/ark2/abtest-statics/build/; index index.html index.htm; expires -1; try_files $uri $uri/ /entry.html; }
開發一個項目,最好須要一個合理的約定,好比代碼風格、模塊定義、方法定義、參數定義等等,這些約定中,還要考慮如何便於寫和維護單元測試這個因素。這些其實仍是挺有挑戰的,只能不斷去完善。
上面方案其實還有不少缺陷待解決,須要慢慢改進了。
相關文章:react技術棧實踐(1)
網易雲免費體驗館,0成本體驗20+款雲產品!
更多網易研發、產品、運營經驗分享請訪問網易雲社區。
相關文章:
【推薦】 教你如何選擇BI數據可視化工具
【推薦】 掃臉動畫
【推薦】 canvas 動畫庫 CreateJs 之 EaselJS(下篇)