小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

利用 React/Redux/React

 quasiceo 2018-08-14
  1. 如何設(shè)計(jì)一個(gè)大型 web 項(xiàng)目?
  2. React + webpack 如何按需加載?
  3. React + React-Router 4 + webpack 如何按需加載?
  4. React + Redux + React-Router 4 + webpack 如何按需加載?

實(shí)錄提要:

  • bundle-loader 和 Webpack 內(nèi)置的 import() 有什么區(qū)別?
  • 按需加載能否支持通過(guò)請(qǐng)求后臺(tái)數(shù)據(jù),動(dòng)態(tài)配置頁(yè)面的的應(yīng)用場(chǎng)景?
  • 參與過(guò)幾個(gè) React 項(xiàng)目,被依賴(lài)包搞的暈暈的,不知道該怎么選擇?
  • 什么包應(yīng)該放到 devDependencies 里面?什么包放到 depedencies 里面?
  • 為什么是 react-router-redux 而不是傳統(tǒng)的 react-redux,其優(yōu)勢(shì)是什么?
  • 按需加載時(shí),每個(gè)單獨(dú)的 bundle 都挺大的,為什么?
  • ECMAScript 每年出一個(gè)版本,對(duì)應(yīng)的 babel 也有一大堆,應(yīng)該如何選擇?
  • 單頁(yè)項(xiàng)目過(guò)大,怎么拆分不同模塊頁(yè)面到不同 js 來(lái)動(dòng)態(tài)加載?

題外話

經(jīng)驗(yàn)尚淺,尚不足以教導(dǎo),若理解有誤,望能指導(dǎo)三分,語(yǔ)言若有偏激,請(qǐng)理解我年輕氣盛。

之所以寫(xiě)這篇文章,是因?yàn)槲易罱魂囎咏?jīng)歷了一個(gè)部門(mén)的技術(shù)選型->項(xiàng)目實(shí)施這些技術(shù)->二次技術(shù)選型->技術(shù)版本升級(jí)的一個(gè)過(guò)程。開(kāi)發(fā)業(yè)務(wù)應(yīng)用為主的我們,很少有時(shí)間去研究某項(xiàng)技術(shù)的源碼,不加班趕項(xiàng)目進(jìn)度就已經(jīng)很慶幸了,大部分時(shí)間都花在了如何靈活使用市面上的一些技術(shù)體系。在這篇文章中,不涉及源碼范圍,我也沒(méi)去研究過(guò)源碼。寫(xiě)這篇文章的初衷是分享我的想法和代碼示例,同時(shí)也希望看這篇文章的你能夠給予寶貴的意見(jiàn),讓我得以進(jìn)步。

web應(yīng)用讓人驚嘆是從Gmail開(kāi)始的,流暢的桌面版體驗(yàn)吸引了很多人,從此web項(xiàng)目開(kāi)始蓬勃發(fā)展。隨后,web應(yīng)用也越來(lái)越復(fù)雜,為了能讓web應(yīng)用如同桌面版應(yīng)用一樣流暢,出現(xiàn)了SPA。這就是今天我想說(shuō)的,react/redux等等一系列的產(chǎn)品的出現(xiàn)都是為了實(shí)現(xiàn)體驗(yàn)度更佳的SPA。

