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有三种记录路由历史记录的方式:hashHistory
、browserHistory
和createMemoryHistory
。他们的区别可以自行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-a
和module-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;
}
};
该种情况具体可以参考这里