相信很多刚入坑React的小伙伴们有一个同样的疑惑,由于React相关库不断的再进行版本迭代,网上很多以前的技术分享变得不再适用。比如react-touter2
与react-router4
在写法上存在不少区别,以前的调用方法将无法使得项目正常工作。我最近用React全家桶在构建一个spa,由于官方文档给的比较繁琐,使用类似react-cli
的脚手架工具会使你的理解停留在表面,能用单反相机就不用傻瓜相机~~最好还是自己动手丰衣足食。在这里希望能用通俗易懂的方式来说一下如何快速构建spa。(PS:此文旨在让大家少走弯路,因此在项目结构上力求全而简)
在此之前你先需要懂得基本的 nodejs 操作与 ES2015 语法。
通过npm
安装webpack:npm install webpack
,然后用node运行配配置文件(这里并非绝对,也可以直接在cmd里运行,但不推荐)
首先给出项目结构:
--component //组件文件夹
ㄴ--hello.jsx //组件jsx
--more-component //嵌套组件可以放在次级目录
--js
ㄴ--common.js //自己常用的js方法,
--css
ㄴ--hello.css //每个组件对应一个css文件,便于管理
--img
--route
ㄴ--router.jsx //路由配置组件
--store //redux相关
ㄴ--action.js //状态发起动作方法
ㄴ--reducer.js //接受动作后改变状态
entry.jsx //打包入口
temp.html //打包模板html
webpack.config.js //webpack配置
项目结构根据个人习惯可以修改,但原则上要保持条理清晰,有时候还要根据项目结构修改webpack配置等。
接下来配置webpack
,同时npm
安装所需要的 loader
。webpack
相关配置请参考webpack中文文档。本章不多做赘述。
给出一个简单配置:
webpack.config.js
const webpack = require("webpack");
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const compiler = webpack({
entry: "./entry.jsx",
output:{
path: path.resolve(__dirname, "./dist"),
filename:"code.min.js"
},
module:{
rules:[
{
test:/\.css$/,
include:[path.resolve(__dirname, "./")],
loader:"style-loader!css-loader"
},
{
test:/\.js$/,
include:[path.resolve(__dirname, "./")],
loader:"babel-loader",
options: {
presets: ['es2015',"stage-0"]
}
},
{
test:/\.jsx$/,
include:[path.resolve(__dirname, "./")],
loader:"babel-loader",
options: {
presets: ['es2015',"stage-0","react"]
}
},
{
test: /\.(png|jpeg|jpg)$/,
include:[path.resolve(__dirname, "./img")],
loader:'file-loader?name=img/[name]-[hash].[ext]'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template:'./temp.html',
filename:'./spa.html',
inject:'body'
})
]
});
const watching = compiler.watch({
aggregateTimeout: 300,
poll: undefined
}, (err, stats) => {
if (err || stats.hasErrors())console.log(stats.compilation.errors);
else{console.log('success')}
})
当编写好webpack.config.js
文件后,我们只需要用node运行它,那么当我们的react项目改变时会自行编译到一个自动生成的dist
文件夹里(建议一定开启监听文件改变编译,而不是每次改变后手动运行webpack.config.js
,因为那样会很慢!)
做好了这些准备工作,接下来正式进入 React
世界:
entry.js
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import todoApp from './store/reducers'
import Main from './route/router.jsx'
let store = createStore(todoApp)
render(
<Main store={store} />,
document.body
)
上面import
的模块请npm
安装,我们在entry
里仅仅创建一个状态管理的store
对象,并且将router.jsx
的路由模块渲染到body
中,reducers
是redux
中具体需要更改哪些状态的js文件,在creatStore
里绑定。(关于redux的更多使用方法及理解需要详细具体讲解,涉及篇幅较大,本文暂不涉及,有兴趣可以看文档redux中文文档,我会整理后再单独章节分享)
接下来我们将编写路由组件
router.jsx
import React from 'react'
import { HashRouter as Router,Route } from 'react-router-dom'
import { Provider } from 'react-redux'
import Hello from '../component/hello.jsx';
class Main extends React.Component {
render(){
const { store } = this.props
return (
<Router hashType="noslash">
<Provider store={store}>
<Route render={({ location }) => {
return(
<div key={location.pathname} name={location.pathname}>
<Route location={location} path="/hello" component={Hello}/>
</div>
)
}}/>
</Provider>
</Router>
)
}
}
export default Main ;
这与react-router2
有一些差别,原来的方法已经不再使用,在react-router4
中HashRouter
或BrowserRouter
组件从react-redux-dom
中引入。
关于业务组件的编写,相信大家都很熟悉,即使以前使用es5
开发的小伙伴也应该能很快上手
hello.jsx
import '../css/xxx.css';
import React from 'react';
import { connect } from 'react-redux';
import * as action from '../store/actions';
class Hello extends React.Component{
constructor(props){
super(props)
this.state={...}
}
componentDidMount(){
this.props.dispatch(action.hi())
}
render() {
const { name } = this.props
return (
<div>{name}</div>
)
}
}
export default connect(state => state)(Hello)
在这个组件里,我们将redux
中管理的state
和触发状态更改的dispatch
方法通过connect
绑定在了props
中,可以随时调用,同时该组件将监听redux
中state
的变化实时更新数据。
我们需要改变redux
状态时所触发的动作
action.js
export const hi = () => {
return {
type:'hi',
...//其他你需要的属性
}
}
根据redux
要求,这里的type
属性是必须的,不能用别的字段名,否则运行时浏览器会报type
不存在。
接收action
后执行状态改变的文件就是
reducers.js
import { combineReducers } from 'redux'
const name = (state='', action) => {
switch (action.type) {
case 'hi':
return "hello world!"
default :
return state
}
}
const todoApp = combineReducers({
name,
//more state
})
export default todoApp;
reducer
首先用action
中传入的type
属性来判断我们要做的是哪种操作,然后再根据传入的其他属性当做参数做你想要的改变,最后返回一个{name : ...}
的对象,然后所有类似的对象通过combineReducers
合并为一个总状态对象暴露给组件访问。
当以上文件利用webpack编译打包好以后,一个最简单的react全家桶spa就完成了(虽然只包含一个组件)。
在实际的使用过程中,需要更多的库来使我们的应用更强大且美观。比如路由过度动画react-addons-css-transition-group
,redux异步更改state数据redux-thunk
,Ajax的兼容shimwhatwg-fetch
,移动端滚动iscroll
等等。
关于react-router4
与redux
的详细用法还是建议要静下心来理解文档,这样才能在变化多端的开发需求中运用自如。(我之前也用过vuex
,感觉相比之下redux
文档稍显繁琐,vuex
文档看了很容易就理解上手了)
如果感兴趣可以访问我的成熟项目源码React医疗类移动app –Github,欢迎各位多多指教,多多star ^_^