兩年前,我開(kāi)發(fā)web項(xiàng)目,都只是用javaweb,使用模板引擎,后端渲染出頁(yè)面。對(duì)于訪問(wèn)量不是很大、單個(gè)頁(yè)面復(fù)雜度不是很高、項(xiàng)目的迭代周期不頻繁、二次開(kāi)發(fā)的次數(shù)很少的系統(tǒng),這種模式無(wú)疑很適用、性能也沒(méi)什么大的影響,一個(gè)java程序員就可以做到全棧。而事實(shí)上,我手頭的web項(xiàng)目并非這么簡(jiǎn)單,隨著周期的迭代,項(xiàng)目越來(lái)越臃腫,后端代碼和前端代碼摻雜在一起混亂不堪,當(dāng)時(shí)我自認(rèn)為自己技術(shù)不錯(cuò),代碼寫(xiě)的自己都認(rèn)識(shí),然而我離職之后發(fā)現(xiàn)一個(gè)很多人都知道的道理,一個(gè)技術(shù)真正好的程序員,寫(xiě)出來(lái)的代碼是要能夠讓他人讀的懂,最起碼要讓接替你繼續(xù)這個(gè)項(xiàng)目的人讀得懂,技術(shù)不行那是例外,當(dāng)時(shí)我沒(méi)有做到。而后,進(jìn)入新的公司,讓我能夠有機(jī)會(huì)去對(duì)部門(mén)進(jìn)行技術(shù)選型,主要是前端部分。我果斷選擇了前后端分離模式,我不想以前不好的地方繼續(xù)發(fā)生在以后。人員安排最好是這樣,專(zhuān)職的人做專(zhuān)職的事,全棧人員要能夠補(bǔ)位。設(shè)計(jì)一個(gè)優(yōu)質(zhì)的web項(xiàng)目,最重要的是人,而不是技術(shù)!

設(shè)計(jì)web最好是前端設(shè)計(jì)成SPA、后端設(shè)計(jì)成微服務(wù),有很多企業(yè)使用react、vue、angular這些,結(jié)果是多頁(yè)應(yīng)用,增加復(fù)雜度,降低頁(yè)面切換的流暢度。我真不知道他們是怎么想的,首先多頁(yè)應(yīng)用是不可取的,如果他們是開(kāi)發(fā)webapp,封裝成apk,那就更不可取了!web項(xiàng)目設(shè)計(jì)成SPA,很多人會(huì)想,隨著代碼量的增大,首次加載的文件就會(huì)增大,沒(méi)錯(cuò),這時(shí)就需要用到code splitting。這也就是我今天要講的實(shí)際項(xiàng)目中如何進(jìn)行按需加載。我見(jiàn)過(guò)有些開(kāi)發(fā)人員將一個(gè)js文件拆分成多個(gè)js文件,而每個(gè)頁(yè)面都加載這些js文件,這顯然是不可取的,這樣子的不需要拆分。隨著現(xiàn)在網(wǎng)速的提升,首次加載文件稍微大點(diǎn)都是可以接受的。首次加載后緩存在瀏覽器處,下次加載的時(shí)候會(huì)更快。引入第三方UI組件,基礎(chǔ)組件要單一,不可以引入antd了再去引用bootstrap,還有用了react這些就不要在項(xiàng)目中出現(xiàn)jQuery,要純粹!

廢話就講到這里,下面介紹我將一個(gè)項(xiàng)目重構(gòu)三次的過(guò)程,這三個(gè)過(guò)程里有三種不同的按需加載方式。示例是我將實(shí)際項(xiàng)目刪減過(guò)后可運(yùn)行的例子。code splitting和react/react-router是沒(méi)有直接關(guān)系的。

一、react(v.0.14.8) / react-router(v.1.0.3) / webpack(v.1.13.3)

兼容IE8+及現(xiàn)代瀏覽器,示例代碼地址->https://github.com/love-fay/fay-webpack-redux-code-splitting/tree/master/react。

先貼下依賴(lài):

依賴(lài)

因?yàn)闃I(yè)務(wù)的需求,需要兼容到IE8,不得不被動(dòng)地選擇低版本庫(kù)。處于對(duì)react的首次使用,并沒(méi)有加入redux相關(guān)技術(shù)。

在react-router 1.x版本中Route組件上擁有g(shù)etComponent、onEnter參數(shù)(4.x之后被移除),getComponent是異步的,所以我們可以在這個(gè)參數(shù)里進(jìn)行按需加載,getComponent這個(gè)函數(shù)有兩個(gè)參數(shù)nextState、callback,根據(jù)nextState.pathname可以獲取到路由地址,然后再利用webpack的require.ensure異步加載所屬組件的js文件,最后通過(guò)callback將該組件返回。示例代碼如下:

