日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

react技术栈实践

發布時間:2024/10/12 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 react技术栈实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

背景

最近開發一個全新AB測試平臺,思考了下正好可以使用react技術開發。

實踐前技術準備

首先遇到一個概念,redux。這貨還真不好理解,大體的理解:Store包含所有數據,視圖觸發一個Action,Store收到Action后,返回一個新的 State,這樣視圖就發生變化,State計算過程叫做 Reducer,Reducer其實就是一個處理數據的函數,接受 Action和 當前State作為參數,返回一個新的 State。
明白這個后,就可以開始實踐了。

搭建平臺的腳手架

對于我這方面沒搞過的菜鳥,還真是不容易。接下來說下作為新手如何實踐的。

  • 第一步:依賴包
  • "devDependencies": {"babel-core": "^6.26.0","babel-eslint": "^8.2.2","babel-loader": "^7.1.2","babel-plugin-import": "^1.6.6","babel-preset-es2015": "^6.22.0","babel-preset-react": "^6.24.1","babel-preset-stage-0": "^6.24.1","css-loader": "^0.28.7","eslint": "^4.18.2","eslint-config-airbnb": "^16.1.0","eslint-loader": "^2.0.0","eslint-plugin-import": "^2.9.0","eslint-plugin-jsx-a11y": "^6.0.3","eslint-plugin-react": "^7.7.0","extract-text-webpack-plugin": "^3.0.2","html-webpack-plugin": "^3.0.4","less": "^2.7.3","less-loader": "^4.0.6","style-loader": "^0.19.1","url-loader": "^1.0.1","webpack": "^3.1.0"},"dependencies": {"normalize.css": "^8.0.0","react": "^16.2.0","react-dom": "^16.2.0","react-redux": "^5.0.7","react-router-dom": "^4.2.2","redux": "^3.7.2"} 復制代碼

    dependencies 中引入的依賴包,是react的標配了,不用解釋。
    devDependencies 中引入了 webpack,babel,babel插件,eslint語法檢測,eslint配置包airbnb,html模板資源替換插件 html-webpack-plugin,css提取插件 extract-text-webpack-plugin,less編譯相關插件,圖片等靜態資源路徑處理插件 url-loader。
    這里作為新手,一般都是參考網上的配置,比如我就是github上找了個項目,摸索一下。推薦一本教程書《React全棧》,作者寫的很詳細,對入門絕對有幫助。
    至此,基本依賴包已加載完。

  • 第二步:webpack配置 這里不得不說,新手真不容易。 首先介紹下項目結構:
    views/entry.html(靜態模板),
    src/entry.jsx(入口文件),
    src/actions(redux概念中Actions所在的文件夾) ,
    src/reducers(redux概念中Reducers所在的文件夾) ,
    src/store(redux概念中Store所在的文件夾) ,
    src/pages(存放頁面的文件夾,jsx),
    src/compinents(存放業務組件的文件夾,jsx),
    src/style(公共樣式文件夾,less),
    src/utils(幫助類文件夾),
    src/constants(常量所在文件夾,保存各自的actions的type),
    src/plugins(第三方插件文件夾),
    build/(編譯后文件),
    webpack/(webpack編譯配置所在文件夾),
    .eslintrc(eslint配置文件),
    .gitignore(git配置文件),
    package.json
  • 接下來就是webpack的配置了,先上代碼

    const path = require('path'); const webpack = require('webpack'); // html中替換編譯后的js const HtmlwebpackPlugin = require('html-webpack-plugin'); // css提取 const ExtractTextPlugin = require('extract-text-webpack-plugin');const ROOT_PATH = path.resolve(__dirname); const APP_PATH = path.resolve(ROOT_PATH, '../src'); const BUILD_PATH = path.resolve(ROOT_PATH, '../build');module.exports = {entry: {entry: path.resolve(APP_PATH, './entry.jsx'),vendor: ['react', 'react-dom', 'pace']},output: {filename: '[name].js',path: BUILD_PATH,chunkFilename: '[name].js',publicPath: '../'},devtool: 'eval-source-map',module: {rules: [{test: /\.(js|jsx)$/,exclude: /node_modules/,use: [{loader: 'babel-loader',query: {presets: ['es2015', 'react', 'stage-0'],plugins: ['syntax-dynamic-import', ['import', { libraryName: 'antd', style: 'css' }]]}}]},{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/},{test: /\.(jpg|jpeg|png|svg|gif|bmp)/i,use: ['url-loader?limit=5000&name=img/[name].[sha512:hash:base64:8].[ext]']},{test: /\.(woff|woff2|ttf|eot)($|\?)/i,use: ['url-loader?limit=5000&name=fonts/[name].[sha512:hash:base64:8].[ext]']}]},resolve: {extensions: ['.js', '.jsx', '.less', '.css', '.png', '.jpg', '.svg', '.gif', '.eot'],alias: {pace: path.resolve(ROOT_PATH, '../src/plugins/pace/index.js'),ImagesPath: path.resolve(ROOT_PATH, '../src/')}},devServer: {historyApiFallback: true,hot: true,inline: true,progress: true},plugins: [new webpack.optimize.CommonsChunkPlugin({name: ['commons', 'vendor'],minChunks: 2}),new ExtractTextPlugin('commons.css', {allChunks: true}),new HtmlwebpackPlugin({template: path.resolve(ROOT_PATH, '../views/entry.html'),filename: path.resolve(ROOT_PATH, '../build/entry.html'),chunks: ['entry', 'vendor'],hash: false}),// 加署名new webpack.BannerPlugin('Copyright by xxx')] };復制代碼

    第一次接觸配置,真的找不到北,太多插件,太多功能。作為新手,那需要怎么個思路,我總結:按項目需求來配置。不要認為其他人配置的就適合自己項目,要不然給自己帶來各種麻煩。 摸索這個過程還挺長的:
    A. 首先需求還是明確的:less編譯、jsx編譯、公共文件單獨打包、html靜態模板中插入編譯后的文件路徑、css提取。 上面這些對應配置:

    const path = require('path'); const webpack = require('webpack'); // html中替換編譯后的js const HtmlwebpackPlugin = require('html-webpack-plugin'); // css提取 const ExtractTextPlugin = require('extract-text-webpack-plugin');const ROOT_PATH = path.resolve(__dirname); const APP_PATH = path.resolve(ROOT_PATH, '../src'); const BUILD_PATH = path.resolve(ROOT_PATH, '../build');module.exports = {entry: {entry: path.resolve(APP_PATH, './entry.jsx'),vendor: ['react', 'react-dom', 'pace']},output: {filename: '[name].js',path: BUILD_PATH,chunkFilename: '[name].js',publicPath: '../'},devtool: 'eval-source-map',module: {rules: [{test: /\.(js|jsx)$/,exclude: /node_modules/,use: [{loader: 'babel-loader',query: {presets: ['es2015', 'react', 'stage-0']}}]},{test: /\.(css|less)$/,use: ExtractTextPlugin.extract({fallback: 'style-loader',use: ['css-loader','less-loader']}),exclude: /node_modules/},{test: /\.(jpg|jpeg|png|svg|gif|bmp)/i,use: ['url-loader?limit=5000&name=img/[name].[sha512:hash:base64:8].[ext]']},{test: /\.(woff|woff2|ttf|eot)($|\?)/i,use: ['url-loader?limit=5000&name=fonts/[name].[sha512:hash:base64:8].[ext]']}]},plugins: [new webpack.optimize.CommonsChunkPlugin({name: ['commons', 'vendor'],minChunks: 2}),new ExtractTextPlugin('commons.css', {allChunks: true}),new HtmlwebpackPlugin({template: path.resolve(ROOT_PATH, '../views/entry.html'),filename: path.resolve(ROOT_PATH, '../build/entry.html'),chunks: ['entry', 'vendor'],hash: false})] }; 復制代碼

    B. 配置到這步后,就能滿足基本開發了。試用之后,這時候對自己提出了幾個問題:

  • 命名css,開發的時候能不能不用擔心命名沖突的問題。
  • css中引入圖片后,編譯失敗問題。
  • 第三方插件 加載效果pace組件,引入問題。
  • 現在文件過大,有根據路由按需加載需求。
  • 針對上面4個問題,重新配置:
    第2個和3個解決方案一致:即聲明別名

    resolve: {extensions: ['.js', '.jsx', '.less', '.css', '.png', '.jpg', '.svg', '.gif', '.eot'],alias: {pace: path.resolve(ROOT_PATH, '../src/plugins/pace/index.js'),ImagesPath: path.resolve(ROOT_PATH, '../src/')}} 復制代碼

    當中第3個問題,網上找了好多資料,都沒有結果,后來請教了前端群的同行,才解決該問題。
    解決第1個問題過程中,我學習到了cssModule的概念,一開始菜鳥還不好理解,實踐了后,還真是個好東西。

    {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/}, 復制代碼

    只要css-loader啟動modules就好了。為了支持 react,引入了 react-css-modules 依賴包。

    這時候還沒完,又有兩個問題引出來了。

  • 按照上面的配置,第三方庫 antd 竟然也被編譯了,導致樣式失敗。
  • react中,一旦包裹了子組件,子組件沒辦法直接使用 styleName。
  • 第2個問題,還好解決,查了下 react-css-modules 資料,子組件中通過props獲取

    const template = (<div className={this.props.styles['loadingBox']}><Loading /></div>); 復制代碼

    第1個問題糾結了好久,后來找了個折中的方案,好心酸。 在entry.jsx中引入的antd組件樣式,改成

    import 'antd/dist/antd.css'; 復制代碼

    對,直接引入 css文件,跳過less編譯。
    然后在webpack中新增配置

    {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。

    接下來就是第4個大問題待解決,路由按需加載。
    作為新手,當然首先是搜索一下 react-router 4.x 如何實現按需加載的,果然好多答案。至于如何選擇,當然是哪個方便哪個來的原則。 react-loadable 這個插件,當然這個貨得依賴 babel-plugin-syntax-dynamic-import 包。
    webpack配置,加入 babel的 syntax-dynamic-import插件

    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 stateif (pastDelay) {return <div>Loading...</div>;}// Handle the error stateelse 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就起作用了。

    Store初始化

    這個過程,就是把 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實例

    至于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

    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 實踐

    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實踐結束。

    nginx配置

    因為是單頁面模式,且使用了 BrowserRouter,故nginx配置如下:

  • 入口html的訪問地址為: xxx.com/
  • server {location / {root E:/program/ark2/abtest-statics/build/;index index.html index.htm;expires -1;try_files $uri $uri/ /entry.html;} } 復制代碼
  • 入口html的訪問地址為:xxx.com/abtest/
  • listen 80;#rewrite_log on;location /abtest {rewrite ^/abtest/(.*\.(gif|jpg|jpeg|png|css|js|ico)$) /abtest-statics/build/$1 last;rewrite ^/abtest.* /abtest-statics/build/entry.html;}location /abtest-statics/build {expires -1;root E:/program/ark2;index entry.html;} 復制代碼

    說明: 'E:/program/ark2' 為項目地址,當訪問 xxx.com/abtest/ 后,rewrite(重定向)到 /abtest-statics/build 下。

    其它

    開發一個項目,最好需要一個合理的約定,比如代碼風格、模塊定義、方法定義、參數定義等等,這些約定中,還要考慮如何便于寫和維護單元測試這個因素。這些其實還是挺有挑戰的,只能不斷去完善。
    上面方案其實還有很多缺陷待解決,需要慢慢改進了。

    @作者:白云飄飄(534591395@qq.com)

    @github: github.com/534591395 歡迎關注我的微信公眾號:

    或者微信公眾號搜索 新夢想兔,關注我哦。

    轉載于:https://juejin.im/post/5d5cb0075188257e19733bd8

    總結

    以上是生活随笔為你收集整理的react技术栈实践的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。