react項目開發中遇到的問題

前言

做爲一個前端愛好者來講,都想在react上一試生手,那麼在搭建react項目開發時,確定會有這樣或者那樣的問題,尤爲是對初學者來講,下面就我的在開發過程當中遇到的問題總結一下,好在有google幫我解決了各類問題。本人項目的技術棧爲react+redux+router+ant ui +webpackjavascript

export * from 'x-module'在配置babel-plugin-transform-runtime插件下致使不可用

export * from 'x-moudule'是es6經常使用的語法糖,在es6中使用它應該在正常不過。乖乖的,在項目跑起來後一直報錯,死活通不過。
瀏覽器控制檯報錯,webpack的babel loader編譯時也有報錯:You may need an appropriate loader to handle this file type.html


錯誤信息出現的模塊自己沒有錯誤,主要是由於引用的文件constants/index.js中有export * from ...提供對外接口致使。經過babel編輯也出現錯誤,由此能夠推斷是babel編譯es6語法時出現的問題。前端

是什麼致使babel出現這個編譯錯誤問題呢?java

經過google發現原來是babel-plugin-transform-runtime的bug致使,用的版本爲*6.15.0*尚未修復。

既然沒有修復,那麼根據這篇討論有兩種方式來解決這個問題:node

  • Comment this line 'defineProperty: "object/define-property"' in babel-plugin-transform-runtime/lib/definitions.js can temporarily solve this problem.
    即在node_modules/babel-plugin-transform-runtime/lib/definitions.js中找到這行defineProperty: "object/define-property"將其註釋掉
    react

  • 由於最新 babel-plugin-transform-runtime版本增長了一個polyfill的配置項,能夠將其設置false來禁止加載core-js也能解決咱們的問題
"plugins": [
    ["transform-runtime", { "polyfill": false }]
  ]

react router Link不起做用

在項目中用到的路由跳轉時,有時用到react-router提供的Link組件,可是使用Link時須要注意一些細節,不然一不當心就掉坑了。webpack

本人在項目中將頁面公共的導航部分提取做爲頂級組件,而後將其子組件做爲內容區域的展現內容,而子組件使用了Router來進行路由,部分代碼以下git

//App.jsx的render方法
render(){
        return (
            <MainLayout> //注意這個地方,MainLayout組件是做爲Router的父組件
                <Router history={history}>
                    <Route path="/" component={Home} />
                    <Route path="/dataSource/create" component={CreateForm} />
                    <Route path="/about" component={About} />
                </Router>
           </MainLayout>
    );
    }

   //MainLayout的render方法
render(){
    return(
        <aside className="ant-layout-sider">
              <div className="ant-layout-logo"></div>
              <Menu mode={mode} theme="dark">
                    <SubMenu key="index_1">
                        <Menu.Item key="index_1_1">
                            <Link to="/dataSource/create">建立數據</Link>
                        </Menu.Item>
                        .
                        .
                        .
              </Menu>
            </aside>
    )
}

當點擊導航進行路由時,控制報錯提示程序員

Link.js:126 Uncaught TypeError: Cannot read property 'push' of undefined

爲啥會出現這種狀況呢?經過這篇討論找到答案:es6

使用Link組件必須做爲react-router提供的Router組件的子組件,也就是說,Link必須位於Router內部,不然Link不起做用

正因如此:
在執行Link組件的內部點擊事件處理函數時,由於獲取不到router信息致使執行這行代碼this.context.router.push(_location);出錯。由於Link不在任何Router內部

如何優雅組織每一個頁面共有的部分,如導航和header、footer

在一個web應用系統中,尤爲是企業級後臺應用系統中,頁面導航是一個頁面不可或缺的部分。通常的後臺應用有三個大的部分組成。就拿本人系統來講吧,頁面上面有header部分,頁面左側有導航部分,頁面中間有主內容展現區域

那麼問題來了,對於初次搭建react項目來講,如何優雅組織一個頁面中各自獨有的主內容組件和公共組件部分呢?

下面就本人摸索的過程來進行描述。

開始抽取公共的導航組件MainLayout,具體render方法以下:

render() {
    const {layout, actions} = this.props;
    return (
        <div className={layout.collapse ? "ant-layout-aside ant-layout-aside-collapse" : "ant-layout-aside"}>
           <Header userName={layout.userName}/>  //頁面頂部header部分,抽取一個組件
            <Aside collapse={layout.collapse} actions={actions}/> //頁面左側導航部分,抽取一個組件
            <div className="ant-layout-main">
                 <div className="ant-layout-main-header"></div>
                 <div className="ant-layout-container">
                    <div className="ant-layout-content">
                        {this.props.children}    //MainLayout組件的全部子組件做爲頁面的主內容展現組件
                    </div>
                 </div>
             </div>
       </div>
        );
}

而後,全部主內容區組件被包裹在MainLayout組件中,如系統首頁主內容區組件的render方法以下:

render(){
        return(
            <MainLayout>
                <div className="welcome-pic">
                    <div>歡迎來到xxx平臺</div>
                </div>
            </MainLayout>
        )
    }

最後,你會發現每一個主內容區域要引入MainLayout模塊來配置每一個頁面公共部分,這樣作能夠實現功能,可是對於有這潔癖的程序員來講,這實在是太low,由於每一個頁面重複着引入與本頁面主內容區域沒有太大關係的無用功。