<Route path="app" getComponent={this.getUumsComponent} onEnter={this.requireAuth}/>
getUumsComponent = (nextState, callback) ={    let pathname = nextState.pathname;    switch (pathname) {        case 'app':        require.ensure([], (require) ={            callback(null, require('../app/components/App'));                }, 'App');            break;        default :            historyConfig.pushState({nextPathname: pathname}, '/404');    }};

打包過(guò)后主要文件的對(duì)比:

code splitting

code splitting

not code splitting

not code splitting

這種方式的按需加載就這些,很簡(jiǎn)單~

二、react(v.15.6.1) / react-router(v.4.2.2)b / webpack(v.3.5.6)

兼容IE9+及現(xiàn)代瀏覽器,示例代碼地址->https://github.com/love-fay/fay-webpack-redux-code-splitting/tree/master/react-rr4。

先貼下依賴(lài):

依賴(lài)

這次決定拋棄IE8,甚至都不想兼容IE。很多人口口聲聲說(shuō)用戶體驗(yàn)、用戶需求,卻一味地去支持IE8,甚至還有支持IE6的!其實(shí)用戶體驗(yàn)和需求不是用戶單方面的要求,還有就是開(kāi)發(fā)方需要去改變用戶習(xí)慣、引導(dǎo)用戶對(duì)未來(lái)的需求,在這基礎(chǔ)上不斷地提高用戶體驗(yàn)。(你不告知你的用戶有個(gè)瀏覽器叫做谷歌瀏覽器,他這輩子就會(huì)覺(jué)得IE就是瀏覽器,瀏覽器就是IE。)

這次主要是將react/react-router/webpack進(jìn)行了升級(jí),并升級(jí)到最新(當(dāng)時(shí)的最新)。

按需加載其實(shí)跟react-router沒(méi)多大關(guān)系,只不過(guò)需要借助它更好的完成按需加載這項(xiàng)任務(wù)。react-router升級(jí)到4后,便沒(méi)有了getComponent這個(gè)參數(shù),所以我們得換種方式,react-router4官方示例也提供了code splitting的方法,利用webpack結(jié)合bundle-loader,它是在require.ensure基礎(chǔ)上封裝的,更友好的實(shí)現(xiàn)異步加載過(guò)程。

bundle-loader可以在webpack文件中進(jìn)行配置,這里我就不介紹了,webpack官方文檔都有寫(xiě)。我這里是寫(xiě)在代碼里的。我簡(jiǎn)單說(shuō)下,基本跟react-router4官方文檔說(shuō)的差不多。

首先先寫(xiě)一個(gè)bundle.js這個(gè)組件,代碼如下:

import React, { Component } from 'react';import PropTypes from 'prop-types';class Bundle extends Component {    static propTypes = {        load: PropTypes.any,        children: PropTypes.any,    };    state = {        mod: null,    };    componentWillMount () {        this.load(this.props);    }    componentWillReceiveProps (nextProps) {        if (nextProps.load !== this.props.load) {            this.load(nextProps);        }    }    load (props) {        this.setState({            mod: null,        });        props.load((mod) ={            this.setState({                mod: mod['default'] ? mod['default'] : mod,            });        });    }    render () {        return this.state.mod ? this.props.children(this.state.mod) : <div></div>;    }}export default Bundle;

然后在用到需要按需加載的組件的組件中,引入的時(shí)候,在文件路徑前面使用bundle-loader?lazy&name=[App]!,如下:

import loadApp from 'bundle-loader?lazy&name=[App]!../../app/components/App';

然后比如我這里使用 <Route path="/app" component={App}/> 加載這個(gè)App組件,我們需要用到剛才自己寫(xiě)的bundle組件:

import Bundle from '../bundle/components/Bundle';const App = (props) =(    <Bundle load={loadApp}>        {(App) ={            return <App {...props}/>;        }}    </Bundle>);

打包過(guò)后主要文件的對(duì)比:

code splitting

code splitting

not code splitting

not code splitting

到這里,這第二種方式介紹完了,很簡(jiǎn)單~

