react項目開發中遇到的問題


前言

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

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.


錯誤信息出現的模塊本身沒有錯誤,主要是因為引用的文件constants/index.js中有export * from ...提供對外接口導致。通過babel編輯也出現錯誤,由此可以推斷是babel編譯es6語法時出現的問題。

是什么導致babel出現這個編譯錯誤問題呢?

通過google發現原來是babel-plugin-transform-runtime的bug導致,用的版本為*6.15.0*還沒有修復。

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

  • 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"將其注釋掉

  • 因為最新 babel-plugin-transform-runtime版本增加了一個polyfill的配置項,可以將其設置false來禁止加載core-js也能解決我們的問題

    "plugins": [
    ["transform-runtime", { "polyfill": false }]
  ]

react router Link不起作用

在項目中用到的路由跳轉時,有時用到react-router提供的Link組件,但是使用Link時需要注意一些細節,否則一不小心就掉坑了。

本人在項目中將頁面公共的導航部分提取作為頂級組件,然后將其子組件作為內容區域的展示內容,而子組件使用了Router來進行路由,部分代碼如下

//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

為啥會出現這種情況呢?通過這篇討論找到答案:

    使用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;
  }  
};

該種情況具體可以參考這里

未完待續


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM