react学习总结–梳理
说明
使用 React 开发项目,仅仅靠它自己是不够的,需要花费更多的时间去学习与之配套的技术,像是:Redux、React-Router 、Gulp(或者是Webpack)、Browserify、ES6 等,下面是我梳理的使用React的简易流程
技术要求
- 开发工具 : Atom
- 构建工具 : Gulp + Browserify
- ES6 语法编写 JS,Sass 编写 CSS
- React + React-Router + Redux 创建应用
- jshint 语法检查
版本信息
- “gulp” : “^3.9.1”
- “browserify” : “^13.1.1”
- “react” : “^15.4.1”
- “react-router” : “^3.0.0”
- “redux” : “^3.6.0”
- “react-redux” : “^4.4.6”
- “react-router-redux” : “^4.0.7”
- “react-tap-event-plugin” : “^2.0.1”
项目结构
目录结构
目录设计的很烂,但是这里为了说明,还是粘出来了
app
|---_data
| |---a.json //存放json文件
|
|---css
| |---index.css //编译后的css文件
|
|---img
| |---a文件夹
| |---b文件夹
| |---a.jpg //根据容器组件分成各个目录
|
|---js
| |---actions
| | |---action.js //action文件,在一个目录下
| |
| |---components
| | |---AppCom.js //App容器组件下的唯一子组件,负责其他子组件的嵌套
| | |---AppCom文件夹 //AppCom需要嵌套的子组件
| | |---common文件夹 //共有的组件
| |
| |---containers
| | |---App.js //根容器组件,router的最外层
| | |---DevTools.js //调试用组件
| | |---Home.js //普通容器组件
| |
| |---reducers
| | |---reducer.js //根reducer,需要引入其他所有reducer合并
| | |---appReducer.js //普通的reducer分支
| |
| |---store
| | |---configureStore.js //store构造器
| |---constants.js //静态常量
| |---index.js //项目入口文件
| |---routes.js //路由配置文件
|
|---scss
| |---_common.scss //通用样式文件
| |---index.scss //主样式文件
| |---home.scss //一般容器组件的样式文件
| |---commoncom.scss //通用组件的样式文件
|
|---index.html //项目主页
注: 查资料的时候,看到有的文章上建议将一个组件的所有文件都放在一个目录,也可以试试看
|---components
| |---AppCom //文件夹
| | |---index.js //组件
| | |---app.scss //样式
| | |---AppCom1.js //子组件
| | |---AppCom2.js //子组件
| | |---AppCom3.js //子组件
1.搭建开发环境
前端资源依赖于
npm
,所以请先确保已经安装了node
所有资源都是通过
npm install
安装的,Mac 需要sudo npm install
然后就是安装 Gulp 和 Browserify 了,详细的可以看 上一篇 文章
不要忘记编写.jshintrc
,要不然命令行上就都是警告了gulp 环境搭建好后,就需要下载 React 相关的文件了,我安装了这些:
- es6-promise fetch-jsonp isomorphic-fetch :这三个用来代替ajax请求数据
- react react-dom react-tap-event-plugin : React 基本使用
- react-router react-router-redux : 解决 React 应用中的路由
- redux react-redux :使用redux,管理应用状态
- redux-thunk : 异步的中间件
- redux-devtools redux-devtools-dock-monitor redux-devtools-log-monitor : redux 调试插件
我用 Atom 做编辑器,为了更好的开发也要安装相应的插件,安装了很多,这里列举一部分
- emmet atom-ctags : 都是增强代码补全的
- language-babel react redux-snippets :增强代码高亮等
- linter linter-jshint jshint :js语法检查,安装jshint时,需要注意,可以设置支持jsx语法
- autocomplete-paths autocomplete-modules :自动补全路径,还有模块
- atom-ternjs : 代码支持增强
- split-diff :检查文件差异
- 还有很多插件…
2.开始写应用
2.1 app / index.html (主页)
与一般页面的内容一样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
<title>标题</title>
<!-- 引入编译后的css文件 -->
<link rel="stylesheet" href="./css/app.css">
</head>
<body>
<!-- 项目的容器 -->
<div id="app"></div>
<!-- 引入编译后的js文件 -->
<script src="./js/bundle.js"></script>
</body>
</html>
2.2 app / js / index.js (入口文件)
在这里,确定容器(#app),将组件导入容器中,创建 store ,通过,使得所有子组件都可以拿到 store 中的 state。
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {Router, browserHistory} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux'; //结合store同步导航事件
import configureStore from './store/configureStore'; //引入store生成器
import reducer from './reducers/reducer'; //引入合并后的reducer
import routes from './routes.js'; //引入路由配置文件
// import DevTools from './containers/DevTools'; //引入redux调试插件
import injectTapEventPlugin from 'react-tap-event-plugin'; //引入提供Touch事件的库
injectTapEventPlugin(); //初始化Touch事件
// 给增强后的store传入reducer
const store = configureStore(reducer);
// 创建一个增强版的history来结合store同步导航事件
const browhistory = syncHistoryWithStore(browserHistory, store);
ReactDOM.render((
<Provider store={store}>
<div>
<Router history={browhistory} routes={routes} />
{/* <DevTools /> */}
</div>
</Provider>
), document.getElementById('app'));
2.3 app / js / routes.js (路由配置文件)
引入 路由配置文件
import React from 'react';
import {Route, IndexRoute} from 'react-router';
// 引入容器组件
import App from './containers/App';
import Home from './containers/Home';
import Temp from './containers/Temp';
export default (
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="home" component={Home}/>
<Route path="temp" component={Temp}/>
</Route>
);
2.4 app / js / constants.js (静态常量)
LOGOUT
这种完全大写的常量是用来做action.type
的, 看了很多资料,都建议将这些字符串用常量的形式保存,避免更改出错
export const LOGOUT = "LOGOUT";
export const LOGIN = "LOGIN";
2.5 app / js / store / configureStore.js ( store 生成器)
在这里是把createStore,增强了一下,加入了中间件便于处理异步操作,
compose,是redux的方法,意思是:按照顺序组合多个函数,这是函数式编程的方法,用来把多个store增强器依次执行
import { compose, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'; // 引入thunk 中间件,处理异步操作
// import createLogger from 'redux-logger'; // 利用 redux-logger打印日志
import DevTools from '../containers/DevTools'; // 引入DevTools调试组件
// const loggerMiddleware = createLogger(); // 调用日志打印方法
const middleware = [thunk];
/*
调用 applyMiddleware ,使用 middleware 来增强 createStore
*/
const configureStore = compose(
applyMiddleware(...middleware),
DevTools.instrument()
)(createStore);
export default configureStore;
2.6 app / js / reducers / reducer.js (合并后的reducer)
所有分支的reducer,都会被引入到这里,合并后作为创建 store 的参数
// 引入分支reducer
import rootReducer from './rootReducer';
import appReducer from './appReducer';
import {combineReducers} from 'redux';
import {routerReducer} from 'react-router-redux'; // 导入routerReducer,将url信息合并到store中
// 合并到主reducer
const reducer = combineReducers({
rootReducer,
appReducer,
routing: routerReducer,
});
export default reducer;
2.7 app / js / reducers / appReducer.js (分支reducer)
一般是每个容器组件,建立一个reducer分支,管理这个容器组件内的state
const initialState = { //初始化数据
isLogin : true
userId : 99990002
};
/* jshint -W138 */
export default function rootReducer(state = initialState, action) {
switch (action.type) { //根据action的type属性,确定执行什么操作
case "LOGOUT":
// 退出登录
return Object.assign({}, state, {
isLogin : false,
userId : null
});
case "LOGIN":
// 登录
let {isLogin,userId} = action.msg;
return Object.assign({}, state, {
isLogin : isLogin,
userId : userId
});
case "ERROR_LOGIN":
console.log(action.msg);
return state;
default:
return state;
}
}
2.8 app / js / actions / appAction.js (app组件的Action)
同样更多容器组件定义相应的action,使用fetch代替ajax获取数据,也可以使用其他类库:axios,jQuery等。
// 导入type类型常量
import {LOGOUT,LOGIN,ERROR_LOGIN} from '../constants.js';
require('es6-promise').polyfill(); //使支持fetch
require('isomorphic-fetch');
//退出登录action
export function logout() {
return type:LOGOUT};
}
// 登录action
export function login(userId) {
// 登录后或者注册后获取用户userId,根据userId获取余额信息
console.log('root get userId = '+userId);
return dispatch => {
fetch('../../_data/a.json').then(resp => {
if (resp.status === 200)
return resp.json();
throw new Error('false of json');
}).then(json => {
dispatch({type:LOGIN,msg : {userId:userId,isLogin:true}});
}).catch(error => {
dispatch({type:ERROR_LOGIN,msg : error});
});
};
}
2.9 app / js / containers / App.js (容器组件)
容器组件只负责处理state,和action,并将其传入子组件
import React, {Component} from 'react';
import AppCom from '../components/AppCom';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import{openDatePicker,closeDatePicker,closeAndChangeDate} from '../actions/appAction'; //引入action
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<AppCom {...this.props}>{this.props.children}</AppCom>
);
}
}
// 绑定action,将其传入子组件中,子组件通过this.props.fn调用函数,就会触发action
const mapDispatchToProps = dispatch => {
return {
openDatePicker : bindActionCreators(openDatePicker, dispatch),
closeDatePicker : bindActionCreators(closeDatePicker, dispatch),
closeAndChangeDate : bindActionCreators(closeAndChangeDate, dispatch)
};
};
export default connect(
// 转换state,将store中的state,按照需求,传入子组件,子组件通过this.props使用
state => ({
rootState:state.rootReducer,
appState: state.appReducer,
appRoute : state.routing}),
mapDispatchToProps //绑定action多的时候就分离出去,少的话直接写在里边
)(App);
2.10 app / js / components / AppCom.js (UI组件)
展示组件,负责样式展示,数据都是从容器组件中得到
import React,{ Component } from 'react';
import BaiduMap from './HomeCom/BaiduMap';
import PullupDetails from './HomeCom/PullupDetails';
export default class HomeCom extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.close(); //调用父组件传递过来的事件处理函数,触发action
}
render() {
return (
<div id="home-container">
<div className="home-nav" onClick={this.handleClick}>
{this.props.appState.userId} // 使用父组件传递过来的store中的state
</div>
{this.props.children}
</div>
);
}
}