二、react(v.16.1.1) / redux(v.3.7.2) / react-router(v.4.2.2) / webpack(v.3.8.1)

兼容IE9+及現(xiàn)代瀏覽器,示例代碼地址->https://github.com/love-fay/fay-webpack-redux-code-splitting/tree/master/react-rr4-redux

先貼下依賴(lài):

enter image description here

這次又一次對(duì)引用的技術(shù)進(jìn)行了更新,同時(shí)加入了redux,項(xiàng)目復(fù)雜度的提高,組件之間的交流變得復(fù)雜,此時(shí)就需要用到redux。有些開(kāi)發(fā)人員會(huì)覺(jué)得好煩,不斷地升級(jí),不斷地改造,很費(fèi)時(shí)費(fèi)力,什么時(shí)候才能穩(wěn)定,其實(shí)不然,項(xiàng)目的穩(wěn)定不代表技術(shù)的不變,穩(wěn)定是相對(duì)的。如果想要一勞永逸的話,就不要讓公司給你漲工資了,公司也想一勞永逸~以后人工智能一旦鋪開(kāi)到企業(yè)級(jí)開(kāi)發(fā)中,將會(huì)導(dǎo)致大量在安逸中度過(guò)的程序員失業(yè)!學(xué)習(xí)是無(wú)止境的,學(xué)習(xí)也是人一輩子免費(fèi)的技能,曾經(jīng)后端Java一家獨(dú)大的時(shí)候,spring3穩(wěn)定的時(shí)候,很多后端程序員就開(kāi)始陷入了一勞永逸的幻覺(jué)當(dāng)中,導(dǎo)致他們中的很多人一度抱怨前端是在瞎折騰~這就好比有自行車(chē)為什么要造汽車(chē)的理論是一樣的~我是以Java程序員入行的,很清楚Java寫(xiě)后端的時(shí)候,輪子很多,很多程序員就是使用CV大法,甚至很多項(xiàng)目經(jīng)理啊什么的就說(shuō)程序員是搬運(yùn)工,代碼不就是增刪改查么~

使用了redux后,全局只有一個(gè)Store,而這個(gè)Store在頁(yè)面打開(kāi)的時(shí)候就已經(jīng)聲明了,于是讓我很糾結(jié)如何按需加載。后來(lái)我了解到redux這個(gè)東西的存在,內(nèi)部運(yùn)用了react中的context,同時(shí)這個(gè)context算是隱藏著的秘密。利用它我可以改變?nèi)值腟tore。我這里使用了react-redux,在頂級(jí)組件處加入。

import {Provider} from 'react-redux';<Provider store={store}>    ......</Provider>

然后在需要引入store信息的子組件處利用它提供的connect方法將store派發(fā)下去,這里派發(fā)是根據(jù)上下文context。項(xiàng)目中少不了用到路由,這時(shí)候,我使用了react-router-redux(一定要5.x版本npm i react-router-redux@next),在總的reducer中加入routerReducer,然后在寫(xiě)路由組件的部分的頂級(jí)處使用。

import createBrowserHistory from 'history/createBrowserHistory';import { ConnectedRouter} from 'react-router-redux';const history = createBrowserHistory();<ConnectedRouter history={history}>    ......</ConnectedRouter>

讓我們?cè)倩氐缴弦粋€(gè)代碼片,其中的store來(lái)源如下:

import configureStore from '../Store';let store = configureStore();

Store.js