一直沒有找到更好的組織方式時,忽然看到react-router的路由配置一節裏講到路由能夠嵌套,嵌套的路由對應的展現組件能夠做爲被嵌套路由對應組件的子組件。看到這裏我就有了更好的解決方案,在配置應用路由時,讓MainLayout做爲根路由,全部路由都做爲它的子路由。

render(){
        return (
            <Router history={history}>
                <Route path="/" component={MainLayout}>
                    <IndexRoute component={Home}/>
                    <Route path="/dataSource/list" component={DataSourceList} />
                    <Route path="/dataSource/create" component={QueryForm} />
                </Route>
            </Router>
    );
}

使用react-router的browserHistory配置路由歷史記錄時刷新或者單獨打開一個頁面時出現NOT FOUND

react-router有三種記錄路由歷史記錄的方式:hashHistorybrowserHistorycreateMemoryHistory。他們的區別能夠自行google。

單說一下browserHistory,他是根據HTML5的history API來實現的,基於瀏覽器瀏覽記錄來實現路由的。它會新建立一個瀏覽器瀏覽記錄形式的路由URL。

react-router官方推薦browserHistory做爲路由的歷史記錄,緣由主要是:

  • browserHistory能夠支持服務端渲染,hashHistory卻不能
  • browserHistory可以有一個更乾淨的URL環境

可是browserHistory在某個指定的路由刷新或者新打開時,會出現not found的狀況,緣由以下:

因爲是單頁面應用,而browserHistory是基於瀏覽器瀏覽記錄來進行路由的,你刷新一個頁面或者導航到某個頁面的路由時,至關於新打開了一個單頁應用,而要刷新或者要打開的頁面是這個單頁應用的第一個頁面,這時這個新的單頁應用尚未瀏覽記錄,因此出現not found狀況。

解決這種狀況就須要進行服務端改造,具體有兩種方法,詳情請猛戳服務端改造

模塊按需加載

應用系統比較大的時候,若是一次性把全部路由信息都進行加載完,會致使單頁應用首屏加載文件過大,可能會出現白屏的狀況;另外有些可能絕大部分狀況下不會用到的模塊都加載進來。

這種狀況的解決辦法儘量實現模塊的按需加載。配合着webpack,能夠很容易實現模塊的按需加載。

require.ensure(["module-a", "module-b"], function(require) {
  var a = require("module-a");
  // ...
});

例如本人在項目中使用的按需加載,當點擊頁面上按鈕時,按需加載Todo組件模塊並展現在頁面中。

_onClick(){
        let self = this;
        let text = 'hello boy! welcome to the world of react!';
        require.ensure(['../todos/Todo.jsx'], function(require){
            var Todo = require('../todos/Todo.jsx');
            self.Todo = Todo;
            self.props.actions.setMessage(text);
        })
        // this.props.actions.setMessage(text);
    }
    render(){
        let {welcomeText} =  this.props;
        let Todo = this.Todo;
        return(
            <div>
                <Button type="large" onClick={this._onClick.bind(this)}>獲取歡迎詞</Button>
                <span>{this.props.welcomeText}</span>
                {
                    Todo ? <Todo/> : ''
                }

            </div>
        )
    }

使用require.ensure方法能夠實現模塊的按需加載,例如上面例子在依賴module-amodule-b模塊,webpack會將兩者打包成一個單獨的文件進行按需加載,從而實現模塊的按需加載。具體能夠參考這篇文章深刻淺出React(二):React開發神器Webpack

html-webpack-plugin與atool-build混用的坑

具體的能夠這篇文章file-loader引發的html-webpack-plugin坑

defaultValue與value相關的受控組件與非受控組件

具體能夠參考本人這篇總結:淺談react的受控組件與非受控組件

調用組件的setState報警告called setState() on an unmounted component

這個主要是發生在異步處理的狀況下,例如事件或者異步請求的狀況,這時在回調函數中調用component的setState方法時,可能會出現當前組件尚未mounted到dom中,此時調用該方法會報以下錯誤警告:

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the LineSuggest component.

因爲ES6下的react component是extends React.Component,因此component的isMount()方法不可用

雖然isMount()方法爲一個反模式,不推薦;可是爲了解決問題,爲此網上有一些對應的解決方法,以下

  • componentDidMount函數中設置一個flag, 而後在componentWillUnMount中重置該flag,具體代碼以下:
componentDidMount(){
        this.mounted = true; //flag
        listener = document.body.addEventListener('click', ()=>{
            if(this.mounted){
                this.setState({open: false});
            }
        }, false)
    }

    componentWillUnMount(){
        this.mounted = false; //重置flag
        listener && document.body.removeEventListener('click', listener, false);
    }

該種狀況具體能夠參考這裏

  • hack一個isMount方法。
    因爲React.findDOMNode(component)是在component mounted時才能正常使用的方法,不然會拋異常;因此利用這個狀況能夠hack一個方法,具體以下:
function isMounted (component) {
  // exceptions for flow control :(
  try {
    React.findDOMNode(component);
    return true;
  } catch (e) {
    // Error: Invariant Violation: Component (with keys: props,context,state,refs,_reactInternalInstance) contains `render` method but is not mounted in the DOM
    return false;
  }  
};

該種狀況具體能夠參考這裏

未完待續

相關文章
相關標籤/搜索