import {createStore, applyMiddleware, compose} from 'redux';import { createLogger } from 'redux-logger';const logger = createLogger();import { routerMiddleware } from 'react-router-redux';import createHistory from 'history/createBrowserHistory';import createSagaMiddleware from 'redux-saga';const history = createHistory();const rMiddleware = routerMiddleware(history);const win = window;export const sagaMiddleware = createSagaMiddleware();const middlewares = [rMiddleware, sagaMiddleware];if (process.env.NODE_ENV !== 'production') {    middlewares.push(require('redux-immutable-state-invariant').default());}const storeEnhancers = compose(    applyMiddleware(...middlewares, logger),    (win && win.devToolsExtension) ? win.devToolsExtension() : (f) =f,);import createReducer from './reducers';export function injectAsyncStore(store, asyncReducers, sagas) {    asyncReducers && injectAsyncReducers(store, asyncReducers);    sagas && injectAsyncSagas(store, sagas);}function injectAsyncReducers(store, asyncReducers) {    let flag = false;    for (let key in asyncReducers) {        if(Object.prototype.hasOwnProperty.call(asyncReducers, key)) {            if (!store.asyncReducers[key]) {                store.asyncReducers[key] = asyncReducers[key];                flag = true;            }        }    }    flag && store.replaceReducer(createReducer(store.asyncReducers));}function injectAsyncSagas(store, sagas) {    for (let key in sagas) {        if(Object.prototype.hasOwnProperty.call(sagas, key)) {            if (!store.asyncSagas[key]) {                store.asyncSagas[key] = sagas[key];                store.sagaMiddleware.run(sagas[key]);            }        }    }}export default function configureStore() {    let store = createStore(createReducer(), {}, storeEnhancers);    store.asyncReducers = {};    store.asyncSagas = {};    store.sagaMiddleware = sagaMiddleware;    return store;}

reducers.js

import { combineReducers } from 'redux';import { routerReducer } from 'react-router-redux';export default function createReducer(asyncReducers) {    const reducers = {        ...asyncReducers,        router: routerReducer    };    return combineReducers(reducers);}

我沒(méi)有進(jìn)行刪減,主要是動(dòng)態(tài)改變store中兩個(gè)東西,一個(gè)是reducer還有一個(gè)就是saga。異步請(qǐng)求這塊我用的是redux-saga,雖然官方文檔上露臉的是redux-thunk和redux-promise,但是后起之秀redux-saga做到低耦合,在項(xiàng)目中作為獨(dú)立的一層出現(xiàn),不與action creator和reducer耦合。還有就是它強(qiáng)大的異步流程控制。

再來(lái)看看路由部分是怎么寫(xiě)的:

<Provider store={store}>    <ConnectedRouter history={history}>        <Switch>            <Route path='/app' component={App}/>        </Switch>    </ConnectedRouter></Provider>

這里的App組件便是我們要按需加載的組件。我是按照模塊來(lái)組織我的代碼的,先來(lái)看下App模塊的代碼排版:

app

這張圖中sagas.js是用來(lái)處理異步請(qǐng)求的,bundle.js和lazy.js以及公用的bundle.js是用來(lái)完成code splitting的。

lazy.js【需要懶加載的文件】

import appSagas from './sagas';import appReducer from './reducer';import view from './views/app';const reducer = {    appReducer: appReducer};const sagas = {    appSagas: appSagas};export {sagas, reducer, view};

bundle.js【code splitting】

import React from 'react';import Bundle from '../../bundle/views/bundle';import load from 'bundle-loader?lazy&name=[App]!./bundle';import {injectAsyncStore} from '../../Store';export default (props) ={    return (        <Bundle load={(store, cb) ={            load((target) ={                const {reducer, view, sagas} = target;                injectAsyncStore(store, reducer, sagas);                cb(view);            })        }}>            {(View) ={                return <View {...props}/>            }}        </Bundle>    );};

公用的bundle.js【對(duì)其進(jìn)行了改造,加入了store】

import React, { Component } from 'react';import PropTypes from 'prop-types';class Bundle extends Component {    static propTypes = {        load: PropTypes.any,        children: PropTypes.any,    };    static contextTypes = {        store: PropTypes.object    };    state = {        mod: null,    };    componentWillMount () {        this._isMounted = true;        this.load(this.props);    }    componentWillUnmount() {        this._isMounted = false;    }    componentWillReceiveProps (nextProps) {        if (nextProps.load !== this.props.load) {            this.load(nextProps);        }    }    load (props) {        this.setState({            mod: null,        });        props.load(this.context.store, (mod) ={            if (this._isMounted) {                this.setState({                    mod: mod['default'] ? mod['default'] : mod,                });            }        });    }    render () {        return this.state.mod ? this.props.children(this.state.mod) : <div>組件加載中...</div>;    }}export default Bundle;

index.js【對(duì)外暴露的組件】

import view from './bundle';export {view};

code splitting就完成了~當(dāng)然我又進(jìn)行了更改,下文有講。

這里要說(shuō)下公用的bundle.js中的this.context.store,這里一定要定義contextTypes,不然獲取不到this.context,當(dāng)然官方?jīng)]有提供這個(gè)api,也不推薦使用,但是按需加載就得需要它,并且我們要謹(jǐn)慎使用它即可,因?yàn)閠his.context一旦改變,它關(guān)聯(lián)的上下文就會(huì)重新render,所以加載某個(gè)頁(yè)面的時(shí)候,把它所要使用到的reducer和sagas也都關(guān)聯(lián)進(jìn)去,這樣加載這個(gè)頁(yè)面其他組件的時(shí)候就已經(jīng)存在相關(guān)的reducer和sagas,不需要再改變上下文的store了。還有組件設(shè)計(jì)很重要,如果不合理會(huì)導(dǎo)致頁(yè)面不可控。lazy.js中的reducer和sagas是個(gè)對(duì)象,比如app這個(gè)組件中如果嵌套了其他組件,而這些其他組件中需要引入reducer和sagas,這時(shí)可以將這些reducer和sagas結(jié)合到app模塊的lazy.js中。不加入也可以,這時(shí)需要靈活運(yùn)用shouldComponentUpdate這個(gè)生命周期來(lái)控制頁(yè)面。

從上面的代碼中,可以發(fā)現(xiàn)每個(gè)模塊的bundle.js中存在類(lèi)似的代碼,這樣我們可以給其剝離出來(lái),這不是必須的,因?yàn)閯冸x出來(lái)后,我們需要約定好每個(gè)模塊lazy.js中必須是export {sagas, reducer, view},當(dāng)然也可以約定其他,一致就行。這樣代碼進(jìn)過(guò)改造后,每個(gè)模塊的bundle.js代碼就可以分離到公共的bundle.js和模塊中的index.js中,代碼如下。

bundle.js中改動(dòng)的代碼片:

load (props) {    this.setState({        mod: null,    });    props.load((mod) ={        const {reducer, view, sagas} = mod;        injectAsyncStore(this.context.store, reducer, sagas);        if (this._isMounted) {            this.setState({                mod: view['default'] ? view['default'] : view,            });        }    });}

index.js【以app模塊為例】

import React from 'react';import Bundle from '../../bundle/views/bundle';import load from 'bundle-loader?lazy&name=[App]!./lazy';const view = (props) ={    return (        <Bundle load={load}>            {(View) ={                return <View {...props}/>            }}        </Bundle>    );};export {view};

打包過(guò)后主要文件的對(duì)比:

code splitting

code splitting

not code splitting

not code splitting

關(guān)于組件設(shè)計(jì),使用reactjs的時(shí)候組件設(shè)計(jì)一定要足夠的扁平化,也就是平級(jí),這樣不僅提高了計(jì)算的效率,同時(shí)也會(huì)很少出現(xiàn)父組件中嵌套子組件,而父組件更新的時(shí)候,子組件也跟著更新,實(shí)際上子組件并不想更新。當(dāng)然遇到逼不得已嵌套的情況的時(shí)候,可以使用shouldComponentUpdate這個(gè)組件存在時(shí)期的生命周期來(lái)控制子組件是否render。可喜的是react16版本中B組件不嵌套在A組件中,渲染后出現(xiàn)在A組件里,也可以掛載到任何一個(gè)組件里,這就是portals

看到這里,你會(huì)發(fā)現(xiàn)其實(shí)code splitting跟react和react-router沒(méi)多大關(guān)系,直接的聯(lián)系是redux和webpack,所以這種方式同時(shí)也適用于其他使用redux和webpack這種類(lèi)似的技術(shù)體系。

react技術(shù)棧是目前前端最美的技術(shù)棧。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多